Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 8A58E200C29 for ; Tue, 28 Feb 2017 23:58:04 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 88DEC160B83; Tue, 28 Feb 2017 22:58:04 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 686ED160B81 for ; Tue, 28 Feb 2017 23:58:02 +0100 (CET) Received: (qmail 32114 invoked by uid 500); 28 Feb 2017 22:58:01 -0000 Mailing-List: contact notifications-help@freemarker.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@freemarker.incubator.apache.org Delivered-To: mailing list notifications@freemarker.incubator.apache.org Received: (qmail 32011 invoked by uid 99); 28 Feb 2017 22:58:01 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 28 Feb 2017 22:58:01 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id E4342C6AE2 for ; Tue, 28 Feb 2017 22:58:00 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -2.569 X-Spam-Level: X-Spam-Status: No, score=-2.569 tagged_above=-999 required=6.31 tests=[BANG_GUAR=1, KAM_ASCII_DIVIDERS=0.8, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.001, SPF_NEUTRAL=0.652] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id WpCSagt7pEFZ for ; Tue, 28 Feb 2017 22:57:55 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id ADF255FCD9 for ; Tue, 28 Feb 2017 22:57:36 +0000 (UTC) Received: (qmail 28014 invoked by uid 99); 28 Feb 2017 22:57:35 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 28 Feb 2017 22:57:35 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id C6AD2F31F3; Tue, 28 Feb 2017 22:57:35 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ddekany@apache.org To: notifications@freemarker.incubator.apache.org Date: Tue, 28 Feb 2017 22:57:49 -0000 Message-Id: <815a09b103c549f584ce5831ad8210a5@git.apache.org> In-Reply-To: <3139a58e775349839a22ba38c860eadf@git.apache.org> References: <3139a58e775349839a22ba38c860eadf@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [15/20] incubator-freemarker git commit: Removed BeansWrapper, merging it into DefaultObjectWrapper (the o.a.f.core.model.impl.beans packageis gone now). It works, but there's a lot of unused classes and logic now, which will have to be removed. archived-at: Tue, 28 Feb 2017 22:58:04 -0000 http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java new file mode 100644 index 0000000..a34c390 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java @@ -0,0 +1,245 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.lang.reflect.Array; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.util.BugException; + +/** + * Stores the varargs methods for a {@link OverloadedMethods} object. + */ +class OverloadedVarArgsMethods extends OverloadedMethodsSubset { + + OverloadedVarArgsMethods() { + super(); + } + + /** + * 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); + } + } + + mergeInTypesFlags(paramCountOfWidened, wideningTypeFlags); + } + + @Override + MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, DefaultObjectWrapper 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 (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); + } + 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, DefaultObjectWrapper 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; + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java b/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java new file mode 100644 index 0000000..46e1cdf --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/PrimtiveArrayBackedReadOnlyList.java @@ -0,0 +1,47 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.lang.reflect.Array; +import java.util.AbstractList; + +/** + * Similar to {@link NonPrimitiveArrayBackedReadOnlyList}, but uses reflection so that it works with primitive arrays + * too. + */ +class PrimtiveArrayBackedReadOnlyList extends AbstractList { + + private final Object array; + + PrimtiveArrayBackedReadOnlyList(Object array) { + this.array = array; + } + + @Override + public Object get(int index) { + return Array.get(array, index); + } + + @Override + public int size() { + return Array.getLength(array); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java new file mode 100644 index 0000000..61eb87c --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/ReflectionCallableMemberDescriptor.java @@ -0,0 +1,95 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * The most commonly used {@link CallableMemberDescriptor} implementation. + */ +final class ReflectionCallableMemberDescriptor extends CallableMemberDescriptor { + + private final Member/*Method|Constructor*/ member; + + /** + * Don't modify this array! + */ + final Class[] paramTypes; + + ReflectionCallableMemberDescriptor(Method member, Class[] paramTypes) { + this.member = member; + this.paramTypes = paramTypes; + } + + ReflectionCallableMemberDescriptor(Constructor member, Class[] paramTypes) { + this.member = member; + this.paramTypes = paramTypes; + } + + @Override + TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj, Object[] args) + throws TemplateModelException, InvocationTargetException, IllegalAccessException { + return ow.invokeMethod(obj, (Method) member, args); + } + + @Override + Object invokeConstructor(DefaultObjectWrapper ow, Object[] args) + throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { + return ((Constructor) member).newInstance(args); + } + + @Override + String getDeclaration() { + return _MethodUtil.toString(member); + } + + @Override + boolean isConstructor() { + return member instanceof Constructor; + } + + @Override + boolean isStatic() { + return (member.getModifiers() & Modifier.STATIC) != 0; + } + + @Override + boolean isVarargs() { + return _MethodUtil.isVarargs(member); + } + + @Override + Class[] getParamTypes() { + return paramTypes; + } + + @Override + String getName() { + return member.getName(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java b/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java new file mode 100644 index 0000000..27bdf96 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java @@ -0,0 +1,190 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.text.MessageFormat; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +import org.apache.freemarker.core._DelayedJQuote; +import org.apache.freemarker.core._TemplateModelException; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + *

A hash model that wraps a resource bundle. Makes it convenient to store + * localized content in the data model. It also acts as a method model that will + * take a resource key and arbitrary number of arguments and will apply + * {@link MessageFormat} with arguments on the string represented by the key.

+ * + *

Typical usages:

+ *
    + *
  • bundle.resourceKey will retrieve the object from resource bundle + * with key resourceKey
  • + *
  • bundle("patternKey", arg1, arg2, arg3) will retrieve the string + * from resource bundle with key patternKey, and will use it as a pattern + * for MessageFormat with arguments arg1, arg2 and arg3
  • + *
+ */ +public class ResourceBundleModel + extends + BeanModel + implements + TemplateMethodModelEx { + static final ModelFactory FACTORY = + new ModelFactory() + { + @Override + public TemplateModel create(Object object, ObjectWrapper wrapper) { + return new ResourceBundleModel((ResourceBundle) object, (DefaultObjectWrapper) wrapper); + } + }; + + private Hashtable formats = null; + + public ResourceBundleModel(ResourceBundle bundle, DefaultObjectWrapper wrapper) { + super(bundle, wrapper); + } + + /** + * Overridden to invoke the getObject method of the resource bundle. + */ + @Override + protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key) + throws TemplateModelException { + try { + return wrap(((ResourceBundle) object).getObject(key)); + } catch (MissingResourceException e) { + throw new _TemplateModelException(e, + "No ", new _DelayedJQuote(key), " key in the ResourceBundle. " + + "Note that conforming to the ResourceBundle Java API, this is an error and not just " + + "a missing sub-variable (a null)."); + } + } + + /** + * Returns true if this bundle contains no objects. + */ + @Override + public boolean isEmpty() { + return !((ResourceBundle) object).getKeys().hasMoreElements() && + super.isEmpty(); + } + + @Override + public int size() { + return keySet().size(); + } + + @Override + protected Set keySet() { + Set set = super.keySet(); + Enumeration e = ((ResourceBundle) object).getKeys(); + while (e.hasMoreElements()) { + set.add(e.nextElement()); + } + return set; + } + + /** + * Takes first argument as a resource key, looks up a string in resource bundle + * with this key, then applies a MessageFormat.format on the string with the + * rest of the arguments. The created MessageFormats are cached for later reuse. + */ + @Override + public Object exec(List arguments) + throws TemplateModelException { + // Must have at least one argument - the key + if (arguments.size() < 1) + throw new TemplateModelException("No message key was specified"); + // Read it + Iterator it = arguments.iterator(); + String key = unwrap((TemplateModel) it.next()).toString(); + try { + if (!it.hasNext()) { + return wrap(((ResourceBundle) object).getObject(key)); + } + + // Copy remaining arguments into an Object[] + int args = arguments.size() - 1; + Object[] params = new Object[args]; + for (int i = 0; i < args; ++i) + params[i] = unwrap((TemplateModel) it.next()); + + // Invoke format + return new StringModel(format(key, params), wrapper); + } catch (MissingResourceException e) { + throw new TemplateModelException("No such key: " + key); + } catch (Exception e) { + throw new TemplateModelException(e.getMessage()); + } + } + + /** + * Provides direct access to caching format engine from code (instead of from script). + */ + public String format(String key, Object[] params) + throws MissingResourceException { + // Check to see if we already have a cache for message formats + // and construct it if we don't + // NOTE: this block statement should be synchronized. However + // concurrent creation of two caches will have no harmful + // consequences, and we avoid a performance hit. + /* synchronized(this) */ + { + if (formats == null) + formats = new Hashtable(); + } + + MessageFormat format = null; + // Check to see if we already have a requested MessageFormat cached + // and construct it if we don't + // NOTE: this block statement should be synchronized. However + // concurrent creation of two formats will have no harmful + // consequences, and we avoid a performance hit. + /* synchronized(formats) */ + { + format = (MessageFormat) formats.get(key); + if (format == null) { + format = new MessageFormat(((ResourceBundle) object).getString(key)); + format.setLocale(getBundle().getLocale()); + formats.put(key, format); + } + } + + // Perform the formatting. We synchronize on it in case it + // contains date formatting, which is not thread-safe. + synchronized (format) { + return format.format(params); + } + } + + public ResourceBundle getBundle() { + return (ResourceBundle) object; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java new file mode 100644 index 0000000..96da1b8 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java @@ -0,0 +1,68 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.AbstractList; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelAdapter; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.util.UndeclaredThrowableException; + +/** + */ +class SequenceAdapter extends AbstractList implements TemplateModelAdapter { + private final DefaultObjectWrapper wrapper; + private final TemplateSequenceModel model; + + SequenceAdapter(TemplateSequenceModel model, DefaultObjectWrapper wrapper) { + this.model = model; + this.wrapper = wrapper; + } + + @Override + public TemplateModel getTemplateModel() { + return model; + } + + @Override + public int size() { + try { + return model.size(); + } catch (TemplateModelException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public Object get(int index) { + try { + return wrapper.unwrap(model.get(index)); + } catch (TemplateModelException e) { + throw new UndeclaredThrowableException(e); + } + } + + public TemplateSequenceModel getTemplateSequenceModel() { + return model; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java new file mode 100644 index 0000000..a589040 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java @@ -0,0 +1,32 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.Set; + +import org.apache.freemarker.core.model.TemplateCollectionModel; + +/** + */ +class SetAdapter extends CollectionAdapter implements Set { + SetAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) { + super(model, wrapper); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java index 40c33f7..fa44144 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java @@ -37,7 +37,6 @@ import org.apache.freemarker.core.model.TemplateHashModelEx2; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.WrappingTemplateModel; -import org.apache.freemarker.core.model.impl.beans.BeansWrapper; /** * A simple implementation of the {@link TemplateHashModelEx} interface, using its own underlying {@link Map} or @@ -308,13 +307,13 @@ public class SimpleHash extends WrappingTemplateModel implements TemplateHashMod } // Create a copy to maintain immutability semantics and // Do nested unwrapping of elements if necessary. - BeansWrapper bw = _StaticObjectWrappers.BEANS_WRAPPER; + DefaultObjectWrapper ow = _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER; for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Object key = entry.getKey(); Object value = entry.getValue(); if (value instanceof TemplateModel) { - value = bw.unwrap((TemplateModel) value); + value = ow.unwrap((TemplateModel) value); } m.put(key, value); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java new file mode 100644 index 0000000..cf9836e --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMapModel.java @@ -0,0 +1,129 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.List; +import java.util.Map; + +import org.apache.freemarker.core.model.AdapterTemplateModel; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.RichObjectWrapper; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateHashModelEx2; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelWithAPISupport; +import org.apache.freemarker.core.model.WrapperTemplateModel; +import org.apache.freemarker.core.model.WrappingTemplateModel; + +/** + * Model used by {@link DefaultObjectWrapper} when simpleMapWrapper + * mode is enabled. Provides a simple hash model interface to the + * underlying map (does not copy like {@link org.apache.freemarker.core.model.impl.SimpleHash}), + * and a method interface to non-string keys. + */ +public class SimpleMapModel extends WrappingTemplateModel +implements TemplateHashModelEx2, TemplateMethodModelEx, AdapterTemplateModel, +WrapperTemplateModel, TemplateModelWithAPISupport { + static final ModelFactory FACTORY = + new ModelFactory() + { + @Override + public TemplateModel create(Object object, ObjectWrapper wrapper) { + return new SimpleMapModel((Map) object, (DefaultObjectWrapper) wrapper); + } + }; + + private final Map map; + + public SimpleMapModel(Map map, DefaultObjectWrapper wrapper) { + super(wrapper); + this.map = map; + } + + @Override + public TemplateModel get(String key) throws TemplateModelException { + Object val = map.get(key); + if (val == null) { + if (key.length() == 1) { + // just check for Character key if this is a single-character string + Character charKey = Character.valueOf(key.charAt(0)); + val = map.get(charKey); + if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) { + return null; + } + } else if (!map.containsKey(key)) { + return null; + } + } + return wrap(val); + } + + @Override + public Object exec(List args) throws TemplateModelException { + Object key = ((DefaultObjectWrapper) getObjectWrapper()).unwrap((TemplateModel) args.get(0)); + Object value = map.get(key); + if (value == null && !map.containsKey(key)) { + return null; + } + return wrap(value); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public TemplateCollectionModel keys() { + return new CollectionAndSequence(new SimpleSequence(map.keySet(), getObjectWrapper())); + } + + @Override + public TemplateCollectionModel values() { + return new CollectionAndSequence(new SimpleSequence(map.values(), getObjectWrapper())); + } + + @Override + public KeyValuePairIterator keyValuePairIterator() { + return new MapKeyValuePairIterator(map, getObjectWrapper()); + } + + @Override + public Object getAdaptedObject(Class hint) { + return map; + } + + @Override + public Object getWrappedObject() { + return map; + } + + @Override + public TemplateModel getAPI() throws TemplateModelException { + return ((RichObjectWrapper) getObjectWrapper()).wrapAsAPI(map); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java new file mode 100644 index 0000000..4fc81d0 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java @@ -0,0 +1,174 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.lang.reflect.Array; +import java.lang.reflect.Member; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.freemarker.core._DelayedFTLTypeDescription; +import org.apache.freemarker.core._DelayedOrdinal; +import org.apache.freemarker.core._ErrorDescriptionBuilder; +import org.apache.freemarker.core._TemplateModelException; +import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; +import org.apache.freemarker.core.model.TemplateMarkupOutputModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.util._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, DefaultObjectWrapper 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."); + } + + return unwrapArguments(arguments, argTypes, isVarArg, wrapper); + } + + private Object[] unwrapArguments(List args, Class[] argTypes, boolean isVarargs, + DefaultObjectWrapper 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; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java new file mode 100644 index 0000000..943d8f0 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethodModel.java @@ -0,0 +1,133 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.util.FTLUtil; + +/** + * A class that will wrap a reflected method call into a + * {@link org.apache.freemarker.core.model.TemplateMethodModel} interface. + * It is used by {@link BeanModel} to wrap reflected method calls + * for non-overloaded methods. + */ +public final class SimpleMethodModel extends SimpleMethod + implements + TemplateMethodModelEx, + TemplateSequenceModel, + _UnexpectedTypeErrorExplainerTemplateModel { + private final Object object; + private final DefaultObjectWrapper wrapper; + + /** + * Creates a model for a specific method on a specific object. + * @param object the object to call the method on, or {@code null} for a static method. + * @param method the method that will be invoked. + * @param argTypes Either pass in {@code Method#getParameterTypes() method.getParameterTypes()} here, + * or reuse an earlier result of that call (for speed). Not {@code null}. + */ + SimpleMethodModel(Object object, Method method, Class[] argTypes, + DefaultObjectWrapper wrapper) { + super(method, argTypes); + this.object = object; + this.wrapper = wrapper; + } + + /** + * Invokes the method, passing it the arguments from the list. + */ + @Override + public Object exec(List arguments) + throws TemplateModelException { + try { + return wrapper.invokeMethod(object, (Method) getMember(), + unwrapArguments(arguments, wrapper)); + } catch (TemplateModelException e) { + throw e; + } catch (Exception e) { + throw _MethodUtil.newInvocationTemplateModelException(object, getMember(), e); + } + } + + @Override + public TemplateModel get(int index) throws TemplateModelException { + return (TemplateModel) exec(Collections.singletonList( + new SimpleNumber(Integer.valueOf(index)))); + } + + @Override + public int size() throws TemplateModelException { + throw new TemplateModelException( + "Getting the number of items or enumerating the items is not supported on this " + + FTLUtil.getTypeDescription(this) + " value.\n" + + "(" + + "Hint 1: Maybe you wanted to call this method first and then do something with its return value. " + + "Hint 2: Getting items by intex possibly works, hence it's a \"+sequence\"." + + ")"); + } + + @Override + public String toString() { + return getMember().toString(); + } + + /** + * Implementation of experimental interface; don't use it, no backward compatibility guarantee! + */ + @Override + public Object[] explainTypeError(Class[] expectedClasses) { + final Member member = getMember(); + if (!(member instanceof Method)) { + return null; // This shouldn't occur + } + Method m = (Method) member; + + final Class returnType = m.getReturnType(); + if (returnType == null || returnType == void.class || returnType == Void.class) { + return null; // Calling it won't help + } + + String mName = m.getName(); + if (mName.startsWith("get") && mName.length() > 3 && Character.isUpperCase(mName.charAt(3)) + && (m.getParameterTypes().length == 0)) { + return new Object[] { + "Maybe using obj.something instead of obj.getSomething will yield the desired value." }; + } else if (mName.startsWith("is") && mName.length() > 2 && Character.isUpperCase(mName.charAt(2)) + && (m.getParameterTypes().length == 0)) { + return new Object[] { + "Maybe using obj.something instead of obj.isSomething will yield the desired value." }; + } else { + return new Object[] { + "Maybe using obj.something(", + (m.getParameterTypes().length != 0 ? "params" : ""), + ") instead of obj.something will yield the desired value" }; + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java index 61378f0..281b8ac 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleObjectWrapper.java @@ -23,7 +23,7 @@ import org.apache.freemarker.core.Version; import org.apache.freemarker.core.model.TemplateHashModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.impl.beans.BeansWrapper; +import org.w3c.dom.Node; /** * A restricted object wrapper that will not expose arbitrary object, just those that directly correspond to the @@ -33,21 +33,30 @@ import org.apache.freemarker.core.model.impl.beans.BeansWrapper; public class SimpleObjectWrapper extends DefaultObjectWrapper { /** - * @param incompatibleImprovements see in {@link BeansWrapper#BeansWrapper(Version)}. + * @param incompatibleImprovements see in {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. * * @since 2.3.21 */ public SimpleObjectWrapper(Version incompatibleImprovements) { super(incompatibleImprovements); } - + + @Override + protected TemplateModel handW3CNode(Node node) throws TemplateModelException { + throw newUnhandledTypeException(node); + } + /** * Called if a type other than the simple ones we know about is passed in. * In this implementation, this just throws an exception. */ @Override protected TemplateModel handleUnknownType(Object obj) throws TemplateModelException { - throw new TemplateModelException("SimpleObjectWrapper deliberately won't wrap this type: " + throw newUnhandledTypeException(obj); + } + + private TemplateModelException newUnhandledTypeException(Object obj) throws TemplateModelException { + return new TemplateModelException("SimpleObjectWrapper deliberately won't wrap this type: " + obj.getClass().getName()); } @@ -55,5 +64,5 @@ public class SimpleObjectWrapper extends DefaultObjectWrapper { public TemplateHashModel wrapAsAPI(Object obj) throws TemplateModelException { throw new TemplateModelException("SimpleObjectWrapper deliberately doesn't allow ?api."); } - + } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java b/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java index b0d5b81..fa59010 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java @@ -33,7 +33,6 @@ import org.apache.freemarker.core.model.TemplateModelException; import org.apache.freemarker.core.model.TemplateModelIterator; import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.model.WrappingTemplateModel; -import org.apache.freemarker.core.model.impl.beans.BeansWrapper; /** * A simple implementation of the {@link TemplateSequenceModel} interface, using its own underlying {@link List} for @@ -223,11 +222,11 @@ public class SimpleSequence extends WrappingTemplateModel implements TemplateSeq throw new TemplateModelException("Error instantiating an object of type " + listClass.getName(), e); } - BeansWrapper bw = _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER; + DefaultObjectWrapper ow = _StaticObjectWrappers.DEFAULT_OBJECT_WRAPPER; for (int i = 0; i < list.size(); i++) { Object elem = list.get(i); if (elem instanceof TemplateModel) { - elem = bw.unwrap((TemplateModel) elem); + elem = ow.unwrap((TemplateModel) elem); } result.add(elem); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java b/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java new file mode 100644 index 0000000..70f50f6 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/SingletonCustomizer.java @@ -0,0 +1,51 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import org.apache.freemarker.core.model.ObjectWrapper; + +/** + * Marker interface useful when used together with {@link MethodAppearanceFineTuner} and such customizer objects, to + * indicate that it doesn't contain reference to the {@link ObjectWrapper} (so beware with non-static inner + * classes) and can be and should be used in call introspection cache keys. This also implies that you won't + * create many instances of the class, rather just reuse the same (or same few) instances over and over. Furthermore, + * the instances must be thread-safe. The typical pattern in which this instance should be used is like this: + * + *
static class MyMethodAppearanceFineTuner implements MethodAppearanceFineTuner, SingletonCustomizer {
+ *      
+ *    // This is the singleton:
+ *    static final MyMethodAppearanceFineTuner INSTANCE = new MyMethodAppearanceFineTuner();
+ *     
+ *    // Private, so it can't be constructed from outside this class.
+ *    private MyMethodAppearanceFineTuner() { }
+ *
+ *    @Override
+ *    public void fineTuneMethodAppearance(...) {
+ *       // Do something here, only using the parameters and maybe some other singletons. 
+ *       ...
+ *    }
+ *     
+ * }
+ * + * @since 2.3.21 + */ +public interface SingletonCustomizer { + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java b/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java new file mode 100644 index 0000000..1a6263c --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java @@ -0,0 +1,177 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.freemarker.core._CoreLogs; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.slf4j.Logger; + +/** + * Wraps the static fields and methods of a class in a + * {@link org.apache.freemarker.core.model.TemplateHashModel}. + * Fields are wrapped using {@link DefaultObjectWrapper#wrap(Object)}, and + * methods are wrapped into an appropriate {@link org.apache.freemarker.core.model.TemplateMethodModelEx} instance. + * Unfortunately, there is currently no support for bean property-style + * calls of static methods, similar to that in {@link BeanModel}. + */ +final class StaticModel implements TemplateHashModelEx { + + private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER; + + private final Class clazz; + private final DefaultObjectWrapper wrapper; + private final Map map = new HashMap(); + + StaticModel(Class clazz, DefaultObjectWrapper wrapper) throws TemplateModelException { + this.clazz = clazz; + this.wrapper = wrapper; + populate(); + } + + /** + * Returns the field or method named by the key + * parameter. + */ + @Override + public TemplateModel get(String key) throws TemplateModelException { + Object model = map.get(key); + // Simple method, overloaded method or final field -- these have cached + // template models + if (model instanceof TemplateModel) + return (TemplateModel) model; + // Non-final field; this must be evaluated on each call. + if (model instanceof Field) { + try { + return wrapper.getOuterIdentity().wrap(((Field) model).get(null)); + } catch (IllegalAccessException e) { + throw new TemplateModelException( + "Illegal access for field " + key + " of class " + clazz.getName()); + } + } + + throw new TemplateModelException( + "No such key: " + key + " in class " + clazz.getName()); + } + + /** + * Returns true if there is at least one public static + * field or method in the underlying class. + */ + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public TemplateCollectionModel keys() throws TemplateModelException { + return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.keySet()); + } + + @Override + public TemplateCollectionModel values() throws TemplateModelException { + return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.values()); + } + + private void populate() throws TemplateModelException { + if (!Modifier.isPublic(clazz.getModifiers())) { + throw new TemplateModelException( + "Can't wrap the non-public class " + clazz.getName()); + } + + if (wrapper.getExposureLevel() == DefaultObjectWrapper.EXPOSE_NOTHING) { + return; + } + + Field[] fields = clazz.getFields(); + for (Field field : fields) { + int mod = field.getModifiers(); + if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) { + if (Modifier.isFinal(mod)) + try { + // public static final fields are evaluated once and + // stored in the map + map.put(field.getName(), wrapper.getOuterIdentity().wrap(field.get(null))); + } catch (IllegalAccessException e) { + // Intentionally ignored + } + else + // This is a special flagging value: Field in the map means + // that this is a non-final field, and it must be evaluated + // on each get() call. + map.put(field.getName(), field); + } + } + if (wrapper.getExposureLevel() < DefaultObjectWrapper.EXPOSE_PROPERTIES_ONLY) { + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + int mod = method.getModifiers(); + if (Modifier.isPublic(mod) && Modifier.isStatic(mod) + && wrapper.getClassIntrospector().isAllowedToExpose(method)) { + String name = method.getName(); + Object obj = map.get(name); + if (obj instanceof Method) { + OverloadedMethods overloadedMethods = new OverloadedMethods(); + overloadedMethods.addMethod((Method) obj); + overloadedMethods.addMethod(method); + map.put(name, overloadedMethods); + } else if (obj instanceof OverloadedMethods) { + OverloadedMethods overloadedMethods = (OverloadedMethods) obj; + overloadedMethods.addMethod(method); + } else { + if (obj != null) { + if (LOG.isInfoEnabled()) { + LOG.info("Overwriting value [" + obj + "] for " + + " key '" + name + "' with [" + method + + "] in static model for " + clazz.getName()); + } + } + map.put(name, method); + } + } + } + for (Iterator entries = map.entrySet().iterator(); entries.hasNext(); ) { + Map.Entry entry = (Map.Entry) entries.next(); + Object value = entry.getValue(); + if (value instanceof Method) { + Method method = (Method) value; + entry.setValue(new SimpleMethodModel(null, method, + method.getParameterTypes(), wrapper)); + } else if (value instanceof OverloadedMethods) { + entry.setValue(new OverloadedMethodsModel(null, (OverloadedMethods) value, wrapper)); + } + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java b/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java new file mode 100644 index 0000000..04dd3a5 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/StaticModels.java @@ -0,0 +1,43 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Utility class for instantiating {@link StaticModel} instances from + * templates. If your template's data model contains an instance of + * StaticModels (named, say StaticModels), then you can + * instantiate an arbitrary StaticModel using get syntax (i.e. + * StaticModels["java.lang.System"].currentTimeMillis()). + */ +class StaticModels extends ClassBasedModelFactory { + + StaticModels(DefaultObjectWrapper wrapper) { + super(wrapper); + } + + @Override + protected TemplateModel createModel(Class clazz) + throws TemplateModelException { + return new StaticModel(clazz, getWrapper()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java b/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java new file mode 100644 index 0000000..499c291 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/StringModel.java @@ -0,0 +1,63 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateScalarModel; + +/** + * Subclass of {@link BeanModel} that exposes the return value of the {@link + * java.lang.Object#toString()} method through the {@link TemplateScalarModel} + * interface. + */ +public class StringModel extends BeanModel +implements TemplateScalarModel { + static final ModelFactory FACTORY = + new ModelFactory() + { + @Override + public TemplateModel create(Object object, ObjectWrapper wrapper) { + return new StringModel(object, (DefaultObjectWrapper) wrapper); + } + }; + + /** + * Creates a new model that wraps the specified object with BeanModel + scalar + * functionality. + * @param object the object to wrap into a model. + * @param wrapper the {@link DefaultObjectWrapper} associated with this model. + * Every model has to have an associated {@link DefaultObjectWrapper} instance. The + * model gains many attributes from its wrapper, including the caching + * behavior, method exposure level, method-over-item shadowing policy etc. + */ + public StringModel(Object object, DefaultObjectWrapper wrapper) { + super(object, wrapper); + } + + /** + * Returns the result of calling {@link Object#toString()} on the wrapped + * object. + */ + @Override + public String getAsString() { + return object.toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java b/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java new file mode 100644 index 0000000..d81ab74 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/TypeFlags.java @@ -0,0 +1,130 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Flag values and masks for "type flags". "Type flags" is a set of bits that store information about the possible + * destination types at a parameter position of overloaded methods. + */ +class TypeFlags { + + /** + * Indicates that the unwrapping hint will not be a specific numerical type; it must not be set if there's no + * numerical type at the given parameter index. + */ + static final int WIDENED_NUMERICAL_UNWRAPPING_HINT = 1; + + static final int BYTE = 4; + static final int SHORT = 8; + static final int INTEGER = 16; + static final int LONG = 32; + static final int FLOAT = 64; + static final int DOUBLE = 128; + static final int BIG_INTEGER = 256; + static final int BIG_DECIMAL = 512; + static final int UNKNOWN_NUMERICAL_TYPE = 1024; + + static final int ACCEPTS_NUMBER = 0x800; + static final int ACCEPTS_DATE = 0x1000; + static final int ACCEPTS_STRING = 0x2000; + static final int ACCEPTS_BOOLEAN = 0x4000; + static final int ACCEPTS_MAP = 0x8000; + static final int ACCEPTS_LIST = 0x10000; + static final int ACCEPTS_SET = 0x20000; + static final int ACCEPTS_ARRAY = 0x40000; + + /** + * Indicates the presence of the char or Character type + */ + static final int CHARACTER = 0x80000; + + static final int ACCEPTS_ANY_OBJECT = ACCEPTS_NUMBER | ACCEPTS_DATE | ACCEPTS_STRING | ACCEPTS_BOOLEAN + | ACCEPTS_MAP | ACCEPTS_LIST | ACCEPTS_SET | ACCEPTS_ARRAY; + + static final int MASK_KNOWN_INTEGERS = BYTE | SHORT | INTEGER | LONG | BIG_INTEGER; + static final int MASK_KNOWN_NONINTEGERS = FLOAT | DOUBLE | BIG_DECIMAL; + static final int MASK_ALL_KNOWN_NUMERICALS = MASK_KNOWN_INTEGERS | MASK_KNOWN_NONINTEGERS; + static final int MASK_ALL_NUMERICALS = MASK_ALL_KNOWN_NUMERICALS | UNKNOWN_NUMERICAL_TYPE; + + static int classToTypeFlags(Class pClass) { + // We start with the most frequent cases + if (pClass == Object.class) { + return ACCEPTS_ANY_OBJECT; + } else if (pClass == String.class) { + return ACCEPTS_STRING; + } else if (pClass.isPrimitive()) { + if (pClass == Integer.TYPE) return INTEGER | ACCEPTS_NUMBER; + else if (pClass == Long.TYPE) return LONG | ACCEPTS_NUMBER; + else if (pClass == Double.TYPE) return DOUBLE | ACCEPTS_NUMBER; + else if (pClass == Float.TYPE) return FLOAT | ACCEPTS_NUMBER; + else if (pClass == Byte.TYPE) return BYTE | ACCEPTS_NUMBER; + else if (pClass == Short.TYPE) return SHORT | ACCEPTS_NUMBER; + else if (pClass == Character.TYPE) return CHARACTER; + else if (pClass == Boolean.TYPE) return ACCEPTS_BOOLEAN; + else return 0; + } else if (Number.class.isAssignableFrom(pClass)) { + if (pClass == Integer.class) return INTEGER | ACCEPTS_NUMBER; + else if (pClass == Long.class) return LONG | ACCEPTS_NUMBER; + else if (pClass == Double.class) return DOUBLE | ACCEPTS_NUMBER; + else if (pClass == Float.class) return FLOAT | ACCEPTS_NUMBER; + else if (pClass == Byte.class) return BYTE | ACCEPTS_NUMBER; + else if (pClass == Short.class) return SHORT | ACCEPTS_NUMBER; + else if (BigDecimal.class.isAssignableFrom(pClass)) return BIG_DECIMAL | ACCEPTS_NUMBER; + else if (BigInteger.class.isAssignableFrom(pClass)) return BIG_INTEGER | ACCEPTS_NUMBER; + else return UNKNOWN_NUMERICAL_TYPE | ACCEPTS_NUMBER; + } else if (pClass.isArray()) { + return ACCEPTS_ARRAY; + } else { + int flags = 0; + if (pClass.isAssignableFrom(String.class)) { + flags |= ACCEPTS_STRING; + } + if (pClass.isAssignableFrom(Date.class)) { + flags |= ACCEPTS_DATE; + } + if (pClass.isAssignableFrom(Boolean.class)) { + flags |= ACCEPTS_BOOLEAN; + } + if (pClass.isAssignableFrom(Map.class)) { + flags |= ACCEPTS_MAP; + } + if (pClass.isAssignableFrom(List.class)) { + flags |= ACCEPTS_LIST; + } + if (pClass.isAssignableFrom(Set.class)) { + flags |= ACCEPTS_SET; + } + + if (pClass == Character.class) { + flags |= CHARACTER; + } + + return flags; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java new file mode 100644 index 0000000..b54ddbf --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/UnsafeMethods.java @@ -0,0 +1,112 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; + +import org.apache.freemarker.core._CoreLogs; +import org.apache.freemarker.core.util._ClassUtil; +import org.slf4j.Logger; + +class UnsafeMethods { + + private static final Logger LOG = _CoreLogs.OBJECT_WRAPPER; + private static final String UNSAFE_METHODS_PROPERTIES = "unsafeMethods.properties"; + private static final Set UNSAFE_METHODS = createUnsafeMethodsSet(); + + private UnsafeMethods() { } + + static boolean isUnsafeMethod(Method method) { + return UNSAFE_METHODS.contains(method); + } + + private static Set createUnsafeMethodsSet() { + Properties props = new Properties(); + InputStream in = DefaultObjectWrapper.class.getResourceAsStream(UNSAFE_METHODS_PROPERTIES); + if (in == null) { + throw new IllegalStateException("Class loader resource not found: " + + DefaultObjectWrapper.class.getPackage().getName() + UNSAFE_METHODS_PROPERTIES); + } + String methodSpec = null; + try { + try { + props.load(in); + } finally { + in.close(); + } + Set set = new HashSet(props.size() * 4 / 3, 1f); + Map primClasses = createPrimitiveClassesMap(); + for (Iterator iterator = props.keySet().iterator(); iterator.hasNext(); ) { + methodSpec = (String) iterator.next(); + try { + set.add(parseMethodSpec(methodSpec, primClasses)); + } catch (ClassNotFoundException | NoSuchMethodException e) { + LOG.debug("Failed to get unsafe method", e); + } + } + return set; + } catch (Exception e) { + throw new RuntimeException("Could not load unsafe method " + methodSpec + " " + e.getClass().getName() + " " + e.getMessage()); + } + } + + private static Method parseMethodSpec(String methodSpec, Map primClasses) + throws ClassNotFoundException, + NoSuchMethodException { + int brace = methodSpec.indexOf('('); + int dot = methodSpec.lastIndexOf('.', brace); + Class clazz = _ClassUtil.forName(methodSpec.substring(0, dot)); + String methodName = methodSpec.substring(dot + 1, brace); + String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 1); + StringTokenizer tok = new StringTokenizer(argSpec, ","); + int argcount = tok.countTokens(); + Class[] argTypes = new Class[argcount]; + for (int i = 0; i < argcount; i++) { + String argClassName = tok.nextToken(); + argTypes[i] = (Class) primClasses.get(argClassName); + if (argTypes[i] == null) { + argTypes[i] = _ClassUtil.forName(argClassName); + } + } + return clazz.getMethod(methodName, argTypes); + } + + private static Map createPrimitiveClassesMap() { + Map map = new HashMap(); + map.put("boolean", Boolean.TYPE); + map.put("byte", Byte.TYPE); + map.put("char", Character.TYPE); + map.put("short", Short.TYPE); + map.put("int", Integer.TYPE); + map.put("long", Long.TYPE); + map.put("float", Float.TYPE); + map.put("double", Double.TYPE); + return map; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java b/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java new file mode 100644 index 0000000..19efe76 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/_EnumModels.java @@ -0,0 +1,54 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.freemarker.core.model.TemplateModel; + +/** + * Don't use this class; it's only public to work around Google App Engine Java + * compliance issues. FreeMarker developers only: treat this class as package-visible. + */ +public class _EnumModels extends ClassBasedModelFactory { + + public _EnumModels(DefaultObjectWrapper wrapper) { + super(wrapper); + } + + @Override + protected TemplateModel createModel(Class clazz) { + Object[] obj = clazz.getEnumConstants(); + if (obj == null) { + // Return null - it'll manifest itself as undefined in the template. + // We're doing this rather than throw an exception as this way + // people can use someEnumModel?default({}) to gracefully fall back + // to an empty hash if they want to. + return null; + } + Map map = new LinkedHashMap(); + for (Object anObj : obj) { + Enum value = (Enum) anObj; + map.put(value.name(), value); + } + return new SimpleMapModel(map, getWrapper()); + } +}