added basic alias system

This commit is contained in:
Timo Westkämper 2009-01-06 13:07:53 +00:00
parent a2a6385373
commit 153fe67d18
11 changed files with 457 additions and 44 deletions

View File

@ -16,7 +16,7 @@
<description>Hibernate / HQL support for querydsl</description>
<packaging>jar</packaging>
<dependencies>
<dependencies>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-core</artifactId>
@ -27,20 +27,25 @@
<artifactId>collections-generic</artifactId>
<version>4.01</version>
<!-- license : Apache License 2.0 -->
</dependency>
</dependency>
<dependency>
<groupId>janino</groupId>
<artifactId>janino</artifactId>
<version>2.5.10</version>
</dependency>
<dependency>
<groupId>janino</groupId>
<artifactId>janino</artifactId>
<version>2.5.10</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.4</version>
</dependency>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
<!-- test -->
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>

View File

@ -1,20 +0,0 @@
package com.mysema.query.collections;
/**
* AliasFactory provides
*
* @author tiwe
* @version $Id$
*/
public class AliasFactory {
public <A> A createAlias(Class<A> cl, String var){
try {
// TODO : create real alias
return cl.newInstance();
} catch (Exception e) {
throw new RuntimeException("error", e);
}
}
}

View File

@ -56,7 +56,7 @@ public class InnerQuery extends QueryBase<Object, InnerQuery> {
private <RT> Iterator<RT> createIterator(Expr<RT> projection) throws Exception {
// from
List<Expr<?>> sources = new ArrayList<Expr<?>>();
MultiIterator multiIt = new MultiIterator();
MultiIterator multiIt = new MultiIterator();
for (JoinExpression<?> join : joins) {
sources.add(join.getTarget());
multiIt.add(pathToIterable.get(join.getTarget()));

View File

@ -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<Object> it = new PSimple<Object>(Object.class,PathMetadata.forVariable("it"));
public static <A> A alias(Class<A> cl, String var){
return aliasFactory.createAlias(cl, var);
return aliasFactory.createAliasForVar(cl, var);
}
public static <A> ColQuery<?> from(Path<A> path, A... arr){

View File

@ -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"));

View File

@ -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.<PBoolean>getCurrent() : super.create(arg);
}
public PBooleanArray create(Boolean[] args){
return aliasFactory.isBound() ? aliasFactory.<PBooleanArray>getCurrent() : super.create(args);
}
public <D> PComponentCollection<D> create(Collection<D> arg) {
return aliasFactory.isBound() ? aliasFactory.<PComponentCollection<D>>getCurrent() : super.create(arg);
}
public <D extends Comparable<D>> PComparable<D> create(D arg){
return aliasFactory.isBound() ? aliasFactory.<PComparable<D>>getCurrent() : super.create(arg);
}
@SuppressWarnings("unchecked")
public <D> PSimple<D> create(D arg){
PSimple<D> path = (PSimple<D>) aliasFactory.pathForAlias(arg);
return path != null ? path : super.create(arg);
}
public <D extends Comparable<D>> PComparableArray<D> create(D[] args){
return aliasFactory.isBound() ? aliasFactory.<PComparableArray<D>>getCurrent() : super.create(args);
}
public <D> PComponentList<D> create(List<D> arg) {
return aliasFactory.isBound() ? aliasFactory.<PComponentList<D>>getCurrent() : super.create(arg);
}
public ExtString create(String arg){
return aliasFactory.isBound() ? aliasFactory.<ExtString>getCurrent() : super.create(arg);
}
public PStringArray create(String[] args){
return aliasFactory.isBound() ? aliasFactory.<PStringArray>getCurrent() : super.create(args);
}
}

View File

@ -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<WeakIdentityHashMap<Object, Path<?>>> bindings = new ThreadLocal<WeakIdentityHashMap<Object, Path<?>>>() {
@Override
protected WeakIdentityHashMap<Object, Path<?>> initialValue() {
return new WeakIdentityHashMap<Object, Path<?>>();
}
};
private final ThreadLocal<Path<?>> current = new ThreadLocal<Path<?>>();
public <A> A createAliasForProp(Class<A> cl, Object parent, String prop, Path<?> path){
A proxy = createProxy(cl);
bindings.get().put(proxy, path);
return proxy;
}
public <A> A createAliasForVar(Class<A> cl, String var){
Path<?> path = new Path.PSimple<A>(cl, PathMetadata.forVariable(var));
A proxy = createProxy(cl);
bindings.get().put(proxy, path);
return proxy;
}
@SuppressWarnings("unchecked")
private <A> A createProxy(Class<A> 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 extends Path<?>> 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);
}
}

