| /* |
| * 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.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.lang.reflect.AccessibleObject; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.ResourceBundle; |
| import java.util.Set; |
| |
| import freemarker.core.BugException; |
| import freemarker.core._DelayedFTLTypeDescription; |
| import freemarker.core._DelayedShortClassName; |
| import freemarker.core._TemplateModelException; |
| import freemarker.ext.util.ModelCache; |
| import freemarker.ext.util.ModelFactory; |
| import freemarker.ext.util.WrapperTemplateModel; |
| import freemarker.log.Logger; |
| import freemarker.template.AdapterTemplateModel; |
| import freemarker.template.Configuration; |
| import freemarker.template.DefaultObjectWrapper; |
| import freemarker.template.ObjectWrapper; |
| import freemarker.template.ObjectWrapperAndUnwrapper; |
| import freemarker.template.SimpleObjectWrapper; |
| import freemarker.template.TemplateBooleanModel; |
| import freemarker.template.TemplateCollectionModel; |
| import freemarker.template.TemplateDateModel; |
| import freemarker.template.TemplateHashModel; |
| import freemarker.template.TemplateMethodModelEx; |
| import freemarker.template.TemplateModel; |
| import freemarker.template.TemplateModelAdapter; |
| import freemarker.template.TemplateModelException; |
| import freemarker.template.TemplateNumberModel; |
| import freemarker.template.TemplateScalarModel; |
| import freemarker.template.TemplateSequenceModel; |
| import freemarker.template.Version; |
| import freemarker.template._TemplateAPI; |
| import freemarker.template._VersionInts; |
| import freemarker.template.utility.ClassUtil; |
| import freemarker.template.utility.RichObjectWrapper; |
| import freemarker.template.utility.WriteProtectable; |
| |
| /** |
| * {@link ObjectWrapper} that is able to expose the Java API of arbitrary Java objects. This is also the superclass of |
| * {@link DefaultObjectWrapper}. Note that instances of this class generally should be created with a |
| * {@link BeansWrapperBuilder}, not with its public constructors. |
| * |
| * <p> |
| * As of 2.3.22, using {@link BeansWrapper} unextended is not recommended. Instead, {@link DefaultObjectWrapper} with |
| * its {@code incompatibleImprovements} property set to 2.3.22 (or higher) is the recommended {@link ObjectWrapper}. |
| * |
| * <p> |
| * This class is only thread-safe after you have finished calling its setter methods, and then safely published it (see |
| * JSR 133 and related literature). When used as part of {@link Configuration}, of course it's enough if that was safely |
| * published and then left unmodified. Using {@link BeansWrapperBuilder} also guarantees thread safety. |
| */ |
| public class BeansWrapper implements RichObjectWrapper, WriteProtectable { |
| private static final Logger LOG = Logger.getLogger("freemarker.beans"); |
| |
| /** |
| * @deprecated Use {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} instead. It's not a public field |
| * anyway. |
| */ |
| @Deprecated |
| static final Object CAN_NOT_UNWRAP = ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; |
| |
| /** |
| * At this level of exposure, all methods and properties of the |
| * wrapped objects are exposed to the template, and even the {@link MemberAccessPolicy} |
| * is ignored. |
| */ |
| public static final int EXPOSE_ALL = 0; |
| |
| /** |
| * At this level of exposure, all methods and properties of the wrapped |
| * objects are exposed to the template except methods that are deemed |
| * not safe. The not safe methods are java.lang.Object methods wait() and |
| * notify(), java.lang.Class methods getClassLoader() and newInstance(), |
| * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and |
| * newInstance() methods, all java.lang.reflect.Field set methods, all |
| * java.lang.Thread and java.lang.ThreadGroup methods that can change its |
| * state, as well as the usual suspects in java.lang.System and |
| * java.lang.Runtime. |
| * |
| * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible. That mechanism was introduced |
| * much later than "exposure levels", and it's the primary place to look at if you are concerned with safety. |
| */ |
| public static final int EXPOSE_SAFE = 1; |
| |
| /** |
| * At this level of exposure, only property getters are exposed. |
| * Additionally, property getters that map to unsafe methods are not |
| * exposed (i.e. Class.classLoader and Thread.contextClassLoader). |
| * |
| * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible. |
| */ |
| public static final int EXPOSE_PROPERTIES_ONLY = 2; |
| |
| /** |
| * At this level of exposure, no bean properties and methods are exposed. |
| * Only map items, resource bundle items, and objects retrieved through |
| * the generic get method (on objects of classes that have a generic get |
| * method) can be retrieved through the hash interface. You might want to |
| * call {@link #setMethodsShadowItems(boolean)} with {@code false} value to |
| * speed up map item retrieval. |
| */ |
| public static final int EXPOSE_NOTHING = 3; |
| |
| // ----------------------------------------------------------------------------------------------------------------- |
| // Introspection cache: |
| |
| private final Object sharedIntrospectionLock; |
| |
| /** |
| * {@link Class} to class info cache. |
| * This object is possibly shared with other {@link BeansWrapper}-s! |
| * |
| * <p>To write this, always use {@link #replaceClassIntrospector(ClassIntrospectorBuilder)}. |
| * |
| * <p>When reading this, it's good idea to synchronize on sharedInrospectionLock when it doesn't hurt overall |
| * performance. In theory that's not needed, but apps might fail to keep the rules. |
| */ |
| private ClassIntrospector classIntrospector; |
| |
| /** |
| * {@link String} class name to {@link StaticModel} cache. |
| * This object only belongs to a single {@link BeansWrapper}. |
| * This has to be final as {@link #getStaticModels()} might return it any time and then it has to remain a good |
| * reference. |
| */ |
| private final StaticModels staticModels; |
| |
| /** |
| * {@link String} class name to {@link EnumerationModel} cache. |
| * This object only belongs to a single {@link BeansWrapper}. |
| * This has to be final as {@link #getStaticModels()} might return it any time and then it has to remain a good |
| * reference. |
| */ |
| private final ClassBasedModelFactory enumModels; |
| |
| /** |
| * Object to wrapped object cache; not used by default. |
| * This object only belongs to a single {@link BeansWrapper}. |
| */ |
| private final ModelCache modelCache; |
| |
| private final BooleanModel falseModel; |
| private final BooleanModel trueModel; |
| |
| // ----------------------------------------------------------------------------------------------------------------- |
| |
| // Why volatile: In principle it need not be volatile, but we want to catch modification attempts even if the |
| // object was published improperly to other threads. After all, the main goal of WriteProtectable is protecting |
| // things from buggy user code. |
| private volatile boolean writeProtected; |
| |
| private TemplateModel nullModel = null; |
| private int defaultDateType; // initialized from the BeansWrapperConfiguration |
| private ObjectWrapper outerIdentity = this; |
| private boolean methodsShadowItems = true; |
| private boolean simpleMapWrapper; // initialized from the BeansWrapperConfiguration |
| private boolean strict; // initialized from the BeansWrapperConfiguration |
| private boolean preferIndexedReadMethod; // initialized from the BeansWrapperConfiguration |
| |
| private final Version incompatibleImprovements; |
| |
| /** |
| * Creates a new instance with the incompatible-improvements-version specified in |
| * {@link Configuration#DEFAULT_INCOMPATIBLE_IMPROVEMENTS}. |
| * |
| * @deprecated Use {@link BeansWrapperBuilder} or, in rare cases, {@link #BeansWrapper(Version)} instead. |
| */ |
| @Deprecated |
| public BeansWrapper() { |
| this(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); |
| // Attention! Don't change fields here, as the instance is possibly already visible to other threads. |
| } |
| |
| /** |
| * Use {@link BeansWrapperBuilder} instead of the public constructors if possible. |
| * The main disadvantage of using the public constructors is that the instances won't share caches. So unless having |
| * a private cache is your goal, don't use them. See |
| * |
| * @param incompatibleImprovements |
| * Sets which of the non-backward-compatible improvements should be enabled. Not {@code null}. This version number |
| * is the same as the FreeMarker version number with which the improvements were implemented. |
| * |
| * <p>For new projects, it's recommended to set this to the FreeMarker version that's used during the development. |
| * For released products that are still actively developed it's a low risk change to increase the 3rd |
| * version number further as FreeMarker is updated, but of course you should always check the list of effects |
| * below. Increasing the 2nd or 1st version number possibly mean substantial changes with higher risk of breaking |
| * the application, but again, see the list of effects below. |
| * |
| * <p>The reason it's separate from {@link Configuration#setIncompatibleImprovements(Version)} is that |
| * {@link ObjectWrapper} objects are often shared among multiple {@link Configuration}-s, so the two version |
| * numbers are technically independent. But it's recommended to keep those two version numbers the same. |
| * |
| * <p>The changes enabled by {@code incompatibleImprovements} are: |
| * <ul> |
| * <li> |
| * <p>2.3.0: No changes; this is the starting point, the version used in older projects. |
| * </li> |
| * <li> |
| * <p>2.3.21 (or higher): |
| * Several glitches were fixed in <em>overloaded</em> method selection. This usually just gets |
| * rid of errors (like ambiguity exceptions and numerical precision loses due to bad overloaded method |
| * choices), still, as in some cases the method chosen can be a different one now (that was the point of |
| * the reworking after all), it can mean a change in the behavior of the application. The most important |
| * change is that the treatment of {@code null} arguments were fixed, as earlier they were only seen |
| * applicable to parameters of type {@code Object}. Now {@code null}-s are seen to be applicable to any |
| * non-primitive parameters, and among those the one with the most specific type will be preferred (just |
| * like in Java), which is hence never the one with the {@code Object} parameter type. For more details |
| * about overloaded method selection changes see the version history in the FreeMarker Manual. |
| * </li> |
| * <li> |
| * <p>2.3.24 (or higher): |
| * {@link Iterator}-s were always said to be non-empty when using {@code ?has_content} and such (i.e., |
| * operators that check emptiness without reading any elements). Now an {@link Iterator} counts as |
| * empty exactly if it has no elements left. (Note that this bug has never affected basic functionality, like |
| * {@code <#list ...>}.) |
| * </li> |
| * <li> |
| * <p>2.3.26 (or higher): |
| * The default of {@link BeansWrapper#getTreatDefaultMethodsAsBeanMembers()} changes from {@code false} to |
| * {@code true}. Thus, Java 8 default methods (and the bean properties they define) are exposed, despite that |
| * {@link java.beans.Introspector} (the official JavaBeans introspector) ignores them, at least as of Java 8. |
| * </li> |
| * <li> |
| * <p>2.3.27 (or higher): |
| * The default of the {@link #setPreferIndexedReadMethod(boolean) preferIndexedReadMethod} setting changes |
| * from {@code true} to {@code false}. |
| * </li> |
| * </ul> |
| * |
| * <p>Note that the version will be normalized to the lowest version where the same incompatible |
| * {@link BeansWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might return |
| * a lower version than what you have specified. |
| * |
| * @since 2.3.21 |
| */ |
| public BeansWrapper(Version incompatibleImprovements) { |
| this(new BeansWrapperConfiguration(incompatibleImprovements) {}, false); |
| // Attention! Don't don anything here, as the instance is possibly already visible to other threads through the |
| // model factory callbacks. |
| } |
| |
| private static volatile boolean ftmaDeprecationWarnLogged; |
| |
| /** |
| * Same as {@link #BeansWrapper(BeansWrapperConfiguration, boolean, boolean)} with {@code true} |
| * {@code finalizeConstruction} argument. |
| * |
| * @since 2.3.21 |
| */ |
| protected BeansWrapper(BeansWrapperConfiguration bwConf, boolean writeProtected) { |
| this(bwConf, writeProtected, true); |
| } |
| |
| /** |
| * Initializes the instance based on the the {@link BeansWrapperConfiguration} specified. |
| * |
| * @param writeProtected Makes the instance's configuration settings read-only via |
| * {@link WriteProtectable#writeProtect()}; this way it can use the shared class introspection cache. |
| * |
| * @param finalizeConstruction Decides if the construction is finalized now, or the caller will do some more |
| * adjustments on the instance and then call {@link #finalizeConstruction(boolean)} itself. |
| * |
| * @since 2.3.22 |
| */ |
| protected BeansWrapper(BeansWrapperConfiguration bwConf, boolean writeProtected, boolean finalizeConstruction) { |
| // Backward-compatibility hack for "finetuneMethodAppearance" overrides to work: |
| if (bwConf.getMethodAppearanceFineTuner() == null) { |
| Class<?> thisClass = this.getClass(); |
| boolean overridden = false; |
| boolean testFailed = false; |
| try { |
| while (!overridden |
| && thisClass != DefaultObjectWrapper.class |
| && thisClass != BeansWrapper.class |
| && thisClass != SimpleObjectWrapper.class) { |
| try { |
| thisClass.getDeclaredMethod("finetuneMethodAppearance", |
| new Class<?>[] { Class.class, Method.class, MethodAppearanceDecision.class }); |
| overridden = true; |
| } catch (NoSuchMethodException e) { |
| thisClass = thisClass.getSuperclass(); |
| } |
| } |
| } catch (Throwable e) { |
| // The security manager sometimes doesn't allow this |
| LOG.info("Failed to check if finetuneMethodAppearance is overidden in " + thisClass.getName() |
| + "; acting like if it was, but this way it won't utilize the shared class introspection " |
| + "cache.", |
| e); |
| overridden = true; |
| testFailed = true; |
| } |
| if (overridden) { |
| if (!testFailed && !ftmaDeprecationWarnLogged) { |
| LOG.warn("Overriding " + BeansWrapper.class.getName() + ".finetuneMethodAppearance is deprecated " |
| + "and will be banned sometimes in the future. Use setMethodAppearanceFineTuner instead."); |
| ftmaDeprecationWarnLogged = true; |
| } |
| bwConf = (BeansWrapperConfiguration) bwConf.clone(false); |
| bwConf.setMethodAppearanceFineTuner(new MethodAppearanceFineTuner() { |
| |
| @Override |
| public void process( |
| MethodAppearanceDecisionInput in, MethodAppearanceDecision out) { |
| BeansWrapper.this.finetuneMethodAppearance(in.getContainingClass(), in.getMethod(), out); |
| } |
| |
| }); |
| } |
| } |
| |
| this.incompatibleImprovements = bwConf.getIncompatibleImprovements(); // normalized |
| |
| simpleMapWrapper = bwConf.isSimpleMapWrapper(); |
| preferIndexedReadMethod = bwConf.getPreferIndexedReadMethod(); |
| defaultDateType = bwConf.getDefaultDateType(); |
| outerIdentity = bwConf.getOuterIdentity() != null ? bwConf.getOuterIdentity() : this; |
| strict = bwConf.isStrict(); |
| |
| if (!writeProtected) { |
| // As this is not a read-only BeansWrapper, the classIntrospector will be possibly replaced for a few times, |
| // but we need to use the same sharedInrospectionLock forever, because that's what the model factories |
| // synchronize on, even during the classIntrospector is being replaced. |
| sharedIntrospectionLock = new Object(); |
| classIntrospector = new ClassIntrospector( |
| _BeansAPI.getClassIntrospectorBuilder(bwConf), sharedIntrospectionLock, false, false); |
| } else { |
| // As this is a read-only BeansWrapper, the classIntrospector is never replaced, and since it's shared by |
| // other BeansWrapper instances, we use the lock belonging to the shared ClassIntrospector. |
| classIntrospector = _BeansAPI.getClassIntrospectorBuilder(bwConf).build(); |
| sharedIntrospectionLock = classIntrospector.getSharedLock(); |
| } |
| |
| falseModel = new BooleanModel(Boolean.FALSE, this); |
| trueModel = new BooleanModel(Boolean.TRUE, this); |
| |
| staticModels = new StaticModels(this); |
| enumModels = new _EnumModels(this); |
| modelCache = new BeansModelCache(this); |
| setUseCache(bwConf.getUseModelCache()); |
| |
| finalizeConstruction(writeProtected); |
| } |
| |
| /** |
| * Meant to be called after {@link BeansWrapper#BeansWrapper(BeansWrapperConfiguration, boolean, boolean)} when |
| * its last argument was {@code false}; makes the instance read-only if necessary, then registers the model |
| * factories in the class introspector. No further changes should be done after calling this, if |
| * {@code writeProtected} was {@code true}. |
| * |
| * @since 2.3.22 |
| */ |
| protected void finalizeConstruction(boolean writeProtected) { |
| if (writeProtected) { |
| writeProtect(); |
| } |
| |
| // Attention! At this point, the BeansWrapper must be fully initialized, as when the model factories are |
| // registered below, the BeansWrapper can immediately get concurrent callbacks. That those other threads will |
| // see consistent image of the BeansWrapper is ensured that callbacks are always sync-ed on |
| // classIntrospector.sharedLock, and so is classIntrospector.registerModelFactory(...). |
| |
| registerModelFactories(); |
| } |
| |
| /** |
| * Makes the configuration properties (settings) of this {@link BeansWrapper} object read-only. As changing them |
| * after the object has become visible to multiple threads leads to undefined behavior, it's recommended to call |
| * this when you have finished configuring the object. |
| * |
| * <p>Consider using {@link BeansWrapperBuilder} instead, which gives an instance that's already |
| * write protected and also uses some shared caches/pools. |
| * |
| * @since 2.3.21 |
| */ |
| @Override |
| public void writeProtect() { |
| writeProtected = true; |
| } |
| |
| /** |
| * @since 2.3.21 |
| */ |
| @Override |
| public boolean isWriteProtected() { |
| return writeProtected; |
| } |
| |
| Object getSharedIntrospectionLock() { |
| return sharedIntrospectionLock; |
| } |
| |
| /** |
| * If this object is already read-only according to {@link WriteProtectable}, throws {@link IllegalStateException}, |
| * otherwise does nothing. |
| * |
| * @since 2.3.21 |
| */ |
| protected void checkModifiable() { |
| if (writeProtected) throw new IllegalStateException( |
| "Can't modify the " + this.getClass().getName() + " object, as it was write protected."); |
| } |
| |
| /** |
| * @see #setStrict(boolean) |
| */ |
| public boolean isStrict() { |
| return strict; |
| } |
| |
| /** |
| * Specifies if an attempt to read a bean property that doesn't exist in the |
| * wrapped object should throw an {@link InvalidPropertyException}. |
| * |
| * <p>If this property is {@code false} (the default) then an attempt to read |
| * a missing bean property is the same as reading an existing bean property whose |
| * value is {@code null}. The template can't tell the difference, and thus always |
| * can use {@code ?default('something')} and {@code ?exists} and similar built-ins |
| * to handle the situation. |
| * |
| * <p>If this property is {@code true} then an attempt to read a bean propertly in |
| * the template (like {@code myBean.aProperty}) that doesn't exist in the bean |
| * object (as opposed to just holding {@code null} value) will cause |
| * {@link InvalidPropertyException}, which can't be suppressed in the template |
| * (not even with {@code myBean.noSuchProperty?default('something')}). This way |
| * {@code ?default('something')} and {@code ?exists} and similar built-ins can be used to |
| * handle existing properties whose value is {@code null}, without the risk of |
| * hiding typos in the property names. Typos will always cause error. But mind you, it |
| * goes against the basic approach of FreeMarker, so use this feature only if you really |
| * know what you are doing. |
| */ |
| public void setStrict(boolean strict) { |
| checkModifiable(); |
| this.strict = strict; |
| } |
| |
| /** |
| * When wrapping an object, the BeansWrapper commonly needs to wrap |
| * "sub-objects", for example each element in a wrapped collection. |
| * Normally it wraps these objects using itself. However, this makes |
| * it difficult to delegate to a BeansWrapper as part of a custom |
| * aggregate ObjectWrapper. This method lets you set the ObjectWrapper |
| * which will be used to wrap the sub-objects. |
| * @param outerIdentity the aggregate ObjectWrapper |
| */ |
| public void setOuterIdentity(ObjectWrapper outerIdentity) { |
| checkModifiable(); |
| this.outerIdentity = outerIdentity; |
| } |
| |
| /** |
| * By default returns {@code this}. |
| * @see #setOuterIdentity(ObjectWrapper) |
| */ |
| public ObjectWrapper getOuterIdentity() { |
| return outerIdentity; |
| } |
| |
| /** |
| * When set to {@code true}, the keys in {@link Map}-s won't mix with the method names when looking at them |
| * from templates. The default is {@code false} for backward-compatibility, but is not recommended. |
| * |
| * <p>When this is {@code false}, {@code myMap.foo} or {@code myMap['foo']} either returns the method {@code foo}, |
| * or calls {@code Map.get("foo")}. If both exists (the method and the {@link Map} key), one will hide the other, |
| * depending on the {@link #isMethodsShadowItems()}, which default to {@code true} (the method |
| * wins). Some frameworks use this so that you can call {@code myMap.get(nonStringKey)} from templates [*], but it |
| * comes on the cost of polluting the key-set with the method names, and risking methods accidentally hiding |
| * {@link Map} entries (or the other way around). Thus, this setup is not recommended. |
| * (Technical note: {@link Map}-s will be wrapped into {@link MapModel} in this case.) |
| * |
| * <p>When this is {@code true}, {@code myMap.foo} or {@code myMap['foo']} always calls {@code Map.get("foo")}. |
| * The methods of the {@link Map} object aren't visible from templates in this case. This, however, spoils the |
| * {@code myMap.get(nonStringKey)} workaround. But now you can use {@code myMap(nonStringKey)} instead, that is, you |
| * can use the map itself as the {@code get} method. |
| * (Technical note: {@link Map}-s will be wrapped into {@link SimpleMapModel} in this case.) |
| * |
| * <p>*: For historical reasons, FreeMarker 2.3.X doesn't support non-string keys with the {@code []} operator, |
| * hence the workarounds. This will be likely fixed in FreeMarker 2.4.0. Also note that the method- and |
| * the "field"-namespaces aren't separate in FreeMarker, hence {@code myMap.get} can return the {@code get} |
| * method. |
| */ |
| public void setSimpleMapWrapper(boolean simpleMapWrapper) { |
| checkModifiable(); |
| this.simpleMapWrapper = simpleMapWrapper; |
| } |
| |
| /** |
| * Tells whether Maps are exposed as simple maps, without access to their |
| * method. See {@link #setSimpleMapWrapper(boolean)} for details. |
| * @return true if Maps are exposed as simple hashes, false if they're |
| * exposed as full JavaBeans. |
| */ |
| public boolean isSimpleMapWrapper() { |
| return simpleMapWrapper; |
| } |
| |
| /** |
| * Getter pair of {@link #setPreferIndexedReadMethod(boolean)} |
| * |
| * @since 2.3.27 |
| */ |
| public boolean getPreferIndexedReadMethod() { |
| return preferIndexedReadMethod; |
| } |
| |
| /** |
| * Sets if when a JavaBean property has both a normal read method (like {@code String[] getFoos()}) and an indexed |
| * read method (like {@code String getFoos(int index)}), and the Java {@link Introspector} exposes both (which only |
| * happens since Java 8, apparently), which read method will be used when the property is accessed with the |
| * shorthand syntax (like {@code myObj.foos}). Before {@link #getIncompatibleImprovements() incompatibleImprovements} |
| * 2.3.27 it defaults to {@code true} for backward compatibility (although it's actually less backward compatible if |
| * you are just switching to Java 8; see later), but the recommended value and the default starting with |
| * {@link #getIncompatibleImprovements() incompatibleImprovements} 2.3.27 is {@code false}. This setting has no |
| * effect on properties that only has normal read method, or only has indexed read method. In case a property has |
| * both, using the indexed reader method is disadvantageous, as then FreeMarker can't tell what the highest allowed |
| * index is, and so the property will be unlistable ({@code <#list foo as myObj.foos>} will fail). |
| * |
| * <p> |
| * Apparently, this setting only matters since Java 8, as before that {@link Introspector} did not expose the |
| * indexed reader method if there was also a normal reader method. As with Java 8 the behavior of |
| * {@link Introspector} has changed, some old templates started to break, as the property has suddenly become |
| * unlistable (see earlier why). So setting this to {@code false} can be seen as a Java 8 compatibility fix. |
| * |
| * @since 2.3.27 |
| */ |
| public void setPreferIndexedReadMethod(boolean preferIndexedReadMethod) { |
| checkModifiable(); |
| this.preferIndexedReadMethod = preferIndexedReadMethod; |
| } |
| |
| /** |
| * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>. |
| * @param exposureLevel can be any of the <code>EXPOSE_xxx</code> |
| * constants. |
| * Note that {@link #setMemberAccessPolicy(MemberAccessPolicy)} further restricts what's visible, unless this is |
| * set to {@link #EXPOSE_ALL}. |
| */ |
| public void setExposureLevel(int exposureLevel) { |
| checkModifiable(); |
| |
| if (classIntrospector.getExposureLevel() != exposureLevel) { |
| ClassIntrospectorBuilder builder = classIntrospector.createBuilder(); |
| builder.setExposureLevel(exposureLevel); |
| replaceClassIntrospector(builder); |
| } |
| } |
| |
| /** |
| * @since 2.3.21 |
| */ |
| public int getExposureLevel() { |
| return classIntrospector.getExposureLevel(); |
| } |
| |
| /** |
| * Controls whether public instance fields of classes are exposed to |
| * templates. |
| * @param exposeFields if set to true, public instance fields of classes |
| * that do not have a property getter defined can be accessed directly by |
| * their name. If there is a property getter for a property of the same |
| * name as the field (i.e. getter "getFoo()" and field "foo"), then |
| * referring to "foo" in template invokes the getter. If set to false, no |
| * access to public instance fields of classes is given. Default is false. |
| */ |
| public void setExposeFields(boolean exposeFields) { |
| checkModifiable(); |
| |
| if (classIntrospector.getExposeFields() != exposeFields) { |
| ClassIntrospectorBuilder builder = classIntrospector.createBuilder(); |
| builder.setExposeFields(exposeFields); |
| replaceClassIntrospector(builder); |
| } |
| } |
| |
| /** |
| * Controls whether Java 8 default methods that weren't overridden in a class will be recognized as bean property |
| * accessors and/or bean actions, and thus will be visible from templates. (We expose bean properties and bean |
| * actions, not methods in general.) Before {@link #getIncompatibleImprovements incompatibleImprovements} 2.3.26 |
| * this defaults to {@code false} for backward compatibility. Starting with {@link #getIncompatibleImprovements |
| * incompatibleImprovements} 2.3.26 it defaults to {@code true}. |
| * <p> |
| * Some explanation: FreeMarker uses {@link java.beans.Introspector} to discover the bean properties and actions of |
| * classes, for maximum conformance to the JavaBeans specification. But for some reason (perhaps just a bug in the |
| * Oracle/OpenJDK Java 8 implementation) that ignores the Java 8 default methods coming from the interfaces. When |
| * this setting is {@code true}, we search for non-overridden default methods ourselves, and add them to the set of |
| * discovered bean members. |
| * |
| * @since 2.3.26 |
| */ |
| public void setTreatDefaultMethodsAsBeanMembers(boolean treatDefaultMethodsAsBeanMembers) { |
| checkModifiable(); |
| |
| if (classIntrospector.getTreatDefaultMethodsAsBeanMembers() != treatDefaultMethodsAsBeanMembers) { |
| ClassIntrospectorBuilder builder = classIntrospector.createBuilder(); |
| builder.setTreatDefaultMethodsAsBeanMembers(treatDefaultMethodsAsBeanMembers); |
| replaceClassIntrospector(builder); |
| } |
| } |
| |
| /** |
| * Returns whether exposure of public instance fields of classes is |
| * enabled. See {@link #setExposeFields(boolean)} for details. |
| * @return true if public instance fields are exposed, false otherwise. |
| * |
| * @since 2.3.26 |
| */ |
| public boolean isExposeFields() { |
| return classIntrospector.getExposeFields(); |
| } |
| |
| /** |
| * See {@link #setTreatDefaultMethodsAsBeanMembers(boolean)}. |
| */ |
| public boolean getTreatDefaultMethodsAsBeanMembers() { |
| return classIntrospector.getTreatDefaultMethodsAsBeanMembers(); |
| } |
| |
| public MethodAppearanceFineTuner getMethodAppearanceFineTuner() { |
| return classIntrospector.getMethodAppearanceFineTuner(); |
| } |
| |
| /** |
| * Used to tweak certain aspects of how methods appear in the data-model; |
| * see {@link MethodAppearanceFineTuner} for more. |
| */ |
| public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) { |
| checkModifiable(); |
| |
| if (classIntrospector.getMethodAppearanceFineTuner() != methodAppearanceFineTuner) { |
| ClassIntrospectorBuilder builder = classIntrospector.createBuilder(); |
| builder.setMethodAppearanceFineTuner(methodAppearanceFineTuner); |
| replaceClassIntrospector(builder); |
| } |
| } |
| |
| /** |
| * @since 2.3.30 |
| */ |
| public MemberAccessPolicy getMemberAccessPolicy() { |
| return classIntrospector.getMemberAccessPolicy(); |
| } |
| |
| /** |
| * Sets the {@link MemberAccessPolicy}; default is {@link DefaultMemberAccessPolicy#getInstance(Version)}, which |
| * is not appropriate if template editors aren't trusted. |
| * |
| * @since 2.3.30 |
| */ |
| public void setMemberAccessPolicy(MemberAccessPolicy memberAccessPolicy) { |
| checkModifiable(); |
| |
| if (classIntrospector.getMemberAccessPolicy() != memberAccessPolicy) { |
| ClassIntrospectorBuilder builder = classIntrospector.createBuilder(); |
| builder.setMemberAccessPolicy(memberAccessPolicy); |
| replaceClassIntrospector(builder); |
| } |
| } |
| |
| MethodSorter getMethodSorter() { |
| return classIntrospector.getMethodSorter(); |
| } |
| |
| void setMethodSorter(MethodSorter methodSorter) { |
| checkModifiable(); |
| |
| if (classIntrospector.getMethodSorter() != methodSorter) { |
| ClassIntrospectorBuilder builder = classIntrospector.createBuilder(); |
| builder.setMethodSorter(methodSorter); |
| replaceClassIntrospector(builder); |
| } |
| } |
| |
| /** |
| * Tells if this instance acts like if its class introspection cache is sharable with other {@link BeansWrapper}-s. |
| * A restricted cache denies certain too "antisocial" operations, like {@link #clearClassIntrospectionCache()}. |
| * The value depends on how the instance |
| * was created; with a public constructor (then this is {@code false}), or with {@link BeansWrapperBuilder} |
| * (then it's {@code true}). Note that in the last case it's possible that the introspection cache |
| * will not be actually shared because there's no one to share with, but this will {@code true} even then. |
| * |
| * @since 2.3.21 |
| */ |
| public boolean isClassIntrospectionCacheRestricted() { |
| return classIntrospector.getHasSharedInstanceRestrictions(); |
| } |
| |
| /** |
| * Replaces the value of {@link #classIntrospector}, but first it unregisters |
| * the model factories in the old {@link #classIntrospector}. |
| */ |
| private void replaceClassIntrospector(ClassIntrospectorBuilder builder) { |
| checkModifiable(); |
| |
| final ClassIntrospector newCI = new ClassIntrospector(builder, sharedIntrospectionLock, false, false); |
| final ClassIntrospector oldCI; |
| |
| // In principle this need not be synchronized, but as apps might publish the configuration improperly, or |
| // even modify the wrapper after publishing. This doesn't give 100% protection from those violations, |
| // as classIntrospector reading aren't everywhere synchronized for performance reasons. It still decreases the |
| // chance of accidents, because some ops on classIntrospector are synchronized, and because it will at least |
| // push the new value into the common shared memory. |
| synchronized (sharedIntrospectionLock) { |
| oldCI = classIntrospector; |
| if (oldCI != null) { |
| // Note that after unregistering the model factory might still gets some callback from the old |
| // classIntrospector |
| if (staticModels != null) { |
| oldCI.unregisterModelFactory(staticModels); |
| staticModels.clearCache(); |
| } |
| if (enumModels != null) { |
| oldCI.unregisterModelFactory(enumModels); |
| enumModels.clearCache(); |
| } |
| if (modelCache != null) { |
| oldCI.unregisterModelFactory(modelCache); |
| modelCache.clearCache(); |
| } |
| if (trueModel != null) { |
| trueModel.clearMemberCache(); |
| } |
| if (falseModel != null) { |
| falseModel.clearMemberCache(); |
| } |
| } |
| |
| classIntrospector = newCI; |
| |
| registerModelFactories(); |
| } |
| } |
| |
| private void registerModelFactories() { |
| if (staticModels != null) { |
| classIntrospector.registerModelFactory(staticModels); |
| } |
| if (enumModels != null) { |
| classIntrospector.registerModelFactory(enumModels); |
| } |
| if (modelCache != null) { |
| classIntrospector.registerModelFactory(modelCache); |
| } |
| } |
| |
| /** |
| * Sets whether methods shadow items in beans. When true (this is the |
| * default value), <code>${object.name}</code> will first try to locate |
| * a bean method or property with the specified name on the object, and |
| * only if it doesn't find it will it try to call |
| * <code>object.get(name)</code>, the so-called "generic get method" that |
| * is usually used to access items of a container (i.e. elements of a map). |
| * When set to false, the lookup order is reversed and generic get method |
| * is called first, and only if it returns null is method lookup attempted. |
| */ |
| public void setMethodsShadowItems(boolean methodsShadowItems) { |
| // This sync is here as this method was originally synchronized, but was never truly thread-safe, so I don't |
| // want to advertise it in the javadoc, nor I wanted to break any apps that work because of this accidentally. |
| synchronized (this) { |
| checkModifiable(); |
| this.methodsShadowItems = methodsShadowItems; |
| } |
| } |
| |
| boolean isMethodsShadowItems() { |
| return methodsShadowItems; |
| } |
| |
| /** |
| * Sets the default date type to use for date models that result from |
| * a plain {@code java.util.Date} instead of {@code java.sql.Date} or |
| * {@code java.sql.Time} or {@code java.sql.Timestamp}. Default value is |
| * {@link TemplateDateModel#UNKNOWN}. |
| * @param defaultDateType the new default date type. |
| */ |
| public void setDefaultDateType(int defaultDateType) { |
| // This sync is here as this method was originally synchronized, but was never truly thread-safe, so I don't |
| // want to advertise it in the javadoc, nor I wanted to break any apps that work because of this accidentally. |
| synchronized (this) { |
| checkModifiable(); |
| |
| this.defaultDateType = defaultDateType; |
| } |
| } |
| |
| /** |
| * Returns the default date type. See {@link #setDefaultDateType(int)} for |
| * details. |
| * @return the default date type |
| */ |
| public int getDefaultDateType() { |
| return defaultDateType; |
| } |
| |
| /** |
| * Sets whether this wrapper caches the {@link TemplateModel}-s created for the Java objects that has wrapped with |
| * this object wrapper. Default is {@code false}. |
| * When set to {@code true}, calling {@link #wrap(Object)} multiple times for |
| * the same object will likely return the same model (although there is |
| * no guarantee as the cache items can be cleared any time). |
| */ |
| public void setUseCache(boolean useCache) { |
| checkModifiable(); |
| modelCache.setUseCache(useCache); |
| } |
| |
| /** |
| * @since 2.3.21 |
| */ |
| public boolean getUseCache() { |
| return modelCache.getUseCache(); |
| } |
| |
| /** |
| * Sets the null model. This model is returned from the {@link #wrap(Object)} method whenever the wrapped object is |
| * {@code null}. It defaults to {@code null}, which is dealt with quite strictly on engine level, however you can |
| * substitute an arbitrary (perhaps more lenient) model, like an empty string. For proper working, the |
| * {@code nullModel} should be an {@link AdapterTemplateModel} that returns {@code null} for |
| * {@link AdapterTemplateModel#getAdaptedObject(Class)}. |
| * |
| * @deprecated Changing the {@code null} model can cause a lot of confusion; don't do it. |
| */ |
| @Deprecated |
| public void setNullModel(TemplateModel nullModel) { |
| checkModifiable(); |
| this.nullModel = nullModel; |
| } |
| |
| /** |
| * Returns the version given with {@link #BeansWrapper(Version)}, normalized to the lowest version where a change |
| * has occurred. Thus, this is not necessarily the same version than that was given to the constructor. |
| * |
| * @since 2.3.21 |
| */ |
| public Version getIncompatibleImprovements() { |
| return incompatibleImprovements; |
| } |
| |
| boolean is2321Bugfixed() { |
| return is2321Bugfixed(getIncompatibleImprovements()); |
| } |
| |
| static boolean is2321Bugfixed(Version version) { |
| return version.intValue() >= _VersionInts.V_2_3_21; |
| } |
| |
| boolean is2324Bugfixed() { |
| return is2324Bugfixed(getIncompatibleImprovements()); |
| } |
| |
| static boolean is2324Bugfixed(Version version) { |
| return version.intValue() >= _VersionInts.V_2_3_24; |
| } |
| |
| /** |
| * Returns the lowest version number that is equivalent with the parameter version. |
| * @since 2.3.21 |
| */ |
| protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) { |
| _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements); |
| return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_27 ? Configuration.VERSION_2_3_27 |
| : incompatibleImprovements.intValue() == _VersionInts.V_2_3_26 ? Configuration.VERSION_2_3_26 |
| : is2324Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_24 |
| : is2321Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_21 |
| : Configuration.VERSION_2_3_0; |
| } |
| |
| /** |
| * Returns the default instance of the wrapper. This instance is used |
| * when you construct various bean models without explicitly specifying |
| * a wrapper. It is also returned by |
| * {@link freemarker.template.ObjectWrapper#BEANS_WRAPPER} |
| * and this is the sole instance that is used by the JSP adapter. |
| * You can modify the properties of the default instance (caching, |
| * exposure level, null model) to affect its operation. By default, the |
| * default instance is not caching, uses the <code>EXPOSE_SAFE</code> |
| * exposure level, and uses null reference as the null model. |
| * |
| * @deprecated Use {@link BeansWrapperBuilder} instead. The instance returned here is not read-only, so it's |
| * dangerous to use. |
| */ |
| @Deprecated |
| public static final BeansWrapper getDefaultInstance() { |
| return BeansWrapperSingletonHolder.INSTANCE; |
| } |
| |
| /** |
| * Wraps the object with a template model that is most specific for the object's |
| * class. Specifically: |
| * <ul> |
| * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li> |
| * <li>if the object is a Number returns a {@link NumberModel} for it,</li> |
| * <li>if the object is a Date returns a {@link DateModel} for it,</li> |
| * <li>if the object is a Boolean returns |
| * {@link freemarker.template.TemplateBooleanModel#TRUE} or |
| * {@link freemarker.template.TemplateBooleanModel#FALSE}</li> |
| * <li>if the object is already a TemplateModel, returns it unchanged,</li> |
| * <li>if the object is an array, returns a {@link ArrayModel} for it |
| * <li>if the object is a Map, returns a {@link MapModel} for it |
| * <li>if the object is a Collection, returns a {@link CollectionModel} for it |
| * <li>if the object is an Iterator, returns a {@link IteratorModel} for it |
| * <li>if the object is an Enumeration, returns a {@link EnumerationModel} for it |
| * <li>if the object is a String, returns a {@link StringModel} for it |
| * <li>otherwise, returns a generic {@link StringModel} for it. |
| * </ul> |
| */ |
| @Override |
| public TemplateModel wrap(Object object) throws TemplateModelException { |
| if (object == null) return nullModel; |
| return modelCache.getInstance(object); |
| } |
| |
| /** |
| * Wraps a Java method so that it can be called from templates, without wrapping its parent ("this") object. The |
| * result is almost the same as that you would get by wrapping the parent object then getting the method from the |
| * resulting {@link TemplateHashModel} by name. Except, if the wrapped method is overloaded, with this method you |
| * explicitly select an overload, while otherwise you would get a {@link TemplateMethodModelEx} that selects an |
| * overload each time it's called based on the argument values. |
| * |
| * @param object The object whose method will be called, or {@code null} if {@code method} is a static method. |
| * This object will be used "as is", like without unwrapping it if it's a {@link TemplateModelAdapter}. |
| * @param method The method to call, which must be an (inherited) member of the class of {@code object}, as |
| * described by {@link Method#invoke(Object, Object...)} |
| * |
| * @since 2.3.22 |
| */ |
| public TemplateMethodModelEx wrap(Object object, Method method) { |
| return new SimpleMethodModel(object, method, method.getParameterTypes(), this); |
| } |
| |
| /** |
| * @since 2.3.22 |
| */ |
| @Override |
| public TemplateHashModel wrapAsAPI(Object obj) throws TemplateModelException { |
| return new APIModel(obj, this); |
| } |
| |
| /** |
| * @deprecated override {@link #getModelFactory(Class)} instead. Using this |
| * method will now bypass wrapper caching (if it's enabled) and always |
| * result in creation of a new wrapper. This method will be removed in 2.4 |
| * @param object The object to wrap |
| * @param factory The factory that wraps the object |
| */ |
| @Deprecated |
| protected TemplateModel getInstance(Object object, ModelFactory factory) { |
| return factory.create(object, this); |
| } |
| |
| private final ModelFactory BOOLEAN_FACTORY = new ModelFactory() { |
| @Override |
| public TemplateModel create(Object object, ObjectWrapper wrapper) { |
| return ((Boolean) object).booleanValue() ? trueModel : falseModel; |
| } |
| }; |
| |
| private static final ModelFactory ITERATOR_FACTORY = new ModelFactory() { |
| @Override |
| public TemplateModel create(Object object, ObjectWrapper wrapper) { |
| return new IteratorModel((Iterator<?>) object, (BeansWrapper) wrapper); |
| } |
| }; |
| |
| private static final ModelFactory ENUMERATION_FACTORY = new ModelFactory() { |
| @Override |
| public TemplateModel create(Object object, ObjectWrapper wrapper) { |
| return new EnumerationModel((Enumeration<?>) object, (BeansWrapper) wrapper); |
| } |
| }; |
| |
| protected ModelFactory getModelFactory(Class<?> clazz) { |
| if (Map.class.isAssignableFrom(clazz)) { |
| return simpleMapWrapper ? SimpleMapModel.FACTORY : MapModel.FACTORY; |
| } |
| if (Collection.class.isAssignableFrom(clazz)) { |
| return CollectionModel.FACTORY; |
| } |
| if (Number.class.isAssignableFrom(clazz)) { |
| return NumberModel.FACTORY; |
| } |
| if (Date.class.isAssignableFrom(clazz)) { |
| return DateModel.FACTORY; |
| } |
| if (Boolean.class == clazz) { // Boolean is final |
| return BOOLEAN_FACTORY; |
| } |
| if (ResourceBundle.class.isAssignableFrom(clazz)) { |
| return ResourceBundleModel.FACTORY; |
| } |
| if (Iterator.class.isAssignableFrom(clazz)) { |
| return ITERATOR_FACTORY; |
| } |
| if (Enumeration.class.isAssignableFrom(clazz)) { |
| return ENUMERATION_FACTORY; |
| } |
| if (clazz.isArray()) { |
| return ArrayModel.FACTORY; |
| } |
| return StringModel.FACTORY; |
| } |
| |
| /** |
| * Attempts to unwrap a model into underlying object. Generally, this |
| * method is the inverse of the {@link #wrap(Object)} method. In addition |
| * it will unwrap arbitrary {@link TemplateNumberModel} instances into |
| * a number, arbitrary {@link TemplateDateModel} instances into a date, |
| * {@link TemplateScalarModel} instances into a String, arbitrary |
| * {@link TemplateBooleanModel} instances into a Boolean, arbitrary |
| * {@link TemplateHashModel} instances into a Map, arbitrary |
| * {@link TemplateSequenceModel} into a List, and arbitrary |
| * {@link TemplateCollectionModel} into a Set. All other objects are |
| * returned unchanged. |
| * @throws TemplateModelException if an attempted unwrapping fails. |
| */ |
| @Override |
| public Object unwrap(TemplateModel model) throws TemplateModelException { |
| return unwrap(model, Object.class); |
| } |
| |
| /** |
| * Attempts to unwrap a model into an object of the desired class. |
| * Generally, this method is the inverse of the {@link #wrap(Object)} |
| * method. It recognizes a wide range of target classes - all Java built-in |
| * primitives, primitive wrappers, numbers, dates, sets, lists, maps, and |
| * native arrays. |
| * @param model the model to unwrap |
| * @param targetClass the class of the unwrapped result; {@code Object.class} if we don't know what the expected type is. |
| * @return the unwrapped result of the desired class |
| * @throws TemplateModelException if an attempted unwrapping fails. |
| * |
| * @see #tryUnwrapTo(TemplateModel, Class) |
| */ |
| public Object unwrap(TemplateModel model, Class<?> targetClass) |
| throws TemplateModelException { |
| final Object obj = tryUnwrapTo(model, targetClass); |
| if (obj == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| throw new TemplateModelException("Can not unwrap model of type " + |
| model.getClass().getName() + " to type " + targetClass.getName()); |
| } |
| return obj; |
| } |
| |
| /** |
| * @since 2.3.22 |
| */ |
| @Override |
| public Object tryUnwrapTo(TemplateModel model, Class<?> targetClass) throws TemplateModelException { |
| return tryUnwrapTo(model, targetClass, 0); |
| } |
| |
| /** |
| * @param typeFlags |
| * Used when unwrapping for overloaded methods and so the {@code targetClass} is possibly too generic. |
| * Must be 0 when unwrapping parameter values for non-overloaded methods, also if |
| * {@link #is2321Bugfixed()} is {@code false}. |
| * @return {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} or the unwrapped object. |
| */ |
| Object tryUnwrapTo(TemplateModel model, Class<?> targetClass, int typeFlags) |
| throws TemplateModelException { |
| Object res = tryUnwrapTo(model, targetClass, typeFlags, null); |
| if ((typeFlags & TypeFlags.WIDENED_NUMERICAL_UNWRAPPING_HINT) != 0 |
| && res instanceof Number) { |
| return OverloadedNumberUtil.addFallbackType((Number) res, typeFlags); |
| } else { |
| return res; |
| } |
| } |
| |
| /** |
| * See {@link #tryUnwrapTo(TemplateModel, Class, int)}. |
| */ |
| private Object tryUnwrapTo(final TemplateModel model, Class<?> targetClass, final int typeFlags, |
| final Map<Object, Object> recursionStops) |
| throws TemplateModelException { |
| if (model == null || model == nullModel) { |
| return null; |
| } |
| |
| final boolean is2321Bugfixed = is2321Bugfixed(); |
| |
| if (is2321Bugfixed && targetClass.isPrimitive()) { |
| targetClass = ClassUtil.primitiveClassToBoxingClass(targetClass); |
| } |
| |
| // This is for transparent interop with other wrappers (and ourselves) |
| // Passing the targetClass allows i.e. a Jython-aware method that declares a |
| // PyObject as its argument to receive a PyObject from a JythonModel |
| // passed as an argument to TemplateMethodModelEx etc. |
| if (model instanceof AdapterTemplateModel) { |
| Object wrapped = ((AdapterTemplateModel) model).getAdaptedObject( |
| targetClass); |
| if (targetClass == Object.class || targetClass.isInstance(wrapped)) { |
| return wrapped; |
| } |
| |
| // Attempt numeric conversion: |
| if (targetClass != Object.class && (wrapped instanceof Number && ClassUtil.isNumerical(targetClass))) { |
| Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass, is2321Bugfixed); |
| if (number != null) return number; |
| } |
| } |
| |
| if (model instanceof WrapperTemplateModel) { |
| Object wrapped = ((WrapperTemplateModel) model).getWrappedObject(); |
| if (targetClass == Object.class || targetClass.isInstance(wrapped)) { |
| return wrapped; |
| } |
| |
| // Attempt numeric conversion: |
| if (targetClass != Object.class && (wrapped instanceof Number && ClassUtil.isNumerical(targetClass))) { |
| Number number = forceUnwrappedNumberToType((Number) wrapped, targetClass, is2321Bugfixed); |
| if (number != null) { |
| return number; |
| } |
| } |
| } |
| |
| // Translation of generic template models to POJOs. First give priority |
| // to various model interfaces based on the targetClass. This helps us |
| // select the appropriate interface in multi-interface models when we |
| // know what is expected as the return type. |
| if (targetClass != Object.class) { |
| |
| // [2.4][IcI]: Should also check for CharSequence at the end |
| if (String.class == targetClass) { |
| if (model instanceof TemplateScalarModel) { |
| return ((TemplateScalarModel) model).getAsString(); |
| } |
| // String is final, so no other conversion will work |
| return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; |
| } |
| |
| // Primitive numeric types & Number.class and its subclasses |
| if (ClassUtil.isNumerical(targetClass)) { |
| if (model instanceof TemplateNumberModel) { |
| Number number = forceUnwrappedNumberToType( |
| ((TemplateNumberModel) model).getAsNumber(), targetClass, is2321Bugfixed); |
| if (number != null) { |
| return number; |
| } |
| } |
| } |
| |
| if (boolean.class == targetClass || Boolean.class == targetClass) { |
| if (model instanceof TemplateBooleanModel) { |
| return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean()); |
| } |
| // Boolean is final, no other conversion will work |
| return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; |
| } |
| |
| if (Map.class == targetClass) { |
| if (model instanceof TemplateHashModel) { |
| return new HashAdapter((TemplateHashModel) model, this); |
| } |
| } |
| |
| if (List.class == targetClass) { |
| if (model instanceof TemplateSequenceModel) { |
| return new SequenceAdapter((TemplateSequenceModel) model, this); |
| } |
| } |
| |
| if (Set.class == targetClass) { |
| if (model instanceof TemplateCollectionModel) { |
| return new SetAdapter((TemplateCollectionModel) model, this); |
| } |
| } |
| |
| if (Collection.class == targetClass || Iterable.class == targetClass) { |
| if (model instanceof TemplateCollectionModel) { |
| return new CollectionAdapter((TemplateCollectionModel) model, |
| this); |
| } |
| if (model instanceof TemplateSequenceModel) { |
| return new SequenceAdapter((TemplateSequenceModel) model, this); |
| } |
| } |
| |
| // TemplateSequenceModels can be converted to arrays |
| if (targetClass.isArray()) { |
| if (model instanceof TemplateSequenceModel) { |
| return unwrapSequenceToArray((TemplateSequenceModel) model, targetClass, true, recursionStops); |
| } |
| // array classes are final, no other conversion will work |
| return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; |
| } |
| |
| // Allow one-char strings to be coerced to characters |
| if (char.class == targetClass || targetClass == Character.class) { |
| if (model instanceof TemplateScalarModel) { |
| String s = ((TemplateScalarModel) model).getAsString(); |
| if (s.length() == 1) { |
| return Character.valueOf(s.charAt(0)); |
| } |
| } |
| // Character is final, no other conversion will work |
| return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; |
| } |
| |
| if (Date.class.isAssignableFrom(targetClass) && model instanceof TemplateDateModel) { |
| Date date = ((TemplateDateModel) model).getAsDate(); |
| if (targetClass.isInstance(date)) { |
| return date; |
| } |
| } |
| } // End: if (targetClass != Object.class) |
| |
| // Since the targetClass was of no help initially, now we use |
| // a quite arbitrary order in which we walk through the TemplateModel subinterfaces, and unwrapp them to |
| // their "natural" Java correspondent. We still try exclude unwrappings that won't fit the target parameter |
| // type(s). This is mostly important because of multi-typed FTL values that could be unwrapped on multiple ways. |
| int itf = typeFlags; // Iteration's Type Flags. Should be always 0 for non-overloaded and when !is2321Bugfixed. |
| // If itf != 0, we possibly execute the following loop body at twice: once with utilizing itf, and if it has not |
| // returned, once more with itf == 0. Otherwise we execute this once with itf == 0. |
| do { |
| if ((itf == 0 || (itf & TypeFlags.ACCEPTS_NUMBER) != 0) |
| && model instanceof TemplateNumberModel) { |
| Number number = ((TemplateNumberModel) model).getAsNumber(); |
| if (itf != 0 || targetClass.isInstance(number)) { |
| return number; |
| } |
| } |
| if ((itf == 0 || (itf & TypeFlags.ACCEPTS_DATE) != 0) |
| && model instanceof TemplateDateModel) { |
| Date date = ((TemplateDateModel) model).getAsDate(); |
| if (itf != 0 || targetClass.isInstance(date)) { |
| return date; |
| } |
| } |
| if ((itf == 0 || (itf & (TypeFlags.ACCEPTS_STRING | TypeFlags.CHARACTER)) != 0) |
| && model instanceof TemplateScalarModel |
| && (itf != 0 || targetClass.isAssignableFrom(String.class))) { |
| String strVal = ((TemplateScalarModel) model).getAsString(); |
| if (itf == 0 || (itf & TypeFlags.CHARACTER) == 0) { |
| return strVal; |
| } else { // TypeFlags.CHAR == 1 |
| if (strVal.length() == 1) { |
| if ((itf & TypeFlags.ACCEPTS_STRING) != 0) { |
| return new CharacterOrString(strVal); |
| } else { |
| return Character.valueOf(strVal.charAt(0)); |
| } |
| } else if ((itf & TypeFlags.ACCEPTS_STRING) != 0) { |
| return strVal; |
| } |
| // It had to be unwrapped to Character, but the string length wasn't 1 => Fall through |
| } |
| } |
| // Should be earlier than TemplateScalarModel, but we keep it here until FM 2.4 or such |
| if ((itf == 0 || (itf & TypeFlags.ACCEPTS_BOOLEAN) != 0) |
| && model instanceof TemplateBooleanModel |
| && (itf != 0 || targetClass.isAssignableFrom(Boolean.class))) { |
| return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean()); |
| } |
| if ((itf == 0 || (itf & TypeFlags.ACCEPTS_MAP) != 0) |
| && model instanceof TemplateHashModel |
| && (itf != 0 || targetClass.isAssignableFrom(HashAdapter.class))) { |
| return new HashAdapter((TemplateHashModel) model, this); |
| } |
| if ((itf == 0 || (itf & TypeFlags.ACCEPTS_LIST) != 0) |
| && model instanceof TemplateSequenceModel |
| && (itf != 0 || targetClass.isAssignableFrom(SequenceAdapter.class))) { |
| return new SequenceAdapter((TemplateSequenceModel) model, this); |
| } |
| if ((itf == 0 || (itf & TypeFlags.ACCEPTS_SET) != 0) |
| && model instanceof TemplateCollectionModel |
| && (itf != 0 || targetClass.isAssignableFrom(SetAdapter.class))) { |
| return new SetAdapter((TemplateCollectionModel) model, this); |
| } |
| |
| // In 2.3.21 bugfixed mode only, List-s are convertible to arrays on invocation time. Only overloaded |
| // methods need this. As itf will be 0 in non-bugfixed mode and for non-overloaded method calls, it's |
| // enough to check if the TypeFlags.ACCEPTS_ARRAY bit is 1: |
| if ((itf & TypeFlags.ACCEPTS_ARRAY) != 0 |
| && model instanceof TemplateSequenceModel) { |
| return new SequenceAdapter((TemplateSequenceModel) model, this); |
| } |
| |
| if (itf == 0) { |
| break; |
| } |
| itf = 0; // start 2nd iteration |
| } while (true); |
| |
| // Last ditch effort - is maybe the model itself is an instance of the required type? |
| // Note that this will be always true for Object.class targetClass. |
| if (targetClass.isInstance(model)) { |
| return model; |
| } |
| |
| return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; |
| } |
| |
| /** |
| * @param tryOnly |
| * If {@code true}, if the conversion of an item to the component type isn't possible, the method returns |
| * {@link ObjectWrapperAndUnwrapper#CANT_UNWRAP_TO_TARGET_CLASS} instead of throwing a |
| * {@link TemplateModelException}. |
| */ |
| Object unwrapSequenceToArray( |
| TemplateSequenceModel seq, Class<?> arrayClass, boolean tryOnly, Map<Object, Object> recursionStops) |
| throws TemplateModelException { |
| if (recursionStops != null) { |
| Object retval = recursionStops.get(seq); |
| if (retval != null) { |
| return retval; |
| } |
| } else { |
| recursionStops = new IdentityHashMap<>(); |
| } |
| Class<?> componentType = arrayClass.getComponentType(); |
| final int size = seq.size(); |
| Object array = Array.newInstance(componentType, size); |
| recursionStops.put(seq, array); |
| try { |
| for (int i = 0; i < size; i++) { |
| final TemplateModel seqItem = seq.get(i); |
| Object val = tryUnwrapTo(seqItem, componentType, 0, recursionStops); |
| if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { |
| if (tryOnly) { |
| return ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS; |
| } else { |
| throw new _TemplateModelException( |
| "Failed to convert ", new _DelayedFTLTypeDescription(seq), |
| " object to ", new _DelayedShortClassName(array.getClass()), |
| ": Problematic sequence item at index ", Integer.valueOf(i) ," with value type: ", |
| new _DelayedFTLTypeDescription(seqItem)); |
| } |
| |
| } |
| Array.set(array, i, val); |
| } |
| } finally { |
| recursionStops.remove(seq); |
| } |
| return array; |
| } |
| |
| Object listToArray(List<?> list, Class<?> arrayClass, Map<Object, Object> recursionStops) |
| throws TemplateModelException { |
| if (list instanceof SequenceAdapter) { |
| return unwrapSequenceToArray( |
| ((SequenceAdapter) list).getTemplateSequenceModel(), |
| arrayClass, false, |
| recursionStops); |
| } |
| |
| if (recursionStops != null) { |
| Object retval = recursionStops.get(list); |
| if (retval != null) { |
| return retval; |
| } |
| } else { |
| recursionStops = new IdentityHashMap<>(); |
| } |
| Class<?> componentType = arrayClass.getComponentType(); |
| Object array = Array.newInstance(componentType, list.size()); |
| recursionStops.put(list, array); |
| try { |
| boolean isComponentTypeExamined = false; |
| boolean isComponentTypeNumerical = false; // will be filled on demand |
| boolean isComponentTypeList = false; // will be filled on demand |
| int i = 0; |
| for (Iterator<?> it = list.iterator(); it.hasNext(); ) { |
| Object listItem = it.next(); |
| if (listItem != null && !componentType.isInstance(listItem)) { |
| // Type conversion is needed. If we can't do it, we just let it fail at Array.set later. |
| if (!isComponentTypeExamined) { |
| isComponentTypeNumerical = ClassUtil.isNumerical(componentType); |
| isComponentTypeList = List.class.isAssignableFrom(componentType); |
| isComponentTypeExamined = true; |
| } |
| if (isComponentTypeNumerical && listItem instanceof Number) { |
| listItem = forceUnwrappedNumberToType((Number) listItem, componentType, true); |
| } else if (componentType == String.class && listItem instanceof Character) { |
| listItem = String.valueOf(((Character) listItem).charValue()); |
| } else if ((componentType == Character.class || componentType == char.class) |
| && listItem instanceof String) { |
| String listItemStr = (String) listItem; |
| if (listItemStr.length() == 1) { |
| listItem = Character.valueOf(listItemStr.charAt(0)); |
| } |
| } else if (componentType.isArray()) { |
| if (listItem instanceof List) { |
| listItem = listToArray((List<?>) listItem, componentType, recursionStops); |
| } else if (listItem instanceof TemplateSequenceModel) { |
| listItem = unwrapSequenceToArray((TemplateSequenceModel) listItem, componentType, false, recursionStops); |
| } |
| } else if (isComponentTypeList && listItem.getClass().isArray()) { |
| listItem = arrayToList(listItem); |
| } |
| } |
| try { |
| Array.set(array, i, listItem); |
| } catch (IllegalArgumentException e) { |
| throw new TemplateModelException( |
| "Failed to convert " + ClassUtil.getShortClassNameOfObject(list) |
| + " object to " + ClassUtil.getShortClassNameOfObject(array) |
| + ": Problematic List item at index " + i + " with value type: " |
| + ClassUtil.getShortClassNameOfObject(listItem), e); |
| } |
| i++; |
| } |
| } finally { |
| recursionStops.remove(list); |
| } |
| return array; |
| } |
| |
| /** |
| * @param array Must be an array (of either a reference or primitive type) |
| */ |
| List<?> arrayToList(Object array) throws TemplateModelException { |
| if (array instanceof Object[]) { |
| // Array of any non-primitive type. |
| // Note that an array of non-primitive type is always instanceof Object[]. |
| Object[] objArray = (Object[]) array; |
| return objArray.length == 0 ? Collections.EMPTY_LIST : new NonPrimitiveArrayBackedReadOnlyList(objArray); |
| } else { |
| // Array of any primitive type |
| return Array.getLength(array) == 0 ? Collections.EMPTY_LIST : new PrimtiveArrayBackedReadOnlyList(array); |
| } |
| } |
| |
| /** |
| * Converts a number to the target type aggressively (possibly with overflow or significant loss of precision). |
| * @param n Non-{@code null} |
| * @return {@code null} if the conversion has failed. |
| */ |
| static Number forceUnwrappedNumberToType(final Number n, final Class<?> targetType, final boolean bugfixed) { |
| // We try to order the conditions by decreasing probability. |
| if (targetType == n.getClass()) { |
| return n; |
| } else if (targetType == int.class || targetType == Integer.class) { |
| return n instanceof Integer ? (Integer) n : Integer.valueOf(n.intValue()); |
| } else if (targetType == long.class || targetType == Long.class) { |
| return n instanceof Long ? (Long) n : Long.valueOf(n.longValue()); |
| } else if (targetType == double.class || targetType == Double.class) { |
| return n instanceof Double ? (Double) n : Double.valueOf(n.doubleValue()); |
| } else if (targetType == BigDecimal.class) { |
| if (n instanceof BigDecimal) { |
| return n; |
| } else if (n instanceof BigInteger) { |
| return new BigDecimal((BigInteger) n); |
| } else if (n instanceof Long) { |
| // Because we can't represent long accurately as double |
| return BigDecimal.valueOf(n.longValue()); |
| } else { |
| return new BigDecimal(n.doubleValue()); |
| } |
| } else if (targetType == float.class || targetType == Float.class) { |
| return n instanceof Float ? (Float) n : Float.valueOf(n.floatValue()); |
| } else if (targetType == byte.class || targetType == Byte.class) { |
| return n instanceof Byte ? (Byte) n : Byte.valueOf(n.byteValue()); |
| } else if (targetType == short.class || targetType == Short.class) { |
| return n instanceof Short ? (Short) n : Short.valueOf(n.shortValue()); |
| } else if (targetType == BigInteger.class) { |
| if (n instanceof BigInteger) { |
| return n; |
| } else if (bugfixed) { |
| if (n instanceof OverloadedNumberUtil.IntegerBigDecimal) { |
| return ((OverloadedNumberUtil.IntegerBigDecimal) n).bigIntegerValue(); |
| } else if (n instanceof BigDecimal) { |
| return ((BigDecimal) n).toBigInteger(); |
| } else { |
| return BigInteger.valueOf(n.longValue()); |
| } |
| } else { |
| // This is wrong, because something like "123.4" will cause NumberFormatException instead of flooring. |
| return new BigInteger(n.toString()); |
| } |
| } else { |
| final Number oriN = n instanceof OverloadedNumberUtil.NumberWithFallbackType |
| ? ((OverloadedNumberUtil.NumberWithFallbackType) n).getSourceNumber() : n; |
| if (targetType.isInstance(oriN)) { |
| // Handle nonstandard Number subclasses as well as directly java.lang.Number. |
| return oriN; |
| } else { |
| // Fails |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Invokes the specified method, wrapping the return value. All method invocations done in templates should go |
| * through this (assuming the target object was wrapped with this {@link ObjectWrapper}). |
| * |
| * <p>This method is protected since 2.3.30; before that it was package private. The intended application of |
| * overriding this is monitoring what calls are made from templates. That can be useful to asses what will be needed |
| * in a {@link WhitelistMemberAccessPolicy} for example. Note that {@link Object#toString} calls caused by type |
| * conversion (like when you have <code>${myObject}</code>) will not go through here, as they aren't called by the |
| * template directly (and aren't called via reflection). On the other hand, <code>${myObject[key]}</code>, |
| * if {@code myObject} is not a {@link Map}, will go through here as a {@code get(String|Object)} method call, if |
| * there's a such method. |
| * |
| * <p>If the return value is null, and the return type of the invoked method is void, |
| * {@link TemplateModel#NOTHING} is returned. |
| * |
| * @param object the object to invoke the method on ({@code null} may be null for static methods) |
| * @param method the method to invoke |
| * @param args the arguments to the method |
| * @return the wrapped return value of the method. |
| * @throws InvocationTargetException if the invoked method threw an exception |
| * @throws IllegalAccessException if the method can't be invoked due to an |
| * access restriction. |
| * @throws TemplateModelException if the return value couldn't be wrapped |
| * (this can happen if the wrapper has an outer identity or is subclassed, |
| * and the outer identity or the subclass throws an exception. Plain |
| * BeansWrapper never throws TemplateModelException). |
| * |
| * @see #readField(Object, Field) |
| * |
| * @since 2.3.30 |
| */ |
| protected TemplateModel invokeMethod(Object object, Method method, Object[] args) |
| throws InvocationTargetException, |
| IllegalAccessException, |
| TemplateModelException { |
| // [2.4]: Java's Method.invoke truncates numbers if the target type has not enough bits to hold the value. |
| // There should at least be an option to check this. |
| Object retval = method.invoke(object, args); |
| return |
| method.getReturnType() == void.class |
| ? TemplateModel.NOTHING |
| : getOuterIdentity().wrap(retval); |
| } |
| |
| /** |
| * Reads the specified field, returns its value as {@link TemplateModel}. All field reading done in templates |
| * should go through this (assuming the target object was wrapped with this {@link ObjectWrapper}). |
| * |
| * <p>Just like in the case of {@link #invokeMethod(Object, Method, Object[])}, overriding this can be useful if you |
| * want to monitor what members are accessed by templates. However, it has the caveat that final field values are |
| * possibly cached, so you won't see all reads. Furthermore, at least static models pre-read final fields, so |
| * they will be read even if the templates don't read them. |
| * |
| * @see #invokeMethod(Object, Method, Object[]) |
| * |
| * @since 2.3.30 |
| */ |
| protected TemplateModel readField(Object object, Field field) |
| throws IllegalAccessException, TemplateModelException { |
| return getOuterIdentity().wrap(field.get(object)); |
| } |
| |
| /** |
| * Returns a hash model that represents the so-called class static models. |
| * Every class static model is itself a hash through which you can call |
| * static methods on the specified class. To obtain a static model for a |
| * class, get the element of this hash with the fully qualified class name. |
| * For example, if you place this hash model inside the root data model |
| * under name "statics", you can use i.e. <code>statics["java.lang. |
| * System"]. currentTimeMillis()</code> to call the {@link |
| * java.lang.System#currentTimeMillis()} method. |
| * @return a hash model whose keys are fully qualified class names, and |
| * that returns hash models whose elements are the static models of the |
| * classes. |
| */ |
| public TemplateHashModel getStaticModels() { |
| return staticModels; |
| } |
| |
| /** |
| * Returns a hash model that represents the so-called class enum models. |
| * Every class' enum model is itself a hash through which you can access |
| * enum value declared by the specified class, assuming that class is an |
| * enumeration. To obtain an enum model for a class, get the element of this |
| * hash with the fully qualified class name. For example, if you place this |
| * hash model inside the root data model under name "enums", you can use |
| * i.e. <code>enums["java.math.RoundingMode"].UP</code> to access the |
| * {@link java.math.RoundingMode#UP} value. |
| * @return a hash model whose keys are fully qualified class names, and |
| * that returns hash models whose elements are the enum models of the |
| * classes. |
| * @throws UnsupportedOperationException if this method is invoked on a |
| * pre-1.5 JRE, as Java enums aren't supported there. |
| */ |
| public TemplateHashModel getEnumModels() { |
| if (enumModels == null) { |
| throw new UnsupportedOperationException( |
| "Enums not supported before J2SE 5."); |
| } |
| return enumModels; |
| } |
| |
| /** For Unit tests only */ |
| ModelCache getModelCache() { |
| return modelCache; |
| } |
| |
| /** |
| * Creates a new instance of the specified class using the method call logic of this object wrapper for calling the |
| * constructor. Overloaded constructors and varargs are supported. Only public constructors will be called. |
| * |
| * @param clazz The class whose constructor we will call. |
| * @param arguments The list of {@link TemplateModel}-s to pass to the constructor after unwrapping them |
| * @return The instance created; it's not wrapped into {@link TemplateModel}. |
| */ |
| public Object newInstance(Class<?> clazz, List/*<? extends TemplateModel>*/ arguments) |
| throws TemplateModelException { |
| try { |
| Object ctors = classIntrospector.get(clazz).get(ClassIntrospector.CONSTRUCTORS_KEY); |
| if (ctors == null) { |
| throw new TemplateModelException("Class " + clazz.getName() + |
| " has no exposed constructors."); |
| } |
| Constructor<?> ctor = null; |
| Object[] objargs; |
| if (ctors instanceof SimpleMethod) { |
| SimpleMethod sm = (SimpleMethod) ctors; |
| ctor = (Constructor<?>) sm.getMember(); |
| objargs = sm.unwrapArguments(arguments, this); |
| try { |
| return ctor.newInstance(objargs); |
| } catch (Exception e) { |
| if (e instanceof TemplateModelException) throw (TemplateModelException) e; |
| throw _MethodUtil.newInvocationTemplateModelException(null, ctor, e); |
| } |
| } else if (ctors instanceof OverloadedMethods) { |
| final MemberAndArguments mma = ((OverloadedMethods) ctors).getMemberAndArguments(arguments, this); |
| try { |
| return mma.invokeConstructor(this); |
| } catch (Exception e) { |
| if (e instanceof TemplateModelException) throw (TemplateModelException) e; |
| |
| throw _MethodUtil.newInvocationTemplateModelException(null, mma.getCallableMemberDescriptor(), e); |
| } |
| } else { |
| // Cannot happen |
| throw new BugException(); |
| } |
| } catch (TemplateModelException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new TemplateModelException( |
| "Error while creating new instance of class " + clazz.getName() + "; see cause exception", e); |
| } |
| } |
| |
| /** |
| * Removes the introspection data for a class from the cache. |
| * Use this if you know that a class is not used anymore in templates. |
| * If the class will be still used, the cache entry will be silently |
| * re-created, so this isn't a dangerous operation. |
| * |
| * @since 2.3.20 |
| */ |
| public void removeFromClassIntrospectionCache(Class<?> clazz) { |
| classIntrospector.remove(clazz); |
| } |
| |
| /** |
| * <p>Removes all class introspection data from the cache. |
| * |
| * <p>Use this if you want to free up memory on the expense of recreating |
| * the cache entries for the classes that will be used later in templates. |
| * |
| * @throws IllegalStateException if {@link #isClassIntrospectionCacheRestricted()} is {@code true}. |
| * |
| * @since 2.3.20 |
| * |
| * @deprecated There's a typo in this method name, so use {@link #clearClassIntrospectionCache()} instead. |
| */ |
| @Deprecated |
| public void clearClassIntrospecitonCache() { |
| classIntrospector.clearCache(); |
| } |
| |
| /** |
| * Removes all class introspection data from the cache. |
| * |
| * <p>Use this if you want to free up memory on the expense of recreating |
| * the cache entries for the classes that will be used later in templates. |
| * |
| * @throws IllegalStateException if {@link #isClassIntrospectionCacheRestricted()} is {@code true}. |
| * |
| * @since 2.3.29 (in earlier versions use {@link #clearClassIntrospecitonCache()}) |
| */ |
| public void clearClassIntrospectionCache() { |
| classIntrospector.clearCache(); |
| } |
| |
| ClassIntrospector getClassIntrospector() { |
| return classIntrospector; |
| } |
| |
| /** |
| * @deprecated Use {@link #setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}; |
| * no need to extend this class anymore. |
| * Soon this method will be final, so trying to override it will break your app. |
| * Note that if the {@code methodAppearanceFineTuner} property is set to non-{@code null}, this method is not |
| * called anymore. |
| */ |
| @Deprecated |
| protected void finetuneMethodAppearance( |
| Class<?> clazz, Method m, MethodAppearanceDecision decision) { |
| // left everything on its default; do nothing |
| } |
| |
| /** |
| * Converts any {@link BigDecimal}s in the passed array to the type of |
| * the corresponding formal argument of the method. |
| */ |
| // Unused? |
| public static void coerceBigDecimals(AccessibleObject callable, Object[] args) { |
| Class<?>[] formalTypes = null; |
| for (int i = 0; i < args.length; ++i) { |
| Object arg = args[i]; |
| if (arg instanceof BigDecimal) { |
| if (formalTypes == null) { |
| if (callable instanceof Method) { |
| formalTypes = ((Method) callable).getParameterTypes(); |
| } else if (callable instanceof Constructor) { |
| formalTypes = ((Constructor<?>) callable).getParameterTypes(); |
| } else { |
| throw new IllegalArgumentException("Expected method or " |
| + " constructor; callable is " + |
| callable.getClass().getName()); |
| } |
| } |
| args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]); |
| } |
| } |
| } |
| |
| /** |
| * Converts any {@link BigDecimal}-s in the passed array to the type of |
| * the corresponding formal argument of the method via {@link #coerceBigDecimal(BigDecimal, Class)}. |
| */ |
| public static void coerceBigDecimals(Class<?>[] formalTypes, Object[] args) { |
| int typeLen = formalTypes.length; |
| int argsLen = args.length; |
| int min = Math.min(typeLen, argsLen); |
| for (int i = 0; i < min; ++i) { |
| Object arg = args[i]; |
| if (arg instanceof BigDecimal) { |
| args[i] = coerceBigDecimal((BigDecimal) arg, formalTypes[i]); |
| } |
| } |
| if (argsLen > typeLen) { |
| Class<?> varArgType = formalTypes[typeLen - 1]; |
| for (int i = typeLen; i < argsLen; ++i) { |
| Object arg = args[i]; |
| if (arg instanceof BigDecimal) { |
| args[i] = coerceBigDecimal((BigDecimal) arg, varArgType); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Converts {@link BigDecimal} to the class given in the {@code formalType} argument if that's a known numerical |
| * type, returns the {@link BigDecimal} as is otherwise. Overflow and precision loss are possible, similarly as |
| * with casting in Java. |
| */ |
| public static Object coerceBigDecimal(BigDecimal bd, Class<?> formalType) { |
| // int is expected in most situations, so we check it first |
| if (formalType == int.class || formalType == Integer.class) { |
| return Integer.valueOf(bd.intValue()); |
| } else if (formalType == double.class || formalType == Double.class) { |
| return Double.valueOf(bd.doubleValue()); |
| } else if (formalType == long.class || formalType == Long.class) { |
| return Long.valueOf(bd.longValue()); |
| } else if (formalType == float.class || formalType == Float.class) { |
| return Float.valueOf(bd.floatValue()); |
| } else if (formalType == short.class || formalType == Short.class) { |
| return Short.valueOf(bd.shortValue()); |
| } else if (formalType == byte.class || formalType == Byte.class) { |
| return Byte.valueOf(bd.byteValue()); |
| } else if (java.math.BigInteger.class.isAssignableFrom(formalType)) { |
| return bd.toBigInteger(); |
| } else { |
| return bd; |
| } |
| } |
| |
| /** |
| * Returns the exact class name and the identity hash, also the values of the most often used {@link BeansWrapper} |
| * configuration properties, also if which (if any) shared class introspection cache it uses. |
| * |
| * @since 2.3.21 |
| */ |
| @Override |
| public String toString() { |
| final String propsStr = toPropertiesString(); |
| return ClassUtil.getShortClassNameOfObject(this) + "@" + System.identityHashCode(this) |
| + "(" + incompatibleImprovements + ", " |
| + (propsStr.length() != 0 ? propsStr + ", ..." : "") |
| + ")"; |
| } |
| |
| /** |
| * Returns the name-value pairs that describe the configuration of this {@link BeansWrapper}; called from |
| * {@link #toString()}. The expected format is like {@code "foo=bar, baaz=wombat"}. When overriding this, you should |
| * call the super method, and then insert the content before it with a following {@code ", "}, or after it with a |
| * preceding {@code ", "}. |
| * |
| * @since 2.3.22 |
| */ |
| protected String toPropertiesString() { |
| // Start with "simpleMapWrapper", because the override in DefaultObjectWrapper expects it to be there! |
| return "simpleMapWrapper=" + simpleMapWrapper + ", " |
| + "exposureLevel=" + classIntrospector.getExposureLevel() + ", " |
| + "exposeFields=" + classIntrospector.getExposeFields() + ", " |
| + "preferIndexedReadMethod=" + preferIndexedReadMethod + ", " |
| + "treatDefaultMethodsAsBeanMembers=" |
| + classIntrospector.getTreatDefaultMethodsAsBeanMembers() + ", " |
| + "sharedClassIntrospCache=" |
| + (classIntrospector.isShared() ? "@" + System.identityHashCode(classIntrospector) : "none"); |
| } |
| |
| /** |
| * Used for |
| * {@link MethodAppearanceFineTuner#process} |
| * to store the results; see there. |
| */ |
| static public final class MethodAppearanceDecision { |
| private PropertyDescriptor exposeAsProperty; |
| private boolean replaceExistingProperty; |
| private String exposeMethodAs; |
| private boolean methodShadowsProperty; |
| |
| void setDefaults(Method m) { |
| exposeAsProperty = null; |
| replaceExistingProperty = false; |
| exposeMethodAs = m.getName(); |
| methodShadowsProperty = true; |
| } |
| |
| /** |
| * See in the documentation of {@link MethodAppearanceFineTuner#process}. |
| */ |
| public PropertyDescriptor getExposeAsProperty() { |
| return exposeAsProperty; |
| } |
| |
| /** |
| * See in the documentation of {@link MethodAppearanceFineTuner#process}. |
| * Note that you may also want to call |
| * {@link #setMethodShadowsProperty(boolean) setMethodShadowsProperty(false)} when you call this. |
| */ |
| public void setExposeAsProperty(PropertyDescriptor exposeAsProperty) { |
| this.exposeAsProperty = exposeAsProperty; |
| } |
| |
| /** |
| * Getter pair of {@link #setReplaceExistingProperty(boolean)}. |
| * |
| * @since 2.3.28 |
| */ |
| public boolean getReplaceExistingProperty() { |
| return replaceExistingProperty; |
| } |
| |
| /** |
| * If {@link #getExposeAsProperty()} is non-{@code null}, and a {@link PropertyDescriptor} with the same |
| * property name was already added to the class introspection data, this decides if that will be replaced |
| * with the {@link PropertyDescriptor} returned by {@link #getExposeAsProperty()}. The default is {@code false}, |
| * that is, the old {@link PropertyDescriptor} is kept, and the new one is ignored. |
| * JavaBean properties discovered with the standard (non-{@link MethodAppearanceFineTuner}) mechanism |
| * are added before those created by the {@link MethodAppearanceFineTuner}, so with this you can decide if a |
| * real JavaBeans property can be replaced by the "fake" one created with |
| * {@link #setExposeAsProperty(PropertyDescriptor)}. |
| * |
| * @since 2.3.28 |
| */ |
| public void setReplaceExistingProperty(boolean overrideExistingProperty) { |
| this.replaceExistingProperty = overrideExistingProperty; |
| } |
| |
| /** |
| * See in the documentation of {@link MethodAppearanceFineTuner#process}. |
| */ |
| public String getExposeMethodAs() { |
| return exposeMethodAs; |
| } |
| |
| /** |
| * See in the documentation of {@link MethodAppearanceFineTuner#process}. |
| */ |
| public void setExposeMethodAs(String exposeAsMethod) { |
| this.exposeMethodAs = exposeAsMethod; |
| } |
| |
| /** |
| * See in the documentation of {@link MethodAppearanceFineTuner#process}. |
| */ |
| public boolean getMethodShadowsProperty() { |
| return methodShadowsProperty; |
| } |
| |
| /** |
| * See in the documentation of {@link MethodAppearanceFineTuner#process}. |
| */ |
| public void setMethodShadowsProperty(boolean shadowEarlierProperty) { |
| this.methodShadowsProperty = shadowEarlierProperty; |
| } |
| |
| } |
| |
| /** |
| * Used for {@link MethodAppearanceFineTuner#process} as input parameter; see there. |
| */ |
| static public final class MethodAppearanceDecisionInput { |
| private Method method; |
| private Class<?> containingClass; |
| |
| void setMethod(Method method) { |
| this.method = method; |
| } |
| |
| void setContainingClass(Class<?> containingClass) { |
| this.containingClass = containingClass; |
| } |
| |
| public Method getMethod() { |
| return method; |
| } |
| |
| public Class/*<?>*/ getContainingClass() { |
| return containingClass; |
| } |
| |
| } |
| |
| } |