/*
* 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.
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.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.
*/
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.
*/
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).
*/
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 <tt>false</tt> 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 returns 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 returns 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 returns
* 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() {
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);
} 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
*/
public void
writeProtect() {
writeProtected = true;
}
/**
* @since 2.3.21
*/
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 <tt>false</tt> (the default) then an attempt to read
* a missing bean property is the same as reading an existing bean property whose
* value is <tt>null</tt>. The template can't tell the difference, and thus always
* can use <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins
* to handle the situation.
*
* <p>If this property is <tt>true</tt> then an attempt to read a bean propertly in
* the template (like <tt>myBean.aProperty</tt>) that doesn't exist in the bean
* object (as opposed to just holding <tt>null</tt> value) will cause
* {@link InvalidPropertyException}, which can't be suppressed in the template
* (not even with <tt>myBean.noSuchProperty?default('something')</tt>). This way
* <tt>?default('something')</tt> and <tt>?exists</tt> and similar built-ins can be used to
* handle existing properties whose value is <tt>null</tt>, 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 <tt>this</tt>.
* @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.
*/
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);
}
}
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 #clearClassIntrospecitonCache()}.
* 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.
getHasSharedInstanceRestrictons();
}
/**
* 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);
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 <tt>java.util.Date</tt> instead of <tt>java.sql.Date</tt> or
* <tt>java.sql.Time</tt> or <tt>java.sql.Timestamp</tt>. 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() >=
_TemplateAPI.
VERSION_INT_2_3_21;
}
boolean
is2324Bugfixed() {
return
is2324Bugfixed(
getIncompatibleImprovements());
}
static boolean
is2324Bugfixed(
Version version) {
return
version.
intValue() >=
_TemplateAPI.
VERSION_INT_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);
if (
incompatibleImprovements.
intValue() <
_TemplateAPI.
VERSION_INT_2_3_0) {
throw new
IllegalArgumentException("Version must be at least 2.3.0.");
}
return
incompatibleImprovements.
intValue() >=
_TemplateAPI.
VERSION_INT_2_3_27 ?
Configuration.
VERSION_2_3_27
:
incompatibleImprovements.
intValue() ==
_TemplateAPI.
VERSION_INT_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>
*/
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
*/
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() {
public
TemplateModel create(
Object object,
ObjectWrapper wrapper) {
return ((
Boolean)
object).
booleanValue() ?
trueModel :
falseModel;
}
};
private static final
ModelFactory ITERATOR_FACTORY = new
ModelFactory() {
public
TemplateModel create(
Object object,
ObjectWrapper wrapper) {
return new
IteratorModel((
Iterator<?>)
object, (
BeansWrapper)
wrapper);
}
};
private static final
ModelFactory ENUMERATION_FACTORY = new
ModelFactory() {
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.
*/
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} of 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
*/
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<
Object,
Object>();
}
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<
Object,
Object>();
}
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. The specialty
* of this method is that 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
* @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).
*/
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);
}
/**
* 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 public 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);
}
/**
* 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
*/
public void
clearClassIntrospecitonCache() {
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;
}
}
}