/* * 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.collections; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import javax.tools.JavaCompiler; import com.google.common.primitives.Primitives; import com.mysema.codegen.ECJEvaluatorFactory; import com.mysema.codegen.Evaluator; import com.mysema.codegen.EvaluatorFactory; import com.mysema.codegen.JDKEvaluatorFactory; import com.mysema.codegen.model.*; import com.mysema.codegen.support.ClassUtils; import com.querydsl.core.JoinExpression; import com.querydsl.core.JoinType; import com.querydsl.core.QueryMetadata; import com.querydsl.core.support.CollectionAnyVisitor; import com.querydsl.core.support.Context; import com.querydsl.core.types.*; /** * {@code DefaultEvaluatorFactory} provides Java source templates for evaluation of {@link CollQuery} queries * * @author tiwe * */ public class DefaultEvaluatorFactory { private final EvaluatorFactory factory; private final CollQueryTemplates templates; private final CollectionAnyVisitor collectionAnyVisitor = new CollectionAnyVisitor(); public DefaultEvaluatorFactory(CollQueryTemplates templates) { this(templates, Thread.currentThread().getContextClassLoader()); } public DefaultEvaluatorFactory(CollQueryTemplates templates, EvaluatorFactory factory) { this.templates = templates; this.factory = factory; } protected DefaultEvaluatorFactory(CollQueryTemplates templates, URLClassLoader classLoader, JavaCompiler compiler) { this.templates = templates; this.factory = new JDKEvaluatorFactory(classLoader, compiler); } protected DefaultEvaluatorFactory(CollQueryTemplates templates, ClassLoader classLoader) { this.templates = templates; if (classLoader instanceof URLClassLoader) { this.factory = new JDKEvaluatorFactory((URLClassLoader) classLoader); } else { // for OSGi compatibility this.factory = new ECJEvaluatorFactory(classLoader); } } /** * Create an Evaluator for the given query sources and projection * * @param <T> * @param metadata query metadata * @param sources sources of the query * @param projection projection of the query * @return evaluator */ public <T> Evaluator<T> create(QueryMetadata metadata, List<? extends Expression<?>> sources, Expression<T> projection) { final CollQuerySerializer serializer = new CollQuerySerializer(templates); serializer.append("return "); if (projection instanceof FactoryExpression<?>) { serializer.append("("); serializer.append(ClassUtils.getName(projection.getType())); serializer.append(")("); serializer.handle(projection); serializer.append(")"); } else { serializer.handle(projection); } serializer.append(";"); Map<Object,String> constantToLabel = serializer.getConstantToLabel(); Map<String, Object> constants = getConstants(metadata, constantToLabel); Class<?>[] types = new Class<?>[sources.size()]; String[] names = new String[sources.size()]; for (int i = 0; i < sources.size(); i++) { types[i] = sources.get(i).getType(); names[i] = sources.get(i).toString(); } // normalize types for (int i = 0; i < types.length; i++) { if (Primitives.isWrapperType(types[i])) { types[i] = Primitives.unwrap(types[i]); } } return factory.createEvaluator(serializer.toString(), projection.getType(), names, types, constants); } /** * Create an Evaluator for the given source and filter * * @param <T> * @param source source of the query * @param filter filter of the query * @return evaluator */ public <T> Evaluator<List<T>> createEvaluator(QueryMetadata metadata, Expression<? extends T> source, Predicate filter) { String typeName = ClassUtils.getName(source.getType()); CollQuerySerializer ser = new CollQuerySerializer(templates); ser.append("java.util.List<" + typeName + "> rv = new java.util.ArrayList<" + typeName + ">();\n"); ser.append("for (" + typeName + " " + source + " : " + source + "_) {\n"); ser.append(" try {\n"); ser.append(" if (").handle(filter).append(") {\n"); ser.append(" rv.add(" + source + ");\n"); ser.append(" }\n"); ser.append(" } catch (NullPointerException npe) { }\n"); ser.append("}\n"); ser.append("return rv;"); Map<Object,String> constantToLabel = ser.getConstantToLabel(); Map<String, Object> constants = getConstants(metadata, constantToLabel); Type sourceType = new ClassType(TypeCategory.SIMPLE, source.getType()); ClassType sourceListType = new ClassType(TypeCategory.SIMPLE, Iterable.class, sourceType); return factory.createEvaluator( ser.toString(), sourceListType, new String[]{source + "_"}, new Type[]{sourceListType}, new Class<?>[]{Iterable.class}, constants); } /** * Create an Evaluator for the given sources and the given optional filter * * @param metadata query metadata * @param joins joins * @param filter where condition * @return evaluator */ public Evaluator<List<Object[]>> createEvaluator(QueryMetadata metadata, List<JoinExpression> joins, @Nullable Predicate filter) { List<String> sourceNames = new ArrayList<String>(); List<Type> sourceTypes = new ArrayList<Type>(); List<Class<?>> sourceClasses = new ArrayList<Class<?>>(); StringBuilder vars = new StringBuilder(); CollQuerySerializer ser = new CollQuerySerializer(templates); ser.append("java.util.List<Object[]> rv = new java.util.ArrayList<Object[]>();\n"); List<String> anyJoinMatchers = new ArrayList<String>(); // creating context for (JoinExpression join : joins) { Expression<?> target = join.getTarget(); String typeName = com.mysema.codegen.support.ClassUtils.getName(target.getType()); if (vars.length() > 0) { vars.append(","); } switch (join.getType()) { case DEFAULT: ser.append("for (" + typeName + " " + target + " : " + target + "_) {\n"); vars.append(target); sourceNames.add(target + "_"); sourceTypes.add(new SimpleType(Types.ITERABLE, new ClassType(TypeCategory.SIMPLE,target.getType()))); sourceClasses.add(Iterable.class); break; case INNERJOIN: case LEFTJOIN: Operation<?> alias = (Operation<?>) join.getTarget(); boolean colAnyJoin = join.getCondition() != null && join.getCondition().toString().equals("any"); boolean leftJoin = join.getType() == JoinType.LEFTJOIN; String matcher = null; if (colAnyJoin) { matcher = alias.getArg(1).toString() + "_matched"; ser.append("boolean " + matcher + " = false;\n"); anyJoinMatchers.add(matcher); } ser.append("for (" + typeName + " " + alias.getArg(1) + " : "); if (leftJoin) { ser.append(CollQueryFunctions.class.getName() + ".leftJoin("); } if (colAnyJoin) { Context context = new Context(); Expression<?> replacement = alias.getArg(0) .accept(collectionAnyVisitor, context); ser.handle(replacement); } else { ser.handle(alias.getArg(0)); } if (alias.getArg(0).getType().equals(Map.class)) { ser.append(".values()"); } if (leftJoin) { ser.append(")"); } ser.append(") {\n"); if (matcher != null) { ser.append("if (!" + matcher + ") {\n"); } vars.append(alias.getArg(1)); break; default: throw new IllegalArgumentException("Illegal join expression " + join); } } // filter if (filter != null) { ser.append("try {\n"); ser.append("if ("); ser.handle(filter).append(") {\n"); for (String matcher : anyJoinMatchers) { ser.append(" " + matcher + " = true;\n"); } ser.append(" rv.add(new Object[]{" + vars + "});\n"); ser.append("}\n"); ser.append("} catch (NullPointerException npe) { }\n"); } else { ser.append("rv.add(new Object[]{" + vars + "});\n"); } // closing context int amount = joins.size() + anyJoinMatchers.size(); for (int i = 0; i < amount; i++) { ser.append("}\n"); } ser.append("return rv;"); Map<Object,String> constantToLabel = ser.getConstantToLabel(); Map<String, Object> constants = getConstants(metadata, constantToLabel); ClassType projectionType = new ClassType(TypeCategory.LIST, List.class, Types.OBJECTS); return factory.createEvaluator( ser.toString(), projectionType, sourceNames.toArray(new String[sourceNames.size()]), sourceTypes.toArray(new Type[sourceTypes.size()]), sourceClasses.toArray(new Class<?>[sourceClasses.size()]), constants); } private Map<String, Object> getConstants(QueryMetadata metadata, Map<Object, String> constantToLabel) { Map<String,Object> constants = new HashMap<String,Object>(); for (Map.Entry<Object,String> entry : constantToLabel.entrySet()) { if (entry.getKey() instanceof ParamExpression<?>) { Object value = metadata.getParams().get(entry.getKey()); if (value == null) { throw new ParamNotSetException((ParamExpression<?>) entry.getKey()); } constants.put(entry.getValue(), value); } else { constants.put(entry.getValue(), entry.getKey()); } } return constants; } }