/*
 * 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 ObjectWrapper interface. Usually, you don't need to create instances of this, as an instance of this is already the default value of the object_wrapper setting. Then the incompatibleImprovements of the DefaultObjectWrapper will be the same that you have set for the Configuration itself. As of this writing, it's highly recommended to use incompatibleImprovements 2.3.22 (or higher).

If you still need to create an instance, that should be done with an DefaultObjectWrapperBuilder (or with Configuration.setSetting(String, String) with "object_wrapper" key), not with its constructor, as that allows FreeMarker to reuse singletons. For new projects, it's recommended to set forceLegacyNonListCollections to false, and iterableSupport to true; setting incompatibleImprovements to 2.3.22 won't do these, as they could break legacy templates too easily.

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 Configuration, of course it's enough if that was safely published and then left unmodified.

/** * 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 DefaultObjectWrapperBuilder instead, but mind its performance
/** @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 Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS.
Deprecated:Use DefaultObjectWrapperBuilder, or in rare cases, DefaultObjectWrapper(Version) instead.
/** * 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 DefaultObjectWrapperBuilder instead if possible. Instances created with this constructor won't share the class introspection caches with other instances. See BeansWrapper(Version) (the superclass constructor) for more details.
Params:
  • incompatibleImprovements – It's the same as in BeansWrapper(Version), plus these changes:
    • 2.3.22 (or higher): The default value of useAdaptersForContainers changes to true.
    • 2.3.24 (or higher): When wrapping an Iterator, operations on it that only check if the collection is empty without reading an element from it, such as ?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".
    • 2.3.26 (or higher): Enumeration-s are wrapped into DefaultEnumerationAdapter instead of into EnumerationModel (as far as useAdaptersForContainers is true, which is the default). This adapter is cleaner than 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 ?api).
Since:2.3.21
/** * 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 DefaultObjectWrapper(DefaultObjectWrapperConfiguration, boolean) instead if possible; it does the same, except that it tolerates a non-DefaultObjectWrapperConfiguration configuration too.
Since:2.3.21
/** * 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); }
Since:2.3.22
/** * 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 TemplateModel interface(s). Simple types like numbers, strings, booleans and dates will be wrapped into the corresponding SimpleXxx classes (like SimpleNumber). Map-s, List-s, other Collection-s, arrays and Iterator-s will be wrapped into the corresponding SimpleXxx or DefaultXxxAdapter classes (like SimpleHash or DefaultMapAdapter), depending on getUseAdaptersForContainers() and getForceLegacyNonListCollections(). After that, the wrapping is handled by handleUnknownType(Object), so see more there.
/** * 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 Node-s will be wrapped as NodeModel-s (allows DOM tree traversal), Jython objects will be delegated to the JythonWrapper, others will be wrapped using BeansWrapper.wrap(Object).

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).

/** * 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.
/** * 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; }
Since:2.3.22
/** * The getter pair of {@link #setUseAdaptersForContainers(boolean)}. * * @since 2.3.22 */
public boolean getUseAdaptersForContainers() { return useAdaptersForContainers; }
Sets if to wrap container objects (Map-s, List-s, arrays and such) the legacy copying approach or the newer adapter approach should be used. true is recommended, which is also the default when the incompatible_improvements of this instance was set to Configuration.VERSION_2_3_22 or higher. To understand the difference, check some of the classes that implement the two approaches:

See also the related Version History entry under 2.3.22 in the FreeMarker Manual, which gives a breakdown of the consequences.

Attention: For backward compatibility, currently, non-List collections (like Set-s) will only be wrapped with adapter approach (with DefaultNonListCollectionAdapter) if forceLegacyNonListCollections was set to false. Currently the default is true, but in new projects you should set it to false. See setForceLegacyNonListCollections(boolean) for more.

See Also:
Since:2.3.22
/** * 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 setForceLegacyNonListCollections(boolean); see there.
Since:2.3.22
/** * Getter pair of {@link #setForceLegacyNonListCollections(boolean)}; see there. * * @since 2.3.22 */
public boolean getForceLegacyNonListCollections() { return forceLegacyNonListCollections; }
Specifies whether non-List Collection-s (like Set-s) must be wrapped by pre-fetching into a SimpleSequence. The modern approach is wrapping into a DefaultNonListCollectionAdapter. This setting only has effect when getUseAdaptersForContainers() is also true, as otherwise SimpleSequence will be used regardless of this. In new projects you should set this to false. At least before incompatible_improvements 2.4.0 it defaults to true, because of backward compatibility concerns: with 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 Also:
Since:2.3.22
/** * 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 setIterableSupport(boolean); see there.
Since:2.3.25
/** * Getter pair of {@link #setIterableSupport(boolean)}; see there. * * @since 2.3.25 */
public boolean getIterableSupport() { return iterableSupport; }
Specifies whether Iterable-s (not to be confused with Iterator-s) that don't implement any other recognized Java interfaces (most notably Collection) will be recognized as listable objects (TemplateCollectionModel-s), or they will be just seen as generic objects (JavaBean-s). Defaults to false for backward compatibility, but in new projects you should set this to true. Before setting this to true in older projects, check if you have called myIterable.iterator() directly from any templates, because the Java API is only exposed to the templates if the Iterable is wrapped as generic object.
Since:2.3.25
/** * 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
/** * 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
/** * @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; } }