http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java new file mode 100644 index 0000000..f5b617d --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java @@ -0,0 +1,129 @@ +/* + * 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.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.freemarker.core.util.CommonBuilder; + +/** + * Utility method for caching {@link DefaultObjectWrapper} (and subclasses) sigletons per Thread Context Class + * Loader. + */ +// [FM3] Maybe generalize and publish this functionality +final class DefaultObjectWrapperTCCLSingletonUtil { + + private DefaultObjectWrapperTCCLSingletonUtil() { + // Not meant to be instantiated + } + + /** + * Contains the common parts of the singleton management for {@link DefaultObjectWrapper} and {@link DefaultObjectWrapper}. + * + * @param dowConstructorInvoker Creates a <em>new</em> read-only object wrapper of the desired + * {@link DefaultObjectWrapper} subclass. + */ + static < + ObjectWrapperT extends DefaultObjectWrapper, + BuilderT extends DefaultObjectWrapper.ExtendableBuilder<ObjectWrapperT, BuilderT>> + ObjectWrapperT getSingleton( + BuilderT builder, + Map<ClassLoader, Map<BuilderT, WeakReference<ObjectWrapperT>>> instanceCache, + ReferenceQueue<ObjectWrapperT> instanceCacheRefQue, + _ConstructorInvoker<ObjectWrapperT, BuilderT> dowConstructorInvoker) { + // DefaultObjectWrapper can't be cached across different Thread Context Class Loaders (TCCL), because the result of + // a class name (String) to Class mappings depends on it, and the staticModels and enumModels need that. + // (The ClassIntrospector doesn't have to consider the TCCL, as it only works with Class-es, not class + // names.) + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + + Reference<ObjectWrapperT> instanceRef; + Map<BuilderT, WeakReference<ObjectWrapperT>> tcclScopedCache; + synchronized (instanceCache) { + tcclScopedCache = instanceCache.get(tccl); + if (tcclScopedCache == null) { + tcclScopedCache = new HashMap<>(); + instanceCache.put(tccl, tcclScopedCache); + instanceRef = null; + } else { + instanceRef = tcclScopedCache.get(builder); + } + } + + ObjectWrapperT instance = instanceRef != null ? instanceRef.get() : null; + if (instance != null) { // cache hit + return instance; + } + // cache miss + + builder = builder.cloneForCacheKey(); // prevent any aliasing issues + instance = dowConstructorInvoker.invoke(builder); + + synchronized (instanceCache) { + instanceRef = tcclScopedCache.get(builder); + ObjectWrapperT concurrentInstance = instanceRef != null ? instanceRef.get() : null; + if (concurrentInstance == null) { + tcclScopedCache.put(builder, new WeakReference<>(instance, instanceCacheRefQue)); + } else { + instance = concurrentInstance; + } + } + + removeClearedReferencesFromCache(instanceCache, instanceCacheRefQue); + + return instance; + } + + private static < + ObjectWrapperT extends DefaultObjectWrapper, BuilderT extends DefaultObjectWrapper.ExtendableBuilder> + void removeClearedReferencesFromCache( + Map<ClassLoader, Map<BuilderT, WeakReference<ObjectWrapperT>>> instanceCache, + ReferenceQueue<ObjectWrapperT> instanceCacheRefQue) { + Reference<? extends ObjectWrapperT> clearedRef; + while ((clearedRef = instanceCacheRefQue.poll()) != null) { + synchronized (instanceCache) { + findClearedRef: for (Map<BuilderT, WeakReference<ObjectWrapperT>> tcclScopedCache : instanceCache.values()) { + for (Iterator<WeakReference<ObjectWrapperT>> it2 = tcclScopedCache.values().iterator(); it2.hasNext(); ) { + if (it2.next() == clearedRef) { + it2.remove(); + break findClearedRef; + } + } + } + } // sync + } // while poll + } + + /** + * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! + * Used when the builder delegates the product creation to something else (typically, an instance cache). Calling + * {@link CommonBuilder#build()} would be infinite recursion in such cases. + */ + public interface _ConstructorInvoker<ProductT, BuilderT> { + + ProductT invoke(BuilderT builder); + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java new file mode 100644 index 0000000..c87c21f --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java @@ -0,0 +1,59 @@ +/* + * 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.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateModelIterator; + +/** + * As opposed to {@link DefaultIteratorAdapter}, this simpler {@link Iterator} adapter is used in situations where the + * {@link TemplateModelIterator} won't be assigned to FreeMarker template variables, only used internally by + * {@code #list} or custom Java code. Because of that, it doesn't have to handle the situation where the user tries to + * iterate over the same value twice. + */ +class DefaultUnassignableIteratorAdapter implements TemplateModelIterator { + + private final Iterator<?> it; + private final ObjectWrapper wrapper; + + DefaultUnassignableIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) { + this.it = it; + this.wrapper = wrapper; + } + + @Override + public TemplateModel next() throws TemplateModelException { + try { + return wrapper.wrap(it.next()); + } catch (NoSuchElementException e) { + throw new TemplateModelException("The collection has no more items.", e); + } + } + + @Override + public boolean hasNext() throws TemplateModelException { + return it.hasNext(); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyCallableMemberDescriptor.java new file mode 100644 index 0000000..424a7f4 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EmptyMemberAndArguments.java new file mode 100644 index 0000000..2b68125 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java new file mode 100644 index 0000000..e4c96c8 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/EnumModels.java @@ -0,0 +1,50 @@ +/* + * 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.LinkedHashMap; +import java.util.Map; + +import org.apache.freemarker.core.model.TemplateModel; + +class EnumModels extends ClassBasedModelFactory { + + public EnumModels(DefaultObjectWrapper wrapper) { + super(wrapper); + } + + @Override + protected TemplateModel createModel(Class clazz) { + Object[] obj = clazz.getEnumConstants(); + if (obj == null) { + // Return null - it'll manifest itself as undefined in the template. + // We're doing this rather than throw an exception as this way + // people can use someEnumModel?default({}) to gracefully fall back + // to an empty hash if they want to. + return null; + } + Map map = new LinkedHashMap(); + for (Object anObj : obj) { + Enum value = (Enum) anObj; + map.put(value.name(), value); + } + return new SimpleHash(map, getWrapper()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java new file mode 100644 index 0000000..add437a --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java @@ -0,0 +1,181 @@ +/* + * 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; + +/** + * Adapts a {@link TemplateHashModel} to a {@link Map}. + */ +class HashAdapter extends AbstractMap implements TemplateModelAdapter { + private final DefaultObjectWrapper wrapper; + private final TemplateHashModel model; + private Set entrySet; + + HashAdapter(TemplateHashModel model, DefaultObjectWrapper wrapper) { + this.model = model; + this.wrapper = wrapper; + } + + @Override + public TemplateModel getTemplateModel() { + return model; + } + + @Override + public boolean isEmpty() { + try { + return model.isEmpty(); + } catch (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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/InvalidPropertyException.java new file mode 100644 index 0000000..ff73a05 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JRebelClassChangeNotifier.java new file mode 100644 index 0000000..15bc8d7 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java new file mode 100644 index 0000000..6408117 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java @@ -0,0 +1,105 @@ +/* + * 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.Member; +import java.lang.reflect.Method; +import java.util.List; + +import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Wraps a {@link Method} into the {@link TemplateMethodModelEx} interface. It is used by {@link BeanModel} to wrap + * non-overloaded methods. + */ +public final class JavaMethodModel extends SimpleMethod implements TemplateMethodModelEx, + _UnexpectedTypeErrorExplainerTemplateModel { + private final Object object; + private final DefaultObjectWrapper wrapper; + + /** + * Creates a model for a specific method on a specific object. + * @param object the object to call the method on, or {@code null} for a static method. + * @param method the method that will be invoked. + * @param argTypes Either pass in {@code Method#getParameterTypes() method.getParameterTypes()} here, + * or reuse an earlier result of that call (for speed). Not {@code null}. + */ + JavaMethodModel(Object object, Method method, Class[] argTypes, DefaultObjectWrapper wrapper) { + super(method, argTypes); + this.object = object; + this.wrapper = wrapper; + } + + /** + * Invokes the method, passing it the arguments from the list. + */ + @Override + public Object exec(List arguments) throws TemplateModelException { + try { + return wrapper.invokeMethod(object, (Method) getMember(), + unwrapArguments(arguments, wrapper)); + } catch (TemplateModelException e) { + throw e; + } catch (Exception e) { + throw _MethodUtil.newInvocationTemplateModelException(object, getMember(), e); + } + } + + @Override + public String toString() { + return getMember().toString(); + } + + /** + * Implementation of experimental interface; don't use it, no backward compatibility guarantee! + */ + @Override + public Object[] explainTypeError(Class[] expectedClasses) { + final Member member = getMember(); + if (!(member instanceof Method)) { + return null; // This shouldn't occur + } + Method m = (Method) member; + + final Class returnType = m.getReturnType(); + if (returnType == null || returnType == void.class || returnType == Void.class) { + return null; // Calling it won't help + } + + String mName = m.getName(); + if (mName.startsWith("get") && mName.length() > 3 && Character.isUpperCase(mName.charAt(3)) + && (m.getParameterTypes().length == 0)) { + return new Object[] { + "Maybe using obj.something instead of obj.getSomething will yield the desired value." }; + } else if (mName.startsWith("is") && mName.length() > 2 && Character.isUpperCase(mName.charAt(2)) + && (m.getParameterTypes().length == 0)) { + return new Object[] { + "Maybe using obj.something instead of obj.isSomething will yield the desired value." }; + } else { + return new Object[] { + "Maybe using obj.something(", + (m.getParameterTypes().length != 0 ? "params" : ""), + ") instead of obj.something will yield the desired value" }; + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java new file mode 100644 index 0000000..85be491 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core.model.impl; + +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateHashModelEx2; +import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePair; +import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Implementation of {@link KeyValuePairIterator} for a {@link TemplateHashModelEx2} that wraps or otherwise uses a + * {@link Map} internally. + * + * @since 2.3.25 + */ +public class MapKeyValuePairIterator implements KeyValuePairIterator { + + private final Iterator<Entry<?, ?>> entrySetIterator; + + private final ObjectWrapper objectWrapper; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public <K, V> MapKeyValuePairIterator(Map<?, ?> map, ObjectWrapper objectWrapper) { + entrySetIterator = ((Map) map).entrySet().iterator(); + this.objectWrapper = objectWrapper; + } + + @Override + public boolean hasNext() { + return entrySetIterator.hasNext(); + } + + @Override + public KeyValuePair next() { + final Entry<?, ?> entry = entrySetIterator.next(); + return new KeyValuePair() { + + @Override + public TemplateModel getKey() throws TemplateModelException { + return wrap(entry.getKey()); + } + + @Override + public TemplateModel getValue() throws TemplateModelException { + return wrap(entry.getValue()); + } + + }; + } + + private TemplateModel wrap(Object obj) throws TemplateModelException { + return (obj instanceof TemplateModel) ? (TemplateModel) obj : objectWrapper.wrap(obj); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyCallableMemberDescriptor.java new file mode 100644 index 0000000..c713ed9 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MaybeEmptyMemberAndArguments.java new file mode 100644 index 0000000..d14a343 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAndArguments.java new file mode 100644 index 0000000..3a37d9d --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java new file mode 100644 index 0000000..c60599d --- /dev/null +++ b/freemarker-core/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.ExtendableBuilder#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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java new file mode 100644 index 0000000..9218bdf --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodSorter.java @@ -0,0 +1,36 @@ +/* + * 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; +import java.util.List; + +/** + * Used for JUnit testing method-order dependence bugs via + * {@link DefaultObjectWrapper.Builder#setMethodSorter(MethodSorter)}. + */ +interface MethodSorter { + + /** + * Sorts the methods in place (that is, by modifying the parameter list). + */ + void sortMethodDescriptors(List<MethodDescriptor> methodDescriptors); + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/NonPrimitiveArrayBackedReadOnlyList.java new file mode 100644 index 0000000..1d5bae8 --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java new file mode 100644 index 0000000..bff717d --- /dev/null +++ b/freemarker-core/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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java new file mode 100644 index 0000000..1ba1a56 --- /dev/null +++ b/freemarker-core/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(JavaMethodModel.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); + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java new file mode 100644 index 0000000..9a66a6d --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsModel.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core.model.impl; + + +import java.util.List; + +import org.apache.freemarker.core.model.TemplateMethodModel; +import org.apache.freemarker.core.model.TemplateMethodModelEx; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Wraps a set of same-name overloaded methods behind {@link TemplateMethodModel} interface, + * like if it was a single method, chooses among them behind the scenes on call-time based on the argument values. + */ +class OverloadedMethodsModel implements TemplateMethodModelEx { + private final Object object; + private final OverloadedMethods overloadedMethods; + private final DefaultObjectWrapper wrapper; + + OverloadedMethodsModel(Object object, OverloadedMethods overloadedMethods, DefaultObjectWrapper wrapper) { + this.object = object; + this.overloadedMethods = overloadedMethods; + this.wrapper = wrapper; + } + + /** + * Invokes the method, passing it the arguments from the list. The actual + * method to call from several overloaded methods will be chosen based + * on the classes of the arguments. + * @throws TemplateModelException if the method cannot be chosen + * unambiguously. + */ + @Override + public Object exec(List arguments) throws TemplateModelException { + MemberAndArguments maa = overloadedMethods.getMemberAndArguments(arguments, wrapper); + try { + return maa.invokeMethod(wrapper, object); + } catch (Exception e) { + if (e instanceof TemplateModelException) throw (TemplateModelException) e; + + throw _MethodUtil.newInvocationTemplateModelException( + object, + maa.getCallableMemberDescriptor(), + e); + } + } +}
