/*
* 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.
*
* <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 {@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);
}
}