/*
* 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.template;
import java.lang.reflect.
Array;
import java.util.
ArrayList;
import java.util.
Collection;
import java.util.
Enumeration;
import java.util.
Iterator;
import java.util.
List;
import java.util.
Map;
import java.util.
Set;
import org.w3c.dom.
Node;
import freemarker.ext.beans.
BeansWrapper;
import freemarker.ext.beans.
BeansWrapperConfiguration;
import freemarker.ext.beans.
EnumerationModel;
import freemarker.ext.dom.
NodeModel;
import freemarker.log.
Logger;
/**
* The default implementation of the {@link ObjectWrapper} interface. Usually, you don't need to create instances of
* this, as an instance of this is already the default value of the
* {@link Configuration#setObjectWrapper(ObjectWrapper) object_wrapper setting}. Then the
* {@link #DefaultObjectWrapper(Version) incompatibleImprovements} of the {@link DefaultObjectWrapper} will be the same
* that you have set for the {@link Configuration} itself. As of this writing, it's highly recommended to use
* {@link Configuration#Configuration(Version) incompatibleImprovements} 2.3.22 (or higher).
*
* <p>
* If you still need to create an instance, that should be done with an {@link DefaultObjectWrapperBuilder} (or
* with {@link Configuration#setSetting(String, String)} with {@code "object_wrapper"} key), not with
* its constructor, as that allows FreeMarker to reuse singletons. For new projects, it's recommended to set
* {@link DefaultObjectWrapperBuilder#setForceLegacyNonListCollections(boolean) forceLegacyNonListCollections} to
* {@code false}, and {@link DefaultObjectWrapperBuilder#setIterableSupport(boolean) iterableSupport} to {@code true};
* setting {@code incompatibleImprovements} to 2.3.22 won't do these, as they could break legacy templates too easily.
*
* <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.
*/
public class
DefaultObjectWrapper extends freemarker.ext.beans.
BeansWrapper {
/** @deprecated Use {@link DefaultObjectWrapperBuilder} instead, but mind its performance */
@
Deprecated
static final
DefaultObjectWrapper instance = new
DefaultObjectWrapper();
static final private
Class<?>
JYTHON_OBJ_CLASS;
static final private
ObjectWrapper JYTHON_WRAPPER;
private boolean
useAdaptersForContainers;
private boolean
forceLegacyNonListCollections;
private boolean
iterableSupport;
private final boolean
useAdapterForEnumerations;
/**
* Creates a new instance with the incompatible-improvements-version specified in
* {@link Configuration#DEFAULT_INCOMPATIBLE_IMPROVEMENTS}.
*
* @deprecated Use {@link DefaultObjectWrapperBuilder}, or in rare cases,
* {@link #DefaultObjectWrapper(Version)} instead.
*/
@
Deprecated
public
DefaultObjectWrapper() {
this(
Configuration.
DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
}
/**
* Use {@link DefaultObjectWrapperBuilder} instead if possible. Instances created with this constructor won't share
* the class introspection caches with other instances. See {@link BeansWrapper#BeansWrapper(Version)} (the
* superclass constructor) for more details.
*
* @param incompatibleImprovements
* It's the same as in {@link BeansWrapper#BeansWrapper(Version)}, plus these changes:
* <ul>
* <li>2.3.22 (or higher): The default value of
* {@link #setUseAdaptersForContainers(boolean) useAdaptersForContainers} changes to
* {@code true}.</li>
* <li>2.3.24 (or higher): When wrapping an {@link Iterator}, operations on it that only check if the
* collection is empty without reading an element from it, such as {@code ?has_content},
* won't cause the a later iteration (or further emptiness check) to fail anymore. Earlier, in
* certain situations, the second operation has failed saying that the iterator "can be listed only
* once".
* <li>2.3.26 (or higher): {@link Enumeration}-s are wrapped into {@link DefaultEnumerationAdapter}
* instead of into {@link EnumerationModel} (as far as
* {@link #setUseAdaptersForContainers(boolean) useAdaptersForContainers} is {@code true}, which is
* the default). This adapter is cleaner than {@link EnumerationModel} as it only implements the
* minimally required FTL type, which avoids some ambiguous situations. (Note that Java API methods
* aren't exposed anymore as subvariables; if you really need them, you can use {@code ?api}).
* </li>
* </ul>
*
* @since 2.3.21
*/
public
DefaultObjectWrapper(
Version incompatibleImprovements) {
this(new
DefaultObjectWrapperConfiguration(
incompatibleImprovements) { }, false);
}
/**
* Use {@link #DefaultObjectWrapper(DefaultObjectWrapperConfiguration, boolean)} instead if possible;
* it does the same, except that it tolerates a non-{@link DefaultObjectWrapperConfiguration} configuration too.
*
* @since 2.3.21
*/
protected
DefaultObjectWrapper(
BeansWrapperConfiguration bwCfg, boolean
writeProtected) {
super(
bwCfg,
writeProtected, false);
DefaultObjectWrapperConfiguration dowDowCfg =
bwCfg instanceof
DefaultObjectWrapperConfiguration
? (
DefaultObjectWrapperConfiguration)
bwCfg
: new
DefaultObjectWrapperConfiguration(
bwCfg.
getIncompatibleImprovements()) { };
useAdaptersForContainers =
dowDowCfg.
getUseAdaptersForContainers();
useAdapterForEnumerations =
useAdaptersForContainers
&&
getIncompatibleImprovements().
intValue() >=
_TemplateAPI.
VERSION_INT_2_3_26;
forceLegacyNonListCollections =
dowDowCfg.
getForceLegacyNonListCollections();
iterableSupport =
dowDowCfg.
getIterableSupport();
finalizeConstruction(
writeProtected);
}
/**
* Calls {@link BeansWrapper#BeansWrapper(BeansWrapperConfiguration, boolean)} and sets up
* {@link DefaultObjectWrapper}-specific fields.
*
* @since 2.3.22
*/
protected
DefaultObjectWrapper(
DefaultObjectWrapperConfiguration dowCfg, boolean
writeProtected) {
this((
BeansWrapperConfiguration)
dowCfg,
writeProtected);
}
static {
Class<?>
cl;
ObjectWrapper ow;
try {
cl =
Class.
forName("org.python.core.PyObject");
ow = (
ObjectWrapper)
Class.
forName(
"freemarker.ext.jython.JythonWrapper")
.
getField("INSTANCE").
get(null);
} catch (
Throwable e) {
cl = null;
ow = null;
if (!(
e instanceof
ClassNotFoundException)) {
try {
Logger.
getLogger("freemarker.template.DefaultObjectWrapper")
.
error("Failed to init Jython support, so it was disabled.",
e);
} catch (
Throwable e2) {
// ignore
}
}
}
JYTHON_OBJ_CLASS =
cl;
JYTHON_WRAPPER =
ow;
}
/**
* Wraps the parameter object to {@link TemplateModel} interface(s). Simple types like numbers, strings, booleans
* and dates will be wrapped into the corresponding {@code SimpleXxx} classes (like {@link SimpleNumber}).
* {@link Map}-s, {@link List}-s, other {@link Collection}-s, arrays and {@link Iterator}-s will be wrapped into the
* corresponding {@code SimpleXxx} or {@code DefaultXxxAdapter} classes (like {@link SimpleHash} or
* {@link DefaultMapAdapter}), depending on {@link #getUseAdaptersForContainers()} and
* {@link #getForceLegacyNonListCollections()}. After that, the wrapping is handled by
* {@link #handleUnknownType(Object)}, so see more there.
*/
@
Override
public
TemplateModel wrap(
Object obj) throws
TemplateModelException {
if (
obj == null) {
return super.wrap(null);
}
if (
obj instanceof
TemplateModel) {
return (
TemplateModel)
obj;
}
if (
obj instanceof
String) {
return new
SimpleScalar((
String)
obj);
}
if (
obj instanceof
Number) {
return new
SimpleNumber((
Number)
obj);
}
if (
obj instanceof java.util.
Date) {
if (
obj instanceof java.sql.
Date) {
return new
SimpleDate((java.sql.
Date)
obj);
}
if (
obj instanceof java.sql.
Time) {
return new
SimpleDate((java.sql.
Time)
obj);
}
if (
obj instanceof java.sql.
Timestamp) {
return new
SimpleDate((java.sql.
Timestamp)
obj);
}
return new
SimpleDate((java.util.
Date)
obj,
getDefaultDateType());
}
final
Class<?>
objClass =
obj.
getClass();
if (
objClass.
isArray()) {
if (
useAdaptersForContainers) {
return
DefaultArrayAdapter.
adapt(
obj, this);
} else {
obj =
convertArray(
obj);
// Falls through (a strange legacy...)
}
}
if (
obj instanceof
Collection) {
if (
useAdaptersForContainers) {
if (
obj instanceof
List) {
return
DefaultListAdapter.
adapt((
List<?>)
obj, this);
} else {
return
forceLegacyNonListCollections
? (
TemplateModel) new
SimpleSequence((
Collection<?>)
obj, this)
: (
TemplateModel)
DefaultNonListCollectionAdapter.
adapt((
Collection<?>)
obj, this);
}
} else {
return new
SimpleSequence((
Collection<?>)
obj, this);
}
}
if (
obj instanceof
Map) {
return
useAdaptersForContainers
? (
TemplateModel)
DefaultMapAdapter.
adapt((
Map<?, ?>)
obj, this)
: (
TemplateModel) new
SimpleHash((
Map<?, ?>)
obj, this);
}
if (
obj instanceof
Boolean) {
return
obj.
equals(
Boolean.
TRUE) ?
TemplateBooleanModel.
TRUE :
TemplateBooleanModel.
FALSE;
}
if (
obj instanceof
Iterator) {
return
useAdaptersForContainers
? (
TemplateModel)
DefaultIteratorAdapter.
adapt((
Iterator<?>)
obj, this)
: (
TemplateModel) new
SimpleCollection((
Iterator<?>)
obj, this);
}
if (
useAdapterForEnumerations &&
obj instanceof
Enumeration) {
return
DefaultEnumerationAdapter.
adapt((
Enumeration<?>)
obj, this);
}
if (
iterableSupport &&
obj instanceof
Iterable) {
return
DefaultIterableAdapter.
adapt((
Iterable<?>)
obj, this);
}
return
handleUnknownType(
obj);
}
/**
* Called for an object that isn't considered to be of a "basic" Java type, like for an application specific type,
* or for a W3C DOM node. In its default implementation, W3C {@link Node}-s will be wrapped as {@link NodeModel}-s
* (allows DOM tree traversal), Jython objects will be delegated to the {@code JythonWrapper}, others will be
* wrapped using {@link BeansWrapper#wrap(Object)}.
*
* <p>
* When you override this method, you should first decide if you want to wrap the object in a custom way (and if so
* then do it and return with the result), and if not, then you should call the super method (assuming the default
* behavior is fine with you).
*/
protected
TemplateModel handleUnknownType(
Object obj) throws
TemplateModelException {
if (
obj instanceof
Node) {
return
wrapDomNode(
obj);
}
if (
JYTHON_WRAPPER != null &&
JYTHON_OBJ_CLASS.
isInstance(
obj)) {
return
JYTHON_WRAPPER.
wrap(
obj);
}
return super.wrap(
obj);
}
public
TemplateModel wrapDomNode(
Object obj) {
return
NodeModel.
wrap((
Node)
obj);
}
/**
* Converts an array to a java.util.List.
*/
protected
Object convertArray(
Object arr) {
// FM 2.4: Use Arrays.asList instead
final int
size =
Array.
getLength(
arr);
ArrayList list = new
ArrayList(
size);
for (int
i = 0;
i <
size;
i++) {
list.
add(
Array.
get(
arr,
i));
}
return
list;
}
/**
* The getter pair of {@link #setUseAdaptersForContainers(boolean)}.
*
* @since 2.3.22
*/
public boolean
getUseAdaptersForContainers() {
return
useAdaptersForContainers;
}
/**
* Sets if to wrap container objects ({@link Map}-s, {@link List}-s, arrays and such) the legacy copying approach or
* the newer adapter approach should be used. {@code true} is recommended, which is also the default when the
* {@code incompatible_improvements} of this instance was set to {@link Configuration#VERSION_2_3_22} or higher. To
* understand the difference, check some of the classes that implement the two approaches:
* <ul>
* <li>Copying approach: {@link SimpleHash}, {@link SimpleSequence}</li>
* <li>Adapter approach: {@link DefaultMapAdapter}, {@link DefaultListAdapter}, {@link DefaultArrayAdapter},
* {@link DefaultIteratorAdapter}</li>
* </ul>
*
* <p>
* See also the related Version History entry under 2.3.22 in the FreeMarker Manual, which gives a breakdown of
* the consequences.
*
* <p>
* <b>Attention:</b> For backward compatibility, currently, non-{@link List} collections (like {@link Set}-s) will
* only be wrapped with adapter approach (with {@link DefaultNonListCollectionAdapter}) if
* {@link #setForceLegacyNonListCollections(boolean) forceLegacyNonListCollections} was set to {@code false}.
* Currently the default is {@code true}, but in new projects you should set it to {@code false}. See
* {@link #setForceLegacyNonListCollections(boolean)} for more.
*
* @see #setForceLegacyNonListCollections(boolean)
*
* @since 2.3.22
*/
public void
setUseAdaptersForContainers(boolean
useAdaptersForContainers) {
checkModifiable();
this.
useAdaptersForContainers =
useAdaptersForContainers;
}
/**
* Getter pair of {@link #setForceLegacyNonListCollections(boolean)}; see there.
*
* @since 2.3.22
*/
public boolean
getForceLegacyNonListCollections() {
return
forceLegacyNonListCollections;
}
/**
* Specifies whether non-{@link List} {@link Collection}-s (like {@link Set}-s) must be wrapped by pre-fetching into
* a {@link SimpleSequence}. The modern approach is wrapping into a {@link DefaultNonListCollectionAdapter}. This
* setting only has effect when {@link #getUseAdaptersForContainers()} is also {@code true}, as otherwise
* {@link SimpleSequence} will be used regardless of this. In new projects you should set this to {@code false}. At
* least before {@code incompatible_improvements} 2.4.0 it defaults to {@code true}, because of backward
* compatibility concerns: with {@link TemplateSequenceModel} templates could access the items by index if they
* wanted to (the index values were defined by the iteration order). This was not very useful, or was even
* confusing, and it conflicts with the adapter approach.
*
* @see #setUseAdaptersForContainers(boolean)
*
* @since 2.3.22
*/
public void
setForceLegacyNonListCollections(boolean
forceLegacyNonListCollections) {
checkModifiable();
this.
forceLegacyNonListCollections =
forceLegacyNonListCollections;
}
/**
* Getter pair of {@link #setIterableSupport(boolean)}; see there.
*
* @since 2.3.25
*/
public boolean
getIterableSupport() {
return
iterableSupport;
}
/**
* Specifies whether {@link Iterable}-s (not to be confused with {@link Iterator}-s) that don't implement any other
* recognized Java interfaces (most notably {@link Collection}) will be recognized as listable objects
* ({@link TemplateCollectionModel}-s), or they will be just seen as generic objects (JavaBean-s). Defaults to
* {@code false} for backward compatibility, but in new projects you should set this to {@code true}. Before setting
* this to {@code true} in older projects, check if you have called {@code myIterable.iterator()} directly from any
* templates, because the Java API is only exposed to the templates if the {@link Iterable} is wrapped as generic
* object.
*
* @since 2.3.25
*/
public void
setIterableSupport(boolean
iterableSupport) {
checkModifiable();
this.
iterableSupport =
iterableSupport;
}
/**
* Returns the lowest version number that is equivalent with the parameter version.
*
* @since 2.3.22
*/
protected static
Version normalizeIncompatibleImprovementsVersion(
Version incompatibleImprovements) {
_TemplateAPI.
checkVersionNotNullAndSupported(
incompatibleImprovements);
Version bwIcI =
BeansWrapper.
normalizeIncompatibleImprovementsVersion(
incompatibleImprovements);
return
incompatibleImprovements.
intValue() <
_TemplateAPI.
VERSION_INT_2_3_22
||
bwIcI.
intValue() >=
_TemplateAPI.
VERSION_INT_2_3_22
?
bwIcI :
Configuration.
VERSION_2_3_22;
}
/**
* @since 2.3.22
*/
@
Override
protected
String toPropertiesString() {
String bwProps = super.toPropertiesString();
// Remove simpleMapWrapper, as its irrelevant for this wrapper:
if (
bwProps.
startsWith("simpleMapWrapper")) {
int
smwEnd =
bwProps.
indexOf(',');
if (
smwEnd != -1) {
bwProps =
bwProps.
substring(
smwEnd + 1).
trim();
}
}
return "useAdaptersForContainers=" +
useAdaptersForContainers + ", forceLegacyNonListCollections="
+
forceLegacyNonListCollections + ", iterableSupport=" +
iterableSupport +
bwProps;
}
}