| /* |
| * 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.lang.reflect.Member; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import freemarker.core.TemplateMarkupOutputModel; |
| import freemarker.core._DelayedFTLTypeDescription; |
| import freemarker.core._DelayedOrdinal; |
| import freemarker.core._ErrorDescriptionBuilder; |
| import freemarker.core._TemplateModelException; |
| import freemarker.template.ObjectWrapperAndUnwrapper; |
| import freemarker.template.TemplateModel; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.utility.ClassUtil; |
| |
| /** |
| * This class is used for as a base for non-overloaded method models and for constructors. |
| * (For overloaded methods and constructors see {@link OverloadedMethods}.) |
| */ |
| class SimpleMethod { |
| |
| static final String MARKUP_OUTPUT_TO_STRING_TIP |
| = "A markup output value can be converted to markup string like value?markup_string. " |
| + "But consider if the Java method whose argument it will be can handle markup strings properly."; |
| |
| private final Member member; |
| private final Class[] argTypes; |
| |
| protected SimpleMethod(Member member, Class[] argTypes) { |
| this.member = member; |
| this.argTypes = argTypes; |
| } |
| |
| Object[] unwrapArguments(List arguments, BeansWrapper wrapper) throws TemplateModelException { |
| if (arguments == null) { |
| arguments = Collections.EMPTY_LIST; |
| } |
| boolean isVarArg = _MethodUtil.isVarargs(member); |
| int typesLen = argTypes.length; |
| if (isVarArg) { |
| if (typesLen - 1 > arguments.size()) { |
| throw new _TemplateModelException( |
| _MethodUtil.invocationErrorMessageStart(member), |
| " takes at least ", Integer.valueOf(typesLen - 1), |
| typesLen - 1 == 1 ? " argument" : " arguments", ", but ", |
| Integer.valueOf(arguments.size()), " was given."); |
| } |
| } else if (typesLen != arguments.size()) { |
| throw new _TemplateModelException( |
| _MethodUtil.invocationErrorMessageStart(member), |
| " takes ", Integer.valueOf(typesLen), typesLen == 1 ? " argument" : " arguments", ", but ", |
| Integer.valueOf(arguments.size()), " was given."); |
| } |
| |
| Object[] args = unwrapArguments(arguments, argTypes, isVarArg, wrapper); |
| return args; |
| } |
| |
| private Object[] unwrapArguments(List args, Class[] argTypes, boolean isVarargs, |
| BeansWrapper w) |
| throws TemplateModelException { |
| if (args == null) return null; |
| |
| int typesLen = argTypes.length; |
| int argsLen = args.size(); |
| |
| Object[] unwrappedArgs = new Object[typesLen]; |
| |
| // Unwrap arguments: |
| Iterator it = args.iterator(); |
| int normalArgCnt = isVarargs ? typesLen - 1 : typesLen; |
| int argIdx = 0; |
| while (argIdx < normalArgCnt) { |
| Class argType = argTypes[argIdx]; |
| TemplateModel argVal = (TemplateModel) it.next(); |
| Object unwrappedArgVal = w.tryUnwrapTo(argVal, argType); |
| if (unwrappedArgVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| throw createArgumentTypeMismarchException(argIdx, argVal, argType); |
| } |
| if (unwrappedArgVal == null && argType.isPrimitive()) { |
| throw createNullToPrimitiveArgumentException(argIdx, argType); |
| } |
| |
| unwrappedArgs[argIdx++] = unwrappedArgVal; |
| } |
| if (isVarargs) { |
| // The last argType, which is the vararg type, wasn't processed yet. |
| |
| Class varargType = argTypes[typesLen - 1]; |
| Class varargItemType = varargType.getComponentType(); |
| if (!it.hasNext()) { |
| unwrappedArgs[argIdx++] = Array.newInstance(varargItemType, 0); |
| } else { |
| TemplateModel argVal = (TemplateModel) it.next(); |
| |
| Object unwrappedArgVal; |
| // We first try to treat the last argument as a vararg *array*. |
| // This is consistent to what OverloadedVarArgMethod does. |
| if (argsLen - argIdx == 1 |
| && (unwrappedArgVal = w.tryUnwrapTo(argVal, varargType)) |
| != ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| // It was a vararg array. |
| unwrappedArgs[argIdx++] = unwrappedArgVal; |
| } else { |
| // It wasn't a vararg array, so we assume it's a vararg |
| // array *item*, possibly followed by further ones. |
| int varargArrayLen = argsLen - argIdx; |
| Object varargArray = Array.newInstance(varargItemType, varargArrayLen); |
| for (int varargIdx = 0; varargIdx < varargArrayLen; varargIdx++) { |
| TemplateModel varargVal = (TemplateModel) (varargIdx == 0 ? argVal : it.next()); |
| Object unwrappedVarargVal = w.tryUnwrapTo(varargVal, varargItemType); |
| if (unwrappedVarargVal == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| throw createArgumentTypeMismarchException( |
| argIdx + varargIdx, varargVal, varargItemType); |
| } |
| |
| if (unwrappedVarargVal == null && varargItemType.isPrimitive()) { |
| throw createNullToPrimitiveArgumentException(argIdx + varargIdx, varargItemType); |
| } |
| Array.set(varargArray, varargIdx, unwrappedVarargVal); |
| } |
| unwrappedArgs[argIdx++] = varargArray; |
| } |
| } |
| } |
| |
| return unwrappedArgs; |
| } |
| |
| private TemplateModelException createArgumentTypeMismarchException( |
| int argIdx, TemplateModel argVal, Class targetType) { |
| _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( |
| _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: Can't convert the ", |
| new _DelayedOrdinal(Integer.valueOf(argIdx + 1)), |
| " argument's value to the target Java type, ", ClassUtil.getShortClassName(targetType), |
| ". The type of the actual value was: ", new _DelayedFTLTypeDescription(argVal)); |
| if (argVal instanceof TemplateMarkupOutputModel && (targetType.isAssignableFrom(String.class))) { |
| desc.tip(MARKUP_OUTPUT_TO_STRING_TIP); |
| } |
| return new _TemplateModelException(desc); |
| } |
| |
| private TemplateModelException createNullToPrimitiveArgumentException(int argIdx, Class targetType) { |
| return new _TemplateModelException( |
| _MethodUtil.invocationErrorMessageStart(member), " couldn't be called: The value of the ", |
| new _DelayedOrdinal(Integer.valueOf(argIdx + 1)), |
| " argument was null, but the target Java parameter type (", ClassUtil.getShortClassName(targetType), |
| ") is primitive and so can't store null."); |
| } |
| |
| protected Member getMember() { |
| return member; |
| } |
| } |