Browse Source

fix for Bugzilla Bug 54621

 	Incremental support ignores binary source
tags/Root_ajdt_support
acolyer 20 years ago
parent
commit
bd589bc1fa

+ 4
- 3
org.aspectj.ajdt.core/src/org/aspectj/ajdt/ajc/BuildArgParser.java View File

@@ -320,6 +320,7 @@ public class BuildArgParser extends Main {
if (args.size() > nextArgIndex) {
buildConfig.getAjOptions().put(AjCompilerOptions.OPTION_Inpath, CompilerOptions.PRESERVE);
List inPath = buildConfig.getInpath();
StringTokenizer st = new StringTokenizer(
((ConfigParser.Arg)args.get(nextArgIndex)).getValue(),
File.pathSeparator);
@@ -327,16 +328,16 @@ public class BuildArgParser extends Main {
String filename = st.nextToken();
File file = makeFile(filename);
if (file.exists() && FileUtil.hasZipSuffix(filename)) {
buildConfig.getInpath().add(file);
inPath.add(file);
} else {
if (file.isDirectory()) {
buildConfig.getInpath().add(file);
inPath.add(file);
} else
showError("bad inpath component: " + filename);
}
}
buildConfig.setInPath(inPath);
args.remove(args.get(nextArgIndex));
}
} else if (arg.equals("-injars")) {;

+ 25
- 13
org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/compiler/AjCompilerAdapter.java View File

@@ -14,6 +14,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -47,8 +48,9 @@ public class AjCompilerAdapter implements ICompilerAdapter {
private IIntermediateResultsRequestor intermediateResultsRequestor;
private IProgressListener progressListener;
private IOutputClassFileNameProvider outputFileNameProvider;
private IBinarySourceProvider binarySourceProvider;
private WeaverMessageHandler weaverMessageHandler;
private List /* InterimCompilationResult */ binarySources = new ArrayList();
private Map /* fileName |-> List<UnwovenClassFile> */ binarySourceSetForFullWeave = new HashMap();
private Collection /*InterimCompilationResult*/ resultSetForFullWeave = Collections.EMPTY_LIST;

@@ -77,7 +79,8 @@ public class AjCompilerAdapter implements ICompilerAdapter {
IIntermediateResultsRequestor intRequestor,
IProgressListener progressListener,
IOutputClassFileNameProvider outputFileNameProvider,
Map binarySourceEntries, /* fileName |-> List<UnwovenClassFile> */
IBinarySourceProvider binarySourceProvider,
Map fullBinarySourceEntries, /* fileName |-> List<UnwovenClassFile> */
Collection /* InterimCompilationResult */ resultSetForFullWeave,
boolean isXNoWeave) {
this.compiler = compiler;
@@ -86,15 +89,15 @@ public class AjCompilerAdapter implements ICompilerAdapter {
this.intermediateResultsRequestor = intRequestor;
this.progressListener = progressListener;
this.outputFileNameProvider = outputFileNameProvider;
this.binarySourceProvider = binarySourceProvider;
this.isXNoWeave = isXNoWeave;
this.binarySourceSetForFullWeave = fullBinarySourceEntries;
this.resultSetForFullWeave = resultSetForFullWeave;
this.eWorld = eFactory;
IMessageHandler msgHandler = world.getMessageHandler();
weaverMessageHandler = new WeaverMessageHandler(msgHandler, compiler);
world.setMessageHandler(weaverMessageHandler);
addBinarySource(binarySourceEntries);
}
public void beforeCompiling(ICompilationUnit[] sourceUnits) {
@@ -134,7 +137,6 @@ public class AjCompilerAdapter implements ICompilerAdapter {
} else {
resultsPendingWeave.add(intRes);
}
}
public void beforeResolving(CompilationUnitDeclaration unit, ICompilationUnit sourceUnit, boolean verifyMethods, boolean analyzeCode, boolean generateCode) {
@@ -178,8 +180,9 @@ public class AjCompilerAdapter implements ICompilerAdapter {
}
}
private void addBinarySource(Map binarySourceEntries) {
private List getBinarySourcesFrom(Map binarySourceEntries) {
// Map is fileName |-> List<UnwovenClassFile>
List ret = new ArrayList();
for (Iterator binIter = binarySourceEntries.keySet().iterator(); binIter.hasNext();) {
String sourceFileName = (String) binIter.next();
List unwovenClassFiles = (List) binarySourceEntries.get(sourceFileName);
@@ -188,8 +191,9 @@ public class AjCompilerAdapter implements ICompilerAdapter {
result.noSourceAvailable();
InterimCompilationResult binarySource =
new InterimCompilationResult(result,unwovenClassFiles);
binarySources.add(binarySource);
ret.add(binarySource);
}
return ret;
}
private void notifyRequestor() {
@@ -209,14 +213,22 @@ public class AjCompilerAdapter implements ICompilerAdapter {
}

weaver.prepareForWeave();
if (isBatchCompile) {
resultsPendingWeave.addAll(binarySources);
// passed into the compiler, the set of classes in injars and inpath...
} else if (weaver.needToReweaveWorld()) {
addAllKnownClassesToWeaveList();
if (weaver.needToReweaveWorld()) {
if (!isBatchCompile) addAllKnownClassesToWeaveList(); // if it's batch, they're already on the list...
resultsPendingWeave.addAll(getBinarySourcesFrom(binarySourceSetForFullWeave));
} else {
Map binarySourcesToAdd = binarySourceProvider.getBinarySourcesForThisWeave();
resultsPendingWeave.addAll(getBinarySourcesFrom(binarySourcesToAdd));
}

// if (isBatchCompile) {
// resultsPendingWeave.addAll(getBinarySourcesFrom(binarySourceSetForFullWeave));
// // passed into the compiler, the set of classes in injars and inpath...
// } else if (weaver.needToReweaveWorld()) {
// addAllKnownClassesToWeaveList();
// resultsPendingWeave.addAll(getBinarySourcesFrom(binarySourceSetForFullWeave));
// }

weaver.weave(new WeaverAdapter(this,weaverMessageHandler,progressListener));
}

+ 27
- 0
org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/compiler/IBinarySourceProvider.java View File

@@ -0,0 +1,27 @@
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.aspectj.ajdt.internal.compiler;

import java.util.Map;

/**
* @author colyer
*
* Implementors of this interface are called by the CompilerAdapter just before
* it does a weave, and should return the set of binary source files (ie. those
* resources from injars and inpath) that are to be included in the weave.
* Used to manage incremental compilation of binary sources.
*/
public interface IBinarySourceProvider {
Map /* fileName |-> List<UnwovenClassFile> */ getBinarySourcesForThisWeave();
}

+ 50
- 1
org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildConfig.java View File

@@ -17,6 +17,7 @@
package org.aspectj.ajdt.internal.core.builder;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -24,6 +25,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.aspectj.util.FileUtil;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;

/**
@@ -37,11 +39,12 @@ public class AjBuildConfig { // XXX needs bootclasspath?
public static final String AJLINT_WARN = "warn";
public static final String AJLINT_ERROR = "error";
public static final String AJLINT_DEFAULT = "default";
private File outputDir;
private File outputJar;
private List/*File*/ sourceRoots = new ArrayList();
private List/*File*/ files = new ArrayList();
private List /*File*/ binaryFiles = new ArrayList(); // .class files in indirs...
private List/*File*/ inJars = new ArrayList();
private List/*File*/ inPath = new ArrayList();
private Map/*String->File*/ sourcePathResources = new HashMap();
@@ -68,6 +71,27 @@ public class AjBuildConfig { // XXX needs bootclasspath?
private boolean incrementalMode;
private File incrementalFile;
public static class BinarySourceFile {
public BinarySourceFile(File dir, File src) {
this.fromInPathDirectory = dir;
this.binSrc = src;
}
public File fromInPathDirectory;
public File binSrc;
public boolean equals(Object obj) {
if ((obj instanceof BinarySourceFile) &&
(obj != null)) {
BinarySourceFile other = (BinarySourceFile)obj;
return(binSrc.equals(other.binSrc));
}
return false;
}
public int hashCode() {
return binSrc != null ? binSrc.hashCode() : 0;
}
}
/**
* Intialises the javaOptions Map to hold the default
* JDT Compiler settings. Added by AMC 01.20.2003 in reponse
@@ -150,6 +174,15 @@ public class AjBuildConfig { // XXX needs bootclasspath?
return files;
}

/**
* returned files includes all .class files found in
* a directory on the inpath, but does not include
* .class files contained within jars.
*/
public List/*BinarySourceFile*/ getBinaryFiles() {
return binaryFiles;
}
public File getOutputDir() {
return outputDir;
}
@@ -219,6 +252,22 @@ public class AjBuildConfig { // XXX needs bootclasspath?
public void setInPath(List dirsOrJars) {
inPath = dirsOrJars;
// remember all the class files in directories on the inpath
binaryFiles = new ArrayList();
FileFilter filter = new FileFilter() {
public boolean accept(File pathname) {
return pathname.getPath().endsWith(".class");
}};
for (Iterator iter = dirsOrJars.iterator(); iter.hasNext();) {
File inpathElement = (File) iter.next();
if (inpathElement.isDirectory()) {
File[] files = FileUtil.listFiles(inpathElement, filter);
for (int i = 0; i < files.length; i++) {
binaryFiles.add(new BinarySourceFile(inpathElement,files[i]));
}
}
}
}

public List getSourceRoots() {

+ 45
- 15
org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjBuildManager.java View File

@@ -19,6 +19,7 @@ import java.util.jar.*;
import java.util.zip.ZipEntry;

import org.aspectj.ajdt.internal.compiler.AjCompilerAdapter;
import org.aspectj.ajdt.internal.compiler.IBinarySourceProvider;
import org.aspectj.ajdt.internal.compiler.IIntermediateResultsRequestor;
import org.aspectj.ajdt.internal.compiler.IOutputClassFileNameProvider;
import org.aspectj.ajdt.internal.compiler.InterimCompilationResult;
@@ -43,11 +44,18 @@ import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
//import org.eclipse.jdt.internal.compiler.util.HashtableOfObject;

public class AjBuildManager implements IOutputClassFileNameProvider,ICompilerAdapterFactory {
public class AjBuildManager implements IOutputClassFileNameProvider,IBinarySourceProvider,ICompilerAdapterFactory {
private static final String CANT_WRITE_RESULT = "unable to write compilation result";
private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";
static final boolean COPY_INPATH_DIR_RESOURCES = false;
static final boolean FAIL_IF_RUNTIME_NOT_FOUND = false;
private static final FileFilter binarySourceFilter =
new FileFilter() {
public boolean accept(File f) {
return f.getName().endsWith(".class");
}};
private IProgressListener progressListener = null;
private int compiledCount;
@@ -57,6 +65,8 @@ public class AjBuildManager implements IOutputClassFileNameProvider,ICompilerAda
private boolean batchCompile = true;
private INameEnvironment environment;
private Map /* String -> List<UCF>*/ binarySourcesForTheNextCompile = new HashMap();
private IHierarchy structureModel;
public AjBuildConfig buildConfig;
@@ -146,6 +156,7 @@ public class AjBuildManager implements IOutputClassFileNameProvider,ICompilerAda
bcelWorld.setModel(AsmManager.getDefault().getHierarchy());
// in incremental build, only get updated model?
}
binarySourcesForTheNextCompile = state.getBinaryFilesToCompile();
performCompilation(buildConfig.getFiles());
if (handler.hasErrors()) {
return false;
@@ -157,13 +168,17 @@ public class AjBuildManager implements IOutputClassFileNameProvider,ICompilerAda
// }
// System.err.println("XXXX start inc ");
List files = state.getFilesToCompile(true);
for (int i = 0; (i < 5) && !files.isEmpty(); i++) {
binarySourcesForTheNextCompile = state.getBinaryFilesToCompile();
boolean hereWeGoAgain = !(files.isEmpty() && binarySourcesForTheNextCompile.isEmpty());
for (int i = 0; (i < 5) && hereWeGoAgain; i++) {
// System.err.println("XXXX inc: " + files);
performCompilation(files);
if (handler.hasErrors() || (progressListener!=null && progressListener.isCancelledRequested())) {
return false;
}
files = state.getFilesToCompile(false);
binarySourcesForTheNextCompile = state.getBinaryFilesToCompile();
hereWeGoAgain = !(files.isEmpty() && binarySourcesForTheNextCompile.isEmpty());
}
if (!files.isEmpty()) {
return batchBuild(buildConfig, baseHandler);
@@ -425,19 +440,26 @@ public class AjBuildManager implements IOutputClassFileNameProvider,ICompilerAda
for (Iterator i = buildConfig.getInpath().iterator(); i.hasNext(); ) {
File inPathElement = (File)i.next();
List unwovenClasses = bcelWeaver.addJarFile(inPathElement,buildConfig.getOutputDir(),true);
state.binarySourceFiles.put(inPathElement.getPath(),unwovenClasses); // good enough for ajc to lump these together
if (!inPathElement.isDirectory()) {
// its a jar file on the inpath
// the weaver method can actually handle dirs, but we don't call it, see next block
List unwovenClasses = bcelWeaver.addJarFile(inPathElement,buildConfig.getOutputDir(),true);
state.binarySourceFiles.put(inPathElement.getPath(),unwovenClasses);
} else {
// add each class file in an in-dir individually, this gives us the best error reporting
// (they are like 'source' files then), and enables a cleaner incremental treatment of
// class file changes in indirs.
File[] binSrcs = FileUtil.listFiles(inPathElement, binarySourceFilter);
for (int j = 0; j < binSrcs.length; j++) {
UnwovenClassFile ucf =
bcelWeaver.addClassFile(binSrcs[j], inPathElement, buildConfig.getOutputDir());
List ucfl = new ArrayList();
ucfl.add(ucf);
state.binarySourceFiles.put(binSrcs[j].getPath(),ucfl);
}
}
}
// if (buildConfig.getSourcePathResources() != null) {
// // XXX buildConfig.getSourcePathResources always returns null (CompileCommand.java)
// for (Iterator i = buildConfig.getSourcePathResources().keySet().iterator(); i.hasNext(); ) {
// // File resource = (File)i.next();
// String resource = (String)i.next();
// bcelWeaver.addResource(resource, (File)buildConfig.getSourcePathResources().get(resource), buildConfig.getOutputDir());
// // bcelWeaver.addResource(resource, buildConfig.getOutputDir());
// }
// }
bcelWeaver.setReweavableMode(buildConfig.isXreweavable(),buildConfig.getXreweavableCompressClasses());

@@ -575,7 +597,7 @@ public class AjBuildManager implements IOutputClassFileNameProvider,ICompilerAda
options.produceReferenceInfo(true); //TODO turn off when not needed
try {
compiler.compile(getCompilationUnits(filenames, encodings));
compiler.compile(getCompilationUnits(filenames, encodings));
} catch (OperationCanceledException oce) {
handler.handleMessage(new Message("build cancelled:"+oce.getMessage(),IMessage.WARNING,null,null));
}
@@ -865,11 +887,19 @@ public class AjBuildManager implements IOutputClassFileNameProvider,ICompilerAda
factory,
getInterimResultRequestor(),
progressListener,
this,
this, // IOutputFilenameProvider
this, // IBinarySourceProvider
state.binarySourceFiles,
state.resultsFromFile.values(),
buildConfig.isNoWeave());
}

/* (non-Javadoc)
* @see org.aspectj.ajdt.internal.compiler.IBinarySourceProvider#getBinarySourcesForThisWeave()
*/
public Map getBinarySourcesForThisWeave() {
return binarySourcesForTheNextCompile;
}
} // class AjBuildManager


+ 87
- 2
org.aspectj.ajdt.core/src/org/aspectj/ajdt/internal/core/builder/AjState.java View File

@@ -26,6 +26,9 @@ import java.util.Map;
import java.util.Set;

import org.aspectj.ajdt.internal.compiler.InterimCompilationResult;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.Message;
import org.aspectj.bridge.SourceLocation;
import org.aspectj.util.FileUtil;
import org.aspectj.weaver.bcel.UnwovenClassFile;
import org.eclipse.jdt.internal.compiler.CompilationResult;
@@ -59,6 +62,8 @@ public class AjState {
Set addedFiles;
Set deletedFiles;
Set /*BinarySourceFile*/addedBinaryFiles;
Set /*BinarySourceFile*/deletedBinaryFiles;
List addedClassFiles;
@@ -101,6 +106,14 @@ public class AjState {
deletedFiles = new HashSet(oldFiles);
deletedFiles.removeAll(newFiles);
Set oldBinaryFiles = new HashSet(buildConfig.getBinaryFiles());
Set newBinaryFiles = new HashSet(newBuildConfig.getBinaryFiles());
addedBinaryFiles = new HashSet(newBinaryFiles);
addedBinaryFiles.removeAll(oldBinaryFiles);
deletedBinaryFiles = new HashSet(oldBinaryFiles);
deletedBinaryFiles.removeAll(newBinaryFiles);
this.newBuildConfig = newBuildConfig;
return true;
@@ -126,6 +139,27 @@ public class AjState {
return ret;
}

private Collection getModifiedBinaryFiles() {
return getModifiedBinaryFiles(lastSuccessfulBuildTime);
}

Collection getModifiedBinaryFiles(long lastBuildTime) {
List ret = new ArrayList();
//not our job to account for new and deleted files
for (Iterator i = buildConfig.getBinaryFiles().iterator(); i.hasNext(); ) {
AjBuildConfig.BinarySourceFile bsfile = (AjBuildConfig.BinarySourceFile)i.next();
File file = bsfile.binSrc;
if (!file.exists()) continue;
long modTime = file.lastModified();
//System.out.println("check: " + file + " mod " + modTime + " build " + lastBuildTime);
if (modTime >= lastBuildTime) {
ret.add(bsfile);
}
}
return ret;
}
private boolean pathChange(AjBuildConfig oldConfig, AjBuildConfig newConfig) {
boolean changed = false;
List oldClasspath = oldConfig.getClasspath();
@@ -153,7 +187,13 @@ public class AjState {
if (!oldPath.get(i).equals(newPath.get(i))) {
return true;
}
File f = new File((String)oldPath.get(i));
Object o = oldPath.get(i); // String on classpath, File on other paths
File f = null;
if (o instanceof String) {
f = new File((String)o);
} else {
f = (File) o;
}
if (f.exists() && !f.isDirectory() && (f.lastModified() >= lastSuccessfulBuildTime)) {
return true;
}
@@ -188,6 +228,28 @@ public class AjState {
return thisTime;
}

public Map /* String -> List<ucf> */ getBinaryFilesToCompile() {
if (lastSuccessfulBuildTime == -1 || buildConfig == null) {
return binarySourceFiles;
}
// else incremental...
Map toWeave = new HashMap();
List addedOrModified = new ArrayList();
addedOrModified.addAll(addedBinaryFiles);
addedOrModified.addAll(getModifiedBinaryFiles());
for (Iterator iter = addedOrModified.iterator(); iter.hasNext();) {
AjBuildConfig.BinarySourceFile bsf = (AjBuildConfig.BinarySourceFile) iter.next();
UnwovenClassFile ucf = createUnwovenClassFile(bsf);
if (ucf == null) continue;
List ucfs = new ArrayList();
ucfs.add(ucf);
binarySourceFiles.put(bsf.binSrc.getPath(),ucfs);
toWeave.put(bsf.binSrc.getPath(),ucfs);
}
deleteBinaryClassFiles();
return toWeave;
}
private void deleteClassFiles() {
for (Iterator i = deletedFiles.iterator(); i.hasNext(); ) {
File deletedFile = (File)i.next();
@@ -203,6 +265,16 @@ public class AjState {
}
}
private void deleteBinaryClassFiles() {
// range of bsf is ucfs, domain is files (.class and jars) in inpath/jars
for (Iterator iter = deletedBinaryFiles.iterator(); iter.hasNext();) {
AjBuildConfig.BinarySourceFile deletedFile = (AjBuildConfig.BinarySourceFile) iter.next();
List ucfs = (List) binarySourceFiles.get(deletedFile.binSrc.getPath());
binarySourceFiles.remove(deletedFile.binSrc.getPath());
deleteClassFile((UnwovenClassFile)ucfs.get(0));
}
}
private void deleteResources() {
List oldResources = new ArrayList();
oldResources.addAll(resources);
@@ -263,7 +335,8 @@ public class AjState {
}
}

private void deleteClassFile(UnwovenClassFile classFile) {
private void deleteClassFile(UnwovenClassFile classFile) {
classesFromName.remove(classFile.getClassName());
buildManager.bcelWeaver.deleteClassFile(classFile.getClassName());
@@ -274,6 +347,18 @@ public class AjState {
}
}
private UnwovenClassFile createUnwovenClassFile(AjBuildConfig.BinarySourceFile bsf) {
UnwovenClassFile ucf = null;
try {
ucf = buildManager.bcelWeaver.addClassFile(bsf.binSrc, bsf.fromInPathDirectory, buildConfig.getOutputDir());
} catch(IOException ex) {
IMessage msg = new Message("can't read class file " + bsf.binSrc.getPath(),
new SourceLocation(bsf.binSrc,0),false);
buildManager.handler.handleMessage(msg);
}
return ucf;
}
public void noteResult(InterimCompilationResult result) {
File sourceFile = new File(result.fileName());
CompilationResult cr = result.result();

+ 19
- 21
weaver/src/org/aspectj/weaver/bcel/BcelWeaver.java View File

@@ -167,27 +167,7 @@ public class BcelWeaver implements IWeaver {
// For each file, add it either as a real .class file or as a resource
for (int i = 0; i < files.length; i++) {
FileInputStream fis = new FileInputStream(files[i]);
byte[] bytes = FileUtil.readAsByteArray(fis);
// String relativePath = files[i].getPath();
// ASSERT: files[i].getAbsolutePath().startsWith(inFile.getAbsolutePath()
// or we are in trouble...
String filename = files[i].getAbsolutePath().substring(
inFile.getAbsolutePath().length()+1);
UnwovenClassFile classFile = new UnwovenClassFile(new File(outDir,filename).getAbsolutePath(),bytes);
if (filename.endsWith(".class")) {
// System.err.println("BCELWeaver: processing class from input directory "+classFile);
this.addClassFile(classFile);
addedClassFiles.add(classFile);
// } else {
// if (CopyResourcesFromInpathDirectoriesToOutput) {
// // System.err.println("BCELWeaver: processing resource from input directory "+filename);
// addResource(filename,classFile);
// }
}
fis.close();
addedClassFiles.add(addClassFile(files[i],inFile,outDir));
}
return addedClassFiles;
@@ -289,6 +269,24 @@ public class BcelWeaver implements IWeaver {
// }
world.addSourceObjectType(classFile.getJavaClass());
}
public UnwovenClassFile addClassFile(File classFile, File inPathDir, File outDir) throws IOException {
FileInputStream fis = new FileInputStream(classFile);
byte[] bytes = FileUtil.readAsByteArray(fis);
// String relativePath = files[i].getPath();
// ASSERT: files[i].getAbsolutePath().startsWith(inFile.getAbsolutePath()
// or we are in trouble...
String filename = classFile.getAbsolutePath().substring(
inPathDir.getAbsolutePath().length()+1);
UnwovenClassFile ucf = new UnwovenClassFile(new File(outDir,filename).getAbsolutePath(),bytes);
if (filename.endsWith(".class")) {
// System.err.println("BCELWeaver: processing class from input directory "+classFile);
this.addClassFile(ucf);
}
fis.close();
return ucf;
}


public void deleteClassFile(String typename) {

Loading…
Cancel
Save