View File

@ -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<String,Object> propToObj = new HashMap<String,Object>();
private final Map<String,Path<?>> propToPath = new HashMap<String,Path<?>>();
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<String> 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<Integer> 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> T makeNew(Class<T> 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>(Integer.class,pm);
rv = (T) new Integer(42);
} else if (Date.class.equals(type)) {
path = new Path.PComparable<Date>(Date.class,pm);
rv = (T) new Date();
} else if (Long.class.equals(type)) {
path = new Path.PComparable<Long>(Long.class,pm);
rv = (T) new Long(42);
} else if (Short.class.equals(type)) {
path = new Path.PComparable<Short>(Short.class,pm);
rv = (T) new Short((short) 42);
} else if (Double.class.equals(type)) {
path = new Path.PComparable<Double>(Double.class,pm);
rv = (T) new Double(42);
} else if (Float.class.equals(type)) {
path = new Path.PComparable<Float>(Float.class,pm);
rv = (T) new Float(42);
} else if (BigInteger.class.equals(type)) {
path = new Path.PComparable<BigInteger>(BigInteger.class,pm);
rv = (T) new BigInteger("42");
} else if (BigDecimal.class.equals(type)) {
path = new Path.PComparable<BigDecimal>(BigDecimal.class,pm);
rv = (T) new BigDecimal(42);
} else if (Boolean.class.equals(type)) {
path = new Path.PComparable<Boolean>(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<T>(type, pm);
rv = type.getEnumConstants()[0];
} else {
path = new Path.PSimple<T>(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);
}
}

View File

@ -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} <i>per se</i> but provides the methods
* we need.
*/
/* default */class WeakIdentityHashMap<K, V> {
private Map<WeakReference<K>, V> map = new HashMap<WeakReference<K>, V>();
private ReferenceQueue<K> referenceQueue = new ReferenceQueue<K>();
private void expunge() {
Reference<? extends K> ref;
while ((ref = referenceQueue.poll()) != null) {
map.remove(ref);
}
}
public V get(K key) {
expunge();
WeakReference<K> keyref = makeReference(key);
return map.get(keyref);
}
private WeakReference<K> makeReference(K referent) {
return new IdentityWeakReference<K>(referent);
}
private WeakReference<K> makeReference(K referent, ReferenceQueue<K> q) {
return new IdentityWeakReference<K>(referent, q);
}
public V put(K key, V value) {
expunge();
if (key == null) {
throw new NullPointerException("Null key");
}
WeakReference<K> keyref = makeReference(key, referenceQueue);
return map.put(keyref, value);
}
public V remove(K key) {
expunge();
WeakReference<K> keyref = makeReference(key);
return map.remove(keyref);
}
/**
* Considers that two objects are equal when they both reference the same
* (with <tt>==</tt> semantics) referent.
*/
private static class IdentityWeakReference<T> extends WeakReference<T> {
private final int hashCode;
IdentityWeakReference(T o) {
this(o, null);
}
IdentityWeakReference(T o, ReferenceQueue<T> 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<T> wr = (IdentityWeakReference<T>) o;
T got = get();
return (got != null && got == wr.get());
}
@Override
public int hashCode() {
return hashCode;
}
}
}

View File

@ -25,7 +25,7 @@ public class ColTypes {
public static class ExtString extends Path.PString{
public ExtString(PathMetadata<String> arg0) {
public ExtString(PathMetadata<?> arg0) {
super(arg0);
}
public Expr<String[]> split(String regex) {

View File

@ -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<Cat> 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<Cat> 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<String,String> map = new HashMap<String,String>();