From 6bdaa71de490b53a0212f1f32a09ce69a8420204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Westk=C3=A4mper?= Date: Mon, 30 Jul 2012 23:38:06 +0300 Subject: [PATCH] added Eclipse JDT based compilation --- pom.xml | 12 +- .../codegen/AbstractEvaluatorFactory.java | 119 +++++++ .../mysema/codegen/ECJEvaluatorFactory.java | 320 ++++++++++++++++++ .../com/mysema/codegen/EvaluatorFactory.java | 176 +--------- .../mysema/codegen/JDKEvaluatorFactory.java | 96 ++++++ .../mysema/codegen/ComplexEvaluationTest.java | 117 ++++++- .../codegen/ECJEvaluatorFactoryTest.java | 123 +++++++ ...Test.java => JDKEvaluatorFactoryTest.java} | 4 +- .../java/com/mysema/codegen/support/Cat.java | 159 +++++++++ template.mf | 1 + 10 files changed, 961 insertions(+), 166 deletions(-) create mode 100644 src/main/java/com/mysema/codegen/AbstractEvaluatorFactory.java create mode 100644 src/main/java/com/mysema/codegen/ECJEvaluatorFactory.java create mode 100644 src/main/java/com/mysema/codegen/JDKEvaluatorFactory.java create mode 100644 src/test/java/com/mysema/codegen/ECJEvaluatorFactoryTest.java rename src/test/java/com/mysema/codegen/{EvaluatorFactoryTest.java => JDKEvaluatorFactoryTest.java} (94%) create mode 100644 src/test/java/com/mysema/codegen/support/Cat.java diff --git a/pom.xml b/pom.xml index ae01288f5..15dfd3acb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.mysema.codegen codegen - 0.5.1 + 0.5.2 Codegen Code generation and compilation for Java @@ -20,6 +20,7 @@ 4.01 3.0.1 11.0.2 + 3.7.2 @@ -28,6 +29,12 @@ guava 11.0.2 + + + org.eclipse.jdt.core.compiler + ecj + ${ecj.version} + @@ -47,7 +54,8 @@ validation-api 1.0.CR3 test - + + diff --git a/src/main/java/com/mysema/codegen/AbstractEvaluatorFactory.java b/src/main/java/com/mysema/codegen/AbstractEvaluatorFactory.java new file mode 100644 index 000000000..2a1af1f41 --- /dev/null +++ b/src/main/java/com/mysema/codegen/AbstractEvaluatorFactory.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.codegen; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + +import com.mysema.codegen.model.ClassType; +import com.mysema.codegen.model.Type; +import com.mysema.codegen.model.TypeCategory; + +/** + * @author tiwe + * + */ +public abstract class AbstractEvaluatorFactory implements EvaluatorFactory{ + + protected ClassLoader loader; + + /** + * @param source + * @param projection + * @param names + * @param types + * @param id + * @param constants + * @throws IOException + */ + protected abstract void compile(String source, ClassType projection, String[] names, Type[] types, + String id, Map constants) throws IOException; + + + @Override + public Evaluator createEvaluator(String source, Class projectionType, + String[] names, Class[] classes, Map constants) { + Type[] types = new Type[classes.length]; + for (int i = 0; i < types.length; i++) { + types[i] = new ClassType(TypeCategory.SIMPLE, classes[i]); + } + return createEvaluator(source, new ClassType(TypeCategory.SIMPLE, projectionType), names, + types, classes, constants); + } + + + /** + * Create a new Evaluator instance + * + * @param + * projection type + * @param source + * expression in Java source code form + * @param projection + * type of the source expression + * @param names + * names of the arguments + * @param types + * types of the arguments + * @param constants + * @return + */ + @SuppressWarnings("unchecked") + @Override + public Evaluator createEvaluator(String source, ClassType projection, String[] names, + Type[] types, Class[] classes, Map constants) { + try { + String id = toId(source, projection.getJavaClass(), types); + Class clazz; + try { + clazz = loader.loadClass(id); + } catch (ClassNotFoundException e) { + compile(source, projection, names, types, id, constants); + // reload + clazz = loader.loadClass(id); + } + + Object object = !constants.isEmpty() ? clazz.newInstance() : null; + + for (Map.Entry entry : constants.entrySet()) { + Field field = clazz.getField(entry.getKey()); + field.set(object, entry.getValue()); + } + + Method method = clazz.getMethod("eval", classes); + return new MethodEvaluator(method, object, (Class) projection.getJavaClass()); + } catch (ClassNotFoundException e) { + throw new CodegenException(e); + } catch (SecurityException e) { + throw new CodegenException(e); + } catch (NoSuchMethodException e) { + throw new CodegenException(e); + } catch (NoSuchFieldException e) { + throw new CodegenException(e); + } catch (InstantiationException e) { + throw new CodegenException(e); + } catch (IOException e) { + throw new CodegenException(e); + } catch (IllegalAccessException e) { + throw new CodegenException(e); + } + + } + + + protected String toId(String source, Class returnType, Type... types) { + StringBuilder b = new StringBuilder("Q"); + b.append("_").append(source.hashCode()); + b.append("_").append(returnType.getName().hashCode()); + for (Type type : types) { + b.append("_").append(type.getFullName().hashCode()); + } + return b.toString().replace('-', '0'); + } + +} diff --git a/src/main/java/com/mysema/codegen/ECJEvaluatorFactory.java b/src/main/java/com/mysema/codegen/ECJEvaluatorFactory.java new file mode 100644 index 000000000..f8436beb9 --- /dev/null +++ b/src/main/java/com/mysema/codegen/ECJEvaluatorFactory.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2010 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.codegen; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; + +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.ClassFile; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.Compiler; +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.classfmt.ClassFileReader; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; +import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; +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 com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.io.ByteStreams; +import com.mysema.codegen.model.ClassType; +import com.mysema.codegen.model.Parameter; +import com.mysema.codegen.model.SimpleType; +import com.mysema.codegen.model.Type; +import com.mysema.codegen.model.TypeCategory; +import com.mysema.codegen.support.ClassUtils; + +/** + * EvaluatorFactory is a factory implementation for creating Evaluator instances + * + * @author tiwe + * + */ +public class ECJEvaluatorFactory extends AbstractEvaluatorFactory { + + private final MemFileManager fileManager; + + private final ClassLoader parentClassLoader; + + private final List problemList = Lists.newArrayList(); + + private final CompilerOptions compilerOptions; + + public static CompilerOptions getDefaultCompilerOptions() { + String javaSpecVersion = System.getProperty("java.specification.version"); + Map settings = Maps.newHashMap(); + settings.put(CompilerOptions.OPTION_Source, javaSpecVersion); + settings.put(CompilerOptions.OPTION_TargetPlatform, javaSpecVersion); + settings.put(CompilerOptions.OPTION_ReportDeprecation, CompilerOptions.IGNORE); + return new CompilerOptions(settings); + } + + public ECJEvaluatorFactory(ClassLoader parent) { + this(parent, getDefaultCompilerOptions()); + } + + public ECJEvaluatorFactory(ClassLoader parent, CompilerOptions compilerOptions) { + this.parentClassLoader = parent; + this.fileManager = new MemFileManager(parent, new EclipseFileManager(Locale.getDefault(), Charset.defaultCharset())); + this.loader = fileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); + this.compilerOptions = compilerOptions; + } + + protected void compile(String source, ClassType projectionType, String[] names, Type[] types, + String id, Map constants) throws IOException { + // create source + StringWriter writer = new StringWriter(); + JavaWriter javaw = new JavaWriter(writer); + SimpleType idType = new SimpleType(id, "", id); + javaw.beginClass(idType, null); + Parameter[] params = new Parameter[names.length]; + for (int i = 0; i < params.length; i++) { + params[i] = new Parameter(names[i], types[i]); + } + + for (Map.Entry entry : constants.entrySet()) { + Type type = new ClassType(TypeCategory.SIMPLE, ClassUtils.normalize(entry.getValue().getClass())); + javaw.publicField(type, entry.getKey()); + } + + if (constants.isEmpty()) { + javaw.beginStaticMethod(projectionType, "eval", params); + } else { + javaw.beginPublicMethod(projectionType, "eval", params); + } + javaw.append(source); + javaw.end(); + javaw.end(); + + // compile + final char[] targetContents = writer.toString().toCharArray(); + final String targetName = idType.getFullName(); + final ICompilationUnit[] targetCompilationUnits = new ICompilationUnit[] { new ICompilationUnit() { + @Override + public char[] getContents() { + return targetContents; + } + + @Override + public char[] getMainTypeName() { + int dot = targetName.lastIndexOf('.'); + if (dot > 0) + return targetName.substring(dot + 1).toCharArray(); + else + return targetName.toCharArray(); + } + + @Override + public char[][] getPackageName() { + StringTokenizer tok = new StringTokenizer(targetName, "."); + char[][] result = new char[tok.countTokens() - 1][]; + for (int j = 0; j < result.length; j++) { + result[j] = tok.nextToken().toCharArray(); + } + return result; + } + + @Override + public char[] getFileName() { + return CharOperation.concat(targetName.toCharArray(), ".java".toCharArray()); + } + } }; + + INameEnvironment env = new INameEnvironment() { + + private String join(char[][] compoundName, char separator) { + if (compoundName == null) { + return ""; + } else { + List parts = Lists.newArrayListWithCapacity(compoundName.length); + for (char[] part: compoundName) { + parts.add(new String(part)); + } + return Joiner.on(separator).join(parts); + } + } + + @Override + public NameEnvironmentAnswer findType(char[][] compoundTypeName) { + return findType(join(compoundTypeName, '.')); + } + + @Override + public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) { + return findType(CharOperation.arrayConcat(packageName, typeName)); + } + + private boolean isClass(String result) { + if (Strings.isNullOrEmpty(result)) + return false; + + // if it's the class we're compiling, then of course it's a class + if (result.equals(targetName)) { + return true; + } + InputStream is = null; + try { + // if this is a class we've already compiled, it's a class + is = loader.getResourceAsStream(result); + if (is == null) { + // use our normal class loader now... + String resourceName = result.replace('.', '/') + ".class"; + is = parentClassLoader.getResourceAsStream(resourceName); + if (is == null && !result.contains(".")) { + // we couldn't find the class, and it has no package; is it a core class? + is = parentClassLoader.getResourceAsStream("java/lang/" + resourceName); + } + } + if (is == null) { + return false; // if it's a class, we sure couldn't load it + } else { + return true; // we actually loaded the class, so it must be one + } + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException ex) {} + } + } + } + + @Override + public boolean isPackage(char[][] parentPackageName, char[] packageName) { + // if the parent is a class, the child can't be a package + String parent = join(parentPackageName, '.'); + if (isClass(parent)) + return false; + + // if the child is a class, it's not a package + String qualifiedName = (parent.isEmpty() ? "" : parent + ".") + new String(packageName); + return !isClass(qualifiedName); + } + + @Override + public void cleanup() { + } + + private NameEnvironmentAnswer findType(String className) { + String resourceName = className.replace('.', '/') + ".class"; + InputStream is = null; + try { + // we're only asking ECJ to compile a single class; we shouldn't need this + if (className.equals(targetName)) { + return new NameEnvironmentAnswer(targetCompilationUnits[0], null); + } + + is = loader.getResourceAsStream(resourceName); + if (is == null) { + is = parentClassLoader.getResourceAsStream(resourceName); + } + + if (is != null) { + ClassFileReader cfr = new ClassFileReader(ByteStreams.toByteArray(is), className.toCharArray(), true); + return new NameEnvironmentAnswer(cfr, null); + } else { + return null; + } + } catch (ClassFormatException ex) { + throw new RuntimeException(ex); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) {} + } + } + + } + }; + + ICompilerRequestor requestor = new ICompilerRequestor() { + + @Override + public void acceptResult(CompilationResult result) { + if (result.hasErrors()) { + for (CategorizedProblem problem: result.getProblems()) { + if (problem.isError()) { + problemList.add(problem.getMessage()); + } + } + } else { + for (ClassFile clazz: result.getClassFiles()) { + try { + MemJavaFileObject jfo = (MemJavaFileObject) fileManager + .getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, + new String(clazz.fileName()), JavaFileObject.Kind.CLASS, null); + OutputStream os = jfo.openOutputStream(); + os.write(clazz.getBytes()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + } + } + }; + + problemList.clear(); + + IErrorHandlingPolicy policy = DefaultErrorHandlingPolicies.exitAfterAllProblems(); + IProblemFactory problemFactory = new DefaultProblemFactory(Locale.getDefault()); + + try { + //Compiler compiler = new Compiler(env, policy, getCompilerOptions(), requestor, problemFactory, true); + Compiler compiler = new Compiler(env, policy, compilerOptions, requestor, problemFactory); + compiler.compile(targetCompilationUnits); + if (!problemList.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (String problem: problemList) { + sb.append("\t").append(problem).append("\n"); + } + throw new CodegenException("Compilation of " + id + " failed:\n" + source + "\n" + sb.toString()); + } + } catch (RuntimeException ex) { + // if we encountered an IOException, unbox and throw it; + // if we encountered a ClassFormatException, box it as an IOException and throw it + // otherwise, it's a legit RuntimeException, + // not one of our checked exceptions boxed as unchecked; just rethrow + Throwable cause = ex.getCause(); + if (cause != null) { + if (cause instanceof IOException) + throw (IOException)cause; + else if (cause instanceof ClassFormatException) + throw new IOException(cause); + } + throw ex; + } + } + + public CompilerOptions getCompilerOptions() { + return compilerOptions; + } + + +} diff --git a/src/main/java/com/mysema/codegen/EvaluatorFactory.java b/src/main/java/com/mysema/codegen/EvaluatorFactory.java index f75d9fde6..85e4212e2 100644 --- a/src/main/java/com/mysema/codegen/EvaluatorFactory.java +++ b/src/main/java/com/mysema/codegen/EvaluatorFactory.java @@ -1,180 +1,38 @@ /* - * Copyright (c) 2010 Mysema Ltd. - * All rights reserved. - * + * To change this template, choose Tools | Templates + * and open the template in the editor. */ package com.mysema.codegen; -import java.io.IOException; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import com.mysema.codegen.model.ClassType; +import com.mysema.codegen.model.Type; import java.util.Map; -import javax.tools.JavaCompiler; -import javax.tools.SimpleJavaFileObject; -import javax.tools.StandardLocation; -import javax.tools.ToolProvider; -import javax.tools.JavaCompiler.CompilationTask; - -import com.mysema.codegen.model.ClassType; -import com.mysema.codegen.model.Parameter; -import com.mysema.codegen.model.SimpleType; -import com.mysema.codegen.model.Type; -import com.mysema.codegen.model.TypeCategory; -import com.mysema.codegen.support.ClassUtils; - /** - * EvaluatorFactory is a factory implementation for creating Evaluator instances - * - * @author tiwe - * + * + * @author pgrant */ -public class EvaluatorFactory { +public interface EvaluatorFactory { - private final MemFileManager fileManager; - - private final String classpath; - - private final List compilationOptions; - - private final JavaCompiler compiler; - - private final ClassLoader loader; - - public EvaluatorFactory(URLClassLoader parent) { - this(parent, ToolProvider.getSystemJavaCompiler()); - } - - public EvaluatorFactory(URLClassLoader parent, JavaCompiler compiler) { - this.fileManager = new MemFileManager(parent, compiler.getStandardFileManager(null, null, null)); - this.compiler = compiler; - this.classpath = SimpleCompiler.getClassPath(parent); - this.loader = fileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); - this.compilationOptions = Arrays.asList("-classpath", classpath, "-g:none"); - } - - private void compile(String source, Type projectionType, String[] names, Type[] types, - String id, Map constants) throws IOException { - // create source - StringWriter writer = new StringWriter(); - JavaWriter javaw = new JavaWriter(writer); - SimpleType idType = new SimpleType(id, "", id); - javaw.beginClass(idType, null); - Parameter[] params = new Parameter[names.length]; - for (int i = 0; i < params.length; i++) { - params[i] = new Parameter(names[i], types[i]); - } - - for (Map.Entry entry : constants.entrySet()) { - Type type = new ClassType(TypeCategory.SIMPLE, ClassUtils.normalize(entry.getValue().getClass())); - javaw.publicField(type, entry.getKey()); - } - - if (constants.isEmpty()) { - javaw.beginStaticMethod(projectionType, "eval", params); - } else { - javaw.beginPublicMethod(projectionType, "eval", params); - } - javaw.append(source); - javaw.end(); - javaw.end(); - - // compile - SimpleJavaFileObject javaFileObject = new MemSourceFileObject(id, writer.toString()); - Writer out = new StringWriter(); - - CompilationTask task = compiler.getTask(out, fileManager, null, compilationOptions, null, - Collections.singletonList(javaFileObject)); - if (!task.call().booleanValue()) { - throw new CodegenException("Compilation of " + source + " failed.\n" + out.toString()); - } - - } - - public Evaluator createEvaluator(String source, Class projectionType, - String[] names, Class[] classes, Map constants) { - Type[] types = new Type[classes.length]; - for (int i = 0; i < types.length; i++) { - types[i] = new ClassType(TypeCategory.SIMPLE, classes[i]); - } - return createEvaluator(source, new ClassType(TypeCategory.SIMPLE, projectionType), names, - types, classes, constants); - } + Evaluator createEvaluator(String source, Class projectionType, String[] names, Class[] classes, Map constants); /** * Create a new Evaluator instance - * + * * @param - * projection type + * projection type * @param source - * expression in Java source code form + * expression in Java source code form * @param projection - * type of the source expression + * type of the source expression * @param names - * names of the arguments + * names of the arguments * @param types - * types of the arguments + * types of the arguments * @param constants * @return */ - @SuppressWarnings("unchecked") - public Evaluator createEvaluator(String source, ClassType projection, String[] names, - Type[] types, Class[] classes, Map constants) { - try { - String id = toId(source, projection.getJavaClass(), types); - Class clazz; - try { - clazz = loader.loadClass(id); - } catch (ClassNotFoundException e) { - compile(source, projection, names, types, id, constants); - // reload - clazz = loader.loadClass(id); - } - - Object object = !constants.isEmpty() ? clazz.newInstance() : null; - - for (Map.Entry entry : constants.entrySet()) { - Field field = clazz.getField(entry.getKey()); - field.set(object, entry.getValue()); - } - - Method method = clazz.getMethod("eval", classes); - return new MethodEvaluator(method, object, (Class) projection.getJavaClass()); - } catch (ClassNotFoundException e) { - throw new CodegenException(e); - } catch (SecurityException e) { - throw new CodegenException(e); - } catch (NoSuchMethodException e) { - throw new CodegenException(e); - } catch (NoSuchFieldException e) { - throw new CodegenException(e); - } catch (UnsupportedEncodingException e) { - throw new CodegenException(e); - } catch (IOException e) { - throw new CodegenException(e); - } catch (InstantiationException e) { - throw new CodegenException(e); - } catch (IllegalAccessException e) { - throw new CodegenException(e); - } - - } - - protected String toId(String source, Class returnType, Type... types) { - StringBuilder b = new StringBuilder("Q"); - b.append("_").append(source.hashCode()); - b.append("_").append(returnType.getName().hashCode()); - for (Type type : types) { - b.append("_").append(type.getFullName().hashCode()); - } - return b.toString().replace('-', '0'); - } - + @SuppressWarnings(value = "unchecked") + Evaluator createEvaluator(String source, ClassType projection, String[] names, Type[] types, Class[] classes, Map constants); + } diff --git a/src/main/java/com/mysema/codegen/JDKEvaluatorFactory.java b/src/main/java/com/mysema/codegen/JDKEvaluatorFactory.java new file mode 100644 index 000000000..60231c44e --- /dev/null +++ b/src/main/java/com/mysema/codegen/JDKEvaluatorFactory.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2010 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.codegen; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import com.mysema.codegen.model.ClassType; +import com.mysema.codegen.model.Parameter; +import com.mysema.codegen.model.SimpleType; +import com.mysema.codegen.model.Type; +import com.mysema.codegen.model.TypeCategory; +import com.mysema.codegen.support.ClassUtils; + +/** + * JDKEvaluatorFactory is a factory implementation for creating Evaluator instances + * + * @author tiwe + * + */ +public class JDKEvaluatorFactory extends AbstractEvaluatorFactory { + + private final MemFileManager fileManager; + + private final String classpath; + + private final List compilationOptions; + + private final JavaCompiler compiler; + + public JDKEvaluatorFactory(URLClassLoader parent) { + this(parent, ToolProvider.getSystemJavaCompiler()); + } + + public JDKEvaluatorFactory(URLClassLoader parent, JavaCompiler compiler) { + this.fileManager = new MemFileManager(parent, compiler.getStandardFileManager(null, null, null)); + this.compiler = compiler; + this.classpath = SimpleCompiler.getClassPath(parent); + this.loader = fileManager.getClassLoader(StandardLocation.CLASS_OUTPUT); + this.compilationOptions = Arrays.asList("-classpath", classpath, "-g:none"); + } + + protected void compile(String source, ClassType projectionType, String[] names, Type[] types, + String id, Map constants) throws IOException { + // create source + StringWriter writer = new StringWriter(); + JavaWriter javaw = new JavaWriter(writer); + SimpleType idType = new SimpleType(id, "", id); + javaw.beginClass(idType, null); + Parameter[] params = new Parameter[names.length]; + for (int i = 0; i < params.length; i++) { + params[i] = new Parameter(names[i], types[i]); + } + + for (Map.Entry entry : constants.entrySet()) { + Type type = new ClassType(TypeCategory.SIMPLE, ClassUtils.normalize(entry.getValue().getClass())); + javaw.publicField(type, entry.getKey()); + } + + if (constants.isEmpty()) { + javaw.beginStaticMethod(projectionType, "eval", params); + } else { + javaw.beginPublicMethod(projectionType, "eval", params); + } + javaw.append(source); + javaw.end(); + javaw.end(); + + // compile + SimpleJavaFileObject javaFileObject = new MemSourceFileObject(id, writer.toString()); + Writer out = new StringWriter(); + + CompilationTask task = compiler.getTask(out, fileManager, null, compilationOptions, null, + Collections.singletonList(javaFileObject)); + if (!task.call().booleanValue()) { + throw new CodegenException("Compilation of " + source + " failed.\n" + out.toString()); + } + + } + +} diff --git a/src/test/java/com/mysema/codegen/ComplexEvaluationTest.java b/src/test/java/com/mysema/codegen/ComplexEvaluationTest.java index b6ac331b2..6a9d55294 100644 --- a/src/test/java/com/mysema/codegen/ComplexEvaluationTest.java +++ b/src/test/java/com/mysema/codegen/ComplexEvaluationTest.java @@ -7,7 +7,6 @@ package com.mysema.codegen; import static org.junit.Assert.*; -import java.net.URLClassLoader; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -18,11 +17,11 @@ import com.mysema.codegen.model.ClassType; import com.mysema.codegen.model.Type; import com.mysema.codegen.model.TypeCategory; import com.mysema.codegen.model.Types; +import com.mysema.codegen.support.Cat; public class ComplexEvaluationTest { - private EvaluatorFactory factory = new EvaluatorFactory((URLClassLoader) getClass() - .getClassLoader()); + private EvaluatorFactory factory = new ECJEvaluatorFactory(getClass().getClassLoader()); @Test @SuppressWarnings("unchecked") @@ -49,4 +48,116 @@ public class ComplexEvaluationTest { assertEquals(Arrays.asList("2", "4"), evaluator.evaluate(a_, b_)); } + @Test + @SuppressWarnings("unchecked") + public void ComplexClassLoading() { + ClassType resultType = new ClassType(TypeCategory.LIST, List.class, Types.OBJECTS); + StringBuilder source = new StringBuilder(); + source.append("java.util.List rv = new java.util.ArrayList();\n"); + source.append("for (com.mysema.codegen.support.Cat cat : (java.util.List)cat_){\n"); + source.append("for (com.mysema.codegen.support.Cat otherCat : (java.util.List)otherCat_){\n"); + source.append("rv.add(new Object[]{cat,otherCat});\n"); + source.append("}\n"); + source.append("}\n"); + source.append("return rv;\n"); + + Cat fuzzy = new Cat("fuzzy"); + Cat spot = new Cat("spot"); + Cat mittens = new Cat("mittens"); + Cat sparkles = new Cat("sparkles"); + + List a_ = Arrays.asList(fuzzy, spot); + List b_ = Arrays.asList(mittens, sparkles); + + ClassType argType = new ClassType(TypeCategory.LIST, List.class, new ClassType(Cat.class)); + Evaluator evaluator = factory.createEvaluator(source.toString(), resultType, + new String[] { "cat_", "otherCat_" }, new Type[] { argType, argType }, new Class[] { + List.class, List.class }, Collections. emptyMap()); + + Object[][] expResults = { {fuzzy, mittens}, {fuzzy, sparkles}, {spot, mittens}, {spot, sparkles} }; + List result = evaluator.evaluate(a_, b_); + assertEquals(expResults.length, result.size()); + + for (int i = 0; i < expResults.length; i++) { + assertEquals(expResults[i].length, result.get(i).length); + for (int j = 0; j < expResults[i].length; j++) { + assertEquals(expResults[i][j], result.get(i)[j]); + } + } + } + + @Test(expected=CodegenException.class) + @SuppressWarnings("unchecked") + public void ComplexClassLoadingFailure() { + ClassType resultType = new ClassType(TypeCategory.LIST, List.class, Types.STRING); + StringBuilder source = new StringBuilder(); + source.append("java.util.List rv = (java.util.List) new java.util.ArrayList();\n"); + source.append("for (String a : a_){\n"); + source.append(" for (String b : b_){\n"); + source.append(" if (a.equals(b)){\n"); + source.append(" rv.add(a);\n"); + source.append(" }\n"); + source.append(" }\n"); + source.append("}\n"); + source.append("return rv;"); + + Evaluator evaluator = factory.createEvaluator(source.toString(), resultType, + new String[] { "a_", "b_" }, new Type[] { resultType, resultType }, new Class[] { + List.class, List.class }, Collections. emptyMap()); + + List a_ = Arrays.asList("1", "2", "3", "4"); + List b_ = Arrays.asList("2", "4", "6", "8"); + + assertEquals(Arrays.asList("2", "4"), evaluator.evaluate(a_, b_)); + } + + @Test + @SuppressWarnings("unchecked") + public void ComplexPrimitiveType() { + ClassType resultType = new ClassType(TypeCategory.LIST, List.class, Types.BOOLEAN); + StringBuilder source = new StringBuilder(); + source.append("java.util.List rv = new java.util.ArrayList();\n"); + source.append("for (boolean a : a_){\n"); + source.append(" for (boolean b : b_){\n"); + source.append(" if (a == b){\n"); + source.append(" rv.add(a);\n"); + source.append(" }\n"); + source.append(" }\n"); + source.append("}\n"); + source.append("return rv;"); + + Evaluator evaluator = factory.createEvaluator(source.toString(), resultType, + new String[] { "a_", "b_" }, new Type[] { resultType, resultType }, new Class[] { + List.class, List.class }, Collections. emptyMap()); + + List a_ = Arrays.asList(true, true, true); + List b_ = Arrays.asList(false, false, true); + + assertEquals(Arrays.asList(true, true, true), evaluator.evaluate(a_, b_)); + } + + @Test + @SuppressWarnings("unchecked") + public void ComplexEmbeddedClass() { + ClassType resultType = new ClassType(TypeCategory.LIST, List.class, Types.BOOLEAN); + StringBuilder source = new StringBuilder(); + source.append("java.util.List rv = new java.util.ArrayList();\n"); + source.append("for (boolean a : a_){\n"); + source.append(" for (boolean b : b_){\n"); + source.append(" if (a == b && new TestEmbedded().DO_RETURN()){\n"); + source.append(" rv.add(a);\n"); + source.append(" }\n"); + source.append(" }\n"); + source.append("}\n"); + source.append("return rv;} private static class TestEmbedded { public TestEmbedded() {} public boolean DO_RETURN() { return true; } "); + + Evaluator evaluator = factory.createEvaluator(source.toString(), resultType, + new String[] { "a_", "b_" }, new Type[] { resultType, resultType }, new Class[] { + List.class, List.class }, Collections. emptyMap()); + + List a_ = Arrays.asList(true, true, true); + List b_ = Arrays.asList(false, false, true); + + assertEquals(Arrays.asList(true, true, true), evaluator.evaluate(a_, b_)); + } } diff --git a/src/test/java/com/mysema/codegen/ECJEvaluatorFactoryTest.java b/src/test/java/com/mysema/codegen/ECJEvaluatorFactoryTest.java new file mode 100644 index 000000000..a3f33c5d9 --- /dev/null +++ b/src/test/java/com/mysema/codegen/ECJEvaluatorFactoryTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2010 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.codegen; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class ECJEvaluatorFactoryTest { + + public static class TestEntity { + + private final String name; + + public TestEntity(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + } + + private EvaluatorFactory factory; + + private List names = Arrays.asList("a", "b"); + + private List> ints = Arrays.> asList(int.class, int.class); + + private List> strings = Arrays.> asList(String.class, String.class); + + private List> string_int = Arrays.> asList(String.class, int.class); + + @Before + public void setUp() throws IOException { + factory = new ECJEvaluatorFactory(getClass().getClassLoader()); + } + + @Test + public void Simple() { + for (String expr : Arrays.asList("a.equals(b)", "a.startsWith(b)", "a.equalsIgnoreCase(b)")) { + long start = System.currentTimeMillis(); + evaluate(expr, boolean.class, names, strings, Arrays.asList("a", "b"), + Collections. emptyMap()); + long duration = System.currentTimeMillis() - start; + System.err.println(expr + " took " + duration + "ms\n"); + } + + for (String expr : Arrays.asList("a != b", "a < b", "a > b", "a <= b", "a >= b")) { + long start = System.currentTimeMillis(); + evaluate(expr, boolean.class, names, ints, Arrays.asList(0, 1), + Collections. emptyMap()); + long duration = System.currentTimeMillis() - start; + System.err.println(expr + " took " + duration + "ms\n"); + } + } + + @Test + public void Results() { + // String + String + test("a + b", String.class, names, strings, Arrays.asList("Hello ", "World"), "Hello World"); + + // String + int + test("a.substring(b)", String.class, names, string_int, + Arrays. asList("Hello World", 6), "World"); + + // int + int + test("a + b", int.class, names, ints, Arrays.asList(1, 2), 3); + } + + @Test + public void WithConstants() { + Map constants = new HashMap(); + constants.put("x", "Hello World"); + List> types = Arrays.> asList(String.class); + List names = Arrays.asList("a"); + assertEquals( + Boolean.TRUE, + evaluate("a.equals(x)", boolean.class, names, types, Arrays.asList("Hello World"), + constants)); + assertEquals( + Boolean.FALSE, + evaluate("a.equals(x)", boolean.class, names, types, Arrays.asList("Hello"), + constants)); + } + + @Test + public void CustomType() { + test("a.getName()", String.class, Collections.singletonList("a"), + Collections.> singletonList(TestEntity.class), + Arrays.asList(new TestEntity("Hello World")), "Hello World"); + } + + private void test(String source, Class projectionType, List names, + List> types, List args, Object expectedResult) { + Assert.assertEquals( + expectedResult, + evaluate(source, projectionType, names, types, args, + Collections. emptyMap())); + } + + private Object evaluate(String source, Class projectionType, List names, + List> types, List args, Map constants) { + Evaluator evaluator = factory.createEvaluator("return " + source + ";", projectionType, + names.toArray(new String[names.size()]), types.toArray(new Class[types.size()]), + constants); + return evaluator.evaluate(args.toArray()); + } + +} diff --git a/src/test/java/com/mysema/codegen/EvaluatorFactoryTest.java b/src/test/java/com/mysema/codegen/JDKEvaluatorFactoryTest.java similarity index 94% rename from src/test/java/com/mysema/codegen/EvaluatorFactoryTest.java rename to src/test/java/com/mysema/codegen/JDKEvaluatorFactoryTest.java index f7d3b27a2..b12fb8f73 100644 --- a/src/test/java/com/mysema/codegen/EvaluatorFactoryTest.java +++ b/src/test/java/com/mysema/codegen/JDKEvaluatorFactoryTest.java @@ -19,7 +19,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; -public class EvaluatorFactoryTest { +public class JDKEvaluatorFactoryTest { public static class TestEntity { @@ -47,7 +47,7 @@ public class EvaluatorFactoryTest { @Before public void setUp() throws IOException { - factory = new EvaluatorFactory((URLClassLoader) getClass().getClassLoader()); + factory = new JDKEvaluatorFactory((URLClassLoader) getClass().getClassLoader()); } @Test diff --git a/src/test/java/com/mysema/codegen/support/Cat.java b/src/test/java/com/mysema/codegen/support/Cat.java new file mode 100644 index 000000000..367f79f7e --- /dev/null +++ b/src/test/java/com/mysema/codegen/support/Cat.java @@ -0,0 +1,159 @@ +/* + * Copyright 2011, Mysema Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mysema.codegen.support; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class Cat { + + private int breed; + + private java.sql.Date dateField; + + public enum Color { + BLUE, GREEN, BROWN + } + private Color eyecolor; + + private List kittens; + + private Cat[] kittenArray; + + private Map kittensByName; + + private Cat mate; + + private String stringAsSimple; + + private java.sql.Time timeField; + + private String name; + + public void setName(String name) { + this.name = name; + } + + private Date birthdate; + private int id; + + public Cat() { + this.kittensByName = Collections.emptyMap(); + } + + public Cat(String name) { + Cat kitten = new Cat(); + this.kittens = Arrays.asList(kitten); + this.kittenArray = new Cat[]{kitten}; + this.kittensByName = Collections.singletonMap("Kitty", kitten); + this.name = name; + } + + public Cat(String name, String kittenName){ + this(name); + kittens.get(0).setName(kittenName); + } + + public Cat(String name, int id) { + this(name); + this.id = id; + } + + public Cat(String name, int id, Date birthdate) { + this(name, id); + this.birthdate = new Date(birthdate.getTime()); + this.dateField = new java.sql.Date(birthdate.getTime()); + this.timeField = new java.sql.Time(birthdate.getTime()); + } + + public int getBreed() { + return breed; + } + + public java.sql.Date getDateField() { + return dateField; + } + + public Color getEyecolor() { + return eyecolor; + } + + public List getKittens() { + return kittens; + } + + public Map getKittensByName() { + return kittensByName; + } + + public Cat getMate() { + return mate; + } + + public String getStringAsSimple() { + return stringAsSimple; + } + + public java.sql.Time getTimeField() { + return timeField; + } + + public void setBreed(int breed) { + this.breed = breed; + } + + public void setDateField(java.sql.Date dateField) { + this.dateField = new java.sql.Date(dateField.getTime()); + } + + public void setEyecolor(Color eyecolor) { + this.eyecolor = eyecolor; + } + + public void setKittens(List kittens) { + this.kittens = kittens; + } + + public void setKittensByName(Map kittensByName) { + this.kittensByName = kittensByName; + } + + public void setMate(Cat mate) { + this.mate = mate; + } + + public void setStringAsSimple(String stringAsSimple) { + this.stringAsSimple = stringAsSimple; + } + + public void setTimeField(java.sql.Time timeField) { + this.timeField = timeField; + } + + public Cat[] getKittenArray() { + return kittenArray; + } + + public void setKittenArray(Cat[] kittenArray) { + this.kittenArray = kittenArray.clone(); + } + + public String toString() { + return name; + } + +} diff --git a/template.mf b/template.mf index 2b838ec97..adc2cf5b1 100644 --- a/template.mf +++ b/template.mf @@ -5,4 +5,5 @@ Bundle-ManifestVersion: 2 Import-Template: javax.annotation.*;version="0", javax.tools.*;version="0", + org.eclipse.jdt.*;version="3.7.2", com.google.common.*;version="${guava.version}"