/* * 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.util; import static com.google.common.collect.Iterables.filter; import static com.querydsl.core.util.ArrayUtils.isEmpty; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Iterator; import java.util.Set; import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.*; import com.google.common.primitives.Primitives; import com.querydsl.core.types.ExpressionException; /** * ConstructorUtils provides constructor resolving functionality * * @author Shredder121 */ public final class ConstructorUtils { private ConstructorUtils() { } /** * The parameter list for the default constructor; */ private static final Class<?>[] NO_ARGS = {}; private static final ClassToInstanceMap<Object> defaultPrimitives = ImmutableClassToInstanceMap.builder() .put(Boolean.TYPE, false) .put(Byte.TYPE, (byte) 0) .put(Character.TYPE, (char) 0) .put(Short.TYPE, (short) 0) .put(Integer.TYPE, 0) .put(Long.TYPE, 0L) .put(Float.TYPE, 0.0F) .put(Double.TYPE, 0.0) .build(); /** * Returns the constructor where the formal parameter list matches the * givenTypes argument. * * It is advisable to first call * {@link #getConstructorParameters(java.lang.Class, java.lang.Class[])} * to get the parameters. * * @param type type * @param givenTypes parameter types * @return matching constructor * @throws NoSuchMethodException */ public static <C> Constructor<C> getConstructor(Class<C> type, Class<?>[] givenTypes) throws NoSuchMethodException { return type.getConstructor(givenTypes); } /** * Returns the parameters for the constructor that matches the given types. * * @param type type * @param givenTypes parameter types * @return constructor parameters */ public static Class<?>[] getConstructorParameters(Class<?> type, Class<?>[] givenTypes) { next_constructor: for (Constructor<?> constructor : type.getConstructors()) { int matches = 0; Class<?>[] parameters = constructor.getParameterTypes(); Iterator<Class<?>> parameterIterator = Arrays .asList(parameters) .iterator(); if (!isEmpty(givenTypes) && !isEmpty(parameters)) { Class<?> parameter = null; for (Class<?> argument : givenTypes) { if (parameterIterator.hasNext()) { parameter = parameterIterator.next(); if (!compatible(parameter, argument)) { continue next_constructor; } matches++; } else if (constructor.isVarArgs()) { if (!compatible(parameter, argument)) { continue next_constructor; } } else { continue next_constructor; //default } } if (matches == parameters.length) { return parameters; } } else if (isEmpty(givenTypes) && isEmpty(parameters)) { return NO_ARGS; } } throw new ExpressionException("No constructor found for " + type.toString() + " with parameters: " + Arrays.deepToString(givenTypes)); } /** * Returns a fetch of transformers applicable to the given constructor. * * @param constructor constructor * @return transformers */ public static Iterable<Function<Object[], Object[]>> getTransformers(Constructor<?> constructor) { Iterable<ArgumentTransformer> transformers = Lists.newArrayList( new PrimitiveAwareVarArgsTransformer(constructor), new PrimitiveTransformer(constructor), new VarArgsTransformer(constructor)); return ImmutableList .<Function<Object[], Object[]>>copyOf(filter(transformers, applicableFilter)); } private static Class<?> normalize(Class<?> clazz) { if (clazz.isArray()) { clazz = clazz.getComponentType(); } return Primitives.wrap(clazz); } private static boolean compatible(Class<?> parameter, Class<?> argument) { return normalize(parameter) .isAssignableFrom(normalize(argument)); } private static final Predicate<ArgumentTransformer> applicableFilter = new Predicate<ArgumentTransformer>() { @Override public boolean apply(ArgumentTransformer transformer) { return transformer != null ? transformer.isApplicable() : false; } }; protected abstract static class ArgumentTransformer implements Function<Object[], Object[]> { @Nullable protected Constructor<?> constructor; protected final Class<?>[] paramTypes; public ArgumentTransformer(Constructor<?> constructor) { this(constructor.getParameterTypes()); this.constructor = constructor; } public ArgumentTransformer(Class<?>[] paramTypes) { this.paramTypes = paramTypes; } public abstract boolean isApplicable(); } private static class VarArgsTransformer extends ArgumentTransformer { protected final Class<?> componentType; public VarArgsTransformer(Constructor<?> constructor) { super(constructor); if (paramTypes.length > 0) { componentType = paramTypes[paramTypes.length - 1].getComponentType(); } else { componentType = null; } } @Override public boolean isApplicable() { return constructor != null && constructor.isVarArgs(); } @Override public Object[] apply(Object[] args) { if (isEmpty(args)) { return args; } int current = 0; // constructor args Object[] cargs = new Object[paramTypes.length]; for (int i = 0; i < cargs.length - 1; i++) { set(cargs, i, args[current++]); } // array with vargs int size = args.length - cargs.length + 1; Object vargs = Array.newInstance( componentType, size); cargs[cargs.length - 1] = vargs; for (int i = 0; i < Array.getLength(vargs); i++) { set(vargs, i, args[current++]); } return cargs; } private void set(Object array, int index, Object value) throws IllegalArgumentException, ArrayIndexOutOfBoundsException { Array.set(array, index, value); } } private static class PrimitiveTransformer extends ArgumentTransformer { private final Set<Integer> primitiveLocations; public PrimitiveTransformer(Constructor<?> constructor) { super(constructor); ImmutableSet.Builder<Integer> builder = ImmutableSet.builder(); Class<?>[] parameterTypes = constructor.getParameterTypes(); for (int location = 0; location < parameterTypes.length; location++) { Class<?> parameterType = parameterTypes[location]; if (parameterType.isPrimitive()) { builder.add(location); } } primitiveLocations = builder.build(); } @Override public boolean isApplicable() { return !primitiveLocations.isEmpty(); } @Override public Object[] apply(Object[] args) { if (isEmpty(args)) { return args; } for (Integer location : primitiveLocations) { if (args[location] == null) { Class<?> primitiveClass = paramTypes[location]; args[location] = defaultPrimitives.getInstance(primitiveClass); } } return args; } } private static class PrimitiveAwareVarArgsTransformer extends VarArgsTransformer { private final Object defaultInstance; public PrimitiveAwareVarArgsTransformer(Constructor<?> constructor) { super(constructor); defaultInstance = (componentType != null) ? defaultPrimitives.getInstance(componentType) : null; } @Override public boolean isApplicable() { return super.isApplicable() && (componentType != null && componentType.isPrimitive()); } @Override public Object[] apply(Object[] args) { if (isEmpty(args)) { return args; } for (int i = paramTypes.length - 1; i < args.length; i++) { if (args[i] == null) { args[i] = defaultInstance; } } return args; } } }