blob: a6b5aeaf5b9b6755af5dcb6d7d2cbc8b5893b2bc [file] [log] [blame]
/*
* 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.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import freemarker.ext.beans.BeansWrapper;
/**
* A simple implementation of the {@link TemplateSequenceModel} interface, using its own underlying {@link List} for
* storing the list items. If you are wrapping an already existing {@link List} or {@code array}, you should certainly
* use {@link DefaultMapAdapter} or {@link DefaultArrayAdapter} (see comparison below).
*
* <p>
* This class is thread-safe if you don't call modifying methods (like {@link #add(Object)}) after you have made the
* object available for multiple threads (assuming you have published it safely to the other threads; see JSR-133 Java
* Memory Model). These methods aren't called by FreeMarker, so it's usually not a concern.
*
* <p>
* <b>{@link SimpleSequence} VS {@link DefaultListAdapter}/{@link DefaultArrayAdapter} - Which to use when?</b>
* </p>
*
* <p>
* For a {@link List} or {@code array} that exists regardless of FreeMarker, only you need to access it from templates,
* {@link DefaultMapAdapter} should be the default choice, as it can be unwrapped to the originally wrapped object
* (important when passing it to Java methods from the template). It also has more predictable performance (no spikes).
*
* <p>
* For a sequence that's made specifically to be used from templates, creating an empty {@link SimpleSequence} then
* filling it with {@link SimpleSequence#add(Object)} is usually the way to go, as the resulting sequence is
* significantly faster to read from templates than a {@link DefaultListAdapter} (though it's somewhat slower to read
* from a plain Java method to which it had to be passed adapted to a {@link List}).
*
* <p>
* It also matters if for how many times will the <em>same</em> {@link List} entry be read from the template(s) later,
* on average. If, on average, you read each entry for more than 4 times, {@link SimpleSequence} will be most
* certainly faster, but if for 2 times or less (and especially if not at all) then {@link DefaultMapAdapter} will
* be faster. Before choosing based on performance though, pay attention to the behavioral differences;
* {@link SimpleSequence} will shallow-copy the original {@link List} at construction time, so it won't reflect
* {@link List} content changes after the {@link SimpleSequence} construction, also {@link SimpleSequence} can't be
* unwrapped to the original wrapped instance.
*
* @see DefaultListAdapter
* @see DefaultArrayAdapter
* @see TemplateSequenceModel
*/
public class SimpleSequence extends WrappingTemplateModel implements TemplateSequenceModel, Serializable {
/**
* The {@link List} that stored the elements of this sequence. It migth contains both {@link TemplateModel} elements
* and non-{@link TemplateModel} elements.
*/
protected final List list;
private List unwrappedList;
/**
* Constructs an empty simple sequence that will use the the default object
* wrapper set in
* {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}.
*
* @deprecated Use {@link #SimpleSequence(ObjectWrapper)} instead.
*/
@Deprecated
public SimpleSequence() {
this((ObjectWrapper) null);
}
/**
* Constructs an empty simple sequence with preallocated capacity and using
* the default object wrapper set in
* {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}.
*
* @deprecated Use {@link #SimpleSequence(int, ObjectWrapper)}.
*/
@Deprecated
public SimpleSequence(int capacity) {
list = new ArrayList(capacity);
}
/**
* Constructs a simple sequence that will contain the elements
* from the specified {@link Collection} and will use the the default
* object wrapper set in
* {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}.
* @param collection the collection containing initial values. Note that a
* copy of the collection is made for internal use.
*
* @deprecated Use {@link #SimpleSequence(Collection, ObjectWrapper)}.
*/
@Deprecated
public SimpleSequence(Collection collection) {
this(collection, null);
}
/**
* Constructs a simple sequence from the passed collection model, which shouldn't be added to later. The internal
* list will be build immediately (not lazily). The resulting sequence shouldn't be extended with
* {@link #add(Object)}, because the appropriate {@link ObjectWrapper} won't be available; use
* {@link #SimpleSequence(Collection, ObjectWrapper)} instead, if you need that.
*/
public SimpleSequence(TemplateCollectionModel tcm) throws TemplateModelException {
ArrayList alist = new ArrayList();
for (TemplateModelIterator it = tcm.iterator(); it.hasNext(); ) {
alist.add(it.next());
}
alist.trimToSize();
list = alist;
}
/**
* Constructs an empty sequence using the specified object wrapper.
*
* @param wrapper
* The object wrapper to use to wrap the list items into {@link TemplateModel} instances. {@code null} is
* allowed, but deprecated, and will cause the deprecated default object wrapper (set in
* {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}) to be used.
*/
public SimpleSequence(ObjectWrapper wrapper) {
super(wrapper);
list = new ArrayList();
}
/**
* Constructs an empty simple sequence with preallocated capacity.
*
* @param wrapper
* See the similar parameter of {@link SimpleSequence#SimpleSequence(ObjectWrapper)}.
*
* @since 2.3.21
*/
public SimpleSequence(int capacity, ObjectWrapper wrapper) {
super(wrapper);
list = new ArrayList(capacity);
}
/**
* Constructs a simple sequence that will contain the elements from the specified {@link Collection}; consider
* using {@link DefaultListAdapter} instead.
*
* @param collection
* The collection containing the initial items of the sequence. A shallow copy of this collection is made
* immediately for internal use (thus, later modification on the parameter collection won't be visible in
* the resulting sequence). The items however, will be only wrapped with the {@link ObjectWrapper}
* lazily, when first needed.
* @param wrapper
* See the similar parameter of {@link SimpleSequence#SimpleSequence(ObjectWrapper)}.
*/
public SimpleSequence(Collection collection, ObjectWrapper wrapper) {
super(wrapper);
list = new ArrayList(collection);
}
/**
* Adds an arbitrary object to the end of this sequence. If the newly added object does not implement the
* {@link TemplateModel} interface, it will be wrapped into the appropriate {@link TemplateModel} interface when
* it's first read (lazily).
*
* @param obj
* The object to be added.
*/
public void add(Object obj) {
list.add(obj);
unwrappedList = null;
}
/**
* Adds a boolean value to the end of this sequence. The newly added boolean will be immediately converted into
* {@link TemplateBooleanModel#TRUE} or {@link TemplateBooleanModel#FALSE}, without using the {@link ObjectWrapper}.
*
* @param b
* The boolean value to be added.
*
* @deprecated Use {@link #add(Object)} instead, as this bypasses the {@link ObjectWrapper}.
*/
@Deprecated
public void add(boolean b) {
add(b ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE);
}
/**
* Builds a deep-copy of the underlying list, unwrapping any values that were already converted to
* {@link TemplateModel}-s. When called for the second time (or later), it just reuses the first result, unless the
* sequence was modified since then.
*
* @deprecated No replacement exists; not a reliable way of getting back the original list elemnts.
*/
@Deprecated
public List toList() throws TemplateModelException {
if (unwrappedList == null) {
Class listClass = list.getClass();
List result = null;
try {
result = (List) listClass.newInstance();
} catch (Exception e) {
throw new TemplateModelException("Error instantiating an object of type " + listClass.getName(),
e);
}
BeansWrapper bw = BeansWrapper.getDefaultInstance();
for (int i = 0; i < list.size(); i++) {
Object elem = list.get(i);
if (elem instanceof TemplateModel) {
elem = bw.unwrap((TemplateModel) elem);
}
result.add(elem);
}
unwrappedList = result;
}
return unwrappedList;
}
/**
* Returns the item at the specified index of the list. If the item isn't yet an {@link TemplateModel}, it will wrap
* it to one now, and writes it back into the backing list.
*/
@Override
public TemplateModel get(int index) throws TemplateModelException {
try {
Object value = list.get(index);
if (value instanceof TemplateModel) {
return (TemplateModel) value;
}
TemplateModel tm = wrap(value);
list.set(index, tm);
return tm;
} catch (IndexOutOfBoundsException e) {
return null;
}
}
@Override
public int size() {
return list.size();
}
/**
* @return a synchronized wrapper for list.
*/
public SimpleSequence synchronizedWrapper() {
return new SynchronizedSequence();
}
@Override
public String toString() {
return list.toString();
}
private class SynchronizedSequence extends SimpleSequence {
private SynchronizedSequence() {
super(SimpleSequence.this.getObjectWrapper());
}
@Override
public void add(Object obj) {
synchronized (SimpleSequence.this) {
SimpleSequence.this.add(obj);
}
}
@Override
public TemplateModel get(int i) throws TemplateModelException {
synchronized (SimpleSequence.this) {
return SimpleSequence.this.get(i);
}
}
@Override
public int size() {
synchronized (SimpleSequence.this) {
return SimpleSequence.this.size();
}
}
@Override
public List toList() throws TemplateModelException {
synchronized (SimpleSequence.this) {
return SimpleSequence.this.toList();
}
}
@Override
public SimpleSequence synchronizedWrapper() {
return this;
}
}
}