From 153fe67d181978f2939cbe458b401c725f79affc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Westk=C3=A4mper?= Date: Tue, 6 Jan 2009 13:07:53 +0000 Subject: [PATCH] added basic alias system --- querydsl-collections/pom.xml | 23 ++- .../query/collections/AliasFactory.java | 20 --- .../mysema/query/collections/InnerQuery.java | 2 +- .../com/mysema/query/collections/MiniApi.java | 8 +- .../query/collections/SimpleExprFactory.java | 2 +- .../alias/AliasAwareExprFactory.java | 67 +++++++ .../query/collections/alias/AliasFactory.java | 79 +++++++++ .../PropertyAccessInvocationHandler.java | 163 ++++++++++++++++++ .../alias/WeakIdentityHashMap.java | 107 ++++++++++++ .../mysema/query/grammar/types/ColTypes.java | 2 +- .../mysema/query/collections/MiniApiTest.java | 28 ++- 11 files changed, 457 insertions(+), 44 deletions(-) delete mode 100644 querydsl-collections/src/main/java/com/mysema/query/collections/AliasFactory.java create mode 100644 querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasAwareExprFactory.java create mode 100644 querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasFactory.java create mode 100644 querydsl-collections/src/main/java/com/mysema/query/collections/alias/PropertyAccessInvocationHandler.java create mode 100644 querydsl-collections/src/main/java/com/mysema/query/collections/alias/WeakIdentityHashMap.java diff --git a/querydsl-collections/pom.xml b/querydsl-collections/pom.xml index 55899df71..2186055c6 100644 --- a/querydsl-collections/pom.xml +++ b/querydsl-collections/pom.xml @@ -16,7 +16,7 @@ Hibernate / HQL support for querydsl jar - + com.mysema.querydsl querydsl-core @@ -27,20 +27,25 @@ collections-generic 4.01 - + + + janino + janino + 2.5.10 + - janino - janino - 2.5.10 - - commons-lang commons-lang 2.4 - + + + cglib + cglib + 2.2 + - + com.mysema.querydsl querydsl-apt diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/AliasFactory.java b/querydsl-collections/src/main/java/com/mysema/query/collections/AliasFactory.java deleted file mode 100644 index 6d81edccd..000000000 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/AliasFactory.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.mysema.query.collections; - -/** - * AliasFactory provides - * - * @author tiwe - * @version $Id$ - */ -public class AliasFactory { - - public A createAlias(Class cl, String var){ - try { - // TODO : create real alias - return cl.newInstance(); - } catch (Exception e) { - throw new RuntimeException("error", e); - } - } - -} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/InnerQuery.java b/querydsl-collections/src/main/java/com/mysema/query/collections/InnerQuery.java index c4d4eeff5..5c29b38ae 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/InnerQuery.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/InnerQuery.java @@ -56,7 +56,7 @@ public class InnerQuery extends QueryBase { private Iterator createIterator(Expr projection) throws Exception { // from List> sources = new ArrayList>(); - MultiIterator multiIt = new MultiIterator(); + MultiIterator multiIt = new MultiIterator(); for (JoinExpression join : joins) { sources.add(join.getTarget()); multiIt.add(pathToIterable.get(join.getTarget())); diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/MiniApi.java b/querydsl-collections/src/main/java/com/mysema/query/collections/MiniApi.java index 30b5853ae..2bcff2229 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/MiniApi.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/MiniApi.java @@ -9,6 +9,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import com.mysema.query.collections.alias.AliasAwareExprFactory; +import com.mysema.query.collections.alias.AliasFactory; import com.mysema.query.grammar.Grammar; import com.mysema.query.grammar.OrderSpecifier; import com.mysema.query.grammar.types.Expr; @@ -26,14 +28,14 @@ import com.mysema.query.grammar.types.Path.*; */ public class MiniApi { - private static final ExprFactory exprFactory = new SimpleExprFactory(); - private static final AliasFactory aliasFactory = new AliasFactory(); + private static final ExprFactory exprFactory = new AliasAwareExprFactory(aliasFactory); + private static final PSimple it = new PSimple(Object.class,PathMetadata.forVariable("it")); public static A alias(Class cl, String var){ - return aliasFactory.createAlias(cl, var); + return aliasFactory.createAliasForVar(cl, var); } public static ColQuery from(Path path, A... arr){ diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/SimpleExprFactory.java b/querydsl-collections/src/main/java/com/mysema/query/collections/SimpleExprFactory.java index 6c6c8ca4a..d0edd6e26 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/collections/SimpleExprFactory.java +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/SimpleExprFactory.java @@ -26,7 +26,7 @@ import com.mysema.query.grammar.types.Path.*; * @version $Id$ */ // TODO : consider moving this later to querydsl-core -class SimpleExprFactory implements ExprFactory { +public class SimpleExprFactory implements ExprFactory { private final ExtString strExt = new ExtString(PathMetadata.forVariable("str")); diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasAwareExprFactory.java b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasAwareExprFactory.java new file mode 100644 index 000000000..adf08cd28 --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasAwareExprFactory.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2008 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.query.collections.alias; + +import java.util.Collection; +import java.util.List; + +import com.mysema.query.collections.SimpleExprFactory; +import com.mysema.query.grammar.types.ColTypes.ExtString; +import com.mysema.query.grammar.types.Path.*; + +/** + * AliasAwareExprFactory provides + * + * @author tiwe + * @version $Id$ + */ +public class AliasAwareExprFactory extends SimpleExprFactory{ + + private final AliasFactory aliasFactory; + + public AliasAwareExprFactory(AliasFactory aliasFactory){ + this.aliasFactory = aliasFactory; + } + + public PBoolean create(Boolean arg){ + return aliasFactory.isBound() ? aliasFactory.getCurrent() : super.create(arg); + } + + public PBooleanArray create(Boolean[] args){ + return aliasFactory.isBound() ? aliasFactory.getCurrent() : super.create(args); + } + + public PComponentCollection create(Collection arg) { + return aliasFactory.isBound() ? aliasFactory.>getCurrent() : super.create(arg); + } + + public > PComparable create(D arg){ + return aliasFactory.isBound() ? aliasFactory.>getCurrent() : super.create(arg); + } + + @SuppressWarnings("unchecked") + public PSimple create(D arg){ + PSimple path = (PSimple) aliasFactory.pathForAlias(arg); + return path != null ? path : super.create(arg); + } + + public > PComparableArray create(D[] args){ + return aliasFactory.isBound() ? aliasFactory.>getCurrent() : super.create(args); + } + + public PComponentList create(List arg) { + return aliasFactory.isBound() ? aliasFactory.>getCurrent() : super.create(arg); + } + + public ExtString create(String arg){ + return aliasFactory.isBound() ? aliasFactory.getCurrent() : super.create(arg); + } + + public PStringArray create(String[] args){ + return aliasFactory.isBound() ? aliasFactory.getCurrent() : super.create(args); + } + +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasFactory.java b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasFactory.java new file mode 100644 index 000000000..6e30e6409 --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/AliasFactory.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2008 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.query.collections.alias; + +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; + +import com.mysema.query.grammar.types.Path; +import com.mysema.query.grammar.types.PathMetadata; + +/** + * AliasFactory provides + * + * @author tiwe + * @version $Id$ + */ +public class AliasFactory { + + private final ThreadLocal>> bindings = new ThreadLocal>>() { + @Override + protected WeakIdentityHashMap> initialValue() { + return new WeakIdentityHashMap>(); + } + }; + + private final ThreadLocal> current = new ThreadLocal>(); + + public A createAliasForProp(Class cl, Object parent, String prop, Path path){ + A proxy = createProxy(cl); + bindings.get().put(proxy, path); + return proxy; + } + + public A createAliasForVar(Class cl, String var){ + Path path = new Path.PSimple(cl, PathMetadata.forVariable(var)); + A proxy = createProxy(cl); + bindings.get().put(proxy, path); + return proxy; + } + + @SuppressWarnings("unchecked") + private A createProxy(Class cl) { + Enhancer enhancer = new Enhancer(); + enhancer.setClassLoader(AliasFactory.class.getClassLoader()); + if (cl.isInterface()){ + enhancer.setInterfaces(new Class[]{cl}); + }else{ + enhancer.setSuperclass(cl); + } + // creates one handler per proxy + MethodInterceptor handler = new PropertyAccessInvocationHandler(this); + enhancer.setCallback(handler); + A rv = (A)enhancer.create(); + return rv; + } + + @SuppressWarnings("unchecked") + public > A getCurrent() { + A rv = (A)current.get(); + current.remove(); + return rv; + } + + public boolean isBound() { + return current.get() != null; + } + + public Path pathForAlias(Object key){ + return bindings.get().get(key); + } + + public void setCurrent(Path path){ + current.set(path); + } + +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/alias/PropertyAccessInvocationHandler.java b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/PropertyAccessInvocationHandler.java new file mode 100644 index 000000000..c55533e59 --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/PropertyAccessInvocationHandler.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2008 Mysema Ltd. + * All rights reserved. + * + */ +package com.mysema.query.collections.alias; + +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; + +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import org.apache.commons.lang.StringUtils; + +import com.mysema.query.grammar.types.Path; +import com.mysema.query.grammar.types.PathMetadata; +import com.mysema.query.grammar.types.ColTypes.ExtString; +import com.mysema.query.grammar.types.Path.PCollection; + +/** + * PropertyAccessInvocationHandler provides + * + * @author tiwe + * @version $Id$ + */ +class PropertyAccessInvocationHandler implements MethodInterceptor{ + + private AliasFactory aliasFactory; + + private final Map propToObj = new HashMap(); + + private final Map> propToPath = new HashMap>(); + + public PropertyAccessInvocationHandler(AliasFactory aliasFactory){ + this.aliasFactory = aliasFactory; + } + + public Object intercept(Object proxy, Method method, Object[] args, + MethodProxy methodProxy) throws Throwable { + if (isGetter(method)){ + String ptyName = propertyNameForGetter(method); + Class ptyClass = method.getReturnType(); + + Object rv; + if (propToObj.containsKey(ptyName)){ + rv = propToObj.get(ptyName); + }else{ + Path parentPath = aliasFactory.pathForAlias(proxy); + if (parentPath == null) throw new IllegalArgumentException("No path for " + proxy); + PathMetadata pm = PathMetadata.forProperty(parentPath, ptyName); + rv = makeNew(ptyClass, proxy, ptyName, pm); + } + aliasFactory.setCurrent(propToPath.get(ptyName)); + return rv; + + }else if (method.getName().equals("size")){ + String ptyName = "_size"; + Object rv; + if (propToObj.containsKey(ptyName)){ + rv = propToObj.get(ptyName); + }else{ + Path parentPath = aliasFactory.pathForAlias(proxy); + if (parentPath == null) throw new IllegalArgumentException("No path for " + proxy); + PathMetadata pm = PathMetadata.forSize((PCollection) parentPath); + rv = makeNew(Integer.class, proxy, ptyName, pm); + } + aliasFactory.setCurrent(propToPath.get(ptyName)); + return rv; + + }else{ + return methodProxy.invokeSuper(proxy, args); + } + } + + private boolean isGetter(Method m){ + return m.getParameterTypes().length == 0 && + (m.getName().startsWith("is") || m.getName().startsWith("get")); + } + + @SuppressWarnings("unchecked") + private T makeNew(Class type, Object parent, String prop, PathMetadata pm) { + Path path; + T rv; + + if (String.class.equals(type)) { + path = new ExtString(pm); + rv = (T) new String(); + + } else if (Integer.class.equals(type)) { + path = new Path.PComparable(Integer.class,pm); + rv = (T) new Integer(42); + + } else if (Date.class.equals(type)) { + path = new Path.PComparable(Date.class,pm); + rv = (T) new Date(); + + } else if (Long.class.equals(type)) { + path = new Path.PComparable(Long.class,pm); + rv = (T) new Long(42); + + } else if (Short.class.equals(type)) { + path = new Path.PComparable(Short.class,pm); + rv = (T) new Short((short) 42); + + } else if (Double.class.equals(type)) { + path = new Path.PComparable(Double.class,pm); + rv = (T) new Double(42); + + } else if (Float.class.equals(type)) { + path = new Path.PComparable(Float.class,pm); + rv = (T) new Float(42); + + } else if (BigInteger.class.equals(type)) { + path = new Path.PComparable(BigInteger.class,pm); + rv = (T) new BigInteger("42"); + + } else if (BigDecimal.class.equals(type)) { + path = new Path.PComparable(BigDecimal.class,pm); + rv = (T) new BigDecimal(42); + + } else if (Boolean.class.equals(type)) { + path = new Path.PComparable(Boolean.class,pm); + rv = (T) new Boolean(true); + + } else if (List.class.isAssignableFrom(type)) { + path = new Path.PComponentList(null,pm); + rv = (T) aliasFactory.createAliasForProp(List.class, parent, prop, path); + + } else if (Set.class.isAssignableFrom(type)) { + path = new Path.PComponentCollection(null,pm); + rv = (T) aliasFactory.createAliasForProp(Set.class, parent, prop, path); + + } else if (Collection.class.isAssignableFrom(type)) { + path = new Path.PComponentCollection(null,pm); + rv = (T) aliasFactory.createAliasForProp(Collection.class, parent, prop, path); + + } else if (Map.class.isAssignableFrom(type)) { + path = new Path.PComponentMap(null,null,pm); + rv = (T) aliasFactory.createAliasForProp(Map.class, parent, prop, path); + + } else if (Enum.class.isAssignableFrom(type)) { + path = new Path.PSimple(type, pm); + rv = type.getEnumConstants()[0]; + + } else { + path = new Path.PSimple(type, pm); + rv = (T) aliasFactory.createAliasForProp(type, parent, prop, path); + } + propToObj.put(prop, rv); + propToPath.put(prop, path); + return rv; + } + + private String propertyNameForGetter(Method method) { + String name = method.getName(); + name = name.startsWith("is") ? name.substring(2) : name.substring(3); + return StringUtils.uncapitalize(name); + } + +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/collections/alias/WeakIdentityHashMap.java b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/WeakIdentityHashMap.java new file mode 100644 index 000000000..9f8595eeb --- /dev/null +++ b/querydsl-collections/src/main/java/com/mysema/query/collections/alias/WeakIdentityHashMap.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008 Eric Bottard + * + * 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.query.collections.alias; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Quick and dirty mix of {@link WeakHashMap} and {@link IdentityHashMap}. This + * class does not implement {@link Map} per se but provides the methods + * we need. + */ +/* default */class WeakIdentityHashMap { + + private Map, V> map = new HashMap, V>(); + + private ReferenceQueue referenceQueue = new ReferenceQueue(); + + private void expunge() { + Reference ref; + while ((ref = referenceQueue.poll()) != null) { + map.remove(ref); + } + } + + public V get(K key) { + expunge(); + WeakReference keyref = makeReference(key); + return map.get(keyref); + } + + private WeakReference makeReference(K referent) { + return new IdentityWeakReference(referent); + } + + private WeakReference makeReference(K referent, ReferenceQueue q) { + return new IdentityWeakReference(referent, q); + } + + public V put(K key, V value) { + expunge(); + if (key == null) { + throw new NullPointerException("Null key"); + } + WeakReference keyref = makeReference(key, referenceQueue); + return map.put(keyref, value); + } + + public V remove(K key) { + expunge(); + WeakReference keyref = makeReference(key); + return map.remove(keyref); + } + + /** + * Considers that two objects are equal when they both reference the same + * (with == semantics) referent. + */ + private static class IdentityWeakReference extends WeakReference { + private final int hashCode; + + IdentityWeakReference(T o) { + this(o, null); + } + + IdentityWeakReference(T o, ReferenceQueue q) { + super(o, q); + this.hashCode = (o == null) ? 0 : System.identityHashCode(o); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof IdentityWeakReference)) { + return false; + } + IdentityWeakReference wr = (IdentityWeakReference) o; + T got = get(); + return (got != null && got == wr.get()); + } + + @Override + public int hashCode() { + return hashCode; + } + } +} diff --git a/querydsl-collections/src/main/java/com/mysema/query/grammar/types/ColTypes.java b/querydsl-collections/src/main/java/com/mysema/query/grammar/types/ColTypes.java index 86640dbe3..9633ebc0c 100644 --- a/querydsl-collections/src/main/java/com/mysema/query/grammar/types/ColTypes.java +++ b/querydsl-collections/src/main/java/com/mysema/query/grammar/types/ColTypes.java @@ -25,7 +25,7 @@ public class ColTypes { public static class ExtString extends Path.PString{ - public ExtString(PathMetadata arg0) { + public ExtString(PathMetadata arg0) { super(arg0); } public Expr split(String regex) { diff --git a/querydsl-collections/src/test/java/com/mysema/query/collections/MiniApiTest.java b/querydsl-collections/src/test/java/com/mysema/query/collections/MiniApiTest.java index 238ff6045..7b56bb816 100644 --- a/querydsl-collections/src/test/java/com/mysema/query/collections/MiniApiTest.java +++ b/querydsl-collections/src/test/java/com/mysema/query/collections/MiniApiTest.java @@ -14,11 +14,7 @@ import static com.mysema.query.collections.MiniApi.select; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import org.junit.Before; import org.junit.Ignore; @@ -83,9 +79,7 @@ public class MiniApiTest { } @Test - @Ignore public void testAlias(){ - // TODO List cats = Arrays.asList(new Cat("Kitty"), new Cat("Bob"), new Cat("Alex"), new Cat("Francis")); // 1st @@ -100,14 +94,18 @@ public class MiniApiTest { for (String name : from($(c),cats).where($(c.getKittens()).size().gt(0)) .iterate($(c.getName()))){ System.out.println(name); + } + + // 2nd - variation + for (String name : from($(c),cats).where($(c.getKittens().size()).gt(0)) + .iterate($(c.getName()))){ + System.out.println(name); } } @Test - @Ignore public void testAlias2(){ - // TODO List cats = Arrays.asList(new Cat("Kitty"), new Cat("Bob"), new Cat("Alex"), new Cat("Francis")); // 1st @@ -125,6 +123,18 @@ public class MiniApiTest { } } + @Test + @Ignore + public void testVariousAlias(){ + // TODO : FIXME + Cat c = alias(Cat.class, "cat"); + + // 1 + from($(c)) + .where($(c.getMate().getBirthdate()).after(new Date())) + .iterate($(c)).iterator(); + } + @Test public void testMapUsage(){ Map map = new HashMap();