/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 freemarker.ext.beans;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.NullArgumentException;

Encapsulates the rules and data structures (including cache) for choosing of the best matching callable member for a parameter list, from a given set of callable members. There are two subclasses of this, one for non-varags methods, and one for varargs methods.
/** * Encapsulates the rules and data structures (including cache) for choosing of the best matching callable member for * a parameter list, from a given set of callable members. There are two subclasses of this, one for non-varags methods, * and one for varargs methods. */
abstract class OverloadedMethodsSubset {
Used for an optimization trick to substitute an array of whatever size that contains only 0-s. Since this array is 0 long, this means that the code that reads the int[] always have to check if the int[] has this value, and then it has to act like if was all 0-s.
/** * Used for an optimization trick to substitute an array of whatever size that contains only 0-s. Since this array * is 0 long, this means that the code that reads the int[] always have to check if the int[] has this value, and * then it has to act like if was all 0-s. */
static final int[] ALL_ZEROS_ARRAY = new int[0]; private static final int[][] ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY = new int[1][]; static { ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY[0] = ALL_ZEROS_ARRAY; } private Class[/*number of args*/][/*arg index*/] unwrappingHintsByParamCount;
Tells what types occur at a given parameter position with a bit field. See TypeFlags.
/** * Tells what types occur at a given parameter position with a bit field. See {@link TypeFlags}. */
private int[/*number of args*/][/*arg index*/] typeFlagsByParamCount; // TODO: This can cause memory-leak when classes are re-loaded. However, first the genericClassIntrospectionCache // and such need to be fixed in this regard. private final Map/*<ArgumentTypes, MaybeEmptyCallableMemberDescriptor>*/ argTypesToMemberDescCache = new ConcurrentHashMap(6, 0.75f, 1); private final List/*<ReflectionCallableMemberDescriptor>*/ memberDescs = new LinkedList();
Enables 2.3.21 BeansWrapper incompatibleImprovements
/** Enables 2.3.21 {@link BeansWrapper} incompatibleImprovements */
protected final boolean bugfixed; OverloadedMethodsSubset(boolean bugfixed) { this.bugfixed = bugfixed; } void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) { memberDescs.add(memberDesc); // Warning: Do not modify this array, or put it into unwrappingHintsByParamCount by reference, as the arrays // inside that are modified! final Class[] prepedParamTypes = preprocessParameterTypes(memberDesc); final int paramCount = prepedParamTypes.length; // Must be the same as the length of the original param list // Merge these unwrapping hints with the existing table of hints: if (unwrappingHintsByParamCount == null) { unwrappingHintsByParamCount = new Class[paramCount + 1][]; unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone(); } else if (unwrappingHintsByParamCount.length <= paramCount) { Class[][] newUnwrappingHintsByParamCount = new Class[paramCount + 1][]; System.arraycopy(unwrappingHintsByParamCount, 0, newUnwrappingHintsByParamCount, 0, unwrappingHintsByParamCount.length); unwrappingHintsByParamCount = newUnwrappingHintsByParamCount; unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone(); } else { Class[] unwrappingHints = unwrappingHintsByParamCount[paramCount]; if (unwrappingHints == null) { unwrappingHintsByParamCount[paramCount] = prepedParamTypes.clone(); } else { for (int paramIdx = 0; paramIdx < prepedParamTypes.length; paramIdx++) { // For each parameter list length, we merge the argument type arrays into a single Class[] that // stores the most specific common types for each position. Hence we will possibly use a too generic // hint for the unwrapping. For correct behavior, for each overloaded methods its own parameter // types should be used as a hint. But without unwrapping the arguments, we couldn't select the // overloaded method. So we had to unwrap with all possible target types of each parameter position, // which would be slow and its result would be uncacheable (as we don't have anything usable as // a lookup key). So we just use this compromise. unwrappingHints[paramIdx] = getCommonSupertypeForUnwrappingHint( unwrappingHints[paramIdx], prepedParamTypes[paramIdx]); } } } int[] typeFlagsByParamIdx = ALL_ZEROS_ARRAY; if (bugfixed) { // Fill typeFlagsByParamCount (if necessary) for (int paramIdx = 0; paramIdx < paramCount; paramIdx++) { final int typeFlags = TypeFlags.classToTypeFlags(prepedParamTypes[paramIdx]); if (typeFlags != 0) { if (typeFlagsByParamIdx == ALL_ZEROS_ARRAY) { typeFlagsByParamIdx = new int[paramCount]; } typeFlagsByParamIdx[paramIdx] = typeFlags; } } mergeInTypesFlags(paramCount, typeFlagsByParamIdx); } afterWideningUnwrappingHints( bugfixed ? prepedParamTypes : unwrappingHintsByParamCount[paramCount], typeFlagsByParamIdx); } Class[][] getUnwrappingHintsByParamCount() { return unwrappingHintsByParamCount; } @SuppressFBWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER", justification="Locks for member descriptor creation only") final MaybeEmptyCallableMemberDescriptor getMemberDescriptorForArgs(Object[] args, boolean varArg) { ArgumentTypes argTypes = new ArgumentTypes(args, bugfixed); MaybeEmptyCallableMemberDescriptor memberDesc = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes); if (memberDesc == null) { // Synchronized so that we won't unnecessarily create the same member desc. for multiple times in parallel. synchronized (argTypesToMemberDescCache) { memberDesc = (MaybeEmptyCallableMemberDescriptor) argTypesToMemberDescCache.get(argTypes); if (memberDesc == null) { memberDesc = argTypes.getMostSpecific(memberDescs, varArg); argTypesToMemberDescCache.put(argTypes, memberDesc); } } } return memberDesc; } Iterator/*<ReflectionCallableMemberDescriptor>*/ getMemberDescriptors() { return memberDescs.iterator(); } abstract Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc); abstract void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes); abstract MaybeEmptyMemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs, BeansWrapper unwrapper) throws TemplateModelException;
Returns the most specific common class (or interface) of two parameter types for the purpose of unwrapping. This is trickier than finding the most specific overlapping superclass of two classes, because:
  • It considers primitive classes as the subclasses of the boxing classes.
  • If the only common class is Object, it will try to find a common interface. If there are more of them, it will start removing those that are known to be uninteresting as unwrapping hints.
Params:
  • c1 – Parameter type 1
  • c2 – Parameter type 2
/** * Returns the most specific common class (or interface) of two parameter types for the purpose of unwrapping. * This is trickier than finding the most specific overlapping superclass of two classes, because: * <ul> * <li>It considers primitive classes as the subclasses of the boxing classes.</li> * <li>If the only common class is {@link Object}, it will try to find a common interface. If there are more * of them, it will start removing those that are known to be uninteresting as unwrapping hints.</li> * </ul> * * @param c1 Parameter type 1 * @param c2 Parameter type 2 */
protected Class getCommonSupertypeForUnwrappingHint(Class c1, Class c2) { if (c1 == c2) return c1; // This also means that the hint for (Integer, Integer) will be Integer, not just Number. This is consistent // with how non-overloaded method hints work. if (bugfixed) { // c1 primitive class to boxing class: final boolean c1WasPrim; if (c1.isPrimitive()) { c1 = ClassUtil.primitiveClassToBoxingClass(c1); c1WasPrim = true; } else { c1WasPrim = false; } // c2 primitive class to boxing class: final boolean c2WasPrim; if (c2.isPrimitive()) { c2 = ClassUtil.primitiveClassToBoxingClass(c2); c2WasPrim = true; } else { c2WasPrim = false; } if (c1 == c2) { // If it was like int and Integer, boolean and Boolean, etc., we return the boxing type (as that's the // less specific, because it allows null.) // (If it was two equivalent primitives, we don't get here, because of the 1st line of the method.) return c1; } else if (Number.class.isAssignableFrom(c1) && Number.class.isAssignableFrom(c2)) { // We don't want the unwrapper to convert to a numerical super-type [*] as it's not yet known what the // actual number type of the chosen method will be. We will postpone the actual numerical conversion // until that, especially as some conversions (like fixed point to floating point) can be lossy. // * Numerical super-type: Like long > int > short > byte. return Number.class; } else if (c1WasPrim || c2WasPrim) { // At this point these all stand: // - At least one of them was primitive // - No more than one of them was numerical // - They don't have the same wrapper (boxing) class return Object.class; } // Falls through } else { // old buggy behavior if (c2.isPrimitive()) { if (c2 == Byte.TYPE) c2 = Byte.class; else if (c2 == Short.TYPE) c2 = Short.class; else if (c2 == Character.TYPE) c2 = Character.class; else if (c2 == Integer.TYPE) c2 = Integer.class; else if (c2 == Float.TYPE) c2 = Float.class; else if (c2 == Long.TYPE) c2 = Long.class; else if (c2 == Double.TYPE) c2 = Double.class; } } // We never get to this point if buxfixed is true and any of these stands: // - One of classes was a primitive type // - One of classes was a numerical type (either boxing type or primitive) Set commonTypes = _MethodUtil.getAssignables(c1, c2); commonTypes.retainAll(_MethodUtil.getAssignables(c2, c1)); if (commonTypes.isEmpty()) { // Can happen when at least one of the arguments is an interface, as // they don't have Object at the root of their hierarchy return Object.class; } // Gather maximally specific elements. Yes, there can be more than one // thank to interfaces. I.e., if you call this method for String.class // and Number.class, you'll have Comparable, Serializable, and Object as // maximal elements. List max = new ArrayList(); listCommonTypes: for (Iterator commonTypesIter = commonTypes.iterator(); commonTypesIter.hasNext(); ) { Class clazz = (Class) commonTypesIter.next(); for (Iterator maxIter = max.iterator(); maxIter.hasNext(); ) { Class maxClazz = (Class) maxIter.next(); if (_MethodUtil.isMoreOrSameSpecificParameterType(maxClazz, clazz, false /*bugfixed [1]*/, 0) != 0) { // clazz can't be maximal, if there's already a more specific or equal maximal than it. continue listCommonTypes; } if (_MethodUtil.isMoreOrSameSpecificParameterType(clazz, maxClazz, false /*bugfixed [1]*/, 0) != 0) { // If it's more specific than a currently maximal element, // that currently maximal is no longer a maximal. maxIter.remove(); } // 1: We don't use bugfixed at the "[1]"-marked points because it's slower and doesn't make any // difference here as it's ensured that nor c1 nor c2 is primitive or numerical. The bugfix has only // affected the treatment of primitives and numerical types. } // If we get here, no current maximal is more specific than the // current class, so clazz is a new maximal so far. max.add(clazz); } if (max.size() > 1) { // we have an ambiguity if (bugfixed) { // Find the non-interface class for (Iterator it = max.iterator(); it.hasNext(); ) { Class maxCl = (Class) it.next(); if (!maxCl.isInterface()) { if (maxCl != Object.class) { // This actually shouldn't ever happen, but to be sure... // If it's not Object, we use it as the most specific return maxCl; } else { // Otherwise remove Object, and we will try with the interfaces it.remove(); } } } // At this point we only have interfaces left. // Try removing interfaces about which we know that they are useless as unwrapping hints: max.remove(Cloneable.class); if (max.size() > 1) { // Still have an ambiguity... max.remove(Serializable.class); if (max.size() > 1) { // Still had an ambiguity... max.remove(Comparable.class); if (max.size() > 1) { return Object.class; // Still had an ambiguity... no luck. } } } } else { return Object.class; } } return (Class) max.get(0); }
Gets the "type flags" of each parameter positions, or null if there's no method with this parameter count or if we are in pre-2.3.21 mode, or ALL_ZEROS_ARRAY if there were no parameters that turned on a flag. The returned int-s are one or more TypeFlags constants binary "or"-ed together.
/** * Gets the "type flags" of each parameter positions, or {@code null} if there's no method with this parameter * count or if we are in pre-2.3.21 mode, or {@link #ALL_ZEROS_ARRAY} if there were no parameters that turned * on a flag. The returned {@code int}-s are one or more {@link TypeFlags} constants binary "or"-ed together. */
final protected int[] getTypeFlags(int paramCount) { return typeFlagsByParamCount != null && typeFlagsByParamCount.length > paramCount ? typeFlagsByParamCount[paramCount] : null; }
Updates the content of the typeFlagsByParamCount field with the parameter type flags of a method. Don't call this when bugfixed is false!
Params:
  • dstParamCount – The parameter count for which we want to merge in the type flags
  • srcTypeFlagsByParamIdx – If shorter than dstParamCount, its last item will be repeated until dstParamCount length is reached. If longer, the excessive items will be ignored. Maybe ALL_ZEROS_ARRAY. Maybe a 0-length array. Can't be null.
/** * Updates the content of the {@link #typeFlagsByParamCount} field with the parameter type flags of a method. * Don't call this when {@link #bugfixed} is {@code false}! * * @param dstParamCount The parameter count for which we want to merge in the type flags * @param srcTypeFlagsByParamIdx If shorter than {@code dstParamCount}, its last item will be repeated until * dstParamCount length is reached. If longer, the excessive items will be ignored. * Maybe {@link #ALL_ZEROS_ARRAY}. Maybe a 0-length array. Can't be {@code null}. */
final protected void mergeInTypesFlags(int dstParamCount, int[] srcTypeFlagsByParamIdx) { NullArgumentException.check("srcTypesFlagsByParamIdx", srcTypeFlagsByParamIdx); // Special case of 0 param count: if (dstParamCount == 0) { if (typeFlagsByParamCount == null) { typeFlagsByParamCount = ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY; } else if (typeFlagsByParamCount != ZERO_PARAM_COUNT_TYPE_FLAGS_ARRAY) { typeFlagsByParamCount[0] = ALL_ZEROS_ARRAY; } return; } // Ensure that typesFlagsByParamCount[dstParamCount] exists: if (typeFlagsByParamCount == null) { typeFlagsByParamCount = new int[dstParamCount + 1][]; } else if (typeFlagsByParamCount.length <= dstParamCount) { int[][] newTypeFlagsByParamCount = new int[dstParamCount + 1][]; System.arraycopy(typeFlagsByParamCount, 0, newTypeFlagsByParamCount, 0, typeFlagsByParamCount.length); typeFlagsByParamCount = newTypeFlagsByParamCount; } int[] dstTypeFlagsByParamIdx = typeFlagsByParamCount[dstParamCount]; if (dstTypeFlagsByParamIdx == null) { // This is the first method added with this number of params => no merging if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) { int srcParamCount = srcTypeFlagsByParamIdx.length; dstTypeFlagsByParamIdx = new int[dstParamCount]; for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) { dstTypeFlagsByParamIdx[paramIdx] = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1]; } } else { dstTypeFlagsByParamIdx = ALL_ZEROS_ARRAY; } typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx; } else { // dstTypeFlagsByParamIdx != null, so we need to merge into it. if (srcTypeFlagsByParamIdx == dstTypeFlagsByParamIdx) { // Used to occur when both are ALL_ZEROS_ARRAY return; } // As we will write dstTypeFlagsByParamIdx, it can't remain ALL_ZEROS_ARRAY anymore. if (dstTypeFlagsByParamIdx == ALL_ZEROS_ARRAY && dstParamCount > 0) { dstTypeFlagsByParamIdx = new int[dstParamCount]; typeFlagsByParamCount[dstParamCount] = dstTypeFlagsByParamIdx; } for (int paramIdx = 0; paramIdx < dstParamCount; paramIdx++) { final int srcParamTypeFlags; if (srcTypeFlagsByParamIdx != ALL_ZEROS_ARRAY) { int srcParamCount = srcTypeFlagsByParamIdx.length; srcParamTypeFlags = srcTypeFlagsByParamIdx[paramIdx < srcParamCount ? paramIdx : srcParamCount - 1]; } else { srcParamTypeFlags = 0; } final int dstParamTypesFlags = dstTypeFlagsByParamIdx[paramIdx]; if (dstParamTypesFlags != srcParamTypeFlags) { int mergedTypeFlags = dstParamTypesFlags | srcParamTypeFlags; if ((mergedTypeFlags & TypeFlags.MASK_ALL_NUMERICALS) != 0) { // Must not be set if we don't have numerical type at this index! mergedTypeFlags |= TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT; } dstTypeFlagsByParamIdx[paramIdx] = mergedTypeFlags; } } } } protected void forceNumberArgumentsToParameterTypes( Object[] args, Class[] paramTypes, int[] typeFlagsByParamIndex) { final int paramTypesLen = paramTypes.length; final int argsLen = args.length; for (int argIdx = 0; argIdx < argsLen; argIdx++) { final int paramTypeIdx = argIdx < paramTypesLen ? argIdx : paramTypesLen - 1; final int typeFlags = typeFlagsByParamIndex[paramTypeIdx]; // Forcing the number type can only be interesting if there are numerical parameter types on that index, // and the unwrapping was not to an exact numerical type. if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0) { final Object arg = args[argIdx]; // If arg isn't a number, we can't do any conversions anyway, regardless of the param type. if (arg instanceof Number) { final Class targetType = paramTypes[paramTypeIdx]; final Number convertedArg = BeansWrapper.forceUnwrappedNumberToType( (Number) arg, targetType, bugfixed); if (convertedArg != null) { args[argIdx] = convertedArg; } } } } } }