/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* 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.querydsl.core.alias;
import java.util.concurrent.ExecutionException;
import javax.annotation.Nullable;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.mysema.commons.lang.Pair;
import com.querydsl.core.QueryException;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.PathMetadataFactory;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
/**
* {@code AliasFactory} is a factory class for alias creation
*
* @author tiwe
*/
class AliasFactory {
private final ThreadLocal<Expression<?>> current = new ThreadLocal<Expression<?>>();
private final PathFactory pathFactory;
private final TypeSystem typeSystem;
// caches top level paths (class/var as key)
private final LoadingCache<Pair<Class<?>,String>, EntityPath<?>> pathCache;
private final LoadingCache<Pair<Class<?>,Expression<?>>, ManagedObject> proxyCache =
CacheBuilder.newBuilder().build(
new CacheLoader<Pair<Class<?>,Expression<?>>,ManagedObject>() {
@Override
public ManagedObject load(Pair<Class<?>, Expression<?>> input) {
return (ManagedObject) createProxy(input.getFirst(), input.getSecond());
}
});
public AliasFactory(final PathFactory pathFactory, TypeSystem typeSystem) {
this.pathFactory = pathFactory;
this.typeSystem = typeSystem;
this.pathCache = CacheBuilder.newBuilder().build(
new CacheLoader<Pair<Class<?>, String>, EntityPath<?>>() {
@Override
public EntityPath<?> load(Pair<Class<?>, String> input) {
return (EntityPath<?>) pathFactory.createEntityPath(
input.getFirst(),
PathMetadataFactory.forVariable(input.getSecond()));
}
});
}
/**
* Create an alias instance for the given class and Expression
*
* @param <A>
* @param cl type for alias
* @param expr underlying expression
* @return alias instance
*/
@SuppressWarnings("unchecked")
public <A> A createAliasForExpr(Class<A> cl, Expression<? extends A> expr) {
try {
return (A) proxyCache.get(Pair.<Class<?>, Expression<?>>of(cl, expr));
} catch (ExecutionException e) {
throw new QueryException(e);
}
}
/**
* Create an alias instance for the given class, parent and path
*
* @param <A>
* @param cl type for alias
* @param path underlying expression
* @return alias instance
*/
public <A> A createAliasForProperty(Class<A> cl, Expression<?> path) {
return createProxy(cl, path);
}
/**
* Create an alias instance for the given class and variable name
*
* @param <A>
* @param cl type for alias
* @param var variable name for the underlying expression
* @return alias instance
*/
@SuppressWarnings("unchecked")
public <A> A createAliasForVariable(Class<A> cl, String var) {
try {
Expression<?> path = pathCache.get(Pair.<Class<?>, String>of(cl, var));
return (A) proxyCache.get(Pair.<Class<?>, Expression<?>>of(cl, path));
} catch (ExecutionException e) {
throw new QueryException(e);
}
}
/**
* Create a proxy instance for the given class and path
*
* @param <A>
* @param cl type of the proxy
* @param path underlying expression
* @return proxy instance
*/
@SuppressWarnings("unchecked")
protected <A> A createProxy(Class<A> cl, Expression<?> path) {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(AliasFactory.class.getClassLoader());
if (cl.isInterface()) {
enhancer.setInterfaces(new Class<?>[] {cl, ManagedObject.class});
} else {
enhancer.setSuperclass(cl);
enhancer.setInterfaces(new Class<?>[] {ManagedObject.class});
}
// creates one handler per proxy
MethodInterceptor handler = new PropertyAccessInvocationHandler(path, this, pathFactory, typeSystem);
enhancer.setCallback(handler);
return (A) enhancer.create();
}
/**
* Get the current thread bound expression without resetting it
*
* @param <A>
* @return expression
*/
@SuppressWarnings("unchecked")
@Nullable
public <A extends Expression<?>> A getCurrent() {
return (A) current.get();
}
/**
* Get the current thread bound expression and reset it
*
* @param <A>
* @return expression
*/
@Nullable
public <A extends Expression<?>> A getCurrentAndReset() {
A rv = this.getCurrent();
reset();
return rv;
}
/**
* Reset the thread bound expression to null
*/
public void reset() {
current.set(null);
}
/**
* Set the thread bound expression to the given value
*
* @param expr expression to be set to current
*/
public void setCurrent(Expression<?> expr) {
current.set(expr);
}
}