diff --git a/querydsl-collections/.classpath b/querydsl-collections/.classpath new file mode 100644 index 000000000..ecf0cf804 --- /dev/null +++ b/querydsl-collections/.classpath @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/querydsl-collections/.project b/querydsl-collections/.project new file mode 100644 index 000000000..6f7ec4e18 --- /dev/null +++ b/querydsl-collections/.project @@ -0,0 +1,13 @@ + + querydsl-collections + Hibernate / HQL support for querydsl + + + + org.eclipse.jdt.core.javabuilder + + + + org.eclipse.jdt.core.javanature + + \ No newline at end of file diff --git a/querydsl-collections/.settings/org.eclipse.jdt.core.prefs b/querydsl-collections/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..5cbd770f6 --- /dev/null +++ b/querydsl-collections/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +#Sun Dec 07 13:18:28 EET 2008 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.source=1.5 +org.eclipse.jdt.core.compiler.compliance=1.5 diff --git a/querydsl-collections/pom.xml b/querydsl-collections/pom.xml new file mode 100644 index 000000000..ede757fe3 --- /dev/null +++ b/querydsl-collections/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + com.mysema.querydsl + querydsl-root + 0.2.8-SNAPSHOT + + + com.mysema.querydsl + querydsl-collections + Querydsl collections + Hibernate / HQL support for querydsl + jar + + + + com.mysema.querydsl + querydsl-core + ${project.parent.version} + + + net.sourceforge.collections + collections-generic + 4.01 + + + + janino + janino + 2.5.10 + + + commons-lang + commons-lang + 2.3 + + + + + + com.mysema.querydsl + querydsl-apt + ${project.parent.version} + provided + + + + + + log4j + log4j + 1.2.14 + + + + org.slf4j + slf4j-log4j12 + 1.3.1 + + + + + \ No newline at end of file diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/ColQuery.java b/querydsl-collections/src/main/java/com/mysema/query/collections/ColQuery.java new file mode 100644 index 000000000..794a7468a --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/ColQuery.java @@ -0,0 +1,94 @@ +package com.mysema.query.collections; + +import java.util.*; + +import org.codehaus.janino.ExpressionEvaluator; + +import com.mysema.query.QueryBase; +import com.mysema.query.collections.internal.JavaSerializer; +import com.mysema.query.collections.internal.Iterators.FilteringIterator; +import com.mysema.query.collections.internal.Iterators.ProjectingIterator; +import com.mysema.query.grammar.types.Expr; + + +/** + * ColQuery provides + * + * @author tiwe + * @version $Id$ + */ +public class ColQuery> extends QueryBase{ + + private Map, Iterable> entityToIterable = new HashMap,Iterable>(); + + public Iterator createIterator(Expr projection){ + // from + Expr source = joins.get(0).getTarget(); + Iterator it = null; + if (joins.size() == 1){ + it = entityToIterable.get(source).iterator(); + }else{ + // TODO + } + + // order by + if (!orderBy.isEmpty()){ + // TODO + } + + // where + if (where.self() != null){ + try { + ExpressionEvaluator ev = new JavaSerializer().handle(where.self()) + .createExpressionEvaluator(source, boolean.class); + it = new FilteringIterator(it, ev); + } catch (Exception e) { + String error = "Caught " + e.getClass().getName(); + throw new RuntimeException(error, e); + } + } + + // group by NOT SUPPORTED + // having NOT SUPPORTED + + // select + if (source.equals(projection)){ + return (Iterator)it; + }else{ + try { + ExpressionEvaluator ev = new JavaSerializer().handle(projection) + .createExpressionEvaluator(source, projection); + return new ProjectingIterator(it, ev); + } catch (Exception e) { + String error = "Caught " + e.getClass().getName(); + throw new RuntimeException(error, e); + } + } + } + + public S from(Expr.Entity entity, A first, A... rest){ + from(entity); + List list = new ArrayList(rest.length +1); + list.add(first); + list.addAll(Arrays.asList(rest)); + entityToIterable.put(entity, list); + return (S)this; + } + + public S from(Expr.Entity entity, Iterable col){ + from(entity); + entityToIterable.put(entity, col); + return (S)this; + } + + public Iterable iterate(final Expr projection){ + select(projection); + return new Iterable(){ + public Iterator iterator() { + return createIterator(projection); + } + }; + } + + +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/internal/ColOps.java b/querydsl-collections/src/main/java/com/mysema/query/collections/internal/ColOps.java new file mode 100644 index 000000000..0b27c2c6d --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/internal/ColOps.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2008 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.query.collections.internal; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import com.mysema.query.grammar.Ops; +import com.mysema.query.grammar.Ops.Op; +import com.mysema.query.grammar.types.PathMetadata; +import com.mysema.query.grammar.types.PathMetadata.PathType; + +/** + * HqlOps provides. + * + * @author tiwe + * @version $Id$ + */ +public class ColOps { + + private static final Map,java.lang.String> patterns = new HashMap,java.lang.String>(); + + + static{ + String functions = ColOps.class.getName(); + // boolean + patterns.put(Ops.AND, "%s && %s"); + patterns.put(Ops.NOT, "!%s"); + patterns.put(Ops.OR, "%s || %s"); + patterns.put(Ops.XNOR, "!(%s ^ %s)"); + patterns.put(Ops.XOR, "%s ^ %s"); + + // comparison + patterns.put(Ops.BETWEEN, functions+".between(%s,%s,%s)"); + patterns.put(Ops.NOTBETWEEN, "!"+functions+".between(%s,%s,%s)"); + patterns.put(Ops.GOE, "%s >= %s"); + patterns.put(Ops.GT, "%s > %s"); + patterns.put(Ops.LOE, "%s <= %s"); + patterns.put(Ops.LT, "%s < %s"); + + patterns.put(Ops.AFTER, "%s > %s"); + patterns.put(Ops.BEFORE, "%s < %s"); + + // numeric + patterns.put(Ops.ADD, "%s + %s"); + patterns.put(Ops.DIV, "%s / %s"); + patterns.put(Ops.MOD, "%s % %s"); + patterns.put(Ops.MULT,"%s * %s"); + patterns.put(Ops.SUB, "%s - %s"); + patterns.put(Ops.SQRT, "Math.sqrt(%s)"); + + // numeric aggregates +// patterns.put(OpNumberAgg.AVG, "avg(%s)"); +// patterns.put(OpNumberAgg.MAX, "max(%s)"); +// patterns.put(OpNumberAgg.MIN, "min(%s)"); + + // various + patterns.put(Ops.EQ, "%s.equals(%s)"); + patterns.put(Ops.ISTYPEOF, "%s.class.equals(%s)"); + patterns.put(Ops.NE, "%s != %s"); + patterns.put(Ops.IN, "%2$s.contains(%1$s)"); + patterns.put(Ops.NOTIN, "!%2$s.contains(%1$s)"); + patterns.put(Ops.ISNULL, "%s == null"); + patterns.put(Ops.ISNOTNULL, "%s != null"); + + // string + patterns.put(Ops.CONCAT, "%s + %s"); + patterns.put(Ops.LIKE, functions+".like(%s,%s)"); + patterns.put(Ops.LOWER, "%s.toLowerCase()"); + patterns.put(Ops.SUBSTR1ARG, "%s.substring(%s)"); + patterns.put(Ops.SUBSTR2ARGS, "%s.substring(%s,%s)"); + patterns.put(Ops.TRIM, "%s.trim()"); + patterns.put(Ops.UPPER, "%s.toUpperCase()"); + + // path types + for (PathType type : new PathType[]{PathMetadata.LISTVALUE, PathMetadata.LISTVALUE_CONSTANT, PathMetadata.MAPVALUE, PathMetadata.MAPVALUE_CONSTANT}){ + patterns.put(type,"%s.get(%s)"); + } + patterns.put(PathMetadata.PROPERTY,"%s.%s"); + patterns.put(PathMetadata.SIZE,"%s.size()"); + patterns.put(PathMetadata.VARIABLE,"%s"); + + } + + public static java.lang.String getPattern(Op op){ + return patterns.get(op); + } + + public static > boolean between(A a, A b, A c){ + return a.compareTo(b) > 0 && a.compareTo(c) < 0; + } + + public static boolean like(String source, String pattern){ + return Pattern.compile(pattern.replace("%", ".*")).matcher(source).matches(); + } + +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/internal/Iterators.java b/querydsl-collections/src/main/java/com/mysema/query/collections/internal/Iterators.java new file mode 100644 index 000000000..5af22eab8 --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/internal/Iterators.java @@ -0,0 +1,74 @@ +package com.mysema.query.collections.internal; + +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; + +import org.apache.commons.collections15.Predicate; +import org.apache.commons.collections15.iterators.FilterIterator; +import org.codehaus.janino.ExpressionEvaluator; + +/** + * Iterators provides + * + * @author tiwe + * @version $Id$ + */ +public class Iterators { + private Iterators(){} + + public static class FilteringIterator extends FilterIterator{ + public FilteringIterator(Iterator it, final ExpressionEvaluator ev) { + super((Iterator)it, new Predicate(){ + public boolean evaluate(RT object) { + try { + return (Boolean) ev.evaluate(new Object[]{object}); + } catch (InvocationTargetException e) { + String error = "Caught " + e.getClass().getName(); + throw new RuntimeException(error, e); + } + } + }); + } + + } + + public abstract static class ItBase implements Iterator{ + public void remove() { + } + } + + public static class ProjectingIterator extends WrappingIt{ + private ExpressionEvaluator ev; + public ProjectingIterator(Iterator it, ExpressionEvaluator ev) { + super(it); + try { + this.ev = ev; + } catch (Exception e) { + String error = "Caught " + e.getClass().getName(); + throw new RuntimeException(error, e); + } + } + public RT next() { + try { + return (RT) ev.evaluate(new Object[]{nextFromOrig()}); + } catch (InvocationTargetException e) { + String error = "Caught " + e.getClass().getName(); + throw new RuntimeException(error, e); + } + } + } + + public abstract static class WrappingIt extends ItBase{ + private Iterator it; + public WrappingIt(Iterator it) { + this.it = it; + } + public boolean hasNext() { + return it.hasNext(); + } + protected Object nextFromOrig() { + return it.next(); + } + + } +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/internal/JavaSerializer.java b/querydsl-collections/src/main/java/com/mysema/query/collections/internal/JavaSerializer.java new file mode 100644 index 000000000..af38ef589 --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/internal/JavaSerializer.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2008 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.query.collections.internal; + +import static com.mysema.query.grammar.types.PathMetadata.LISTVALUE_CONSTANT; +import static com.mysema.query.grammar.types.PathMetadata.PROPERTY; +import static com.mysema.query.grammar.types.PathMetadata.VARIABLE; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang.StringUtils; +import org.codehaus.janino.ExpressionEvaluator; + +import com.mysema.query.grammar.Ops.Op; +import com.mysema.query.grammar.types.Expr; +import com.mysema.query.grammar.types.Operation; +import com.mysema.query.grammar.types.Path; +import com.mysema.query.grammar.types.VisitorAdapter; +import com.mysema.query.grammar.types.Alias.Simple; +import com.mysema.query.grammar.types.Alias.ToPath; +import com.mysema.query.grammar.types.PathMetadata.PathType; + + +/** + * HqlSerializer provides. + * + * @author tiwe + * @version $Id$ + */ +public class JavaSerializer extends VisitorAdapter{ + + private StringBuilder builder = new StringBuilder(); + + private List constants = new ArrayList(); + + private JavaSerializer _append(String str) { + builder.append(str); + return this; + } + + private String _toString(Expr expr, boolean wrap) { + StringBuilder old = builder; + builder = new StringBuilder(); + if (wrap) builder.append("("); + handle(expr); + if (wrap) builder.append(")"); + String ret = builder.toString(); + builder = old; + return ret; + } + + @Override + protected void visit(Expr.Constant expr) { + _append("a"); + if (!constants.contains(expr.getConstant())){ + constants.add(expr.getConstant()); + _append(Integer.toString(constants.size())); + }else{ + _append(Integer.toString(constants.indexOf(expr.getConstant())+1)); + } + } + + @Override + protected void visit(Operation expr) { + visitOperation(expr.getOperator(), expr.getArgs()); + } + + @Override + protected void visit(Path path) { + PathType pathType = path.getMetadata().getPathType(); + String parentAsString = null, exprAsString = null; + + if (path.getMetadata().getParent() != null){ + parentAsString = _toString((Expr)path.getMetadata().getParent(), false); + } + if (pathType == VARIABLE){ + exprAsString = path.getMetadata().getExpression().toString(); + }else if (pathType == PROPERTY ){ + String prefix = "get"; + if (((Expr)path).getType() != null && ((Expr)path).getType().equals(Boolean.class)){ + prefix = "is"; + } + exprAsString = prefix+StringUtils.capitalize(path.getMetadata().getExpression().toString())+"()"; + + }else if (pathType == LISTVALUE_CONSTANT){ + // ?!? + + }else if (path.getMetadata().getExpression() != null){ + exprAsString = _toString(path.getMetadata().getExpression(), false); + } + + String pattern = ColOps.getPattern(pathType); + if (parentAsString != null){ + _append(String.format(pattern, parentAsString, exprAsString)); + }else{ + _append(String.format(pattern, exprAsString)); + } + } + + @Override + protected void visit(Simple expr) { + // TODO Auto-generated method stub + } + + @Override + protected void visit(ToPath expr) { + // TODO Auto-generated method stub + } + + private void visitOperation(Op operator, Expr... args) { + String pattern = ColOps.getPattern(operator); + if (pattern == null) + throw new IllegalArgumentException("Got no operation pattern for " + operator); + Object[] strings = new String[args.length]; + for (int i = 0; i < strings.length; i++){ + strings[i] = _toString(args[i], true); + } + _append(String.format(pattern, strings)); + } + + public ExpressionEvaluator createExpressionEvaluator(Expr source, Class targetType) throws Exception{ + if (targetType == null) throw new IllegalArgumentException("targetType was null"); + String expr = builder.toString(); + System.out.println(expr); + + final Object[] constArray = constants.toArray(); + Class[] types = new Class[constArray.length+1]; + String[] names = new String[constArray.length+1]; + for (int i = 0; i < constArray.length; i++){ + types[i] = constArray[i].getClass(); + names[i] = "a" + (i+1); + } + types[types.length-1] = source.getType(); + names[names.length-1] = ((Path)source).getMetadata().getExpression().toString(); + + return new ExpressionEvaluator(expr, targetType, names, types){ + @Override + public Object evaluate(Object[] origArgs) throws InvocationTargetException{ + Object[] args = new Object[constArray.length + origArgs.length]; + System.arraycopy(constArray, 0, args, 0, constArray.length); + System.arraycopy(origArgs, 0, args, constArray.length, origArgs.length); + return super.evaluate(args); + } + }; + } + + public ExpressionEvaluator createExpressionEvaluator(Expr source, Expr projection) throws Exception{ + Class targetType = projection.getType(); + if (targetType == null) targetType = Object.class; + return createExpressionEvaluator(source, targetType); + } + + public String toString(){ return builder.toString(); } + +} diff --git a/querydsl-collections/src/test/java/com/mysema/query/collections/ColQueryTest.java b/querydsl-collections/src/test/java/com/mysema/query/collections/ColQueryTest.java new file mode 100644 index 000000000..401e5c4c4 --- /dev/null +++ b/querydsl-collections/src/test/java/com/mysema/query/collections/ColQueryTest.java @@ -0,0 +1,66 @@ +package com.mysema.query.collections; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import com.mysema.query.collections.Domain.Cat; +import com.mysema.query.collections.Domain.QCat; +import com.mysema.query.grammar.types.Expr; + + +/** + * ColQueryTest provides + * + * @author tiwe + * @version $Id$ + */ +public class ColQueryTest { + Cat c1 = new Cat("Kitty"); + Cat c2 = new Cat("Bob"); + Cat c3 = new Cat("Alex"); + Cat c4 = new Cat("Francis"); + + QCat cat = new QCat("cat"); + + TestQuery last; + + @Test + public void test(){ + query().select(cat.name); + assertTrue(last.res.size() == 4); + + query().select(cat.kittens); + assertTrue(last.res.size() == 4); + + query().where(cat.kittens.size().gt(0)).select(cat.name); + assertTrue(last.res.size() == 4); + + query().where(cat.name.eq("Kitty")).select(cat.name); + assertTrue(last.res.size() == 1); + + query().where(cat.name.like("Kitt%")).select(cat.name); + assertTrue(last.res.size() == 1); + } + + private TestQuery query(){ + last = new TestQuery().from(cat, c1, c2, c3, c4); + return last; + } + + private static class TestQuery extends ColQuery{ + List res = new ArrayList(); + void select(final Expr projection){ + for (Object o : iterate(projection)){ + System.out.println(o); + res.add(o); + } + System.out.println(); + } + } + + +} diff --git a/querydsl-collections/src/test/java/com/mysema/query/collections/Domain.java b/querydsl-collections/src/test/java/com/mysema/query/collections/Domain.java new file mode 100644 index 000000000..a70375a17 --- /dev/null +++ b/querydsl-collections/src/test/java/com/mysema/query/collections/Domain.java @@ -0,0 +1,115 @@ +package com.mysema.query.collections; + +import static com.mysema.query.grammar.types.PathMetadata.forListAccess; +import static com.mysema.query.grammar.types.PathMetadata.forProperty; + +import java.util.Arrays; +import java.util.List; + +import com.mysema.query.grammar.types.Expr; +import com.mysema.query.grammar.types.Path; +import com.mysema.query.grammar.types.PathMetadata; + +/** + * Domain provides + * + * @author tiwe + * @version $Id$ + */ +public class Domain { + public static class Animal { + boolean alive; + java.util.Date birthdate; + int bodyWeight, weight, toes; + Color color; + int id; + String name; + public java.util.Date getBirthdate() { + return birthdate; + } + public int getBodyWeight() { + return bodyWeight; + } + public Color getColor() { + return color; + } + public int getId() { + return id; + } + public String getName() { + return name; + } + public int getToes() { + return toes; + } + public int getWeight() { + return weight; + } + public boolean isAlive() { + return alive; + } + } + + public static class Cat extends Animal{ + int breed; + Color eyecolor; + List kittens; + Cat mate; + public Cat() {} + public Cat(String name){ + this.name = name; + this.kittens = Arrays.asList(new Cat()); + } + public int getBreed() { + return breed; + } + public Color getEyecolor() { + return eyecolor; + } + public List getKittens() { + return kittens; + } + public Cat getMate() { + return mate; + } + } + + public enum Color { + BLACK, TABBY + } + + public static class QCat extends Path.Entity{ + public final Path.Boolean alive = _boolean("alive"); + public final Path.Comparable birthdate = _comparable("birthdate",java.util.Date.class); + + public final Path.Comparable bodyWeight = _comparable("bodyWeight",java.lang.Integer.class); + public final Path.Comparable breed = _comparable("breed",java.lang.Integer.class); + public final Path.Simple color = _simple("color",Color.class); + public final Path.Simple eyecolor = _simple("eyecolor",Color.class); + public final Path.Comparable id = _comparable("id",java.lang.Integer.class); + public final Path.EntityList kittens = _entitylist("kittens",Cat.class); + public QCat mate; + public final Path.String name = _string("name"); + + public final Path.Comparable toes = _comparable("toes",java.lang.Integer.class); + public final Path.Comparable weight = _comparable("weight",java.lang.Integer.class); + public QCat(java.lang.String path) { + super(Cat.class, path); + _mate(); + } + public QCat(PathMetadata metadata) { + super(Cat.class, metadata); + } + public QCat _mate() { + if (mate == null) mate = new QCat(forProperty(this,"mate")); + return mate; + } + + public QCat kittens(Expr index) { + return new QCat(forListAccess(kittens,index)); + } + public QCat kittens(int index) { + return new QCat(forListAccess(kittens,index)); + } +} +}