Initial version of Eclipse compiler adapter. development
authorJan Vrany <jan.vrany@fit.cvut.cz>
Fri, 29 Mar 2013 16:38:17 +0000
branchdevelopment
changeset 2472 226437481a47
parent 2470 1c4389d15eb5
child 2473 dd4b4b6e9cfb
Initial version of Eclipse compiler adapter. This adapter allows compilation of incomplete classes.
experiments/java/.classpath
experiments/java/src/stx/libjava/tools/compiler/JavaCompilerAdapter.java
experiments/java/src/stx/libjava/tools/compiler/ecj/CompilationUnit.java
experiments/java/src/stx/libjava/tools/compiler/ecj/Compiler.java
experiments/java/src/stx/libjava/tools/compiler/ecj/CompilerEnvironment.java
experiments/java/src/stx/libjava/tools/compiler/ecj/tests/CompilerTests.java
libs/java/.classpath
--- a/experiments/java/.classpath	Wed Mar 27 13:18:41 2013 +0000
+++ b/experiments/java/.classpath	Fri Mar 29 16:38:17 2013 +0000
@@ -3,7 +3,7 @@
 	<classpathentry kind="src" output="bin" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/stx.libjava"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/stx.libjava.libs"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/stx.libjava.tools"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/stx.libjava.libs"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
--- a/experiments/java/src/stx/libjava/tools/compiler/JavaCompilerAdapter.java	Wed Mar 27 13:18:41 2013 +0000
+++ b/experiments/java/src/stx/libjava/tools/compiler/JavaCompilerAdapter.java	Fri Mar 29 16:38:17 2013 +0000
@@ -27,13 +27,13 @@
 		this.setCompiler(compiler);
 	}
 	
-	
-	
 	public void setCompiler(JavaCompiler compiler) {
 		this.compiler = compiler;
 		this.classFileManager = new JavaClassFileManager(compiler.getStandardFileManager(null, null, null));
 	}
 	
