http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultIteratorAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultIteratorAdapter.java new file mode 100644 index 0000000..60d9243 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultIteratorAdapter.java @@ -0,0 +1,138 @@ +/* + * 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.model.AdapterTemplateModel; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.ObjectWrapperWithAPISupport; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateModelWithAPISupport; +import org.apache.freemarker.core.model.WrapperTemplateModel; +import org.apache.freemarker.core.model.WrappingTemplateModel; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Adapts an {@link Iterator} to the corresponding {@link TemplateModel} interface(s), most importantly to + * {@link TemplateCollectionModel}. The resulting {@link TemplateCollectionModel} can only be listed (iterated) once. + * If the user tries list the variable for a second time, an exception will be thrown instead of silently gettig an + * empty (or partial) listing. + * + * <p> + * Thread safety: A {@link DefaultListAdapter} is as thread-safe as the array that it wraps is. Normally you only + * have to consider read-only access, as the FreeMarker template language doesn't allow writing these sequences (though + * of course, Java methods called from the template can violate this rule). + * + * <p> + * This adapter is used by {@link DefaultObjectWrapper} if its {@code useAdaptersForCollections} property is + * {@code true}, which is the default when its {@code incompatibleImprovements} property is 2.3.22 or higher. + * + * @since 2.3.22 + */ +public class DefaultIteratorAdapter extends WrappingTemplateModel implements TemplateCollectionModel, + AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport, Serializable { + + @SuppressFBWarnings(value="SE_BAD_FIELD", justification="We hope it's Seralizable") + private final Iterator iterator; + private boolean iteratorOwnedBySomeone; + + /** + * Factory method for creating new adapter instances. + * + * @param iterator + * The iterator to adapt; can't be {@code null}. + */ + public static DefaultIteratorAdapter adapt(Iterator iterator, ObjectWrapper wrapper) { + return new DefaultIteratorAdapter(iterator, wrapper); + } + + private DefaultIteratorAdapter(Iterator iterator, ObjectWrapper wrapper) { + super(wrapper); + this.iterator = iterator; + } + + @Override + public Object getWrappedObject() { + return iterator; + } + + @Override + public Object getAdaptedObject(Class hint) { + return getWrappedObject(); + } + + @Override + public TemplateModelIterator iterator() throws TemplateModelException { + return new SimpleTemplateModelIterator(); + } + + @Override + public TemplateModel getAPI() throws TemplateModelException { + return ((ObjectWrapperWithAPISupport) getObjectWrapper()).wrapAsAPI(iterator); + } + + /** + * Not thread-safe. + */ + private class SimpleTemplateModelIterator implements TemplateModelIterator { + + private boolean iteratorOwnedByMe; + + @Override + public TemplateModel next() throws TemplateModelException { + if (!iteratorOwnedByMe) { + checkNotOwner(); + iteratorOwnedBySomeone = true; + iteratorOwnedByMe = true; + } + + if (!iterator.hasNext()) { + throw new TemplateModelException("The collection has no more items."); + } + + Object value = iterator.next(); + return value instanceof TemplateModel ? (TemplateModel) value : wrap(value); + } + + @Override + public boolean hasNext() throws TemplateModelException { + // Calling hasNext may looks safe, but I have met sync. problems. + if (!iteratorOwnedByMe) { + checkNotOwner(); + } + + return iterator.hasNext(); + } + + private void checkNotOwner() throws TemplateModelException { + if (iteratorOwnedBySomeone) { + throw new TemplateModelException( + "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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultListAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultListAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultListAdapter.java new file mode 100644 index 0000000..e58cc5e --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultListAdapter.java @@ -0,0 +1,123 @@ +/* + * 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.AbstractSequentialList; +import java.util.List; + +import org.apache.freemarker.core.model.AdapterTemplateModel; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.ObjectWrapperWithAPISupport; +import org.apache.freemarker.core.model.RichObjectWrapper; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateModelWithAPISupport; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.WrapperTemplateModel; +import org.apache.freemarker.core.model.WrappingTemplateModel; + +/** + * Adapts a {@link List} to the corresponding {@link TemplateModel} interface(s), most importantly to + * {@link TemplateSequenceModel}. If you aren't wrapping an already existing {@link List}, but build a sequence + * specifically to be used from a template, also consider using {@link SimpleSequence} (see comparison there). + * + * <p> + * Thread safety: A {@link DefaultListAdapter} is as thread-safe as the {@link List} that it wraps is. Normally you only + * have to consider read-only access, as the FreeMarker template language doesn't allow writing these sequences (though + * of course, Java methods called from the template can violate this rule). + * + * <p> + * This adapter is used by {@link DefaultObjectWrapper} if its {@code useAdaptersForCollections} property is + * {@code true}, which is the default when its {@code incompatibleImprovements} property is 2.3.22 or higher. + * + * @see SimpleSequence + * @see DefaultArrayAdapter + * @see TemplateSequenceModel + * + * @since 2.3.22 + */ +public class DefaultListAdapter extends WrappingTemplateModel implements TemplateSequenceModel, + AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport, Serializable { + + protected final List list; + + /** + * Factory method for creating new adapter instances. + * + * @param list + * The list to adapt; can't be {@code null}. + * @param wrapper + * The {@link ObjectWrapper} used to wrap the items in the array. + */ + public static DefaultListAdapter adapt(List list, RichObjectWrapper wrapper) { + // [2.4] DefaultListAdapter should implement TemplateCollectionModelEx, so this choice becomes unnecessary + return list instanceof AbstractSequentialList + ? new DefaultListAdapterWithCollectionSupport(list, wrapper) + : new DefaultListAdapter(list, wrapper); + } + + private DefaultListAdapter(List list, RichObjectWrapper wrapper) { + super(wrapper); + this.list = list; + } + + @Override + public TemplateModel get(int index) throws TemplateModelException { + return index >= 0 && index < list.size() ? wrap(list.get(index)) : null; + } + + @Override + public int size() throws TemplateModelException { + return list.size(); + } + + @Override + public Object getAdaptedObject(Class hint) { + return getWrappedObject(); + } + + @Override + public Object getWrappedObject() { + return list; + } + + private static class DefaultListAdapterWithCollectionSupport extends DefaultListAdapter implements + TemplateCollectionModel { + + private DefaultListAdapterWithCollectionSupport(List list, RichObjectWrapper wrapper) { + super(list, wrapper); + } + + @Override + public TemplateModelIterator iterator() throws TemplateModelException { + return new DefaultUnassignableIteratorAdapter(list.iterator(), getObjectWrapper()); + } + + } + + @Override + public TemplateModel getAPI() throws TemplateModelException { + return ((ObjectWrapperWithAPISupport) getObjectWrapper()).wrapAsAPI(list); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java new file mode 100644 index 0000000..e3b3115 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java @@ -0,0 +1,171 @@ +/* + * 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.Map; +import java.util.SortedMap; + +import org.apache.freemarker.core._DelayedJQuote; +import org.apache.freemarker.core._TemplateModelException; +import org.apache.freemarker.core.model.AdapterTemplateModel; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.ObjectWrapperWithAPISupport; +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateHashModelEx; +import org.apache.freemarker.core.model.TemplateHashModelEx2; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelWithAPISupport; +import org.apache.freemarker.core.model.WrapperTemplateModel; +import org.apache.freemarker.core.model.WrappingTemplateModel; + +/** + * Adapts a {@link Map} to the corresponding {@link TemplateModel} interface(s), most importantly to + * {@link TemplateHashModelEx}. If you aren't wrapping an already existing {@link Map}, but build a hash specifically to + * be used from a template, also consider using {@link SimpleHash} (see comparison there). + * + * <p> + * Thread safety: A {@link DefaultMapAdapter} is as thread-safe as the {@link Map} that it wraps is. Normally you only + * have to consider read-only access, as the FreeMarker template language doesn't allow writing these hashes (though of + * course, Java methods called from the template can violate this rule). + * + * <p> + * This adapter is used by {@link DefaultObjectWrapper} if its {@code useAdaptersForCollections} property is + * {@code true}, which is the default when its {@code incompatibleImprovements} property is 2.3.22 or higher. + * + * @since 2.3.22 + */ +public class DefaultMapAdapter extends WrappingTemplateModel + implements TemplateHashModelEx2, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport, + Serializable { + + private final Map map; + + /** + * Factory method for creating new adapter instances. + * + * @param map + * The map to adapt; can't be {@code null}. + * @param wrapper + * The {@link ObjectWrapper} used to wrap the items in the array. + */ + public static DefaultMapAdapter adapt(Map map, ObjectWrapperWithAPISupport wrapper) { + return new DefaultMapAdapter(map, wrapper); + } + + private DefaultMapAdapter(Map map, ObjectWrapper wrapper) { + super(wrapper); + this.map = map; + } + + @Override + public TemplateModel get(String key) throws TemplateModelException { + Object val; + try { + val = map.get(key); + } catch (ClassCastException e) { + throw new _TemplateModelException(e, + "ClassCastException while getting Map entry with String key ", + new _DelayedJQuote(key)); + } catch (NullPointerException e) { + throw new _TemplateModelException(e, + "NullPointerException while getting Map entry with String key ", + new _DelayedJQuote(key)); + } + + if (val == null) { + // Check for Character key if this is a single-character string. + // In SortedMap-s, however, we can't do that safely, as it can cause ClassCastException. + if (key.length() == 1 && !(map instanceof SortedMap)) { + Character charKey = Character.valueOf(key.charAt(0)); + try { + val = map.get(charKey); + if (val == null) { + TemplateModel wrappedNull = wrap(null); + if (wrappedNull == null || !(map.containsKey(key) || map.containsKey(charKey))) { + return null; + } else { + return wrappedNull; + } + } + } catch (ClassCastException e) { + throw new _TemplateModelException(e, + "Class casting exception while getting Map entry with Character key ", + new _DelayedJQuote(charKey)); + } catch (NullPointerException e) { + throw new _TemplateModelException(e, + "NullPointerException while getting Map entry with Character key ", + new _DelayedJQuote(charKey)); + } + } else { // No char key fallback was possible + TemplateModel wrappedNull = wrap(null); + if (wrappedNull == null || !map.containsKey(key)) { + return null; + } else { + return wrappedNull; + } + } + } + + return wrap(val); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public TemplateCollectionModel keys() { + return new SimpleCollection(map.keySet(), getObjectWrapper()); + } + + @Override + public TemplateCollectionModel values() { + return new SimpleCollection(map.values(), getObjectWrapper()); + } + + @Override + public KeyValuePairIterator keyValuePairIterator() { + return new MapKeyValuePairIterator(map, getObjectWrapper()); + } + + @Override + public Object getAdaptedObject(Class hint) { + return map; + } + + @Override + public Object getWrappedObject() { + return map; + } + + @Override + public TemplateModel getAPI() throws TemplateModelException { + return ((ObjectWrapperWithAPISupport) getObjectWrapper()).wrapAsAPI(map); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultNonListCollectionAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultNonListCollectionAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultNonListCollectionAdapter.java new file mode 100644 index 0000000..3b128fd --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultNonListCollectionAdapter.java @@ -0,0 +1,103 @@ +/* + * 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.List; + +import org.apache.freemarker.core.model.AdapterTemplateModel; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; +import org.apache.freemarker.core.model.ObjectWrapperWithAPISupport; +import org.apache.freemarker.core.model.TemplateCollectionModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateModelWithAPISupport; +import org.apache.freemarker.core.model.WrapperTemplateModel; +import org.apache.freemarker.core.model.WrappingTemplateModel; + +/** + * Adapts a non-{@link List} Java {@link Collection} to the corresponding {@link TemplateModel} interface(s), most + * importantly to {@link TemplateCollectionModelEx}. For {@link List}-s, use {@link DefaultListAdapter}, or else you + * lose indexed element access. + * + * <p> + * Thread safety: A {@link DefaultNonListCollectionAdapter} is as thread-safe as the {@link Collection} that it wraps + * is. Normally you only have to consider read-only access, as the FreeMarker template language doesn't allow writing + * these collections (though of course, Java methods called from the template can violate this rule). + * + * @since 2.3.22 + */ +public class DefaultNonListCollectionAdapter extends WrappingTemplateModel implements TemplateCollectionModelEx, + AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport, Serializable { + + private final Collection collection; + + /** + * Factory method for creating new adapter instances. + * + * @param collection + * The collection to adapt; can't be {@code null}. + * @param wrapper + * The {@link ObjectWrapper} used to wrap the items in the collection. Has to be + * {@link ObjectWrapperAndUnwrapper} because of planned future features. + */ + public static DefaultNonListCollectionAdapter adapt(Collection collection, ObjectWrapperWithAPISupport wrapper) { + return new DefaultNonListCollectionAdapter(collection, wrapper); + } + + private DefaultNonListCollectionAdapter(Collection collection, ObjectWrapperWithAPISupport wrapper) { + super(wrapper); + this.collection = collection; + } + + @Override + public TemplateModelIterator iterator() throws TemplateModelException { + return new DefaultUnassignableIteratorAdapter(collection.iterator(), getObjectWrapper()); + } + + @Override + public int size() { + return collection.size(); + } + + @Override + public boolean isEmpty() { + return collection.isEmpty(); + } + + @Override + public Object getWrappedObject() { + return collection; + } + + @Override + public Object getAdaptedObject(Class hint) { + return getWrappedObject(); + } + + @Override + public TemplateModel getAPI() throws TemplateModelException { + return ((ObjectWrapperWithAPISupport) getObjectWrapper()).wrapAsAPI(collection); + } + +}
