http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java index ca06017..2d78119 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java @@ -57,11 +57,13 @@ import org.apache.freemarker.core.model.ObjectWrappingException; import org.apache.freemarker.core.model.RichObjectWrapper; import org.apache.freemarker.core.model.TemplateBooleanModel; import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateIterableModel; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateFunctionModel; import org.apache.freemarker.core.model.TemplateHashModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelAdapter; +import org.apache.freemarker.core.model.TemplateModelIterator; import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.model.TemplateSequenceModel; @@ -511,7 +513,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper { * {@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 + * {@link TemplateIterableModel} into a Set. All other objects are * returned unchanged. * @throws TemplateException if an attempted unwrapping fails. */ @@ -649,29 +651,41 @@ public class DefaultObjectWrapper implements RichObjectWrapper { if (Map.class == targetClass) { if (model instanceof TemplateHashModel) { - return new HashAdapter((TemplateHashModel) model, this); + return new TemplateHashModelAdapter((TemplateHashModel) model, this); } } if (List.class == targetClass) { if (model instanceof TemplateSequenceModel) { - return new SequenceAdapter((TemplateSequenceModel) model, this); + return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this); } } if (Set.class == targetClass) { if (model instanceof TemplateCollectionModel) { - return new SetAdapter((TemplateCollectionModel) model, this); + return new TemplateSetModelAdapter((TemplateCollectionModel) model, this); } } - if (Collection.class == targetClass || Iterable.class == targetClass) { + if (Collection.class == targetClass) { + if (model instanceof TemplateSequenceModel) { + return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this); + } if (model instanceof TemplateCollectionModel) { - return new CollectionAdapter((TemplateCollectionModel) model, - this); + return new TemplateCollectionModelAdapter((TemplateCollectionModel) model, this); } + } + + if (Iterable.class == targetClass) { if (model instanceof TemplateSequenceModel) { - return new SequenceAdapter((TemplateSequenceModel) model, this); + return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this); + } + if (model instanceof TemplateCollectionModel) { + return new TemplateCollectionModelAdapter((TemplateCollectionModel) model, this); + } + if (model instanceof TemplateIterableModel) { + return new TemplateIterableModelAdapter((TemplateIterableModel) model, + this); } } @@ -753,23 +767,23 @@ public class DefaultObjectWrapper implements RichObjectWrapper { } if ((itf == 0 || (itf & TypeFlags.ACCEPTS_MAP) != 0) && model instanceof TemplateHashModel - && (itf != 0 || targetClass.isAssignableFrom(HashAdapter.class))) { - return new HashAdapter((TemplateHashModel) model, this); + && (itf != 0 || targetClass.isAssignableFrom(TemplateHashModelAdapter.class))) { + return new TemplateHashModelAdapter((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); + && (itf != 0 || targetClass.isAssignableFrom(TemplateSequenceModelAdapter.class))) { + return new TemplateSequenceModelAdapter((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); + && (itf != 0 || targetClass.isAssignableFrom(TemplateSetModelAdapter.class))) { + return new TemplateSetModelAdapter((TemplateCollectionModel) model, this); } if ((itf & TypeFlags.ACCEPTS_ARRAY) != 0 && model instanceof TemplateSequenceModel) { - return new SequenceAdapter((TemplateSequenceModel) model, this); + return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this); } if (itf == 0) { @@ -805,12 +819,13 @@ public class DefaultObjectWrapper implements RichObjectWrapper { recursionStops = new IdentityHashMap<>(); } Class<?> componentType = arrayClass.getComponentType(); - Object array = Array.newInstance(componentType, seq.size()); + final int size = seq.getCollectionSize(); + Object array = Array.newInstance(componentType, size); recursionStops.put(seq, array); try { - final int size = seq.size(); - for (int i = 0; i < size; i++) { - final TemplateModel seqItem = seq.get(i); + TemplateModelIterator iter = seq.iterator(); + for (int idx = 0; idx < size; idx++) { + final TemplateModel seqItem = iter.next(); Object val = tryUnwrapTo(seqItem, componentType, 0, recursionStops); if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { if (tryOnly) { @@ -819,12 +834,12 @@ public class DefaultObjectWrapper implements RichObjectWrapper { throw new TemplateException( "Failed to convert ", new _DelayedTemplateLanguageTypeDescription(seq), " object to ", new _DelayedShortClassName(array.getClass()), - ": Problematic sequence item at index ", Integer.valueOf(i) ," with value type: ", + ": Problematic sequence item at index ", Integer.valueOf(idx) ," with value type: ", new _DelayedTemplateLanguageTypeDescription(seqItem)); } } - Array.set(array, i, val); + Array.set(array, idx, val); } } finally { recursionStops.remove(seq); @@ -834,9 +849,9 @@ public class DefaultObjectWrapper implements RichObjectWrapper { Object listToArray(List<?> list, Class<?> arrayClass, Map<Object, Object> recursionStops) throws TemplateException { - if (list instanceof SequenceAdapter) { + if (list instanceof TemplateSequenceModelAdapter) { return unwrapSequenceToArray( - ((SequenceAdapter) list).getTemplateSequenceModel(), + ((TemplateSequenceModelAdapter) list).getTemplateSequenceModel(), arrayClass, false, recursionStops); }
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java deleted file mode 100644 index c87f3f7..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 org.apache.freemarker.core.model.impl; - -import java.util.Iterator; -import java.util.NoSuchElementException; - -import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelIterator; - -/** - * As opposed to {@link DefaultIteratorAdapter}, this simpler {@link Iterator} adapter is used in situations where the - * {@link TemplateModelIterator} won't be assigned to FreeMarker template variables, only used internally by - * {@code #list} or custom Java code. Because of that, it doesn't have to handle the situation where the user tries to - * iterate over the same value twice. - */ -class DefaultUnassignableIteratorAdapter implements TemplateModelIterator { - - private final Iterator<?> it; - private final ObjectWrapper wrapper; - - DefaultUnassignableIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) { - this.it = it; - this.wrapper = wrapper; - } - - @Override - public TemplateModel next() throws TemplateException { - try { - return wrapper.wrap(it.next()); - } catch (NoSuchElementException e) { - throw new TemplateException("The collection has no more items.", e); - } - } - - @Override - public boolean hasNext() throws TemplateException { - return it.hasNext(); - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java deleted file mode 100644 index d829f6b..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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 org.apache.freemarker.core.model.impl; - -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.TemplateHashModel; -import org.apache.freemarker.core.model.TemplateHashModelEx; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelAdapter; -import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.util.UndeclaredThrowableException; - -/** - * Adapts a {@link TemplateHashModel} to a {@link Map}. - */ -class HashAdapter extends AbstractMap implements TemplateModelAdapter { - private final DefaultObjectWrapper wrapper; - private final TemplateHashModel model; - private Set entrySet; - - HashAdapter(TemplateHashModel model, DefaultObjectWrapper wrapper) { - this.model = model; - this.wrapper = wrapper; - } - - @Override - public TemplateModel getTemplateModel() { - return model; - } - - @Override - public boolean isEmpty() { - try { - return model.isEmpty(); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - } - - @Override - public Object get(Object key) { - try { - return wrapper.unwrap(model.get(String.valueOf(key))); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - } - - @Override - public boolean containsKey(Object key) { - // A quick check that doesn't require TemplateHashModelEx - if (get(key) != null) { - return true; - } - return super.containsKey(key); - } - - @Override - public Set entrySet() { - if (entrySet != null) { - return entrySet; - } - return entrySet = new AbstractSet() { - @Override - public Iterator iterator() { - final TemplateModelIterator i; - try { - i = getModelEx().keys().iterator(); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - return new Iterator() { - @Override - public boolean hasNext() { - try { - return i.hasNext(); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - } - - @Override - public Object next() { - final Object key; - try { - key = wrapper.unwrap(i.next()); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - return new Map.Entry() { - @Override - public Object getKey() { - return key; - } - - @Override - public Object getValue() { - return get(key); - } - - @Override - public Object setValue(Object value) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry) o; - Object k1 = getKey(); - Object k2 = e.getKey(); - if (k1 == k2 || (k1 != null && k1.equals(k2))) { - Object v1 = getValue(); - Object v2 = e.getValue(); - if (v1 == v2 || (v1 != null && v1.equals(v2))) - return true; - } - return false; - } - - @Override - public int hashCode() { - Object value = getValue(); - return (key == null ? 0 : key.hashCode()) ^ - (value == null ? 0 : value.hashCode()); - } - }; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public int size() { - try { - return getModelEx().size(); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - } - }; - } - - private TemplateHashModelEx getModelEx() { - if (model instanceof TemplateHashModelEx) { - return ((TemplateHashModelEx) model); - } - throw new UnsupportedOperationException( - "Operation supported only on TemplateHashModelEx. " + - model.getClass().getName() + " does not implement it though."); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java new file mode 100644 index 0000000..eb51098 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java @@ -0,0 +1,82 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.ArrayList; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateIterableModel; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateSequenceModel; + +/** + * Add sequence capabilities to an existing iterable. Used by ?keys and ?values built-ins. + */ +// TODO [FM3] Maybe ?keys/?values should just return a TemplateCollectionModel +final public class IterableAndSequence implements TemplateSequenceModel { + private TemplateIterableModel iterable; + private ArrayList<TemplateModel> data; + + public IterableAndSequence(TemplateIterableModel iterable) { + this.iterable = iterable; + } + + @Override + public TemplateModelIterator iterator() throws TemplateException { + return iterable.iterator(); + } + + @Override + public TemplateModel get(int i) throws TemplateException { + initSequence(); + return i < data.size() && i >= 0 ? data.get(i) : null; + } + + @Override + public int getCollectionSize() throws TemplateException { + if (iterable instanceof TemplateCollectionModel) { + return ((TemplateCollectionModel) iterable).getCollectionSize(); + } else { + initSequence(); + return data.size(); + } + } + + @Override + public boolean isEmptyCollection() throws TemplateException { + if (iterable instanceof TemplateCollectionModel) { + return ((TemplateCollectionModel) iterable).isEmptyCollection(); + } else { + return iterable.iterator().hasNext(); + } + } + + private void initSequence() throws TemplateException { + if (data == null) { + data = new ArrayList<>(); + TemplateModelIterator it = iterable.iterator(); + while (it.hasNext()) { + data.add(it.next()); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java new file mode 100644 index 0000000..df31393 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java @@ -0,0 +1,52 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.Iterator; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; + +/** + * Unlike {@link DefaultIteratorAdapter}, this doesn't adapt to some {@link TemplateModel}, but to {@link + * TemplateModelIterator}. + */ +class IteratorToTemplateModelIteratorAdapter implements TemplateModelIterator { + + private final Iterator<?> it; + private final ObjectWrapper wrapper; + + IteratorToTemplateModelIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) { + this.it = it; + this.wrapper = wrapper; + } + + @Override + public TemplateModel next() throws TemplateException { + return wrapper.wrap(it.next()); + } + + @Override + public boolean hasNext() throws TemplateException { + return it.hasNext(); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java index 037ff15..9f12c05 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java @@ -79,13 +79,13 @@ public class ResourceBundleModel extends BeanModel implements TemplateFunctionMo * Returns true if this bundle contains no objects. */ @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return !((ResourceBundle) object).getKeys().hasMoreElements() && - super.isEmpty(); + super.isEmptyHash(); } @Override - public int size() { + public int getHashSize() { return keySet().size(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java deleted file mode 100644 index b7a9136..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 org.apache.freemarker.core.model.impl; - -import java.util.AbstractList; - -import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelAdapter; -import org.apache.freemarker.core.model.TemplateSequenceModel; -import org.apache.freemarker.core.util.UndeclaredThrowableException; - -/** - */ -class SequenceAdapter extends AbstractList implements TemplateModelAdapter { - private final DefaultObjectWrapper wrapper; - private final TemplateSequenceModel model; - - SequenceAdapter(TemplateSequenceModel model, DefaultObjectWrapper wrapper) { - this.model = model; - this.wrapper = wrapper; - } - - @Override - public TemplateModel getTemplateModel() { - return model; - } - - @Override - public int size() { - try { - return model.size(); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - } - - @Override - public Object get(int index) { - try { - return wrapper.unwrap(model.get(index)); - } catch (TemplateException e) { - throw new UndeclaredThrowableException(e); - } - } - - public TemplateSequenceModel getTemplateSequenceModel() { - return model; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java new file mode 100644 index 0000000..ede0bac --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java @@ -0,0 +1,63 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.List; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateIterableModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateSequenceModel; + +/** + * {@link TemplateIterableModel} implementation that can iterate through a {@link TemplateSequenceModel} be going + * through the valid indexes and calling {@link TemplateSequenceModel#get(int)} for each. This should only be used if + * you can be sure that {@link TemplateSequenceModel#get(int)} and {@link TemplateSequenceModel#getCollectionSize()} is efficient. + * ({@link TemplateSequenceModel} doesn't require that to be efficient, as listing the contents should be done with with + * {@link TemplateIterableModel#iterator()}). + * <p> + * This class assumes that the sequence doesn't change during iteration (as it's technically impossible to detect if a + * generic {@link TemplateSequenceModel} was changed, similarly as it's impossible for a generic {@link List}). The + * index range is decided when the instance is created, and it simply iterates through the predefined index range. Thus + * for example, if the sequence length decreases after that, it will return {@code null}-s (means missing element) at + * the end, for the indexes that are now out of bounds. + */ +public class SequenceTemplateModelIterator implements TemplateModelIterator { + + private final TemplateSequenceModel sequence; + private final int size; + private int nextIndex = 0; + + public SequenceTemplateModelIterator(TemplateSequenceModel sequence) throws TemplateException { + this.sequence = sequence; + this.size = sequence.getCollectionSize(); + } + + @Override + public TemplateModel next() throws TemplateException { + return sequence.get(nextIndex++); + } + + @Override + public boolean hasNext() throws TemplateException { + return nextIndex < size; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java deleted file mode 100644 index 0975ac4..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 org.apache.freemarker.core.model.impl; - -import java.util.Set; - -import org.apache.freemarker.core.model.TemplateCollectionModel; - -/** - */ -class SetAdapter extends CollectionAdapter implements Set { - SetAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) { - super(model, wrapper); - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java deleted file mode 100644 index 012beb6..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 org.apache.freemarker.core.model.impl; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Iterator; - -import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.TemplateCollectionModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.model.WrappingTemplateModel; - -/** - * A simple implementation of {@link TemplateCollectionModel}. - * It's able to wrap <tt>java.util.Iterator</tt>-s and <tt>java.util.Collection</tt>-s. - * If you wrap an <tt>Iterator</tt>, the variable can be <#list>-ed only once! - * - * <p>Consider using {@link SimpleSequence} instead of this class if you want to wrap <tt>Iterator</tt>s. - * <tt>SimpleSequence</tt> will read all elements of the <tt>Iterator</tt>, and store them in a <tt>List</tt> - * (this may cause too high resource consumption in some applications), so you can list the variable - * for unlimited times. Also, if you want to wrap <tt>Collection</tt>s, and then list the resulting - * variable for many times, <tt>SimpleSequence</tt> may gives better performance, as the - * wrapping of non-<tt>TemplateModel</tt> objects happens only once. - * - * <p>This class is thread-safe. The returned {@link TemplateModelIterator}-s - * are <em>not</em> thread-safe. - */ -public class SimpleCollection extends WrappingTemplateModel -implements TemplateCollectionModel, Serializable { - - private boolean iteratorOwned; - private final Iterator iterator; - private final Collection collection; - - public SimpleCollection(Iterator iterator, ObjectWrapper wrapper) { - super(wrapper); - this.iterator = iterator; - collection = null; - } - - public SimpleCollection(Collection collection, ObjectWrapper wrapper) { - super(wrapper); - this.collection = collection; - iterator = null; - } - - /** - * Retrieves a template model iterator that is used to iterate over the elements in this collection. - * - * <p>When you wrap an <tt>Iterator</tt> and you get <tt>TemplateModelIterator</tt> for multiple times, - * only one of the returned <tt>TemplateModelIterator</tt> instances can be really used. When you have called a - * method of a <tt>TemplateModelIterator</tt> instance, all other instance will throw a - * {@link TemplateException} when you try to call their methods, since the wrapped <tt>Iterator</tt> - * can't return the first element anymore. - */ - @Override - public TemplateModelIterator iterator() { - return iterator != null - ? new SimpleTemplateModelIterator(iterator, false) - : new SimpleTemplateModelIterator(collection.iterator(), true); - } - - /** - * Wraps an {@link Iterator}; not thread-safe. The encapsulated {@link Iterator} may be accessible from multiple - * threads (as multiple {@link SimpleTemplateModelIterator} instance can wrap the same {@link Iterator} instance), - * but if the {@link Iterator} was marked in the constructor as shared, the first thread which uses the - * {@link Iterator} will monopolize that. - */ - private class SimpleTemplateModelIterator implements TemplateModelIterator { - - private final Iterator iterator; - private boolean iteratorOwnedByMe; - - SimpleTemplateModelIterator(Iterator iterator, boolean iteratorOwnedByMe) { - this.iterator = iterator; - this.iteratorOwnedByMe = iteratorOwnedByMe; - } - - @Override - public TemplateModel next() throws TemplateException { - if (!iteratorOwnedByMe) { - synchronized (SimpleCollection.this) { - checkIteratorNotOwned(); - iteratorOwned = true; - iteratorOwnedByMe = true; - } - } - - if (!iterator.hasNext()) { - throw new TemplateException("The collection has no more items."); - } - - Object value = iterator.next(); - return value instanceof TemplateModel ? (TemplateModel) value : wrap(value); - } - - @Override - public boolean hasNext() throws TemplateException { - // Calling hasNext may looks safe, but I have met sync. problems. - if (!iteratorOwnedByMe) { - synchronized (SimpleCollection.this) { - checkIteratorNotOwned(); - } - } - - return iterator.hasNext(); - } - - private void checkIteratorNotOwned() throws TemplateException { - if (iteratorOwned) { - throw new TemplateException( - "This collection value wraps a java.util.Iterator, thus it can be listed only once."); - } - } - - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java index df2f6ae..e2c867e 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java @@ -30,7 +30,7 @@ import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core._DelayedJQuote; import org.apache.freemarker.core.model.ObjectWrapper; import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateIterableModel; import org.apache.freemarker.core.model.TemplateHashModelEx; import org.apache.freemarker.core.model.TemplateHashModelEx2; import org.apache.freemarker.core.model.TemplateModel; @@ -267,23 +267,23 @@ public class SimpleHash extends WrappingTemplateModel implements TemplateHashMod } @Override - public int size() { + public int getHashSize() { return map.size(); } @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return map == null || map.isEmpty(); } @Override - public TemplateCollectionModel keys() { - return new SimpleCollection(map.keySet(), getObjectWrapper()); + public TemplateIterableModel keys() { + return new SimpleIterable(map.keySet(), getObjectWrapper()); } @Override - public TemplateCollectionModel values() { - return new SimpleCollection(map.values(), getObjectWrapper()); + public TemplateIterableModel values() { + return new SimpleIterable(map.values(), getObjectWrapper()); } @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java new file mode 100644 index 0000000..1856535 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java @@ -0,0 +1,133 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.io.Serializable; +import java.util.Iterator; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateIterableModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.WrappingTemplateModel; + +/** + * A simple implementation of {@link TemplateIterableModel}. + * It's able to wrap <tt>java.util.Iterator</tt>-s and <tt>java.util.Collection</tt>-s. + * If you wrap an <tt>Iterator</tt>, the variable can be <#list>-ed only once! + * + * <p>Consider using {@link SimpleSequence} instead of this class if you want to wrap <tt>Iterator</tt>s. + * <tt>SimpleSequence</tt> will read all elements of the <tt>Iterator</tt>, and store them in a <tt>List</tt> + * (this may cause too high resource consumption in some applications), so you can list the variable + * for unlimited times. Also, if you want to wrap <tt>Collection</tt>s, and then list the resulting + * variable for many times, <tt>SimpleSequence</tt> may gives better performance, as the + * wrapping of non-<tt>TemplateModel</tt> objects happens only once. + * + * <p>This class is thread-safe. The returned {@link TemplateModelIterator}-s + * are <em>not</em> thread-safe. + */ +public class SimpleIterable extends WrappingTemplateModel +implements TemplateIterableModel, Serializable { + + private boolean iteratorOwned; + private final Iterator iterator; + private final Iterable iterable; + + public SimpleIterable(Iterator iterator, ObjectWrapper wrapper) { + super(wrapper); + this.iterator = iterator; + iterable = null; + } + + public SimpleIterable(Iterable iterable, ObjectWrapper wrapper) { + super(wrapper); + this.iterable = iterable; + iterator = null; + } + + /** + * Retrieves a template model iterator that is used to iterate over the elements in this iterable. + * + * <p>When you wrap an <tt>Iterator</tt> and you get <tt>TemplateModelIterator</tt> for multiple times, + * only one of the returned <tt>TemplateModelIterator</tt> instances can be really used. When you have called a + * method of a <tt>TemplateModelIterator</tt> instance, all other instance will throw a + * {@link TemplateException} when you try to call their methods, since the wrapped <tt>Iterator</tt> + * can't return the first element anymore. + */ + @Override + public TemplateModelIterator iterator() { + return iterator != null + ? new SimpleTemplateModelIterator(iterator, false) + : new SimpleTemplateModelIterator(iterable.iterator(), true); + } + + /** + * Wraps an {@link Iterator}; not thread-safe. The encapsulated {@link Iterator} may be accessible from multiple + * threads (as multiple {@link SimpleTemplateModelIterator} instance can wrap the same {@link Iterator} instance), + * but if the {@link Iterator} was marked in the constructor as shared, the first thread which uses the + * {@link Iterator} will monopolize that. + */ + private class SimpleTemplateModelIterator implements TemplateModelIterator { + + private final Iterator iterator; + private boolean iteratorOwnedByMe; + + SimpleTemplateModelIterator(Iterator iterator, boolean iteratorOwnedByMe) { + this.iterator = iterator; + this.iteratorOwnedByMe = iteratorOwnedByMe; + } + + @Override + public TemplateModel next() throws TemplateException { + if (!iteratorOwnedByMe) { + synchronized (SimpleIterable.this) { + checkIteratorNotOwned(); + iteratorOwned = true; + iteratorOwnedByMe = true; + } + } + + Object value = iterator.next(); + return value instanceof TemplateModel ? (TemplateModel) value : wrap(value); + } + + @Override + public boolean hasNext() throws TemplateException { + // Calling hasNext may looks safe, but I have met sync. problems. + if (!iteratorOwnedByMe) { + synchronized (SimpleIterable.this) { + checkIteratorNotOwned(); + } + } + + return iterator.hasNext(); + } + + private void checkIteratorNotOwned() throws TemplateException { + if (iteratorOwned) { + throw new TemplateException( + "This value wraps a java.util.Iterator, thus it can be listed only once."); + } + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java index e0e0397..851147a 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core.model.ObjectWrapper; import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.model.WrappingTemplateModel; @@ -133,25 +134,35 @@ public class SimpleSequence extends WrappingTemplateModel implements TemplateSeq */ @Override public TemplateModel get(int index) throws TemplateException { - 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) { + if (index >= list.size() || index < 0) { return null; } + + Object value = list.get(index); + if (value instanceof TemplateModel) { + return (TemplateModel) value; + } + TemplateModel tm = wrap(value); + list.set(index, tm); + return tm; } @Override - public int size() { + public int getCollectionSize() { return list.size(); } @Override + public boolean isEmptyCollection() throws TemplateException { + return list.isEmpty(); + } + + @Override + public TemplateModelIterator iterator() throws TemplateException { + return new SequenceTemplateModelIterator(this); + } + + @Override public String toString() { return list.toString(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java new file mode 100644 index 0000000..7b8461b --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java @@ -0,0 +1,48 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; + +/** + * {@link TemplateModelIterator} for the special case when you have exactly one element. + */ +public class SingleItemTemplateModelIterator implements TemplateModelIterator { + + private final TemplateModel element; + private boolean hasNext; + + public SingleItemTemplateModelIterator(TemplateModel element) { + this.element = element; + } + + @Override + public TemplateModel next() throws TemplateException { + hasNext = false; + return element; + } + + @Override + public boolean hasNext() throws TemplateException { + return hasNext; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java index 12ecbc3..1ea6027 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java @@ -27,7 +27,7 @@ import java.util.Iterator; import java.util.Map; import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateIterableModel; import org.apache.freemarker.core.model.TemplateFunctionModel; import org.apache.freemarker.core.model.TemplateHashModelEx; import org.apache.freemarker.core.model.TemplateModel; @@ -84,23 +84,23 @@ final class StaticModel implements TemplateHashModelEx { * field or method in the underlying class. */ @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return map.isEmpty(); } @Override - public int size() { + public int getHashSize() { return map.size(); } @Override - public TemplateCollectionModel keys() throws TemplateException { - return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.keySet()); + public TemplateIterableModel keys() throws TemplateException { + return (TemplateIterableModel) wrapper.getOuterIdentity().wrap(map.keySet()); } @Override - public TemplateCollectionModel values() throws TemplateException { - return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.values()); + public TemplateIterableModel values() throws TemplateException { + return (TemplateIterableModel) wrapper.getOuterIdentity().wrap(map.values()); } private void populate() throws TemplateException { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java new file mode 100644 index 0000000..91a38ed --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java @@ -0,0 +1,77 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateIterableModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelAdapter; +import org.apache.freemarker.core.util.UndeclaredThrowableException; + +/** + * Adapts a {@link TemplateIterableModel} to {@link Collection}. + */ +class TemplateCollectionModelAdapter<T> extends AbstractCollection<T> implements TemplateModelAdapter { + private final DefaultObjectWrapper wrapper; + private final TemplateCollectionModel model; + + TemplateCollectionModelAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) { + this.model = model; + this.wrapper = wrapper; + } + + @Override + public TemplateModel getTemplateModel() { + return model; + } + + @Override + public int size() { + try { + return model.getCollectionSize(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public boolean isEmpty() { + try { + return model.isEmptyCollection(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public Iterator<T> iterator() { + try { + return new TemplateModelIteratorAdapter<T>(model.iterator(), wrapper); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java new file mode 100644 index 0000000..b9ca888 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java @@ -0,0 +1,185 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateHashModel; +import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelAdapter; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.util.UndeclaredThrowableException; + +/** + * Adapts a {@link TemplateHashModel} to a {@link Map}. + */ +class TemplateHashModelAdapter extends AbstractMap implements TemplateModelAdapter { + private final DefaultObjectWrapper wrapper; + private final TemplateHashModel model; + private Set entrySet; + + TemplateHashModelAdapter(TemplateHashModel model, DefaultObjectWrapper wrapper) { + this.model = model; + this.wrapper = wrapper; + } + + @Override + public TemplateModel getTemplateModel() { + return model; + } + + @Override + public boolean isEmpty() { + try { + return model.isEmptyHash(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public Object get(Object key) { + try { + return wrapper.unwrap(model.get(String.valueOf(key))); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public boolean containsKey(Object key) { + // A quick check that doesn't require TemplateHashModelEx + if (get(key) != null) { + return true; + } + return super.containsKey(key); + } + + @Override + public Set entrySet() { + if (entrySet != null) { + return entrySet; + } + return entrySet = new AbstractSet() { + @Override + public Iterator iterator() { + final TemplateModelIterator iterator; + try { + iterator = getModelEx().keys().iterator(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + return new Iterator() { + @Override + public boolean hasNext() { + try { + return iterator.hasNext(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public Object next() { + final Object key; + try { + if (!iterator.hasNext()) { + throw new NoSuchElementException(); + } + key = wrapper.unwrap(iterator.next()); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + return new Map.Entry() { + @Override + public Object getKey() { + return key; + } + + @Override + public Object getValue() { + return get(key); + } + + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + Object k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + Object v1 = getValue(); + Object v2 = e.getValue(); + if (v1 == v2 || (v1 != null && v1.equals(v2))) + return true; + } + return false; + } + + @Override + public int hashCode() { + Object value = getValue(); + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + }; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int size() { + try { + return getModelEx().getHashSize(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + }; + } + + private TemplateHashModelEx getModelEx() { + if (model instanceof TemplateHashModelEx) { + return ((TemplateHashModelEx) model); + } + throw new UnsupportedOperationException( + "Operation supported only on TemplateHashModelEx. " + + model.getClass().getName() + " does not implement it though."); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java new file mode 100644 index 0000000..58e1835 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java @@ -0,0 +1,57 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.Collection; +import java.util.Iterator; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateIterableModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelAdapter; +import org.apache.freemarker.core.util.UndeclaredThrowableException; + +/** + * Adapts a {@link TemplateIterableModel} to {@link Collection}. + */ +class TemplateIterableModelAdapter<T> implements TemplateModelAdapter, Iterable<T> { + private final DefaultObjectWrapper wrapper; + private final TemplateIterableModel model; + + TemplateIterableModelAdapter(TemplateIterableModel model, DefaultObjectWrapper wrapper) { + this.model = model; + this.wrapper = wrapper; + } + + @Override + public TemplateModel getTemplateModel() { + return model; + } + + @Override + public Iterator<T> iterator() { + try { + return new TemplateModelIteratorAdapter<T>(model.iterator(), wrapper); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java new file mode 100644 index 0000000..263d6e0 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java @@ -0,0 +1,65 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.util.UndeclaredThrowableException; + +class TemplateModelIteratorAdapter<T> implements Iterator<T> { + private final TemplateModelIterator iterator; + private final DefaultObjectWrapper wrapper; + + public TemplateModelIteratorAdapter(TemplateModelIterator iterator, + DefaultObjectWrapper wrapper) { + this.iterator = iterator; + this.wrapper = wrapper; + } + + @Override + public boolean hasNext() { + try { + return iterator.hasNext(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public T next() { + try { + if (!iterator.hasNext()) { + throw new NoSuchElementException(); + } + return (T) wrapper.unwrap(iterator.next()); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java index a93df33..db4dde1 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java @@ -19,16 +19,17 @@ package org.apache.freemarker.core.model.impl; +import java.util.Iterator; import java.util.List; -import org.apache.freemarker.core.model.TemplateFunctionModel; +import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; import org.apache.freemarker.core.model.TemplateSequenceModel; /** * A sequence that wraps a {@link List} of {@link TemplateModel}-s. It does not copy the original - * list. It's mostly useful when implementing {@link TemplateFunctionModel}-es that collect items from other - * {@link TemplateModel}-s. + * list. The thread safety (and other behavior in face on concurrency) remains the same as of the wrapped list. */ public class TemplateModelListSequence implements TemplateSequenceModel { @@ -43,14 +44,36 @@ public class TemplateModelListSequence implements TemplateSequenceModel { @Override public TemplateModel get(int index) { - return list.get(index); + return index < list.size() && index >= 0 ? list.get(index) : null; } @Override - public int size() { + public int getCollectionSize() { return list.size(); } + @Override + public boolean isEmptyCollection() throws TemplateException { + return list.isEmpty(); + } + + @Override + public TemplateModelIterator iterator() throws TemplateException { + return new TemplateModelIterator() { + private final Iterator<? extends TemplateModel> iterator = list.iterator(); + + @Override + public TemplateModel next() throws TemplateException { + return iterator.next(); + } + + @Override + public boolean hasNext() throws TemplateException { + return iterator.hasNext(); + } + }; + } + /** * Returns the original {@link List} of {@link TemplateModel}-s, so it's not a fully unwrapped value. */ http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java new file mode 100644 index 0000000..49da53a --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java @@ -0,0 +1,88 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.AbstractList; +import java.util.Iterator; + +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelAdapter; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.util.UndeclaredThrowableException; + +/** + */ +class TemplateSequenceModelAdapter<T> extends AbstractList<T> implements TemplateModelAdapter { + private final DefaultObjectWrapper wrapper; + private final TemplateSequenceModel model; + + TemplateSequenceModelAdapter(TemplateSequenceModel model, DefaultObjectWrapper wrapper) { + this.model = model; + this.wrapper = wrapper; + } + + @Override + public TemplateModel getTemplateModel() { + return model; + } + + @Override + public int size() { + try { + return model.getCollectionSize(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public boolean isEmpty() { + try { + return model.isEmptyCollection(); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public T get(int index) { + try { + return (T) wrapper.unwrap(model.get(index)); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public Iterator<T> iterator() { + try { + return new TemplateModelIteratorAdapter<T>(model.iterator(), wrapper); + } catch (TemplateException e) { + throw new UndeclaredThrowableException(e); + } + } + + public TemplateSequenceModel getTemplateSequenceModel() { + return model; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java new file mode 100644 index 0000000..aa98bca --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java @@ -0,0 +1,32 @@ +/* + * 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 org.apache.freemarker.core.model.impl; + +import java.util.Set; + +import org.apache.freemarker.core.model.TemplateCollectionModel; + +// TODO [FM3] There's no TemplateSetModel, but we need to be able to do something like obj.needsASet([1, 2, 3]). But, +// TemplateCollectionModel doesn't guarantee that the items are unique. +class TemplateSetModelAdapter<T> extends TemplateCollectionModelAdapter<T> implements Set<T> { + TemplateSetModelAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) { + super(model, wrapper); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java index b3442d9..f305d3c 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java @@ -289,7 +289,7 @@ public final class CallableUtils { _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( getMessageArgumentProblem( callable, argIdx, - new Object[]{ " should be ", new _DelayedAOrAn(expectedTypesDesc), ", but was ", + new Object[]{ "should be ", new _DelayedAOrAn(expectedTypesDesc), ", but was ", new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(argValue)), "." }, calledAsFunction)); @@ -543,25 +543,33 @@ public final class CallableUtils { } /** - * Convenience method to call + * Convenience method to return {@code null} if the argument is missing, otherwise call * {@link #castArgumentValueToInt(TemplateModel, int, boolean, int, TemplateCallableModel, boolean) - * castArgumentValueToInt(args[argIndex], argIndex, true, null, callable, true)}. + * castArgumentValueToInt(args[argIndex], argIndex, false, 0, callable, false)}. */ - public static int getOptionalIntArgument( + public static Integer getOptionalIntArgument( TemplateModel[] args, int argIndex, TemplateFunctionModel callable) throws TemplateException { - return castArgumentValueToInt(args[argIndex], argIndex, true, 0, callable, true); + TemplateModel argValue = args[argIndex]; + if (argValue == null) { + return null; + } + return castArgumentValueToInt(argValue, argIndex, false, 0, callable, true); } /** - * Convenience method to call + * Convenience method to return {@code null} if the argument is missing, otherwise call * {@link #castArgumentValueToInt(TemplateModel, int, boolean, int, TemplateCallableModel, boolean) - * castArgumentValueToInt(args[argIndex], argIndex, true, null, callable, false)}. + * castArgumentValueToInt(args[argIndex], argIndex, false, 0, callable, false)}. */ - public static int getOptionalIntArgument( + public static Integer getOptionalIntArgument( TemplateModel[] args, int argIndex, TemplateDirectiveModel callable) throws TemplateException { - return castArgumentValueToInt(args[argIndex], argIndex, true, 0, callable, false); + TemplateModel argValue = args[argIndex]; + if (argValue == null) { + return null; + } + return castArgumentValueToInt(argValue, argIndex, false, 0, callable, false); } /** http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java index 62bc25d..eb674cb 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java @@ -27,14 +27,14 @@ import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core.model.AdapterTemplateModel; import org.apache.freemarker.core.model.ObjectWrapper; import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateCollectionModel; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateIterableModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelIterator; import org.apache.freemarker.core.model.TemplateNumberModel; -import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.model.WrapperTemplateModel; /** @@ -61,7 +61,7 @@ public class DeepUnwrap { * <li>If the object implements {@link TemplateBooleanModel}, then the result * of {@link TemplateBooleanModel#getAsBoolean()} is returned. * <li>If the object implements {@link TemplateSequenceModel} or - * {@link TemplateCollectionModel}, then a <code>java.util.ArrayList</code> is + * {@link TemplateIterableModel}, then a <code>java.util.ArrayList</code> is * constructed from the subvariables, and each subvariable is unwrapped with * the rules described here (recursive unwrapping). * <li>If the object implements {@link TemplateHashModelEx}, then a @@ -119,14 +119,16 @@ public class DeepUnwrap { } if (model instanceof TemplateSequenceModel) { TemplateSequenceModel seq = (TemplateSequenceModel) model; - ArrayList list = new ArrayList(seq.size()); - for (int i = 0; i < seq.size(); ++i) { - list.add(unwrap(seq.get(i), nullModel, permissive)); + int size = seq.getCollectionSize(); + ArrayList list = new ArrayList(size); + TemplateModelIterator iter = seq.iterator(); + for (int i = 0; i < size; ++i) { + list.add(unwrap(iter.next(), nullModel, permissive)); } return list; } - if (model instanceof TemplateCollectionModel) { - TemplateCollectionModel coll = (TemplateCollectionModel) model; + if (model instanceof TemplateIterableModel) { + TemplateIterableModel coll = (TemplateIterableModel) model; ArrayList list = new ArrayList(); TemplateModelIterator it = coll.iterator(); while (it.hasNext()) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java index feb9586..830a41f 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java @@ -247,9 +247,11 @@ public final class StringToIndexMap { } /** - * Checks if all entries are in the {@code start} - {@code start}+{@code size()} (exclusive) index range. + * Checks if all entries are in the {@code start} - {@code start}+{@code getCollectionSize()} (exclusive) index + * range. * - * @throws IllegalArgumentException If some entry is not in the specified index range. + * @throws IllegalArgumentException + * If some entry is not in the specified index range. */ public void checkIndexRange(int start) { if (buckets == null) {
