| /* |
| * 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.lang.reflect.Array; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import freemarker.core.BugException; |
| import freemarker.template.ObjectWrapperAndUnwrapper; |
| import freemarker.template.TemplateModel; |
| import freemarker.template.TemplateModelException; |
| |
| /** |
| * Stores the varargs methods for a {@link OverloadedMethods} object. |
| */ |
| class OverloadedVarArgsMethods extends OverloadedMethodsSubset { |
| |
| OverloadedVarArgsMethods(boolean bugfixed) { |
| super(bugfixed); |
| } |
| |
| /** |
| * Replaces the last parameter type with the array component type of it. |
| */ |
| @Override |
| Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) { |
| final Class[] preprocessedParamTypes = memberDesc.getParamTypes().clone(); |
| int ln = preprocessedParamTypes.length; |
| final Class varArgsCompType = preprocessedParamTypes[ln - 1].getComponentType(); |
| if (varArgsCompType == null) { |
| throw new BugException("Only varargs methods should be handled here"); |
| } |
| preprocessedParamTypes[ln - 1] = varArgsCompType; |
| return preprocessedParamTypes; |
| } |
| |
| @Override |
| void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) { |
| // Overview |
| // -------- |
| // |
| // So far, m(t1, t2...) was treated by the hint widening like m(t1, t2). So now we have to continue hint |
| // widening like if we had further methods: |
| // - m(t1, t2, t2), m(t1, t2, t2, t2), ... |
| // - m(t1), because a varargs array can be 0 long |
| // |
| // But we can't do that for real, because we had to add infinite number of methods. Also, for efficiency we |
| // don't want to create unwrappingHintsByParamCount entries at the indices which are still unused. |
| // So we only update the already existing hints. Remember that we already have m(t1, t2) there. |
| |
| final int paramCount = paramTypes.length; |
| final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount(); |
| |
| // The case of e(t1, t2), e(t1, t2, t2), e(t1, t2, t2, t2), ..., where e is an *earlierly* added method. |
| // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method, |
| // so we do that now: |
| // FIXME: Only needed if m(t1, t2) was filled an empty slot, otherwise whatever was there was already |
| // widened by the preceding hints, so this will be a no-op. |
| for (int i = paramCount - 1; i >= 0; i--) { |
| final Class[] previousHints = unwrappingHintsByParamCount[i]; |
| if (previousHints != null) { |
| widenHintsToCommonSupertypes( |
| paramCount, |
| previousHints, getTypeFlags(i)); |
| break; // we only do this for the first hit, as the methods before that has already widened it. |
| } |
| } |
| // The case of e(t1), where e is an *earlier* added method. |
| // When that was added, this method wasn't added yet, so it had no chance updating the hints of this method, |
| // so we do that now: |
| // FIXME: Same as above; it's often unnecessary. |
| if (paramCount + 1 < unwrappingHintsByParamCount.length) { |
| Class[] oneLongerHints = unwrappingHintsByParamCount[paramCount + 1]; |
| if (oneLongerHints != null) { |
| widenHintsToCommonSupertypes( |
| paramCount, |
| oneLongerHints, getTypeFlags(paramCount + 1)); |
| } |
| } |
| |
| // The case of m(t1, t2, t2), m(t1, t2, t2, t2), ..., where m is the currently added method. |
| // Update the longer hints-arrays: |
| for (int i = paramCount + 1; i < unwrappingHintsByParamCount.length; i++) { |
| widenHintsToCommonSupertypes( |
| i, |
| paramTypes, paramNumericalTypes); |
| } |
| // The case of m(t1) where m is the currently added method. |
| // update the one-shorter hints-array: |
| if (paramCount > 0) { // (should be always true, or else it wasn't a varags method) |
| widenHintsToCommonSupertypes( |
| paramCount - 1, |
| paramTypes, paramNumericalTypes); |
| } |
| |
| } |
| |
| private void widenHintsToCommonSupertypes( |
| int paramCountOfWidened, Class[] wideningTypes, int[] wideningTypeFlags) { |
| final Class[] typesToWiden = getUnwrappingHintsByParamCount()[paramCountOfWidened]; |
| if (typesToWiden == null) { |
| return; // no such overload exists; nothing to widen |
| } |
| |
| final int typesToWidenLen = typesToWiden.length; |
| final int wideningTypesLen = wideningTypes.length; |
| int min = Math.min(wideningTypesLen, typesToWidenLen); |
| for (int i = 0; i < min; ++i) { |
| typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], wideningTypes[i]); |
| } |
| if (typesToWidenLen > wideningTypesLen) { |
| Class varargsComponentType = wideningTypes[wideningTypesLen - 1]; |
| for (int i = wideningTypesLen; i < typesToWidenLen; ++i) { |
| typesToWiden[i] = getCommonSupertypeForUnwrappingHint(typesToWiden[i], varargsComponentType); |
| } |
| } |
| |
| if (bugfixed) { |
| mergeInTypesFlags(paramCountOfWidened, wideningTypeFlags); |
| } |
| } |
| |
| @Override |
| MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, BeansWrapper unwrapper) |
| throws TemplateModelException { |
| if (tmArgs == null) { |
| // null is treated as empty args |
| tmArgs = Collections.EMPTY_LIST; |
| } |
| final int argsLen = tmArgs.size(); |
| final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount(); |
| final Object[] pojoArgs = new Object[argsLen]; |
| int[] typesFlags = null; |
| // Going down starting from methods with args.length + 1 parameters, because we must try to match against a case |
| // where all specified args are fixargs, and we have 0 varargs. |
| outer: for (int paramCount = Math.min(argsLen + 1, unwrappingHintsByParamCount.length - 1); paramCount >= 0; --paramCount) { |
| Class[] unwarappingHints = unwrappingHintsByParamCount[paramCount]; |
| if (unwarappingHints == null) { |
| if (paramCount == 0) { |
| return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS; |
| } |
| continue; |
| } |
| |
| typesFlags = getTypeFlags(paramCount); |
| if (typesFlags == ALL_ZEROS_ARRAY) { |
| typesFlags = null; |
| } |
| |
| // Try to unwrap the arguments |
| Iterator it = tmArgs.iterator(); |
| for (int i = 0; i < argsLen; ++i) { |
| int paramIdx = i < paramCount ? i : paramCount - 1; |
| Object pojo = unwrapper.tryUnwrapTo( |
| (TemplateModel) it.next(), |
| unwarappingHints[paramIdx], |
| typesFlags != null ? typesFlags[paramIdx] : 0); |
| if (pojo == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| continue outer; |
| } |
| pojoArgs[i] = pojo; |
| } |
| break outer; |
| } |
| |
| MaybeEmptyCallableMemberDescriptor maybeEmtpyMemberDesc = getMemberDescriptorForArgs(pojoArgs, true); |
| if (maybeEmtpyMemberDesc instanceof CallableMemberDescriptor) { |
| CallableMemberDescriptor memberDesc = (CallableMemberDescriptor) maybeEmtpyMemberDesc; |
| Object[] pojoArgsWithArray; |
| Object argsOrErrorIdx = replaceVarargsSectionWithArray(pojoArgs, tmArgs, memberDesc, unwrapper); |
| if (argsOrErrorIdx instanceof Object[]) { |
| pojoArgsWithArray = (Object[]) argsOrErrorIdx; |
| } else { |
| return EmptyMemberAndArguments.noCompatibleOverload(((Integer) argsOrErrorIdx).intValue()); |
| } |
| if (bugfixed) { |
| if (typesFlags != null) { |
| // Note that overloaded method selection has already accounted for overflow errors when the method |
| // was selected. So this forced conversion shouldn't cause such corruption. Except, conversion from |
| // BigDecimal is allowed to overflow for backward-compatibility. |
| forceNumberArgumentsToParameterTypes(pojoArgsWithArray, memberDesc.getParamTypes(), typesFlags); |
| } |
| } else { |
| BeansWrapper.coerceBigDecimals(memberDesc.getParamTypes(), pojoArgsWithArray); |
| } |
| return new MemberAndArguments(memberDesc, pojoArgsWithArray); |
| } else { |
| return EmptyMemberAndArguments.from((EmptyCallableMemberDescriptor) maybeEmtpyMemberDesc, pojoArgs); |
| } |
| } |
| |
| /** |
| * Converts a flat argument list to one where the last argument is an array that collects the varargs, also |
| * re-unwraps the varargs to the component type. Note that this couldn't be done until we had the concrete |
| * member selected. |
| * |
| * @return An {@code Object[]} if everything went well, or an {@code Integer} the |
| * order (1-based index) of the argument that couldn't be unwrapped. |
| */ |
| private Object replaceVarargsSectionWithArray( |
| Object[] args, List modelArgs, CallableMemberDescriptor memberDesc, BeansWrapper unwrapper) |
| throws TemplateModelException { |
| final Class[] paramTypes = memberDesc.getParamTypes(); |
| final int paramCount = paramTypes.length; |
| final Class varArgsCompType = paramTypes[paramCount - 1].getComponentType(); |
| final int totalArgCount = args.length; |
| final int fixArgCount = paramCount - 1; |
| if (args.length != paramCount) { |
| Object[] packedArgs = new Object[paramCount]; |
| System.arraycopy(args, 0, packedArgs, 0, fixArgCount); |
| Object varargs = Array.newInstance(varArgsCompType, totalArgCount - fixArgCount); |
| for (int i = fixArgCount; i < totalArgCount; ++i) { |
| Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(i), varArgsCompType); |
| if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| return Integer.valueOf(i + 1); |
| } |
| Array.set(varargs, i - fixArgCount, val); |
| } |
| packedArgs[fixArgCount] = varargs; |
| return packedArgs; |
| } else { |
| Object val = unwrapper.tryUnwrapTo((TemplateModel) modelArgs.get(fixArgCount), varArgsCompType); |
| if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| return Integer.valueOf(fixArgCount + 1); |
| } |
| Object array = Array.newInstance(varArgsCompType, 1); |
| Array.set(array, 0, val); |
| args[fixArgCount] = array; |
| return args; |
| } |
| } |
| |
| } |