#550200 : replaced JaninoEvaluator with Java Compiler API based implementation

This commit is contained in:
Timo Westkämper 2010-03-28 19:48:19 +00:00
parent 0b4c783b82
commit 46cca61022
25 changed files with 348 additions and 132 deletions

View File

@ -27,12 +27,6 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>janino</groupId>
<artifactId>janino</artifactId>
<version>2.5.10</version>
</dependency>
<!-- test -->
<dependency>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
@ -35,6 +35,7 @@ import com.mysema.query.types.Order;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.Path;
import com.mysema.query.types.expr.EBoolean;
import com.mysema.util.MultiComparator;
import com.mysema.util.MultiIterator;
/**
@ -123,15 +124,15 @@ public abstract class AbstractColQuery<Q extends AbstractColQuery<Q>>
it = handleFromWhereMultiSource(sources);
}
// group by
if (!md.getGroupBy().isEmpty()){
// TODO
// having
if (md.getHaving() != null){
it = iteratorFactory.multiArgFilter(it, sources, md.getHaving());
}
}
// // group by
// if (!md.getGroupBy().isEmpty()){
// // TODO
//
// // having
// if (md.getHaving() != null){
// it = iteratorFactory.multiArgFilter(it, sources, md.getHaving());
// }
// }
if (it.hasNext()) {
// order

View File

@ -1,3 +1,8 @@
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
import java.util.Collection;
@ -19,6 +24,10 @@ public class ColDeleteClause<T> implements DeleteClause<ColDeleteClause<T>>{
private final Collection<? extends T> col;
public ColDeleteClause(Path<T> expr, Collection<? extends T> col){
this(EvaluatorFactory.DEFAULT, expr, col);
}
public ColDeleteClause(EvaluatorFactory ef, Path<T> expr, Collection<? extends T> col){
this.query = new ColQueryImpl(ef).from(expr, col);
this.expr = expr;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
@ -17,14 +17,28 @@ import com.mysema.query.QueryMetadata;
*/
public class ColQueryImpl extends AbstractColQuery<ColQueryImpl> implements ColQuery, Cloneable{
/**
* Create a new ColQueryImpl instance
*/
public ColQueryImpl() {
super(new DefaultQueryMetadata(), EvaluatorFactory.DEFAULT);
}
/**
* Create a new ColQueryImpl instance
*
* @param evaluatorFactory
*/
public ColQueryImpl(EvaluatorFactory evaluatorFactory) {
super(new DefaultQueryMetadata(), evaluatorFactory);
}
/**
* Create a new ColQueryImpl instance
*
* @param metadata
* @param evaluatorFactory
*/
public ColQueryImpl(QueryMetadata metadata, EvaluatorFactory evaluatorFactory) {
super(metadata, evaluatorFactory);
}

View File

@ -1,12 +1,11 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.StringUtils;
@ -28,13 +27,6 @@ import com.mysema.query.types.Template;
* @version $Id$
*/
public final class ColQuerySerializer extends SerializerBase<ColQuerySerializer> {
private static final List<PathType> nonGeneric = Arrays.asList(
PathType.ARRAYVALUE,
PathType.ARRAYVALUE_CONSTANT,
PathType.PROPERTY,
PathType.VARIABLE
);
public ColQuerySerializer(ColQueryTemplates patterns) {
super(patterns);
@ -44,7 +36,8 @@ public final class ColQuerySerializer extends SerializerBase<ColQuerySerializer>
public void visit(Path<?> path) {
PathType pathType = path.getMetadata().getPathType();
if (pathType == PathType.PROPERTY){
if (pathType == PathType.PROPERTY){
// TODO : move this to PathMetadata ?!?
String prefix = "get";
if (path.getType() != null && path.getType().equals(Boolean.class)) {
prefix = "is";
@ -54,9 +47,6 @@ public final class ColQuerySerializer extends SerializerBase<ColQuerySerializer>
append(StringUtils.capitalize(path.getMetadata().getExpression().toString()) + "()");
}else{
if (!nonGeneric.contains(pathType)){
append("((").append(path.getType().getName()).append(")");
}
List<Expr<?>> args = new ArrayList<Expr<?>>(2);
if (path.getMetadata().getParent() != null){
args.add((Expr<?>)path.getMetadata().getParent());
@ -71,10 +61,7 @@ public final class ColQuerySerializer extends SerializerBase<ColQuerySerializer>
}else{
handle(args.get(element.getIndex()));
}
}
if (!nonGeneric.contains(pathType)){
append(")");
}
}
}
}
@ -109,9 +96,29 @@ public final class ColQuerySerializer extends SerializerBase<ColQuerySerializer>
@Override
protected void visitOperation(Class<?> type, Operator<?> operator, List<Expr<?>> args) {
if (operator.equals(Ops.STRING_CAST)) {
if (args.size() == 2
&& Number.class.isAssignableFrom(args.get(0).getType())
&& Number.class.isAssignableFrom(args.get(1).getType())){
if (operator == Ops.AFTER){
handle(args.get(0)).append(" > ").handle(args.get(1));
return;
}else if (operator == Ops.BEFORE){
handle(args.get(0)).append(" < ").handle(args.get(1));
return;
}else if (operator == Ops.AOE){
handle(args.get(0)).append(" >= ").handle(args.get(1));
return;
}else if (operator == Ops.BOE){
handle(args.get(0)).append(" <= ").handle(args.get(1));
return;
}
// TODO : Ops.BETWEEN
}
if (operator == Ops.STRING_CAST) {
visitCast(operator, args.get(0), String.class);
} else if (operator.equals(Ops.NUMCAST)) {
} else if (operator == Ops.NUMCAST) {
visitCast(operator, args.get(0), (Class<?>) ((Constant<?>) args.get(1)).getConstant());
} else {
super.visitOperation(type, operator, args);
@ -119,7 +126,7 @@ public final class ColQuerySerializer extends SerializerBase<ColQuerySerializer>
}
@Override
public void visit(SubQuery expr) {
public void visit(SubQuery<?> expr) {
throw new IllegalArgumentException("Not supported");
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/

View File

@ -1,3 +1,8 @@
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
import com.mysema.query.dml.UpdateClause;
@ -15,6 +20,10 @@ public class ColUpdateClause<T> implements UpdateClause<ColUpdateClause<T>>{
private final Path<T> expr;
public ColUpdateClause(Path<T> expr, Iterable<? extends T> col){
this(EvaluatorFactory.DEFAULT, expr, col);
}
public ColUpdateClause(EvaluatorFactory ef, Path<T> expr, Iterable<? extends T> col){
this.query = new ColQueryImpl(ef).from(expr, col);
this.expr = expr;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/

View File

@ -1,6 +1,11 @@
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
import java.util.Arrays;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -8,12 +13,7 @@ import java.util.Set;
import net.jcip.annotations.Immutable;
import org.codehaus.janino.CompileException;
import org.codehaus.janino.ExpressionEvaluator;
import org.codehaus.janino.Parser.ParseException;
import org.codehaus.janino.Scanner.ScanException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.lang.ClassUtils;
import com.mysema.query.QueryException;
import com.mysema.query.types.Expr;
@ -25,8 +25,6 @@ import com.mysema.query.types.Expr;
@Immutable
public class EvaluatorFactory {
private static final Logger logger = LoggerFactory.getLogger(EvaluatorFactory.class);
public static final EvaluatorFactory DEFAULT = new EvaluatorFactory(ColQueryTemplates.DEFAULT);
private final ColQueryTemplates templates;
@ -39,10 +37,10 @@ public class EvaluatorFactory {
ColQuerySerializer serializer = new ColQuerySerializer(templates);
serializer.handle(projection);
Map<Object,String> constantToLabel = serializer.getConstantToLabel();
final String javaSource = serializer.toString();
final Object[] constArray = constantToLabel.keySet().toArray();
final Class<?>[] types = new Class<?>[constArray.length + sources.size()];
final String[] names = new String[constArray.length + sources.size()];
String javaSource = serializer.toString();
Object[] constArray = constantToLabel.keySet().toArray();
Class<?>[] types = new Class<?>[constArray.length + sources.size()];
String[] names = new String[constArray.length + sources.size()];
for (int i = 0; i < constArray.length; i++) {
if (List.class.isAssignableFrom(constArray[i].getClass())){
types[i] = List.class;
@ -61,21 +59,24 @@ public class EvaluatorFactory {
types[off + i] = sources.get(i).getType();
names[off + i] = sources.get(i).toString();
}
if (logger.isInfoEnabled()) {
logger.info(javaSource + " " + Arrays.asList(names) + " " + Arrays.asList(types));
// normalize types
for (int i = 0; i < types.length; i++){
if (ClassUtils.wrapperToPrimitive(types[i]) != null){
types[i] = ClassUtils.wrapperToPrimitive(types[i]);
}
}
try {
ExpressionEvaluator evaluator = new ExpressionEvaluator(javaSource, projection.getType(), names, types);
return new JaninoEvaluator<T>(javaSource, evaluator, names, constArray);
} catch (CompileException e) {
throw new QueryException(e.getMessage() + " with source " + javaSource, e);
} catch (ParseException e) {
throw new QueryException(e.getMessage() + " with source " + javaSource, e);
} catch (ScanException e) {
throw new QueryException(e.getMessage() + " with source " + javaSource, e);
return new SimpleEvaluator<T>(javaSource, projection.getType(), names, types, constArray);
} catch (IOException e) {
throw new QueryException(e);
} catch (SecurityException e) {
throw new QueryException(e);
} catch (ClassNotFoundException e) {
throw new QueryException(e);
} catch (NoSuchMethodException e) {
throw new QueryException(e);
}
}

View File

@ -1,10 +1,17 @@
/**
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
import org.apache.commons.collections15.Predicate;
/**
* @author tiwe
*
* @param <S>
*/
public final class EvaluatorPredicate<S> implements Predicate<S> {
private final Evaluator<Boolean> ev;

View File

@ -1,10 +1,18 @@
/**
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
import org.apache.commons.collections15.Transformer;
/**
* @author tiwe
*
* @param <S>
* @param <T>
*/
public final class EvaluatorTransformer<S, T> implements Transformer<S, T> {
private final Evaluator<T> ev;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
@ -14,6 +14,7 @@ import org.apache.commons.collections15.IteratorUtils;
import com.mysema.query.types.Expr;
import com.mysema.query.types.expr.EBoolean;
import com.mysema.util.ArrayTransformer;
/**
* IteratorFactory provides Iterator utilities
@ -35,7 +36,7 @@ public class IteratorFactory {
return multiArgFilter(source, ev);
}
private <S> Iterator<S> multiArgFilter(Iterator<S> source, final Evaluator<Boolean> ev) {
private <S> Iterator<S> multiArgFilter(Iterator<S> source, Evaluator<Boolean> ev) {
return IteratorUtils.filteredIterator(source, new EvaluatorPredicate<S>(ev));
}
@ -44,11 +45,11 @@ public class IteratorFactory {
return transform(source, ev);
}
private <S, T> Iterator<T> transform(Iterator<S> source, final Evaluator<T> ev) {
private <S, T> Iterator<T> transform(Iterator<S> source, Evaluator<T> ev) {
return IteratorUtils.transformedIterator(source, new EvaluatorTransformer<S, T>(ev));
}
public <S> Iterator<S> singleArgFilter(Iterator<S> source, final Evaluator<Boolean> ev) {
public <S> Iterator<S> singleArgFilter(Iterator<S> source, Evaluator<Boolean> ev) {
return IteratorUtils.filteredIterator(source, new SingleArgEvaluatorPredicate<S>(ev));
}

View File

@ -1,45 +0,0 @@
/**
*
*/
package com.mysema.query.collections;
import java.lang.reflect.InvocationTargetException;
import org.codehaus.janino.ExpressionEvaluator;
public final class JaninoEvaluator<T> implements Evaluator<T> {
private final String javaSource;
private final ExpressionEvaluator evaluator;
private final String[] names;
private final Object[] constArray;
JaninoEvaluator(String javaSource, ExpressionEvaluator evaluator, String[] names, Object[] constArray) {
this.javaSource = javaSource;
this.evaluator = evaluator;
this.names = names.clone();
this.constArray = constArray.clone();
}
@Override
public T evaluate(Object... args) {
try {
args = EvaluatorFactory.combine(constArray.length + args.length, constArray, args);
return (T) evaluator.evaluate(args);
} catch (InvocationTargetException e) {
StringBuilder builder = new StringBuilder();
builder.append("Caught exception when evaluating '").append(javaSource);
builder.append("' with arguments ");
for (int i = 0; i < args.length; i++){
builder.append(names[i]).append(" = ").append(args[i]);
if (i < args.length -1){
builder.append(", ");
}
}
throw new RuntimeException(builder.toString(), e);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
@ -58,7 +58,7 @@ public class LimitingIterator<E> implements Iterator<E> {
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/

View File

@ -0,0 +1,133 @@
package com.mysema.query.collections;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;
import com.mysema.query.QueryException;
import com.mysema.util.JavaWriter;
import com.mysema.util.SimpleCompiler;
/**
* @author tiwe
*
*/
public class SimpleEvaluator<T> implements Evaluator<T>{
private static final File dir;
private static final ClassLoader loader;
private static final String classpath;
private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
static{
try {
dir = new File(System.getProperty("java.io.tmpdir"), "files");
dir.mkdirs();
URLClassLoader parent = (URLClassLoader) SimpleEvaluator.class.getClassLoader();
classpath = SimpleCompiler.getClassPath(parent);
loader = new URLClassLoader(new URL[]{dir.toURI().toURL()}, parent);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
private final Method method;
private final Object[] constants;
public SimpleEvaluator(
String source,
Class<? extends T> projectionType,
String[] names,
Class<?>[] types,
Object[] constants)
throws IOException, ClassNotFoundException, SecurityException, NoSuchMethodException {
this.constants = constants;
// create id for evaluator
String id = toId(source, projectionType, types);
// compile
File javaFile = new File(dir, id+".java");
if (!new File(dir, id+".class").exists()){
// long start = System.currentTimeMillis();
// create source
StringWriter writer = new StringWriter();
JavaWriter javaw = new JavaWriter(writer);
javaw.beginClass(id, null);
String[] params = new String[names.length];
for (int i = 0; i < params.length; i++){
params[i] = toName(types[i]) + " " + names[i];
}
javaw.beginStaticMethod(toName(projectionType), "eval", params);
javaw.line("return ", source, ";");
javaw.end();
javaw.end();
FileUtils.writeByteArrayToFile(javaFile, writer.toString().getBytes("ISO-8859-1"));
compiler.run(null, null, null, "-classpath", classpath, javaFile.getAbsolutePath());
// source file is not needed anymore
javaFile.delete();
// long duration = System.currentTimeMillis() - start;
// System.out.println("- " + duration + ": " + source);
}
// load class
Class<?> cl = loader.loadClass(id);
method = cl.getMethod("eval", types);
}
private static String toId(String source, Class<?> projectionType, Class<?>[] types) {
StringBuilder b = new StringBuilder("Q");
b.append("_").append(Math.abs(source.hashCode()));
b.append("_").append(Math.abs(projectionType.hashCode()));
for (Class<?> type : types){
b.append("_").append(Math.abs(type.hashCode()));
}
return b.toString();
}
private static String toName(Class<?> cl){
if (cl.isArray()){
return toName(cl.getComponentType())+"[]";
}else if (cl.getPackage() == null || cl.getPackage().getName().equals("java.lang")){
return cl.getSimpleName();
}else{
return cl.getName().replace('$', '.');
}
}
@SuppressWarnings("unchecked")
@Override
public T evaluate(Object... args) {
try {
if (constants.length > 0){
args = EvaluatorFactory.combine(constants.length + args.length, constants, args);
}
return (T) method.invoke(null, args);
} catch (IllegalAccessException e) {
throw new QueryException(e);
} catch (InvocationTargetException e) {
throw new QueryException(e);
}
}
}

View File

@ -1,10 +1,17 @@
/**
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
import org.apache.commons.collections15.Predicate;
/**
* @author tiwe
*
* @param <S>
*/
public final class SingleArgEvaluatorPredicate<S> implements Predicate<S> {
private final Evaluator<Boolean> ev;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/

View File

@ -1,10 +1,17 @@
/**
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
package com.mysema.util;
import org.apache.commons.collections15.Transformer;
/**
* @author tiwe
*
* @param <S>
*/
public final class ArrayTransformer<S> implements Transformer<S, S[]> {
@SuppressWarnings("unchecked")

View File

@ -1,17 +1,17 @@
/*
* Copyright (c) 2009 Mysema Ltd.
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.collections;
package com.mysema.util;
import java.io.Serializable;
import java.util.Comparator;
import net.jcip.annotations.Immutable;
import org.apache.commons.collections15.comparators.ComparableComparator;
import com.mysema.query.collections.Evaluator;
/**
@ -20,7 +20,6 @@ import org.apache.commons.collections15.comparators.ComparableComparator;
* @author tiwe
* @version $Id$
*/
@Immutable
public class MultiComparator implements Comparator<Object[]>, Serializable {
private static final long serialVersionUID = 1121416260773566299L;

View File

@ -26,10 +26,10 @@ public class QFile extends PComparable<File>{
private static final long serialVersionUID = -7703329992523284173L;
private static final String GET_CONTENT = QFile.class.getName() + "Utils.readFileToString({0}, {1})";
public static final QFile any = new QFile("any");
private static final String GET_CONTENT = QFile.class.getName() + "Utils.readFileToString({0}, {1})";
public static Iterable<File> walk(File dir){
return new DirectoryWalk(dir);
}

View File

@ -8,7 +8,6 @@ package com.mysema.query.file;
import java.io.File;
import java.util.Map;
import org.junit.Ignore;
import org.junit.Test;
import com.mysema.query.collections.MiniApi;
@ -39,7 +38,6 @@ public class QFileTest {
}
@Test
@Ignore
public void getContent(){
QFile anyFile = QFile.any;
Map<File,String> rv = MiniApi

View File

@ -1,3 +1,8 @@
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.file;
import java.io.File;

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010 Mysema Ltd.
* All rights reserved.
*
*/
package com.mysema.query.url;
import static com.mysema.query.types.path.PathMetadataFactory.forVariable;
import java.net.URL;
import com.mysema.query.types.PathMetadata;
import com.mysema.query.types.path.PEntity;
import com.mysema.query.types.path.PNumber;
import com.mysema.query.types.path.PSimple;
import com.mysema.query.types.path.PString;
import com.mysema.query.types.path.PathMetadataFactory;
/**
* @author tiwe
*
*/
public class QURL extends PEntity<URL>{
private static final long serialVersionUID = 9048088068716893900L;
public final PString authority = createString("authority");
public final PSimple<Object> content = createSimple("content",Object.class);
public final PNumber<Integer> defaultPort = createNumber("defaultPort",Integer.class);
public final PString file = createString("file");
public final PString host = createString("host");
public final PString path = createString("path");
public final PNumber<Integer> port = createNumber("port",Integer.class);
public final PString protocol = createString("protocol");
public final PString query = createString("query");
public final PString ref = createString("ref");
public final PString userInfo = createString("userInfo");
public QURL(PathMetadata<?> metadata) {
super(URL.class, metadata);
}
public QURL(QURL parent, String property) {
super(URL.class, PathMetadataFactory.forProperty(parent, property));
}
public QURL(String variable) {
super(URL.class, forVariable(variable));
}
}