freemarker.ext.beans.OverloadedMethods Maven / Gradle / Ivy
/*
* 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 {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} for overloaded methods and
* constructors.
*
* 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/**/ 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/**/ 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), "." }
: "",
unwrappedArgs != null
? new Object[] {
"\nThe Java type of the argument values were: ",
getUnwrappedActualParameterTypes(unwrappedArgs) + "." }
: ""};
}
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 {@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);
}
}