/*
* 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;
}
}
}