http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java index 307d278..a2ca780 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperConfiguration.java @@ -20,8 +20,9 @@ package org.apache.freemarker.core.model.impl; import org.apache.freemarker.core.Version; +import org.apache.freemarker.core._CoreAPI; import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.impl.beans.BeansWrapperConfiguration; +import org.apache.freemarker.core.model.TemplateDateModel; /** * Holds {@link DefaultObjectWrapper} configuration settings and defines their defaults. @@ -29,13 +30,187 @@ import org.apache.freemarker.core.model.impl.beans.BeansWrapperConfiguration; * Unless, you are developing a builder for a custom {@link DefaultObjectWrapper} subclass. In that case, note that * overriding the {@link #equals} and {@link #hashCode} is important, as these objects are used as {@link ObjectWrapper} * singleton lookup keys. - * - * @since 2.3.22 */ -public abstract class DefaultObjectWrapperConfiguration extends BeansWrapperConfiguration { - +public abstract class DefaultObjectWrapperConfiguration implements Cloneable { + + private final Version incompatibleImprovements; + + protected ClassIntrospectorBuilder classIntrospectorFactory; + + // Properties and their *defaults*: + private boolean simpleMapWrapper = false; + private int defaultDateType = TemplateDateModel.UNKNOWN; + private ObjectWrapper outerIdentity = null; + private boolean strict = false; + private boolean useModelCache = false; + // Attention! + // - As this object is a cache key, non-normalized field values should be avoided. + // - Fields with default values must be set until the end of the constructor to ensure that when the lookup happens, + // there will be no unset fields. + // - If you add a new field, review all methods in this class + + /** + * @param incompatibleImprovements + * See the corresponding parameter of {@link DefaultObjectWrapper#DefaultObjectWrapper(Version)}. Not {@code null}. Note + * that the version will be normalized to the lowest version where the same incompatible + * {@link DefaultObjectWrapper} improvements were already present, so for the returned instance + * {@link #getIncompatibleImprovements()} might returns a lower version than what you have specified + * here. + * @param isIncompImprsAlreadyNormalized + * Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> value. + * This parameter meant to be {@code true} when the class that extends {@link DefaultObjectWrapper} needs to add + * additional breaking versions over those of {@link DefaultObjectWrapper}. Thus, if this parameter is + * {@code true}, the versions where {@link DefaultObjectWrapper} had breaking changes must be already factored + * into the {@code incompatibleImprovements} parameter value, as no more normalization will happen. (You + * can use {@link DefaultObjectWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover those.) + * + * @since 2.3.22 + */ + protected DefaultObjectWrapperConfiguration(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) { + _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements); + + incompatibleImprovements = isIncompImprsAlreadyNormalized + ? incompatibleImprovements + : DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements); + this.incompatibleImprovements = incompatibleImprovements; + + classIntrospectorFactory = new ClassIntrospectorBuilder(incompatibleImprovements); + } + + /** + * Same as {@link #DefaultObjectWrapperConfiguration(Version, boolean) DefaultObjectWrapperConfiguration(Version, false)}. + */ protected DefaultObjectWrapperConfiguration(Version incompatibleImprovements) { - super(DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements), true); + this(incompatibleImprovements, false); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + incompatibleImprovements.hashCode(); + result = prime * result + (simpleMapWrapper ? 1231 : 1237); + result = prime * result + defaultDateType; + result = prime * result + (outerIdentity != null ? outerIdentity.hashCode() : 0); + result = prime * result + (strict ? 1231 : 1237); + result = prime * result + (useModelCache ? 1231 : 1237); + result = prime * result + classIntrospectorFactory.hashCode(); + return result; + } + + /** + * Two {@link DefaultObjectWrapperConfiguration}-s are equal exactly if their classes are identical ({@code ==}), and their + * field values are equal. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + DefaultObjectWrapperConfiguration other = (DefaultObjectWrapperConfiguration) obj; + + if (!incompatibleImprovements.equals(other.incompatibleImprovements)) return false; + if (simpleMapWrapper != other.simpleMapWrapper) return false; + if (defaultDateType != other.defaultDateType) return false; + if (outerIdentity != other.outerIdentity) return false; + if (strict != other.strict) return false; + if (useModelCache != other.useModelCache) return false; + return classIntrospectorFactory.equals(other.classIntrospectorFactory); + } + + protected Object clone(boolean deepCloneKey) { + try { + DefaultObjectWrapperConfiguration clone = (DefaultObjectWrapperConfiguration) super.clone(); + if (deepCloneKey) { + clone.classIntrospectorFactory + = (ClassIntrospectorBuilder) classIntrospectorFactory.clone(); + } + return clone; + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Failed to clone DefaultObjectWrapperConfiguration", e); + } + } + + public int getDefaultDateType() { + return defaultDateType; + } + + /** See {@link DefaultObjectWrapper#setDefaultDateType(int)}. */ + public void setDefaultDateType(int defaultDateType) { + this.defaultDateType = defaultDateType; + } + + public ObjectWrapper getOuterIdentity() { + return outerIdentity; + } + + /** + * See {@link DefaultObjectWrapper#setOuterIdentity(ObjectWrapper)}, except here the default is {@code null} that means + * the {@link ObjectWrapper} that you will set up with this {@link DefaultObjectWrapperBuilder} object. + */ + public void setOuterIdentity(ObjectWrapper outerIdentity) { + this.outerIdentity = outerIdentity; + } + + public boolean isStrict() { + return strict; + } + + /** See {@link DefaultObjectWrapper#setStrict(boolean)}. */ + public void setStrict(boolean strict) { + this.strict = strict; + } + + public boolean getUseModelCache() { + return useModelCache; + } + + /** See {@link DefaultObjectWrapper#setUseModelCache(boolean)} (it means the same). */ + public void setUseModelCache(boolean useModelCache) { + this.useModelCache = useModelCache; + } + + public Version getIncompatibleImprovements() { + return incompatibleImprovements; + } + + public int getExposureLevel() { + return classIntrospectorFactory.getExposureLevel(); + } + + /** See {@link DefaultObjectWrapper#setExposureLevel(int)}. */ + public void setExposureLevel(int exposureLevel) { + classIntrospectorFactory.setExposureLevel(exposureLevel); + } + + public boolean getExposeFields() { + return classIntrospectorFactory.getExposeFields(); + } + + /** See {@link DefaultObjectWrapper#setExposeFields(boolean)}. */ + public void setExposeFields(boolean exposeFields) { + classIntrospectorFactory.setExposeFields(exposeFields); + } + + public MethodAppearanceFineTuner getMethodAppearanceFineTuner() { + return classIntrospectorFactory.getMethodAppearanceFineTuner(); + } + + /** + * See {@link DefaultObjectWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}; additionally, + * note that currently setting this to non-{@code null} will disable class introspection cache sharing, unless + * the value implements {@link SingletonCustomizer}. + */ + public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) { + classIntrospectorFactory.setMethodAppearanceFineTuner(methodAppearanceFineTuner); + } + + MethodSorter getMethodSorter() { + return classIntrospectorFactory.getMethodSorter(); + } + + void setMethodSorter(MethodSorter methodSorter) { + classIntrospectorFactory.setMethodSorter(methodSorter); } }
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java new file mode 100644 index 0000000..3ec8e87 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Represents that no member was chosen. Why it wasn't is represented by the two singleton instances, + * {@link #NO_SUCH_METHOD} and {@link #AMBIGUOUS_METHOD}. (Note that instances of these are cached associated with the + * argument types, thus it shouldn't store details that are specific to the actual argument values. In fact, it better + * remains a set of singletons.) + */ +final class EmptyCallableMemberDescriptor extends MaybeEmptyCallableMemberDescriptor { + + static final EmptyCallableMemberDescriptor NO_SUCH_METHOD = new EmptyCallableMemberDescriptor(); + static final EmptyCallableMemberDescriptor AMBIGUOUS_METHOD = new EmptyCallableMemberDescriptor(); + + private EmptyCallableMemberDescriptor() { } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java new file mode 100644 index 0000000..25987d7 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java @@ -0,0 +1,93 @@ +/* + * 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._DelayedOrdinal; + +/** + * Describes a failed member lookup. Instances of this must not be cached as instances may store the actual argument + * values. + */ +final class EmptyMemberAndArguments extends MaybeEmptyMemberAndArguments { + + static final EmptyMemberAndArguments WRONG_NUMBER_OF_ARGUMENTS + = new EmptyMemberAndArguments( + "No compatible overloaded variation was found; wrong number of arguments.", true, null); + + private final Object errorDescription; + private final boolean numberOfArgumentsWrong; + private final Object[] unwrappedArguments; + + private EmptyMemberAndArguments( + Object errorDescription, boolean numberOfArgumentsWrong, Object[] unwrappedArguments) { + this.errorDescription = errorDescription; + this.numberOfArgumentsWrong = numberOfArgumentsWrong; + this.unwrappedArguments = unwrappedArguments; + } + + static EmptyMemberAndArguments noCompatibleOverload(int unwrappableIndex) { + return new EmptyMemberAndArguments( + new Object[] { "No compatible overloaded variation was found; can't convert (unwrap) the ", + new _DelayedOrdinal(Integer.valueOf(unwrappableIndex)), " argument to the desired Java type." }, + false, + null); + } + + static EmptyMemberAndArguments noCompatibleOverload(Object[] unwrappedArgs) { + return new EmptyMemberAndArguments( + "No compatible overloaded variation was found; declared parameter types and argument value types mismatch.", + false, + unwrappedArgs); + } + + static EmptyMemberAndArguments ambiguous(Object[] unwrappedArgs) { + return new EmptyMemberAndArguments( + "Multiple compatible overloaded variations were found with the same priority.", + false, + unwrappedArgs); + } + + static MaybeEmptyMemberAndArguments from( + EmptyCallableMemberDescriptor emtpyMemberDesc, Object[] unwrappedArgs) { + if (emtpyMemberDesc == EmptyCallableMemberDescriptor.NO_SUCH_METHOD) { + return noCompatibleOverload(unwrappedArgs); + } else if (emtpyMemberDesc == EmptyCallableMemberDescriptor.AMBIGUOUS_METHOD) { + return ambiguous(unwrappedArgs); + } else { + throw new IllegalArgumentException("Unrecognized constant: " + emtpyMemberDesc); + } + } + + Object getErrorDescription() { + return errorDescription; + } + + /** + * @return {@code null} if the error has occurred earlier than the full argument list was unwrapped. + */ + Object[] getUnwrappedArguments() { + return unwrappedArguments; + } + + public boolean isNumberOfArgumentsWrong() { + return numberOfArgumentsWrong; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java b/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java new file mode 100644 index 0000000..d7b14cc --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/EnumerationModel.java @@ -0,0 +1,108 @@ +/* + * 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.Enumeration; +import java.util.NoSuchElementException; + +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; + +/** + * <p>A class that adds {@link TemplateModelIterator} functionality to the + * {@link Enumeration} interface implementers. + * </p> <p>Using the model as a collection model is NOT thread-safe, as + * enumerations are inherently not thread-safe. + * Further, you can iterate over it only once. Attempts to call the + * {@link #iterator()} method after it was already driven to the end once will + * throw an exception.</p> + */ + +public class EnumerationModel +extends + BeanModel +implements + TemplateModelIterator, + TemplateCollectionModel { + private boolean accessed = false; + + /** + * Creates a new model that wraps the specified enumeration object. + * @param enumeration the enumeration object to wrap into a model. + * @param wrapper the {@link DefaultObjectWrapper} associated with this model. + * Every model has to have an associated {@link DefaultObjectWrapper} instance. The + * model gains many attributes from its wrapper, including the caching + * behavior, method exposure level, method-over-item shadowing policy etc. + */ + public EnumerationModel(Enumeration enumeration, DefaultObjectWrapper wrapper) { + super(enumeration, wrapper); + } + + /** + * This allows the enumeration to be used in a <tt><#list></tt> block. + * @return "this" + */ + @Override + public TemplateModelIterator iterator() throws TemplateModelException { + synchronized (this) { + if (accessed) { + throw new TemplateModelException( + "This collection is stateful and can not be iterated over the" + + " second time."); + } + accessed = true; + } + return this; + } + + /** + * Calls underlying {@link Enumeration#nextElement()}. + */ + @Override + public boolean hasNext() { + return ((Enumeration) object).hasMoreElements(); + } + + + /** + * Calls underlying {@link Enumeration#nextElement()} and wraps the result. + */ + @Override + public TemplateModel next() + throws TemplateModelException { + try { + return wrap(((Enumeration) object).nextElement()); + } catch (NoSuchElementException e) { + throw new TemplateModelException( + "No more elements in the enumeration."); + } + } + + /** + * Returns {@link Enumeration#hasMoreElements()}. Therefore, an + * enumeration that has no more element evaluates to false, and an + * enumeration that has further elements evaluates to true. + */ + public boolean getAsBoolean() { + return hasNext(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java new file mode 100644 index 0000000..7fefec9 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java @@ -0,0 +1,180 @@ +/* + * 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.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.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.util.UndeclaredThrowableException; + +/** + */ +public 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 (TemplateModelException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public Object get(Object key) { + try { + return wrapper.unwrap(model.get(String.valueOf(key))); + } catch (TemplateModelException 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 (TemplateModelException e) { + throw new UndeclaredThrowableException(e); + } + return new Iterator() { + @Override + public boolean hasNext() { + try { + return i.hasNext(); + } catch (TemplateModelException e) { + throw new UndeclaredThrowableException(e); + } + } + + @Override + public Object next() { + final Object key; + try { + key = wrapper.unwrap(i.next()); + } catch (TemplateModelException 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 (TemplateModelException 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/051a0822/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java b/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java new file mode 100644 index 0000000..41197ca --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java @@ -0,0 +1,34 @@ +/* + * 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.model.TemplateModelException; + +/** + * An exception thrown when there is an attempt to access + * an invalid bean property when we are in a "strict bean" mode + */ + +public class InvalidPropertyException extends TemplateModelException { + + public InvalidPropertyException(String description) { + super(description); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java b/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java new file mode 100644 index 0000000..0eabf21 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/IteratorModel.java @@ -0,0 +1,112 @@ +/* + * 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.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; + +/** + * <p>A class that adds {@link TemplateModelIterator} functionality to the + * {@link Iterator} interface implementers. + * </p> + * <p>It differs from the {@link org.apache.freemarker.core.model.impl.SimpleCollection} in that + * it inherits from {@link BeanModel}, and therefore you can call methods on + * it directly, even to the effect of calling <tt>iterator.remove()</tt> in + * the template.</p> <p>Using the model as a collection model is NOT + * thread-safe, as iterators are inherently not thread-safe. + * Further, you can iterate over it only once. Attempts to call the + * {@link #iterator()} method after it was already driven to the end once will + * throw an exception.</p> + */ + +public class IteratorModel +extends + BeanModel +implements + TemplateModelIterator, + TemplateCollectionModel { + private boolean accessed = false; + + /** + * Creates a new model that wraps the specified iterator object. + * @param iterator the iterator object to wrap into a model. + * @param wrapper the {@link DefaultObjectWrapper} associated with this model. + * Every model has to have an associated {@link DefaultObjectWrapper} instance. The + * model gains many attributes from its wrapper, including the caching + * behavior, method exposure level, method-over-item shadowing policy etc. + */ + public IteratorModel(Iterator iterator, DefaultObjectWrapper wrapper) { + super(iterator, wrapper); + } + + /** + * This allows the iterator to be used in a <tt><#list></tt> block. + * @return "this" + */ + @Override + public TemplateModelIterator iterator() throws TemplateModelException { + synchronized (this) { + if (accessed) { + throw new TemplateModelException( + "This collection is stateful and can not be iterated over the" + + " second time."); + } + accessed = true; + } + return this; + } + + /** + * Calls underlying {@link Iterator#hasNext()}. + */ + @Override + public boolean hasNext() { + return ((Iterator) object).hasNext(); + } + + + /** + * Calls underlying {@link Iterator#next()} and wraps the result. + */ + @Override + public TemplateModel next() + throws TemplateModelException { + try { + return wrap(((Iterator) object).next()); + } catch (NoSuchElementException e) { + throw new TemplateModelException( + "No more elements in the iterator.", e); + } + } + + /** + * Returns {@link Iterator#hasNext()}. Therefore, an + * iterator that has no more element evaluates to false, and an + * iterator that has further elements evaluates to true. + */ + public boolean getAsBoolean() { + return hasNext(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java b/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java new file mode 100644 index 0000000..5f8e222 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java @@ -0,0 +1,58 @@ +/* + * 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.lang.ref.WeakReference; + +import org.zeroturnaround.javarebel.ClassEventListener; +import org.zeroturnaround.javarebel.ReloaderFactory; + +class JRebelClassChangeNotifier implements ClassChangeNotifier { + + static void testAvailability() { + ReloaderFactory.getInstance(); + } + + @Override + public void subscribe(ClassIntrospector classIntrospector) { + ReloaderFactory.getInstance().addClassReloadListener( + new ClassIntrospectorCacheInvalidator(classIntrospector)); + } + + private static class ClassIntrospectorCacheInvalidator + implements ClassEventListener { + private final WeakReference ref; + + ClassIntrospectorCacheInvalidator(ClassIntrospector w) { + ref = new WeakReference(w); + } + + @Override + public void onClassEvent(int eventType, Class pClass) { + ClassIntrospector ci = (ClassIntrospector) ref.get(); + if (ci == null) { + ReloaderFactory.getInstance().removeClassReloadListener(this); + } else if (eventType == ClassEventListener.EVENT_RELOADED) { + ci.remove(pClass); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java b/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java new file mode 100644 index 0000000..95abb2a --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/MapModel.java @@ -0,0 +1,120 @@ +/* + * 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 java.util.Map; +import java.util.Set; + +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * <p>A special case of {@link BeanModel} that adds implementation + * for {@link TemplateMethodModelEx} on map objects that is a shortcut for the + * <tt>Map.get()</tt> method. Note that if the passed argument itself is a + * reflection-wrapper model, then the map lookup will be performed using the + * wrapped object as the key. Note that you can call <tt>get()</tt> using the + * <tt>map.key</tt> syntax inherited from {@link BeanModel} as well, + * however in that case the key is always a string.</p> + * <p>The class itself does not implement the {@link org.apache.freemarker.core.model.TemplateCollectionModel}. + * You can, however use <tt>map.entrySet()</tt>, <tt>map.keySet()</tt>, or + * <tt>map.values()</tt> to obtain {@link org.apache.freemarker.core.model.TemplateCollectionModel} instances for + * various aspects of the map.</p> + */ +public class MapModel +extends + StringModel +implements + TemplateMethodModelEx { + static final ModelFactory FACTORY = + new ModelFactory() + { + @Override + public TemplateModel create(Object object, ObjectWrapper wrapper) { + return new MapModel((Map) object, (DefaultObjectWrapper) wrapper); + } + }; + + /** + * Creates a new model that wraps the specified map object. + * @param map the map object to wrap into a model. + * @param wrapper the {@link DefaultObjectWrapper} associated with this model. + * Every model has to have an associated {@link DefaultObjectWrapper} instance. The + * model gains many attributes from its wrapper, including the caching + * behavior, method exposure level, method-over-item shadowing policy etc. + */ + public MapModel(Map map, DefaultObjectWrapper wrapper) { + super(map, wrapper); + } + + /** + * The first argument is used as a key to call the map's <tt>get</tt> method. + */ + @Override + public Object exec(List arguments) + throws TemplateModelException { + Object key = unwrap((TemplateModel) arguments.get(0)); + return wrap(((Map) object).get(key)); + } + + /** + * Overridden to invoke the generic get method by casting to Map instead of + * through reflection - should yield better performance. + */ + @Override + protected TemplateModel invokeGenericGet(Map keyMap, Class clazz, String key) + throws TemplateModelException { + Map map = (Map) object; + Object val = map.get(key); + if (val == null) { + if (key.length() == 1) { + // just check for Character key if this is a single-character string + Character charKey = Character.valueOf(key.charAt(0)); + val = map.get(charKey); + if (val == null && !(map.containsKey(key) || map.containsKey(charKey))) { + return UNKNOWN; + } + } else if (!map.containsKey(key)) { + return UNKNOWN; + } + } + return wrap(val); + } + + @Override + public boolean isEmpty() { + return ((Map) object).isEmpty() && super.isEmpty(); + } + + @Override + public int size() { + return keySet().size(); + } + + @Override + protected Set keySet() { + Set set = super.keySet(); + set.addAll(((Map) object).keySet()); + return set; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java new file mode 100644 index 0000000..1b62bf8 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java @@ -0,0 +1,25 @@ +/* + * 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; + +/** + * Superclass of the {@link EmptyCallableMemberDescriptor} and {@link CallableMemberDescriptor} "case classes". + */ +abstract class MaybeEmptyCallableMemberDescriptor { } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java new file mode 100644 index 0000000..bfc88ee --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java @@ -0,0 +1,22 @@ +/* + * 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; + +abstract class MaybeEmptyMemberAndArguments { } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java new file mode 100644 index 0000000..e7c735c --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java @@ -0,0 +1,64 @@ +/* + * 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.lang.reflect.InvocationTargetException; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + */ +class MemberAndArguments extends MaybeEmptyMemberAndArguments { + + private final CallableMemberDescriptor callableMemberDesc; + private final Object[] args; + + /** + * @param args The already unwrapped arguments + */ + MemberAndArguments(CallableMemberDescriptor callableMemberDesc, Object[] args) { + this.callableMemberDesc = callableMemberDesc; + this.args = args; + } + + /** + * The already unwrapped arguments. + */ + Object[] getArgs() { + return args; + } + + TemplateModel invokeMethod(DefaultObjectWrapper ow, Object obj) + throws TemplateModelException, InvocationTargetException, IllegalAccessException { + return callableMemberDesc.invokeMethod(ow, obj, args); + } + + Object invokeConstructor(DefaultObjectWrapper ow) + throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, + TemplateModelException { + return callableMemberDesc.invokeConstructor(ow, args); + } + + CallableMemberDescriptor getCallableMemberDescriptor() { + return callableMemberDesc; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java b/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java new file mode 100644 index 0000000..3235c51 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java @@ -0,0 +1,156 @@ +/* + * 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.beans.IndexedPropertyDescriptor; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; + +/** + * Used for customizing how the methods are visible from templates, via + * {@link DefaultObjectWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}. + * The object that implements this should also implement {@link SingletonCustomizer} whenever possible. + * + * @since 2.3.21 + */ +public interface MethodAppearanceFineTuner { + + /** + * Implement this to tweak certain aspects of how methods appear in the + * data-model. {@link DefaultObjectWrapper} will pass in all Java methods here that + * it intends to expose in the data-model as methods (so you can do + * <tt>obj.foo()</tt> in the template). + * With this method you can do the following tweaks: + * <ul> + * <li>Hide a method that would be otherwise shown by calling + * {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)} + * with <tt>null</tt> parameter. Note that you can't un-hide methods + * that are not public or are considered to by unsafe + * (like {@link Object#wait()}) because + * {@link #process} is not called for those.</li> + * <li>Show the method with a different name in the data-model than its + * real name by calling + * {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)} + * with non-<tt>null</tt> parameter. + * <li>Create a fake JavaBean property for this method by calling + * {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeAsProperty(PropertyDescriptor)}. + * For example, if you have <tt>int size()</tt> in a class, but you + * want it to be accessed from the templates as <tt>obj.size</tt>, + * rather than as <tt>obj.size()</tt>, you can do that with this. + * The default is {@code null}, which means that no fake property is + * created for the method. You need not and shouldn't set this + * to non-<tt>null</tt> for the getter methods of real JavaBean + * properties, as those are automatically shown as properties anyway. + * The property name in the {@link PropertyDescriptor} can be anything, + * but the method (or methods) in it must belong to the class that + * is given as the <tt>clazz</tt> parameter or it must be inherited from + * that class, or else whatever errors can occur later. + * {@link IndexedPropertyDescriptor}-s are supported. + * If a real JavaBean property of the same name exists, it won't be + * replaced by the fake one. Also if a fake property of the same name + * was assigned earlier, it won't be replaced. + * <li>Prevent the method to hide a JavaBean property (fake or real) of + * the same name by calling + * {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setMethodShadowsProperty(boolean)} + * with <tt>false</tt>. The default is <tt>true</tt>, so if you have + * both a property and a method called "foo", then in the template + * <tt>myObject.foo</tt> will return the method itself instead + * of the property value, which is often undesirable. + * </ul> + * + * <p>Note that you can expose a Java method both as a method and as a + * JavaBean property on the same time, however you have to chose different + * names for them to prevent shadowing. + * + * @param in Describes the method about which the decision will have to be made. + * + * @param out Stores how the method will be exposed in the + * data-model after {@link #process} returns. + * This is initialized so that it reflects the default + * behavior of {@link DefaultObjectWrapper}, so you don't have to do anything with this + * when you don't want to change the default behavior. + */ + void process(DecisionInput in, Decision out); + + /** + * Used for {@link MethodAppearanceFineTuner#process} to store the results. + */ + final class Decision { + private PropertyDescriptor exposeAsProperty; + private String exposeMethodAs; + private boolean methodShadowsProperty; + + void setDefaults(Method m) { + exposeAsProperty = null; + exposeMethodAs = m.getName(); + methodShadowsProperty = true; + } + + public PropertyDescriptor getExposeAsProperty() { + return exposeAsProperty; + } + + public void setExposeAsProperty(PropertyDescriptor exposeAsProperty) { + this.exposeAsProperty = exposeAsProperty; + } + + public String getExposeMethodAs() { + return exposeMethodAs; + } + + public void setExposeMethodAs(String exposeMethodAs) { + this.exposeMethodAs = exposeMethodAs; + } + + public boolean getMethodShadowsProperty() { + return methodShadowsProperty; + } + + public void setMethodShadowsProperty(boolean methodShadowsProperty) { + this.methodShadowsProperty = methodShadowsProperty; + } + + } + + /** + * Used for {@link org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner#process} as input parameter. + */ + final class DecisionInput { + private Method method; + private Class<?> containingClass; + + void setMethod(Method method) { + this.method = method; + } + + void setContainingClass(Class<?> containingClass) { + this.containingClass = containingClass; + } + + public Method getMethod() { + return method; + } + + public Class/*<?>*/ getContainingClass() { + return containingClass; + } + + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java b/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java new file mode 100644 index 0000000..b893bfe --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.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.beans.MethodDescriptor; + +/** + * Used for JUnit testing method-order dependence bugs via + * {@link DefaultObjectWrapperBuilder#setMethodSorter(MethodSorter)}. + */ +interface MethodSorter { + + MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors); + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java b/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java new file mode 100644 index 0000000..529b988 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/ModelCache.java @@ -0,0 +1,143 @@ +/* + * 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.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.IdentityHashMap; +import java.util.Map; + +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelAdapter; + +/** + * Internally used by various wrapper implementations to implement model + * caching. + */ +public abstract class ModelCache { + private boolean useCache = false; + private Map<Object, ModelReference> modelCache = null; + private ReferenceQueue<TemplateModel> refQueue = null; + + protected ModelCache() { + } + + /** + * Sets whether this wrapper caches model instances. Default is false. + * When set to true, calling {@link #getInstance(Object)} + * multiple times for the same object will return the same model. + */ + public synchronized void setUseCache(boolean useCache) { + this.useCache = useCache; + if (useCache) { + modelCache = new IdentityHashMap<>(); + refQueue = new ReferenceQueue<>(); + } else { + modelCache = null; + refQueue = null; + } + } + + /** + * @since 2.3.21 + */ + public synchronized boolean getUseCache() { + return useCache; + } + + public TemplateModel getInstance(Object object) { + if (object instanceof TemplateModel) { + return (TemplateModel) object; + } + if (object instanceof TemplateModelAdapter) { + return ((TemplateModelAdapter) object).getTemplateModel(); + } + if (useCache && isCacheable(object)) { + TemplateModel model = lookup(object); + if (model == null) { + model = create(object); + register(model, object); + } + return model; + } else { + return create(object); + } + } + + protected abstract TemplateModel create(Object object); + protected abstract boolean isCacheable(Object object); + + public void clearCache() { + if (modelCache != null) { + synchronized (modelCache) { + modelCache.clear(); + } + } + } + + private TemplateModel lookup(Object object) { + ModelReference ref = null; + // NOTE: we're doing minimal synchronizations -- which can lead to + // duplicate wrapper creation. However, this has no harmful side-effects and + // is a lesser performance hit. + synchronized (modelCache) { + ref = modelCache.get(object); + } + + if (ref != null) + return ref.getModel(); + + return null; + } + + private void register(TemplateModel model, Object object) { + synchronized (modelCache) { + // Remove cleared references + for (; ; ) { + ModelReference queuedRef = (ModelReference) refQueue.poll(); + if (queuedRef == null) { + break; + } + modelCache.remove(queuedRef.object); + } + // Register new reference + modelCache.put(object, new ModelReference(model, object, refQueue)); + } + } + + /** + * A special soft reference that is registered in the modelCache. + * When it gets cleared (that is, the model became unreachable) + * it will remove itself from the model cache. + */ + private static final class ModelReference extends SoftReference<TemplateModel> { + Object object; + + ModelReference(TemplateModel ref, Object object, ReferenceQueue<TemplateModel> refQueue) { + super(ref, refQueue); + this.object = object; + } + + TemplateModel getModel() { + return get(); + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.java b/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.java new file mode 100644 index 0000000..cd74342 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/ModelFactory.java @@ -0,0 +1,34 @@ +/* + * 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.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateModel; + +/** + * Interface used to create various wrapper models in the {@link ModelCache}. + */ +public interface ModelFactory { + /** + * Create a wrapping model for the specified object that belongs to + * the specified wrapper. + */ + TemplateModel create(Object object, ObjectWrapper wrapper); +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java b/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java new file mode 100644 index 0000000..2babe9f --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java @@ -0,0 +1,42 @@ +/* + * 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; + +class NonPrimitiveArrayBackedReadOnlyList extends AbstractList { + + private final Object[] array; + + NonPrimitiveArrayBackedReadOnlyList(Object[] array) { + this.array = array; + } + + @Override + public Object get(int index) { + return array[index]; + } + + @Override + public int size() { + return array.length; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java b/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java new file mode 100644 index 0000000..70b50d9 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/NumberModel.java @@ -0,0 +1,60 @@ +/* + * 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.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateNumberModel; + +/** + * Wraps arbitrary subclass of {@link java.lang.Number} into a reflective model. + * Beside acting as a {@link TemplateNumberModel}, you can call all Java methods on + * these objects as well. + */ +public class NumberModel +extends + BeanModel +implements + TemplateNumberModel { + static final ModelFactory FACTORY = + new ModelFactory() + { + @Override + public TemplateModel create(Object object, ObjectWrapper wrapper) { + return new NumberModel((Number) object, (DefaultObjectWrapper) wrapper); + } + }; + /** + * Creates a new model that wraps the specified number object. + * @param number the number object to wrap into a model. + * @param wrapper the {@link DefaultObjectWrapper} associated with this model. + * Every model has to have an associated {@link DefaultObjectWrapper} instance. The + * model gains many attributes from its wrapper, including the caching + * behavior, method exposure level, method-over-item shadowing policy etc. + */ + public NumberModel(Number number, DefaultObjectWrapper wrapper) { + super(number, wrapper); + } + + @Override + public Number getAsNumber() { + return (Number) object; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java new file mode 100644 index 0000000..5b160b6 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java @@ -0,0 +1,99 @@ +/* + * 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.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Stores the non-varargs methods for a {@link OverloadedMethods} object. + */ +class OverloadedFixArgsMethods extends OverloadedMethodsSubset { + + OverloadedFixArgsMethods() { + super(); + } + + @Override + Class[] preprocessParameterTypes(CallableMemberDescriptor memberDesc) { + return memberDesc.getParamTypes(); + } + + @Override + void afterWideningUnwrappingHints(Class[] paramTypes, int[] paramNumericalTypes) { + // Do nothing + } + + @Override + MaybeEmptyMemberAndArguments getMemberAndArguments(List tmArgs, DefaultObjectWrapper unwrapper) + throws TemplateModelException { + if (tmArgs == null) { + // null is treated as empty args + tmArgs = Collections.EMPTY_LIST; + } + final int argCount = tmArgs.size(); + final Class[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount(); + if (unwrappingHintsByParamCount.length <= argCount) { + return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS; + } + Class[] unwarppingHints = unwrappingHintsByParamCount[argCount]; + if (unwarppingHints == null) { + return EmptyMemberAndArguments.WRONG_NUMBER_OF_ARGUMENTS; + } + + Object[] pojoArgs = new Object[argCount]; + + int[] typeFlags = getTypeFlags(argCount); + if (typeFlags == ALL_ZEROS_ARRAY) { + typeFlags = null; + } + + Iterator it = tmArgs.iterator(); + for (int i = 0; i < argCount; ++i) { + Object pojo = unwrapper.tryUnwrapTo( + (TemplateModel) it.next(), + unwarppingHints[i], + typeFlags != null ? typeFlags[i] : 0); + if (pojo == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) { + return EmptyMemberAndArguments.noCompatibleOverload(i + 1); + } + pojoArgs[i] = pojo; + } + + MaybeEmptyCallableMemberDescriptor maybeEmtpyMemberDesc = getMemberDescriptorForArgs(pojoArgs, false); + if (maybeEmtpyMemberDesc instanceof CallableMemberDescriptor) { + CallableMemberDescriptor memberDesc = (CallableMemberDescriptor) maybeEmtpyMemberDesc; + if (typeFlags != null) { + // Note that overloaded method selection has already accounted for overflow errors when the method + // was selected. So this forced conversion shouldn't cause such corruption. Except, conversion from + // BigDecimal is allowed to overflow for backward-compatibility. + forceNumberArgumentsToParameterTypes(pojoArgs, memberDesc.getParamTypes(), typeFlags); + } + return new MemberAndArguments(memberDesc, pojoArgs); + } else { + return EmptyMemberAndArguments.from((EmptyCallableMemberDescriptor) maybeEmtpyMemberDesc, pojoArgs); + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java new file mode 100644 index 0000000..e352bcf --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java @@ -0,0 +1,271 @@ +/* + * 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.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import org.apache.freemarker.core._DelayedConversionToString; +import org.apache.freemarker.core._ErrorDescriptionBuilder; +import org.apache.freemarker.core._TemplateModelException; +import org.apache.freemarker.core.model.TemplateMarkupOutputModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.util.FTLUtil; +import org.apache.freemarker.core.util._ClassUtil; + +/** + * Used instead of {@link java.lang.reflect.Method} or {@link java.lang.reflect.Constructor} for overloaded methods and + * constructors. + * + * <p>After the initialization with the {@link #addMethod(Method)} and {@link #addConstructor(Constructor)} calls are + * done, the instance must be thread-safe. Before that, it's the responsibility of the caller of those methods to + * ensure that the object is properly publishing to other threads. + */ +final class OverloadedMethods { + + private final OverloadedMethodsSubset fixArgMethods; + private OverloadedMethodsSubset varargMethods; + + OverloadedMethods() { + fixArgMethods = new OverloadedFixArgsMethods(); + } + + void addMethod(Method method) { + final Class[] paramTypes = method.getParameterTypes(); + addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(method, paramTypes)); + } + + void addConstructor(Constructor constr) { + final Class[] paramTypes = constr.getParameterTypes(); + addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(constr, paramTypes)); + } + + private void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) { + // Note: "varargs" methods are always callable as oms args, with a sequence (array) as the last parameter. + fixArgMethods.addCallableMemberDescriptor(memberDesc); + if (memberDesc.isVarargs()) { + if (varargMethods == null) { + varargMethods = new OverloadedVarArgsMethods(); + } + varargMethods.addCallableMemberDescriptor(memberDesc); + } + } + + MemberAndArguments getMemberAndArguments(List/*<TemplateModel>*/ tmArgs, DefaultObjectWrapper unwrapper) + throws TemplateModelException { + // Try to find a oms args match: + MaybeEmptyMemberAndArguments fixArgsRes = fixArgMethods.getMemberAndArguments(tmArgs, unwrapper); + if (fixArgsRes instanceof MemberAndArguments) { + return (MemberAndArguments) fixArgsRes; + } + + // Try to find a varargs match: + MaybeEmptyMemberAndArguments varargsRes; + if (varargMethods != null) { + varargsRes = varargMethods.getMemberAndArguments(tmArgs, unwrapper); + if (varargsRes instanceof MemberAndArguments) { + return (MemberAndArguments) varargsRes; + } + } else { + varargsRes = null; + } + + _ErrorDescriptionBuilder edb = new _ErrorDescriptionBuilder( + toCompositeErrorMessage( + (EmptyMemberAndArguments) fixArgsRes, + (EmptyMemberAndArguments) varargsRes, + tmArgs), + "\nThe matching overload was searched among these members:\n", + memberListToString()); + addMarkupBITipAfterNoNoMarchIfApplicable(edb, tmArgs); + throw new _TemplateModelException(edb); + } + + private Object[] toCompositeErrorMessage( + final EmptyMemberAndArguments fixArgsEmptyRes, final EmptyMemberAndArguments varargsEmptyRes, + List tmArgs) { + final Object[] argsErrorMsg; + if (varargsEmptyRes != null) { + if (fixArgsEmptyRes == null || fixArgsEmptyRes.isNumberOfArgumentsWrong()) { + argsErrorMsg = toErrorMessage(varargsEmptyRes, tmArgs); + } else { + argsErrorMsg = new Object[] { + "When trying to call the non-varargs overloads:\n", + toErrorMessage(fixArgsEmptyRes, tmArgs), + "\nWhen trying to call the varargs overloads:\n", + toErrorMessage(varargsEmptyRes, null) + }; + } + } else { + argsErrorMsg = toErrorMessage(fixArgsEmptyRes, tmArgs); + } + return argsErrorMsg; + } + + private Object[] toErrorMessage(EmptyMemberAndArguments res, List/*<TemplateModel>*/ tmArgs) { + final Object[] unwrappedArgs = res.getUnwrappedArguments(); + return new Object[] { + res.getErrorDescription(), + tmArgs != null + ? new Object[] { + "\nThe FTL type of the argument values were: ", getTMActualParameterTypes(tmArgs), "." } + : "", + unwrappedArgs != null + ? new Object[] { + "\nThe Java type of the argument values were: ", + getUnwrappedActualParameterTypes(unwrappedArgs) + "." } + : ""}; + } + + private _DelayedConversionToString memberListToString() { + return new _DelayedConversionToString(null) { + + @Override + protected String doConversion(Object obj) { + final Iterator fixArgMethodsIter = fixArgMethods.getMemberDescriptors(); + final Iterator varargMethodsIter = varargMethods != null ? varargMethods.getMemberDescriptors() : null; + + boolean hasMethods = fixArgMethodsIter.hasNext() || (varargMethodsIter != null && varargMethodsIter.hasNext()); + if (hasMethods) { + StringBuilder sb = new StringBuilder(); + HashSet fixArgMethods = new HashSet(); + while (fixArgMethodsIter.hasNext()) { + if (sb.length() != 0) sb.append(",\n"); + sb.append(" "); + CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) fixArgMethodsIter.next(); + fixArgMethods.add(callableMemberDesc); + sb.append(callableMemberDesc.getDeclaration()); + } + if (varargMethodsIter != null) { + while (varargMethodsIter.hasNext()) { + CallableMemberDescriptor callableMemberDesc = (CallableMemberDescriptor) varargMethodsIter.next(); + if (!fixArgMethods.contains(callableMemberDesc)) { + if (sb.length() != 0) sb.append(",\n"); + sb.append(" "); + sb.append(callableMemberDesc.getDeclaration()); + } + } + } + return sb.toString(); + } else { + return "No members"; + } + } + + }; + } + + /** + * Adds tip to the error message if converting a {@link TemplateMarkupOutputModel} argument to {@link String} might + * allows finding a matching overload. + */ + private void addMarkupBITipAfterNoNoMarchIfApplicable(_ErrorDescriptionBuilder edb, + List tmArgs) { + for (int argIdx = 0; argIdx < tmArgs.size(); argIdx++) { + Object tmArg = tmArgs.get(argIdx); + if (tmArg instanceof TemplateMarkupOutputModel) { + for (Iterator membDescs = fixArgMethods.getMemberDescriptors(); membDescs.hasNext();) { + CallableMemberDescriptor membDesc = (CallableMemberDescriptor) membDescs.next(); + Class[] paramTypes = membDesc.getParamTypes(); + + Class paramType = null; + if (membDesc.isVarargs() && argIdx >= paramTypes.length - 1) { + paramType = paramTypes[paramTypes.length - 1]; + if (paramType.isArray()) { + paramType = paramType.getComponentType(); + } + } + if (paramType == null && argIdx < paramTypes.length) { + paramType = paramTypes[argIdx]; + } + if (paramType != null) { + if (paramType.isAssignableFrom(String.class) && !paramType.isAssignableFrom(tmArg.getClass())) { + edb.tip(SimpleMethodModel.MARKUP_OUTPUT_TO_STRING_TIP); + return; + } + } + } + } + } + } + + private _DelayedConversionToString getTMActualParameterTypes(List arguments) { + final String[] argumentTypeDescs = new String[arguments.size()]; + for (int i = 0; i < arguments.size(); i++) { + argumentTypeDescs[i] = FTLUtil.getTypeDescription((TemplateModel) arguments.get(i)); + } + + return new DelayedCallSignatureToString(argumentTypeDescs) { + + @Override + String argumentToString(Object argType) { + return (String) argType; + } + + }; + } + + private Object getUnwrappedActualParameterTypes(Object[] unwrappedArgs) { + final Class[] argumentTypes = new Class[unwrappedArgs.length]; + for (int i = 0; i < unwrappedArgs.length; i++) { + Object unwrappedArg = unwrappedArgs[i]; + argumentTypes[i] = unwrappedArg != null ? unwrappedArg.getClass() : null; + } + + return new DelayedCallSignatureToString(argumentTypes) { + + @Override + String argumentToString(Object argType) { + return argType != null + ? _ClassUtil.getShortClassName((Class) argType) + : _ClassUtil.getShortClassNameOfObject(null); + } + + }; + } + + private abstract class DelayedCallSignatureToString extends _DelayedConversionToString { + + public DelayedCallSignatureToString(Object[] argTypeArray) { + super(argTypeArray); + } + + @Override + protected String doConversion(Object obj) { + Object[] argTypes = (Object[]) obj; + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < argTypes.length; i++) { + if (i != 0) sb.append(", "); + sb.append(argumentToString(argTypes[i])); + } + + return sb.toString(); + } + + abstract String argumentToString(Object argType); + + } + +}
