/*
 * 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.Constructor;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import freemarker.core.TemplateMarkupOutputModel;
import freemarker.core._DelayedConversionToString;
import freemarker.core._ErrorDescriptionBuilder;
import freemarker.core._TemplateModelException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.ClassUtil;

Used instead of Method or Constructor for overloaded methods and constructors.

After the initialization with the addMethod(Method) and addConstructor(Constructor) calls are done, the instance must be thread-safe. Before that, it's the responsibility of the caller of those methods to ensure that the object is properly publishing to other threads.

/** * Used instead of {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} for overloaded methods and * constructors. * * <p>After the initialization with the {@link #addMethod(Method)} and {@link #addConstructor(Constructor)} calls are * done, the instance must be thread-safe. Before that, it's the responsibility of the caller of those methods to * ensure that the object is properly publishing to other threads. */
final class OverloadedMethods { private final OverloadedMethodsSubset fixArgMethods; private OverloadedMethodsSubset varargMethods; private final boolean bugfixed; OverloadedMethods(boolean bugfixed) { this.bugfixed = bugfixed; fixArgMethods = new OverloadedFixArgsMethods(bugfixed); } void addMethod(Method method) { final Class[] paramTypes = method.getParameterTypes(); addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(method, paramTypes)); } void addConstructor(Constructor constr) { final Class[] paramTypes = constr.getParameterTypes(); addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(constr, paramTypes)); } private void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) { // Note: "varargs" methods are always callable as fixed args, with a sequence (array) as the last parameter. fixArgMethods.addCallableMemberDescriptor(memberDesc); if (memberDesc.isVarargs()) { if (varargMethods == null) { varargMethods = new OverloadedVarArgsMethods(bugfixed); } varargMethods.addCallableMemberDescriptor(memberDesc); } } MemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs, BeansWrapper unwrapper) throws TemplateModelException { // Try to find a fixed args match: MaybeEmptyMemberAndArguments fixArgsRes = fixArgMethods.getMemberAndArguments(tmArgs, unwrapper); if (fixArgsRes instanceof MemberAndArguments) { return (MemberAndArguments) fixArgsRes; } // Try to find a varargs match: MaybeEmptyMemberAndArguments varargsRes; if (varargMethods != null) { varargsRes = varargMethods.getMemberAndArguments(tmArgs, unwrapper); if (varargsRes instanceof MemberAndArguments) { return (MemberAndArguments) varargsRes; } } else { varargsRes = null; } _ErrorDescriptionBuilder edb = new _ErrorDescriptionBuilder( toCompositeErrorMessage( (EmptyMemberAndArguments) fixArgsRes, (EmptyMemberAndArguments) varargsRes, tmArgs), "\nThe matching overload was searched among these members:\n", memberListToString()); if (!bugfixed) { edb.tip("You seem to use BeansWrapper with incompatibleImprovements set below 2.3.21. If you think this " + "error is unfounded, enabling 2.3.21 fixes may helps. See version history for more."); } addMarkupBITipAfterNoNoMarchIfApplicable(edb, tmArgs); throw new _TemplateModelException(edb); } private Object[] toCompositeErrorMessage( final EmptyMemberAndArguments fixArgsEmptyRes, final EmptyMemberAndArguments varargsEmptyRes, List tmArgs) { final Object[] argsErrorMsg; if (varargsEmptyRes != null) { if (fixArgsEmptyRes == null || fixArgsEmptyRes.isNumberOfArgumentsWrong()) { argsErrorMsg = toErrorMessage(varargsEmptyRes, tmArgs); } else { argsErrorMsg = new Object[] { "When trying to call the non-varargs overloads:\n", toErrorMessage(fixArgsEmptyRes, tmArgs), "\nWhen trying to call the varargs overloads:\n", toErrorMessage(varargsEmptyRes, null) }; } } else { argsErrorMsg = toErrorMessage(fixArgsEmptyRes, tmArgs); } return argsErrorMsg; } private Object[] toErrorMessage(EmptyMemberAndArguments res, List/*<TemplateModel>*/ tmArgs) { final Object[] unwrappedArgs = res.getUnwrappedArguments(); return new Object[] { res.getErrorDescription(), tmArgs != null ? new Object[] { "\nThe FTL type of the argument values were: ", getTMActualParameterTypes(tmArgs), "." } : (Object) "", unwrappedArgs != null ? new Object[] { "\nThe Java type of the argument values were: ", getUnwrappedActualParameterTypes(unwrappedArgs) + "." } : (Object) ""}; } private _DelayedConversionToString memberListToString() { return new _DelayedConversionToString(null) { @Override protected String doConversion(Object obj) { final Iterator fixArgMethodsIter = fixArgMethods.getMemberDescriptors(); final Iterator varargMethodsIter = varargMethods != null ? varargMethods.getMemberDescriptors() : null; boolean hasMethods = fixArgMethodsIter.hasNext() || (varargMethodsIter != null && varargMethodsIter.hasNext()); if (hasMethods) { StringBuilder sb = new StringBuilder(); HashSet fixArgMethods = new HashSet(); while (fixArgMethodsIter.hasNext()) { if (sb.length() != 0) sb.append(",\n"); sb.append(" "); CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) fixArgMethodsIter.next(); fixArgMethods.add(callableMemberDesc); sb.append(callableMemberDesc.getDeclaration()); } if (varargMethodsIter != null) { while (varargMethodsIter.hasNext()) { CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) varargMethodsIter.next(); if (!fixArgMethods.contains(callableMemberDesc)) { if (sb.length() != 0) sb.append(",\n"); sb.append(" "); sb.append(callableMemberDesc.getDeclaration()); } } } return sb.toString(); } else { return "No members"; } } }; }
Adds tip to the error message if converting a TemplateMarkupOutputModel argument to String might allows finding a matching overload.
/** * Adds tip to the error message if converting a {@link TemplateMarkupOutputModel} argument to {@link String} might * allows finding a matching overload. */
private void addMarkupBITipAfterNoNoMarchIfApplicable(_ErrorDescriptionBuilder edb, List tmArgs) { for (int argIdx = 0; argIdx < tmArgs.size(); argIdx++) { Object tmArg = tmArgs.get(argIdx); if (tmArg instanceof TemplateMarkupOutputModel) { for (Iterator membDescs = fixArgMethods.getMemberDescriptors(); membDescs.hasNext();) { CallableMemberDescriptor membDesc = (CallableMemberDescriptor) membDescs.next(); Class[] paramTypes = membDesc.getParamTypes(); Class paramType = null; if (membDesc.isVarargs() && argIdx >= paramTypes.length - 1) { paramType = paramTypes[paramTypes.length - 1]; if (paramType.isArray()) { paramType = paramType.getComponentType(); } } if (paramType == null && argIdx < paramTypes.length) { paramType = paramTypes[argIdx]; } if (paramType != null) { if (paramType.isAssignableFrom(String.class) && !paramType.isAssignableFrom(tmArg.getClass())) { edb.tip(SimpleMethodModel.MARKUP_OUTPUT_TO_STRING_TIP); return; } } } } } } private _DelayedConversionToString getTMActualParameterTypes(List arguments) { final String[] argumentTypeDescs = new String[arguments.size()]; for (int i = 0; i < arguments.size(); i++) { argumentTypeDescs[i] = ClassUtil.getFTLTypeDescription((TemplateModel) arguments.get(i)); } return new DelayedCallSignatureToString(argumentTypeDescs) { @Override String argumentToString(Object argType) { return (String) argType; } }; } private Object getUnwrappedActualParameterTypes(Object[] unwrappedArgs) { final Class[] argumentTypes = new Class[unwrappedArgs.length]; for (int i = 0; i < unwrappedArgs.length; i++) { Object unwrappedArg = unwrappedArgs[i]; argumentTypes[i] = unwrappedArg != null ? unwrappedArg.getClass() : null; } return new DelayedCallSignatureToString(argumentTypes) { @Override String argumentToString(Object argType) { return argType != null ? ClassUtil.getShortClassName((Class) argType) : ClassUtil.getShortClassNameOfObject(null); } }; } private abstract class DelayedCallSignatureToString extends _DelayedConversionToString { public DelayedCallSignatureToString(Object[] argTypeArray) { super(argTypeArray); } @Override protected String doConversion(Object obj) { Object[] argTypes = (Object[]) obj; StringBuilder sb = new StringBuilder(); for (int i = 0; i < argTypes.length; i++) { if (i != 0) sb.append(", "); sb.append(argumentToString(argTypes[i])); } return sb.toString(); } abstract String argumentToString(Object argType); } }