+	
+	
 	public Collection<JavaClassFile> getClassFiles() {
 		return classFileManager.getClasses().values();
 	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/experiments/java/src/stx/libjava/tools/compiler/ecj/CompilationUnit.java	Fri Mar 29 16:38:17 2013 +0000
@@ -0,0 +1,66 @@
+package stx.libjava.tools.compiler.ecj;
+
+import java.io.File;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+
+public class CompilationUnit implements ICompilationUnit {
+	/**
+	 * A full Java (dotted) type name, i.e., java.lang.String.
+	 * If compilation unit contains more than one type, this is the name
+	 * of the main type. 
+	 */
+	protected String name;
+	
+	/**
+	 * Source of the class
+	 */
+	protected String source;
+
+	/**
+	 * Creates a new compilation unit. 
+	 * 
+	 * @param name A fully qualified Java name of the main type contained in the source. 
+	 * @param source Source code
+	 */
+	public CompilationUnit(String name,String source) {
+		this.name = name;
+		this.source = source;
+	}
+	
+	@Override
+	public char[] getFileName() {		
+		return (name.substring(name.lastIndexOf('.') + 1) + ".java").toCharArray();
+	}
+
+	@Override
+	public char[] getContents() {
+		return source.toCharArray();
+	}
+
+	@Override
+	public char[] getMainTypeName() {
+		return name.substring(name.lastIndexOf('.') + 1).toCharArray();		
+	}
+
+	@Override
+	public char[][] getPackageName() {
+		char[] nameC = name.toCharArray();
+		int ncomponents = 0;
+		int i;
+		for (i = 0; i < nameC.length; i++) {
+			if (nameC[i] == '.') ncomponents++; 
+		}
+		char[][] packagename = new char[ncomponents][];
+		int j = 0;
+		int ci = 0;
+		for (i = 0; i < nameC.length; i++) {
+			if (nameC[i] == '.') {
+				char[] component = new char[i - j];
+				packagename[ci++] = component;
+				System.arraycopy(nameC, j, component, 0, i-j);
+				j = i+1;
+			}
+		}
+		return packagename;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/experiments/java/src/stx/libjava/tools/compiler/ecj/Compiler.java	Fri Mar 29 16:38:17 2013 +0000
@@ -0,0 +1,84 @@
+package stx.libjava.tools.compiler.ecj;
+
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.tools.ToolProvider;
+
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
+import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
+import org.eclipse.jdt.internal.compiler.IProblemFactory;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager;
+
+import stx.libjava.tools.compiler.JavaClassFile;
+import stx.libjava.tools.compiler.JavaClassFileManager;
+import stx.libjava.tools.compiler.ecj.CompilationUnit;
+
+public class Compiler implements ICompilerRequestor {
+	protected JavaClassFileManager classFileManager = new JavaClassFileManager(new EclipseFileManager(Locale.getDefault(), Charset.defaultCharset())); 
+	protected CompilationResult result;
+	
+	
+	public CompilationResult getResult() {
+		return result;
+	}
+
+	/**
+	 * Compiles classes in given source. The resulting .class files are added
+	 * to an internal list which can be later retrieved by getClassFiles().
+	 * 
+	 * @param name unqualified name of class being compiled. 
+	 * @param source source code of the class as String.   
+	 * @return true, if compilation succeeded, false otherwise.
+	 */
+	public boolean compile(String name, String source) {
+		INameEnvironment environment = new CompilerEnvironment();
+		ICompilerRequestor requestor = this;
+		IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.exitAfterAllProblems();
+	    IProblemFactory problemFactory = new DefaultProblemFactory(Locale.getDefault());
+	    ICompilationUnit[] units = new ICompilationUnit[1];
+	    units[0] = new CompilationUnit(name, source);
+		
+		org.eclipse.jdt.internal.compiler.Compiler compiler = new org.eclipse.jdt.internal.compiler.Compiler(environment, policy, getDefaultCompilerSettings(), requestor, problemFactory);
+		compiler.compile(units);
+		return false;
+	}
+	
+	/**
+	 * Returns a list of JavaClassFiles that contains results of the compilation. 
+	 * 
+	 * @return resulting class files
+	 */
+	public Collection<JavaClassFile> getClassFiles() {
+		return classFileManager.getClasses().values();
+	}
+	
+	public static Map<String, Object> getDefaultCompilerSettings() {
+		String javaSpecVersion = System.getProperty("java.specification.version");
+        Map<String, Object> settings = new HashMap<String, Object>();
+        settings.put(CompilerOptions.OPTION_Source, javaSpecVersion);
+        settings.put(CompilerOptions.OPTION_TargetPlatform, javaSpecVersion);
+        settings.put(CompilerOptions.OPTION_ReportDeprecation, CompilerOptions.IGNORE);
+        return settings;
+	}
+
+    public static CompilerOptions getDefaultCompilerOptions() {
+                return new CompilerOptions(getDefaultCompilerSettings());
+    }
+
+	@Override
+	public void acceptResult(CompilationResult result) {
+		this.result = result;
+	}
+	
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/experiments/java/src/stx/libjava/tools/compiler/ecj/CompilerEnvironment.java	Fri Mar 29 16:38:17 2013 +0000
@@ -0,0 +1,106 @@
+package stx.libjava.tools.compiler.ecj;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+
+public class CompilerEnvironment implements INameEnvironment {
+	/**
+	 * Find a type with the given compound name.
+	 * Answer the binary form of the type if it is known to be consistent.
+	 * Otherwise, answer the compilation unit which defines the type
+	 * or null if the type does not exist.
+	 * Types in the default package are specified as {{typeName}}.
+	 *
+	 * It is unknown whether the package containing the type actually exists.
+	 *
+	 * NOTE: This method can be used to find a member type using its
+	 * internal name A$B, but the source file for A is answered if the binary
+	 * file is inconsistent.
+	 */
+
+	public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
+		StringBuilder sb = new StringBuilder();
+		if (compoundTypeName.length > 1) {
+			for (int i = 0; i < compoundTypeName.length - 1; i++) {
+				sb.append(compoundTypeName[i]);
+			}		
+		}
+		sb.append(compoundTypeName[compoundTypeName.length-1]);
+		return findType(sb.toString());
+	}
+	
+	protected NameEnvironmentAnswer findType(String javaname) {
+		try {
+			Class<?> c = Class.forName(javaname);
+			ClassLoader cl = c.getClassLoader();
+			if (cl == null) { 
+				cl = ClassLoader.getSystemClassLoader();
+			}
+			String cfilename = (new String(c.getName())).replace('.', '/') + ".class";
+			InputStream cfs = cl.getResourceAsStream(cfilename);
+			return new NameEnvironmentAnswer(ClassFileReader.read(cfs, cfilename), null);
+		} catch (ClassNotFoundException cnfe) {
+			return null;
+		} catch (ClassFormatException e) {
+			e.printStackTrace();
+			return null;
+		} catch (IOException e) {
+			e.printStackTrace();
+			return null;
+		}
+	}
+	/**
+	 * Find a type named <typeName> in the package <packageName>.
+	 * Answer the binary form of the type if it is known to be consistent.
+	 * Otherwise, answer the compilation unit which defines the type
+	 * or null if the type does not exist.
+	 * The default package is indicated by char[0][].
+	 *
+	 * It is known that the package containing the type exists.
+	 *
+	 * NOTE: This method can be used to find a member type using its
+	 * internal name A$B, but the source file for A is answered if the binary
+	 * file is inconsistent.
+	 */
+
+	public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) {
+		StringBuffer sb = new StringBuffer();
+		for (int i = 0; i < packageName.length; i++) {
+			sb.append(packageName[i]);
+			sb.append('.');
+		}
+		sb.append(typeName);
+		return findType(sb.toString());
+		
+	}
+	/**
+	 * Answer whether packageName is the name of a known subpackage inside
+	 * the package parentPackageName. A top level package is found relative to null.
+	 * The default package is always assumed to exist.
+	 *
+	 * For example:
+	 *      isPackage({{java}, {awt}}, {event});
+	 *      isPackage(null, {java});
+	 */
+
+	public boolean isPackage(char[][] parentPackageName, char[] packageName) {
+		return Character.isLowerCase(packageName[0]);
+	}
+
+	/**
+	 * This method cleans the environment uo. It is responsible for releasing the memory
+	 * and freeing resources. Passed that point, the name environment is no longer usable.
+	 *
+	 * A name environment can have a long life cycle, therefore it is the responsibility of
+	 * the code which created it to decide when it is a good time to clean it up.
+	 */
+	public void cleanup() {
+		
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/experiments/java/src/stx/libjava/tools/compiler/ecj/tests/CompilerTests.java	Fri Mar 29 16:38:17 2013 +0000
@@ -0,0 +1,72 @@
+package stx.libjava.tools.compiler.ecj.tests;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.InvocationTargetException;
+
+import stx.libjava.tools.compiler.ecj.Compiler;
+
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.junit.Test;
+
+public class CompilerTests {
+	
+	protected class ClassLoader extends java.lang.ClassLoader {
+		
+		@SuppressWarnings("deprecation")
+		public Class<?> load(byte[] classfile) {
+			return this.defineClass(classfile, 0, classfile.length);
+		}
+	}
+
+	@Test
+	public void test_01() {
+		Compiler c = new Compiler();
+		ClassLoader l = new ClassLoader();
+				
+		c.compile("test.pkg.Foo", "package test.pkg; public class Foo {}");
+		
+		assertFalse(c.getResult().hasErrors());
+		
+		ClassFile[] classfiles = c.getResult().getClassFiles();
+		
+		assertEquals(1, classfiles.length);
+		
+		Class<?> clazz = l.load(classfiles[0].getBytes());
+		
+		assertEquals("test.pkg.Foo" , clazz.getName());
+	}
+	
+	
+	/**
+	 * Tests compilation of an incomplete class
+	 */
+	@Test
+	public void test_02() {
+		Compiler c = new Compiler();
+		ClassLoader l = new ClassLoader();
+				
+		c.compile("test.pkg.Foo", "package test.pkg; public class Foo { public void f() { g(); } }");		
+		ClassFile[] classfiles = c.getResult().getClassFiles();
+		
+		assertEquals(1, classfiles.length);
+		
+		Class<?> fooClass = l.load(classfiles[0].getBytes());
+		
+		assertEquals("test.pkg.Foo" , fooClass.getName());
+		assertEquals(1, fooClass.getDeclaredMethods().length);
+		
+		try {
+			fooClass.getMethod("f").invoke(fooClass.newInstance());
+			assertTrue(false);		
+		} catch (InvocationTargetException ite) {
+			assertTrue(ite.getCause() instanceof Error);
+		} catch (IllegalAccessException | IllegalArgumentException
+				| NoSuchMethodException | SecurityException | InstantiationException e) {
+			e.printStackTrace();
+			assertTrue(false);
+		}
+	}
+
+
+}
--- a/libs/java/.classpath	Wed Mar 27 13:18:41 2013 +0000
+++ b/libs/java/.classpath	Fri Mar 29 16:38:17 2013 +0000
@@ -26,7 +26,6 @@
 	<classpathentry exported="true" kind="lib" path="libs/jackson-databind-2.0.5.jar"/>
 	<classpathentry exported="true" kind="lib" path="libs/jasperreports-5.0.1.jar"/>
 	<classpathentry exported="true" kind="lib" path="libs/jcommon-1.0.15.jar"/>
-	<classpathentry exported="true" kind="lib" path="libs/jdtcore-3.1.0.jar"/>
 	<classpathentry exported="true" kind="lib" path="libs/jfreechart-1.0.12.jar"/>
 	<classpathentry exported="true" kind="lib" path="libs/xml-apis-1.3.02.jar"/>
 	<classpathentry kind="output" path="bin"/>