http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java deleted file mode 100644 index 94997cb..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BeansWrapperConfiguration.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -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.TemplateDateModel; -import org.apache.freemarker.core.model.impl.DefaultObjectWrapperBuilder; - -/** - * Holds {@link BeansWrapper} configuration settings and defines their defaults. - * You will not use this abstract class directly, but concrete subclasses like {@link BeansWrapperBuilder} and - * {@link DefaultObjectWrapperBuilder}. Unless, you are developing a builder for a custom {@link BeansWrapper} subclass. - * - * <p>This class is designed so that its instances can be used as lookup keys in a singleton cache. This is also why - * this class defines the configuration setting defaults for {@link BeansWrapper}, instead of leaving that to - * {@link BeansWrapper} itself. (Because, the default values influence the lookup key, and the singleton needs to be - * looked up without creating a {@link BeansWrapper} instance.) However, because instances are mutable, you should - * deep-clone it with {@link #clone(boolean)} before using it as cache key. - * - * @since 2.3.21 - */ -public abstract class BeansWrapperConfiguration 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 BeansWrapper#BeansWrapper(Version)}. Not {@code null}. Note - * that the version will be normalized to the lowest version where the same incompatible - * {@link BeansWrapper} 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 BeansWrapper} needs to add - * additional breaking versions over those of {@link BeansWrapper}. Thus, if this parameter is - * {@code true}, the versions where {@link BeansWrapper} had breaking changes must be already factored - * into the {@code incompatibleImprovements} parameter value, as no more normalization will happen. (You - * can use {@link BeansWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover those.) - * - * @since 2.3.22 - */ - protected BeansWrapperConfiguration(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) { - _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements); - - incompatibleImprovements = isIncompImprsAlreadyNormalized - ? incompatibleImprovements - : BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements); - this.incompatibleImprovements = incompatibleImprovements; - - classIntrospectorFactory = new ClassIntrospectorBuilder(incompatibleImprovements); - } - - /** - * Same as {@link #BeansWrapperConfiguration(Version, boolean) BeansWrapperConfiguration(Version, false)}. - */ - protected BeansWrapperConfiguration(Version incompatibleImprovements) { - 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 BeansWrapperConfiguration}-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; - BeansWrapperConfiguration other = (BeansWrapperConfiguration) 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 { - BeansWrapperConfiguration clone = (BeansWrapperConfiguration) super.clone(); - if (deepCloneKey) { - clone.classIntrospectorFactory - = (ClassIntrospectorBuilder) classIntrospectorFactory.clone(); - } - return clone; - } catch (CloneNotSupportedException e) { - throw new RuntimeException("Failed to clone BeansWrapperConfiguration", e); - } - } - - public boolean isSimpleMapWrapper() { - return simpleMapWrapper; - } - - /** See {@link BeansWrapper#setSimpleMapWrapper(boolean)}. */ - public void setSimpleMapWrapper(boolean simpleMapWrapper) { - this.simpleMapWrapper = simpleMapWrapper; - } - - public int getDefaultDateType() { - return defaultDateType; - } - - /** See {@link BeansWrapper#setDefaultDateType(int)}. */ - public void setDefaultDateType(int defaultDateType) { - this.defaultDateType = defaultDateType; - } - - public ObjectWrapper getOuterIdentity() { - return outerIdentity; - } - - /** - * See {@link BeansWrapper#setOuterIdentity(ObjectWrapper)}, except here the default is {@code null} that means - * the {@link ObjectWrapper} that you will set up with this {@link BeansWrapperBuilder} object. - */ - public void setOuterIdentity(ObjectWrapper outerIdentity) { - this.outerIdentity = outerIdentity; - } - - public boolean isStrict() { - return strict; - } - - /** See {@link BeansWrapper#setStrict(boolean)}. */ - public void setStrict(boolean strict) { - this.strict = strict; - } - - public boolean getUseModelCache() { - return useModelCache; - } - - /** See {@link BeansWrapper#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 BeansWrapper#setExposureLevel(int)}. */ - public void setExposureLevel(int exposureLevel) { - classIntrospectorFactory.setExposureLevel(exposureLevel); - } - - public boolean getExposeFields() { - return classIntrospectorFactory.getExposeFields(); - } - - /** See {@link BeansWrapper#setExposeFields(boolean)}. */ - public void setExposeFields(boolean exposeFields) { - classIntrospectorFactory.setExposeFields(exposeFields); - } - - public MethodAppearanceFineTuner getMethodAppearanceFineTuner() { - return classIntrospectorFactory.getMethodAppearanceFineTuner(); - } - - /** - * See {@link BeansWrapper#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/beans/BooleanModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/BooleanModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/BooleanModel.java deleted file mode 100644 index 857df36..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/BooleanModel.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import org.apache.freemarker.core.model.TemplateBooleanModel; - -/** - * <p>A class that will wrap instances of {@link java.lang.Boolean} into a - * {@link TemplateBooleanModel}. - */ -public class BooleanModel extends BeanModel implements TemplateBooleanModel { - private final boolean value; - - public BooleanModel(Boolean bool, BeansWrapper wrapper) { - super(bool, wrapper, false); - value = bool.booleanValue(); - } - - @Override - public boolean getAsBoolean() { - return value; - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java deleted file mode 100644 index fb55571..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CallableMemberDescriptor.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; - -/** - * Packs a {@link Method} or {@link Constructor} together with its parameter types. The actual - * {@link Method} or {@link Constructor} is not exposed by the API, because in rare cases calling them require - * type conversion that the Java reflection API can't do, hence the developer shouldn't be tempted to call them - * directly. - */ -abstract class CallableMemberDescriptor extends MaybeEmptyCallableMemberDescriptor { - - abstract TemplateModel invokeMethod(BeansWrapper bw, Object obj, Object[] args) - throws TemplateModelException, InvocationTargetException, IllegalAccessException; - - abstract Object invokeConstructor(BeansWrapper bw, Object[] args) - throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, - TemplateModelException; - - abstract String getDeclaration(); - - abstract boolean isConstructor(); - - abstract boolean isStatic(); - - abstract boolean isVarargs(); - - abstract Class[] getParamTypes(); - - abstract String getName(); - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java deleted file mode 100644 index 4bd297b..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CharacterOrString.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import org.apache.freemarker.core.model.TemplateScalarModel; - -/** - * Represents value unwrapped both to {@link Character} and {@link String}. This is needed for unwrapped overloaded - * method parameters where both {@link Character} and {@link String} occurs on the same parameter position when the - * {@link TemplateScalarModel} to unwrapp contains a {@link String} of length 1. - */ -final class CharacterOrString { - - private final String stringValue; - - CharacterOrString(String stringValue) { - this.stringValue = stringValue; - } - - String getAsString() { - return stringValue; - } - - char getAsChar() { - return stringValue.charAt(0); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java deleted file mode 100644 index 1fd4d61..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassBasedModelFactory.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.freemarker.core.model.TemplateHashModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.util._ClassUtil; - -/** - * Base class for hash models keyed by Java class names. - */ -abstract class ClassBasedModelFactory implements TemplateHashModel { - private final BeansWrapper wrapper; - - private final Map/*<String,TemplateModel>*/ cache = new ConcurrentHashMap(); - private final Set classIntrospectionsInProgress = new HashSet(); - - protected ClassBasedModelFactory(BeansWrapper wrapper) { - this.wrapper = wrapper; - } - - @Override - public TemplateModel get(String key) throws TemplateModelException { - try { - return getInternal(key); - } catch (Exception e) { - if (e instanceof TemplateModelException) { - throw (TemplateModelException) e; - } else { - throw new TemplateModelException(e); - } - } - } - - private TemplateModel getInternal(String key) throws TemplateModelException, ClassNotFoundException { - { - TemplateModel model = (TemplateModel) cache.get(key); - if (model != null) return model; - } - - final ClassIntrospector classIntrospector; - int classIntrospectorClearingCounter; - final Object sharedLock = wrapper.getSharedIntrospectionLock(); - synchronized (sharedLock) { - TemplateModel model = (TemplateModel) cache.get(key); - if (model != null) return model; - - while (model == null - && classIntrospectionsInProgress.contains(key)) { - // Another thread is already introspecting this class; - // waiting for its result. - try { - sharedLock.wait(); - model = (TemplateModel) cache.get(key); - } catch (InterruptedException e) { - throw new RuntimeException( - "Class inrospection data lookup aborded: " + e); - } - } - if (model != null) return model; - - // This will be the thread that introspects this class. - classIntrospectionsInProgress.add(key); - - // While the classIntrospector should not be changed from another thread, badly written apps can do that, - // and it's cheap to get the classIntrospector from inside the lock here: - classIntrospector = wrapper.getClassIntrospector(); - classIntrospectorClearingCounter = classIntrospector.getClearingCounter(); - } - try { - final Class clazz = _ClassUtil.forName(key); - - // This is called so that we trigger the - // class-reloading detector. If clazz is a reloaded class, - // the wrapper will in turn call our clearCache method. - // TODO: Why do we check it now and only now? - classIntrospector.get(clazz); - - TemplateModel model = createModel(clazz); - // Warning: model will be null if the class is not good for the subclass. - // For example, EnumModels#createModel returns null if clazz is not an enum. - - if (model != null) { - synchronized (sharedLock) { - // Save it into the cache, but only if nothing relevant has changed while we were outside the lock: - if (classIntrospector == wrapper.getClassIntrospector() - && classIntrospectorClearingCounter == classIntrospector.getClearingCounter()) { - cache.put(key, model); - } - } - } - return model; - } finally { - synchronized (sharedLock) { - classIntrospectionsInProgress.remove(key); - sharedLock.notifyAll(); - } - } - } - - void clearCache() { - synchronized (wrapper.getSharedIntrospectionLock()) { - cache.clear(); - } - } - - void removeFromCache(Class clazz) { - synchronized (wrapper.getSharedIntrospectionLock()) { - cache.remove(clazz.getName()); - } - } - - @Override - public boolean isEmpty() { - return false; - } - - protected abstract TemplateModel createModel(Class clazz) - throws TemplateModelException; - - protected BeansWrapper getWrapper() { - return wrapper; - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java deleted file mode 100644 index f85fafd..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassChangeNotifier.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -/** - * Reports when the non-private interface of a class was changed to the subscribers. - */ -interface ClassChangeNotifier { - - /** - * @param classIntrospector Should only be weak-referenced from the monitor object. - */ - void subscribe(ClassIntrospector classIntrospector); - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java deleted file mode 100644 index d1cf191..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospector.java +++ /dev/null @@ -1,808 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.beans.BeanInfo; -import java.beans.IndexedPropertyDescriptor; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.MethodDescriptor; -import java.beans.PropertyDescriptor; -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.freemarker.core._CoreLogs; -import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecision; -import org.apache.freemarker.core.model.impl.beans.BeansWrapper.MethodAppearanceDecisionInput; -import org.apache.freemarker.core.util.BugException; -import org.apache.freemarker.core.util._NullArgumentException; -import org.slf4j.Logger; - -/** - * Returns information about a {@link Class} that's useful for FreeMarker. Encapsulates a cache for this. Thread-safe, - * doesn't even require "proper publishing" starting from 2.3.24 or Java 5. Immutable, with the exception of the - * internal caches. - * - * <p> - * Note that instances of this are cached on the level of FreeMarker's defining class loader. Hence, it must not do - * operations that depend on the Thread Context Class Loader, such as resolving class names. - */ -class ClassIntrospector { - - // Attention: This class must be thread-safe (not just after proper publishing). This is important as some of - // these are shared by many object wrappers, and concurrency related glitches due to user errors must remain - // local to the object wrappers, not corrupting the shared ClassIntrospector. - - private static final Logger LOG = _CoreLogs.BEANS_WRAPPER; - - private static final String JREBEL_SDK_CLASS_NAME = "org.zeroturnaround.javarebel.ClassEventListener"; - private static final String JREBEL_INTEGRATION_ERROR_MSG - = "Error initializing JRebel integration. JRebel integration disabled."; - - private static final ClassChangeNotifier CLASS_CHANGE_NOTIFIER; - static { - boolean jRebelAvailable; - try { - Class.forName(JREBEL_SDK_CLASS_NAME); - jRebelAvailable = true; - } catch (Throwable e) { - jRebelAvailable = false; - try { - if (!(e instanceof ClassNotFoundException)) { - LOG.error(JREBEL_INTEGRATION_ERROR_MSG, e); - } - } catch (Throwable loggingE) { - // ignore - } - } - - ClassChangeNotifier classChangeNotifier; - if (jRebelAvailable) { - try { - classChangeNotifier = (ClassChangeNotifier) - Class.forName("org.apache.freemarker.core.model.impl.beans.JRebelClassChangeNotifier").newInstance(); - } catch (Throwable e) { - classChangeNotifier = null; - try { - LOG.error(JREBEL_INTEGRATION_ERROR_MSG, e); - } catch (Throwable loggingE) { - // ignore - } - } - } else { - classChangeNotifier = null; - } - - CLASS_CHANGE_NOTIFIER = classChangeNotifier; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Introspection info Map keys: - - /** Key in the class info Map to the Map that maps method to argument type arrays */ - private static final Object ARG_TYPES_BY_METHOD_KEY = new Object(); - /** Key in the class info Map to the object that represents the constructors (one or multiple due to overloading) */ - static final Object CONSTRUCTORS_KEY = new Object(); - /** Key in the class info Map to the get(String|Object) Method */ - static final Object GENERIC_GET_KEY = new Object(); - - // ----------------------------------------------------------------------------------------------------------------- - // Introspection configuration properties: - - // Note: These all must be *declared* final (or else synchronization is needed everywhere where they are accessed). - - final int exposureLevel; - final boolean exposeFields; - final MethodAppearanceFineTuner methodAppearanceFineTuner; - final MethodSorter methodSorter; - - /** See {@link #getHasSharedInstanceRestrictons()} */ - final private boolean hasSharedInstanceRestrictons; - - /** See {@link #isShared()} */ - final private boolean shared; - - // ----------------------------------------------------------------------------------------------------------------- - // State fields: - - private final Object sharedLock; - private final Map<Class<?>, Map<Object, Object>> cache - = new ConcurrentHashMap<>(0, 0.75f, 16); - private final Set<String> cacheClassNames = new HashSet<>(0); - private final Set<Class<?>> classIntrospectionsInProgress = new HashSet<>(0); - - private final List<WeakReference<Object/*ClassBasedModelFactory|ModelCache>*/>> modelFactories - = new LinkedList<>(); - private final ReferenceQueue<Object> modelFactoriesRefQueue = new ReferenceQueue<>(); - - private int clearingCounter; - - // ----------------------------------------------------------------------------------------------------------------- - // Instantiation: - - /** - * Creates a new instance, that is hence surely not shared (singleton) instance. - * - * @param pa - * Stores what the values of the JavaBean properties of the returned instance will be. Not {@code null}. - */ - ClassIntrospector(ClassIntrospectorBuilder pa, Object sharedLock) { - this(pa, sharedLock, false, false); - } - - /** - * @param hasSharedInstanceRestrictons - * {@code true} exactly if we are creating a new instance with {@link ClassIntrospectorBuilder}. Then - * it's {@code true} even if it won't put the instance into the cache. - */ - ClassIntrospector(ClassIntrospectorBuilder builder, Object sharedLock, - boolean hasSharedInstanceRestrictons, boolean shared) { - _NullArgumentException.check("sharedLock", sharedLock); - - exposureLevel = builder.getExposureLevel(); - exposeFields = builder.getExposeFields(); - methodAppearanceFineTuner = builder.getMethodAppearanceFineTuner(); - methodSorter = builder.getMethodSorter(); - - this.sharedLock = sharedLock; - - this.hasSharedInstanceRestrictons = hasSharedInstanceRestrictons; - this.shared = shared; - - if (CLASS_CHANGE_NOTIFIER != null) { - CLASS_CHANGE_NOTIFIER.subscribe(this); - } - } - - /** - * Returns a {@link ClassIntrospectorBuilder}-s that could be used to create an identical {@link #ClassIntrospector} - * . The returned {@link ClassIntrospectorBuilder} can be modified without interfering with anything. - */ - ClassIntrospectorBuilder getPropertyAssignments() { - return new ClassIntrospectorBuilder(this); - } - - // ------------------------------------------------------------------------------------------------------------------ - // Introspection: - - /** - * Gets the class introspection data from {@link #cache}, automatically creating the cache entry if it's missing. - * - * @return A {@link Map} where each key is a property/method/field name (or a special {@link Object} key like - * {@link #CONSTRUCTORS_KEY}), each value is a {@link PropertyDescriptor} or {@link Method} or - * {@link OverloadedMethods} or {@link Field} (but better check the source code...). - */ - Map<Object, Object> get(Class<?> clazz) { - { - Map<Object, Object> introspData = cache.get(clazz); - if (introspData != null) return introspData; - } - - String className; - synchronized (sharedLock) { - Map<Object, Object> introspData = cache.get(clazz); - if (introspData != null) return introspData; - - className = clazz.getName(); - if (cacheClassNames.contains(className)) { - onSameNameClassesDetected(className); - } - - while (introspData == null && classIntrospectionsInProgress.contains(clazz)) { - // Another thread is already introspecting this class; - // waiting for its result. - try { - sharedLock.wait(); - introspData = cache.get(clazz); - } catch (InterruptedException e) { - throw new RuntimeException( - "Class inrospection data lookup aborded: " + e); - } - } - if (introspData != null) return introspData; - - // This will be the thread that introspects this class. - classIntrospectionsInProgress.add(clazz); - } - try { - Map<Object, Object> introspData = createClassIntrospectionData(clazz); - synchronized (sharedLock) { - cache.put(clazz, introspData); - cacheClassNames.add(className); - } - return introspData; - } finally { - synchronized (sharedLock) { - classIntrospectionsInProgress.remove(clazz); - sharedLock.notifyAll(); - } - } - } - - /** - * Creates a {@link Map} with the content as described for the return value of {@link #get(Class)}. - */ - private Map<Object, Object> createClassIntrospectionData(Class<?> clazz) { - final Map<Object, Object> introspData = new HashMap<>(); - - if (exposeFields) { - addFieldsToClassIntrospectionData(introspData, clazz); - } - - final Map<MethodSignature, List<Method>> accessibleMethods = discoverAccessibleMethods(clazz); - - addGenericGetToClassIntrospectionData(introspData, accessibleMethods); - - if (exposureLevel != BeansWrapper.EXPOSE_NOTHING) { - try { - addBeanInfoToClassIntrospectionData(introspData, clazz, accessibleMethods); - } catch (IntrospectionException e) { - LOG.warn("Couldn't properly perform introspection for class {}", clazz.getName(), e); - introspData.clear(); // FIXME NBC: Don't drop everything here. - } - } - - addConstructorsToClassIntrospectionData(introspData, clazz); - - if (introspData.size() > 1) { - return introspData; - } else if (introspData.size() == 0) { - return Collections.emptyMap(); - } else { // map.size() == 1 - Entry<Object, Object> e = introspData.entrySet().iterator().next(); - return Collections.singletonMap(e.getKey(), e.getValue()); - } - } - - private void addFieldsToClassIntrospectionData(Map<Object, Object> introspData, Class<?> clazz) - throws SecurityException { - for (Field field : clazz.getFields()) { - if ((field.getModifiers() & Modifier.STATIC) == 0) { - introspData.put(field.getName(), field); - } - } - } - - private void addBeanInfoToClassIntrospectionData( - Map<Object, Object> introspData, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods) - throws IntrospectionException { - BeanInfo beanInfo = Introspector.getBeanInfo(clazz); - - PropertyDescriptor[] pda = beanInfo.getPropertyDescriptors(); - if (pda != null) { - int pdaLength = pda.length; - for (int i = pdaLength - 1; i >= 0; --i) { - addPropertyDescriptorToClassIntrospectionData( - introspData, pda[i], clazz, - accessibleMethods); - } - } - - if (exposureLevel < BeansWrapper.EXPOSE_PROPERTIES_ONLY) { - final MethodAppearanceDecision decision = new MethodAppearanceDecision(); - MethodAppearanceDecisionInput decisionInput = null; - final MethodDescriptor[] mda = sortMethodDescriptors(beanInfo.getMethodDescriptors()); - if (mda != null) { - int mdaLength = mda.length; - for (int i = mdaLength - 1; i >= 0; --i) { - final MethodDescriptor md = mda[i]; - final Method method = getMatchingAccessibleMethod(md.getMethod(), accessibleMethods); - if (method != null && isAllowedToExpose(method)) { - decision.setDefaults(method); - if (methodAppearanceFineTuner != null) { - if (decisionInput == null) { - decisionInput = new MethodAppearanceDecisionInput(); - } - decisionInput.setContainingClass(clazz); - decisionInput.setMethod(method); - - methodAppearanceFineTuner.process(decisionInput, decision); - } - - PropertyDescriptor propDesc = decision.getExposeAsProperty(); - if (propDesc != null && !(introspData.get(propDesc.getName()) instanceof PropertyDescriptor)) { - addPropertyDescriptorToClassIntrospectionData( - introspData, propDesc, clazz, accessibleMethods); - } - - String methodKey = decision.getExposeMethodAs(); - if (methodKey != null) { - Object previous = introspData.get(methodKey); - if (previous instanceof Method) { - // Overloaded method - replace Method with a OverloadedMethods - OverloadedMethods overloadedMethods = new OverloadedMethods(); - overloadedMethods.addMethod((Method) previous); - overloadedMethods.addMethod(method); - introspData.put(methodKey, overloadedMethods); - // Remove parameter type information - getArgTypesByMethod(introspData).remove(previous); - } else if (previous instanceof OverloadedMethods) { - // Already overloaded method - add new overload - ((OverloadedMethods) previous).addMethod(method); - } else if (decision.getMethodShadowsProperty() - || !(previous instanceof PropertyDescriptor)) { - // Simple method (this far) - introspData.put(methodKey, method); - getArgTypesByMethod(introspData).put(method, - method.getParameterTypes()); - } - } - } - } // for each in mda - } // if mda != null - } // end if (exposureLevel < EXPOSE_PROPERTIES_ONLY) - } - - private void addPropertyDescriptorToClassIntrospectionData(Map<Object, Object> introspData, - PropertyDescriptor pd, Class<?> clazz, Map<MethodSignature, List<Method>> accessibleMethods) { - if (pd instanceof IndexedPropertyDescriptor) { - IndexedPropertyDescriptor ipd = - (IndexedPropertyDescriptor) pd; - Method readMethod = ipd.getIndexedReadMethod(); - Method publicReadMethod = getMatchingAccessibleMethod(readMethod, accessibleMethods); - if (publicReadMethod != null && isAllowedToExpose(publicReadMethod)) { - try { - if (readMethod != publicReadMethod) { - ipd = new IndexedPropertyDescriptor( - ipd.getName(), ipd.getReadMethod(), - null, publicReadMethod, - null); - } - introspData.put(ipd.getName(), ipd); - getArgTypesByMethod(introspData).put(publicReadMethod, publicReadMethod.getParameterTypes()); - } catch (IntrospectionException e) { - LOG.warn("Failed creating a publicly-accessible property descriptor " - + "for {} indexed property {}, read method {}", - clazz.getName(), pd.getName(), publicReadMethod, - e); - } - } - } else { - Method readMethod = pd.getReadMethod(); - Method publicReadMethod = getMatchingAccessibleMethod(readMethod, accessibleMethods); - if (publicReadMethod != null && isAllowedToExpose(publicReadMethod)) { - try { - if (readMethod != publicReadMethod) { - pd = new PropertyDescriptor(pd.getName(), publicReadMethod, null); - pd.setReadMethod(publicReadMethod); - } - introspData.put(pd.getName(), pd); - } catch (IntrospectionException e) { - LOG.warn("Failed creating a publicly-accessible property descriptor " - + "for {} property {}, read method {}", - clazz.getName(), pd.getName(), publicReadMethod, - e); - } - } - } - } - - private void addGenericGetToClassIntrospectionData(Map<Object, Object> introspData, - Map<MethodSignature, List<Method>> accessibleMethods) { - Method genericGet = getFirstAccessibleMethod( - MethodSignature.GET_STRING_SIGNATURE, accessibleMethods); - if (genericGet == null) { - genericGet = getFirstAccessibleMethod( - MethodSignature.GET_OBJECT_SIGNATURE, accessibleMethods); - } - if (genericGet != null) { - introspData.put(GENERIC_GET_KEY, genericGet); - } - } - - private void addConstructorsToClassIntrospectionData(final Map<Object, Object> introspData, - Class<?> clazz) { - try { - Constructor<?>[] ctors = clazz.getConstructors(); - if (ctors.length == 1) { - Constructor<?> ctor = ctors[0]; - introspData.put(CONSTRUCTORS_KEY, new SimpleMethod(ctor, ctor.getParameterTypes())); - } else if (ctors.length > 1) { - OverloadedMethods overloadedCtors = new OverloadedMethods(); - for (Constructor<?> ctor : ctors) { - overloadedCtors.addConstructor(ctor); - } - introspData.put(CONSTRUCTORS_KEY, overloadedCtors); - } - } catch (SecurityException e) { - LOG.warn("Can't discover constructors for class {}", clazz.getName(), e); - } - } - - /** - * Retrieves mapping of {@link MethodSignature}-s to a {@link List} of accessible methods for a class. In case the - * class is not public, retrieves methods with same signature as its public methods from public superclasses and - * interfaces. Basically upcasts every method to the nearest accessible method. - */ - private static Map<MethodSignature, List<Method>> discoverAccessibleMethods(Class<?> clazz) { - Map<MethodSignature, List<Method>> accessibles = new HashMap<>(); - discoverAccessibleMethods(clazz, accessibles); - return accessibles; - } - - private static void discoverAccessibleMethods(Class<?> clazz, Map<MethodSignature, List<Method>> accessibles) { - if (Modifier.isPublic(clazz.getModifiers())) { - try { - Method[] methods = clazz.getMethods(); - for (Method method : methods) { - MethodSignature sig = new MethodSignature(method); - // Contrary to intuition, a class can actually have several - // different methods with same signature *but* different - // return types. These can't be constructed using Java the - // language, as this is illegal on source code level, but - // the compiler can emit synthetic methods as part of - // generic type reification that will have same signature - // yet different return type than an existing explicitly - // declared method. Consider: - // public interface I<T> { T m(); } - // public class C implements I<Integer> { Integer m() { return 42; } } - // C.class will have both "Object m()" and "Integer m()" methods. - List<Method> methodList = accessibles.get(sig); - if (methodList == null) { - // TODO Collection.singletonList is more efficient, though read only. - methodList = new LinkedList<>(); - accessibles.put(sig, methodList); - } - methodList.add(method); - } - return; - } catch (SecurityException e) { - LOG.warn("Could not discover accessible methods of class {}, attemping superclasses/interfaces.", - clazz.getName(), e); - // Fall through and attempt to discover superclass/interface methods - } - } - - Class<?>[] interfaces = clazz.getInterfaces(); - for (Class<?> anInterface : interfaces) { - discoverAccessibleMethods(anInterface, accessibles); - } - Class<?> superclass = clazz.getSuperclass(); - if (superclass != null) { - discoverAccessibleMethods(superclass, accessibles); - } - } - - private static Method getMatchingAccessibleMethod(Method m, Map<MethodSignature, List<Method>> accessibles) { - if (m == null) { - return null; - } - MethodSignature sig = new MethodSignature(m); - List<Method> ams = accessibles.get(sig); - if (ams == null) { - return null; - } - for (Method am : ams) { - if (am.getReturnType() == m.getReturnType()) { - return am; - } - } - return null; - } - - private static Method getFirstAccessibleMethod(MethodSignature sig, Map<MethodSignature, List<Method>> accessibles) { - List<Method> ams = accessibles.get(sig); - if (ams == null || ams.isEmpty()) { - return null; - } - return ams.get(0); - } - - /** - * As of this writing, this is only used for testing if method order really doesn't mater. - */ - private MethodDescriptor[] sortMethodDescriptors(MethodDescriptor[] methodDescriptors) { - return methodSorter != null ? methodSorter.sortMethodDescriptors(methodDescriptors) : methodDescriptors; - } - - boolean isAllowedToExpose(Method method) { - return exposureLevel < BeansWrapper.EXPOSE_SAFE || !UnsafeMethods.isUnsafeMethod(method); - } - - private static Map<Method, Class<?>[]> getArgTypesByMethod(Map<Object, Object> classInfo) { - @SuppressWarnings("unchecked") - Map<Method, Class<?>[]> argTypes = (Map<Method, Class<?>[]>) classInfo.get(ARG_TYPES_BY_METHOD_KEY); - if (argTypes == null) { - argTypes = new HashMap<>(); - classInfo.put(ARG_TYPES_BY_METHOD_KEY, argTypes); - } - return argTypes; - } - - private static final class MethodSignature { - private static final MethodSignature GET_STRING_SIGNATURE = - new MethodSignature("get", new Class[] { String.class }); - private static final MethodSignature GET_OBJECT_SIGNATURE = - new MethodSignature("get", new Class[] { Object.class }); - - private final String name; - private final Class<?>[] args; - - private MethodSignature(String name, Class<?>[] args) { - this.name = name; - this.args = args; - } - - MethodSignature(Method method) { - this(method.getName(), method.getParameterTypes()); - } - - @Override - public boolean equals(Object o) { - if (o instanceof MethodSignature) { - MethodSignature ms = (MethodSignature) o; - return ms.name.equals(name) && Arrays.equals(args, ms.args); - } - return false; - } - - @Override - public int hashCode() { - return name.hashCode() ^ args.length; // TODO That's a poor quality hash... isn't this a problem? - } - } - - // ----------------------------------------------------------------------------------------------------------------- - // Cache management: - - /** - * Corresponds to {@link BeansWrapper#clearClassIntrospecitonCache()}. - * - * @since 2.3.20 - */ - void clearCache() { - if (getHasSharedInstanceRestrictons()) { - throw new IllegalStateException( - "It's not allowed to clear the whole cache in a read-only " + getClass().getName() + - "instance. Use removeFromClassIntrospectionCache(String prefix) instead."); - } - forcedClearCache(); - } - - private void forcedClearCache() { - synchronized (sharedLock) { - cache.clear(); - cacheClassNames.clear(); - clearingCounter++; - - for (WeakReference<Object> regedMfREf : modelFactories) { - Object regedMf = regedMfREf.get(); - if (regedMf != null) { - if (regedMf instanceof ClassBasedModelFactory) { - ((ClassBasedModelFactory) regedMf).clearCache(); - } else if (regedMf instanceof ModelCache) { - ((ModelCache) regedMf).clearCache(); - } else { - throw new BugException(); - } - } - } - - removeClearedModelFactoryReferences(); - } - } - - /** - * Corresponds to {@link BeansWrapper#removeFromClassIntrospectionCache(Class)}. - * - * @since 2.3.20 - */ - void remove(Class<?> clazz) { - synchronized (sharedLock) { - cache.remove(clazz); - cacheClassNames.remove(clazz.getName()); - clearingCounter++; - - for (WeakReference<Object> regedMfREf : modelFactories) { - Object regedMf = regedMfREf.get(); - if (regedMf != null) { - if (regedMf instanceof ClassBasedModelFactory) { - ((ClassBasedModelFactory) regedMf).removeFromCache(clazz); - } else if (regedMf instanceof ModelCache) { - ((ModelCache) regedMf).clearCache(); // doesn't support selective clearing ATM - } else { - throw new BugException(); - } - } - } - - removeClearedModelFactoryReferences(); - } - } - - /** - * Returns the number of events so far that could make class introspection data returned earlier outdated. - */ - int getClearingCounter() { - synchronized (sharedLock) { - return clearingCounter; - } - } - - private void onSameNameClassesDetected(String className) { - // TODO: This behavior should be pluggable, as in environments where - // some classes are often reloaded or multiple versions of the - // same class is normal (OSGi), this will drop the cache contents - // too often. - LOG.info( - "Detected multiple classes with the same name, \"{}\". " - + "Assuming it was a class-reloading. Clearing class introspection caches to release old data.", - className); - forcedClearCache(); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Managing dependent objects: - - void registerModelFactory(ClassBasedModelFactory mf) { - registerModelFactory((Object) mf); - } - - void registerModelFactory(ModelCache mf) { - registerModelFactory((Object) mf); - } - - private void registerModelFactory(Object mf) { - // Note that this `synchronized (sharedLock)` is also need for the BeansWrapper constructor to work safely. - synchronized (sharedLock) { - modelFactories.add(new WeakReference<>(mf, modelFactoriesRefQueue)); - removeClearedModelFactoryReferences(); - } - } - - void unregisterModelFactory(ClassBasedModelFactory mf) { - unregisterModelFactory((Object) mf); - } - - void unregisterModelFactory(ModelCache mf) { - unregisterModelFactory((Object) mf); - } - - void unregisterModelFactory(Object mf) { - synchronized (sharedLock) { - for (Iterator<WeakReference<Object>> it = modelFactories.iterator(); it.hasNext(); ) { - Object regedMf = it.next().get(); - if (regedMf == mf) { - it.remove(); - } - } - - } - } - - private void removeClearedModelFactoryReferences() { - Reference<?> cleardRef; - while ((cleardRef = modelFactoriesRefQueue.poll()) != null) { - synchronized (sharedLock) { - findClearedRef: for (Iterator<WeakReference<Object>> it = modelFactories.iterator(); it.hasNext(); ) { - if (it.next() == cleardRef) { - it.remove(); - break findClearedRef; - } - } - } - } - } - - // ----------------------------------------------------------------------------------------------------------------- - // Extracting from introspection info: - - static Class<?>[] getArgTypes(Map<Object, Object> classInfo, Method method) { - @SuppressWarnings("unchecked") - Map<Method, Class<?>[]> argTypesByMethod = (Map<Method, Class<?>[]>) classInfo.get(ARG_TYPES_BY_METHOD_KEY); - return argTypesByMethod.get(method); - } - - /** - * Returns the number of introspected methods/properties that should be available via the TemplateHashModel - * interface. - */ - int keyCount(Class<?> clazz) { - Map<Object, Object> map = get(clazz); - int count = map.size(); - if (map.containsKey(CONSTRUCTORS_KEY)) count--; - if (map.containsKey(GENERIC_GET_KEY)) count--; - if (map.containsKey(ARG_TYPES_BY_METHOD_KEY)) count--; - return count; - } - - /** - * Returns the Set of names of introspected methods/properties that should be available via the TemplateHashModel - * interface. - */ - Set<Object> keySet(Class<?> clazz) { - Set<Object> set = new HashSet<>(get(clazz).keySet()); - set.remove(CONSTRUCTORS_KEY); - set.remove(GENERIC_GET_KEY); - set.remove(ARG_TYPES_BY_METHOD_KEY); - return set; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Properties - - int getExposureLevel() { - return exposureLevel; - } - - boolean getExposeFields() { - return exposeFields; - } - - MethodAppearanceFineTuner getMethodAppearanceFineTuner() { - return methodAppearanceFineTuner; - } - - MethodSorter getMethodSorter() { - return methodSorter; - } - - /** - * Returns {@code true} if this instance was created with {@link ClassIntrospectorBuilder}, even if it wasn't - * actually put into the cache (as we reserve the right to do so in later versions). - */ - boolean getHasSharedInstanceRestrictons() { - return hasSharedInstanceRestrictons; - } - - /** - * Tells if this instance is (potentially) shared among {@link BeansWrapper} instances. - * - * @see #getHasSharedInstanceRestrictons() - */ - boolean isShared() { - return shared; - } - - /** - * Almost always, you want to use {@link BeansWrapper#getSharedIntrospectionLock()}, not this! The only exception is - * when you get this to set the field returned by {@link BeansWrapper#getSharedIntrospectionLock()}. - */ - Object getSharedLock() { - return sharedLock; - } - - // ----------------------------------------------------------------------------------------------------------------- - // Monitoring: - - /** For unit testing only */ - Object[] getRegisteredModelFactoriesSnapshot() { - synchronized (sharedLock) { - return modelFactories.toArray(); - } - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospectorBuilder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospectorBuilder.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospectorBuilder.java deleted file mode 100644 index c6f6cb6..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/ClassIntrospectorBuilder.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -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.Version; -import org.apache.freemarker.core.util._NullArgumentException; - -final class ClassIntrospectorBuilder implements Cloneable { - - private static final Map/*<PropertyAssignments, Reference<ClassIntrospector>>*/ INSTANCE_CACHE = new HashMap(); - private static final ReferenceQueue INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue(); - - // Properties and their *defaults*: - private int exposureLevel = BeansWrapper.EXPOSE_SAFE; - private boolean exposeFields; - private MethodAppearanceFineTuner methodAppearanceFineTuner; - private MethodSorter methodSorter; - // Attention: - // - This is also used as a cache key, so non-normalized field values should be avoided. - // - If some field has a default value, it must be set until the end of the constructor. No field that has a - // default can be left unset (like null). - // - If you add a new field, review all methods in this class, also the ClassIntrospector constructor - - ClassIntrospectorBuilder(ClassIntrospector ci) { - exposureLevel = ci.exposureLevel; - exposeFields = ci.exposeFields; - methodAppearanceFineTuner = ci.methodAppearanceFineTuner; - methodSorter = ci.methodSorter; - } - - ClassIntrospectorBuilder(Version incompatibleImprovements) { - // Warning: incompatibleImprovements must not affect this object at versions increments where there's no - // change in the BeansWrapper.normalizeIncompatibleImprovements results. That is, this class may don't react - // to some version changes that affects BeansWrapper, but not the other way around. - _NullArgumentException.check(incompatibleImprovements); - // Currently nothing depends on incompatibleImprovements - } - - @Override - protected Object clone() { - try { - return super.clone(); - } catch (CloneNotSupportedException e) { - throw new RuntimeException("Failed to clone ClassIntrospectorBuilder", e); - } - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (exposeFields ? 1231 : 1237); - result = prime * result + exposureLevel; - result = prime * result + System.identityHashCode(methodAppearanceFineTuner); - result = prime * result + System.identityHashCode(methodSorter); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - ClassIntrospectorBuilder other = (ClassIntrospectorBuilder) obj; - - if (exposeFields != other.exposeFields) return false; - if (exposureLevel != other.exposureLevel) return false; - if (methodAppearanceFineTuner != other.methodAppearanceFineTuner) return false; - return methodSorter == other.methodSorter; - } - - public int getExposureLevel() { - return exposureLevel; - } - - /** See {@link BeansWrapper#setExposureLevel(int)}. */ - public void setExposureLevel(int exposureLevel) { - if (exposureLevel < BeansWrapper.EXPOSE_ALL || exposureLevel > BeansWrapper.EXPOSE_NOTHING) { - throw new IllegalArgumentException("Illegal exposure level: " + exposureLevel); - } - - this.exposureLevel = exposureLevel; - } - - public boolean getExposeFields() { - return exposeFields; - } - - /** See {@link BeansWrapper#setExposeFields(boolean)}. */ - public void setExposeFields(boolean exposeFields) { - this.exposeFields = exposeFields; - } - - public MethodAppearanceFineTuner getMethodAppearanceFineTuner() { - return methodAppearanceFineTuner; - } - - public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) { - this.methodAppearanceFineTuner = methodAppearanceFineTuner; - } - - public MethodSorter getMethodSorter() { - return methodSorter; - } - - public void setMethodSorter(MethodSorter methodSorter) { - this.methodSorter = methodSorter; - } - - private static void removeClearedReferencesFromInstanceCache() { - Reference clearedRef; - while ((clearedRef = INSTANCE_CACHE_REF_QUEUE.poll()) != null) { - synchronized (INSTANCE_CACHE) { - findClearedRef: for (Iterator it = INSTANCE_CACHE.values().iterator(); it.hasNext(); ) { - if (it.next() == clearedRef) { - it.remove(); - break findClearedRef; - } - } - } - } - } - - /** For unit testing only */ - static void clearInstanceCache() { - synchronized (INSTANCE_CACHE) { - INSTANCE_CACHE.clear(); - } - } - - /** For unit testing only */ - static Map getInstanceCache() { - return INSTANCE_CACHE; - } - - /** - * Returns an instance that is possibly shared (singleton). Note that this comes with its own "shared lock", - * since everyone who uses this object will have to lock with that common object. - */ - ClassIntrospector build() { - if ((methodAppearanceFineTuner == null || methodAppearanceFineTuner instanceof SingletonCustomizer) - && (methodSorter == null || methodSorter instanceof SingletonCustomizer)) { - // Instance can be cached. - ClassIntrospector instance; - synchronized (INSTANCE_CACHE) { - Reference instanceRef = (Reference) INSTANCE_CACHE.get(this); - instance = instanceRef != null ? (ClassIntrospector) instanceRef.get() : null; - if (instance == null) { - ClassIntrospectorBuilder thisClone = (ClassIntrospectorBuilder) clone(); // prevent any aliasing issues - instance = new ClassIntrospector(thisClone, new Object(), true, true); - INSTANCE_CACHE.put(thisClone, new WeakReference(instance, INSTANCE_CACHE_REF_QUEUE)); - } - } - - removeClearedReferencesFromInstanceCache(); - - return instance; - } else { - // If methodAppearanceFineTuner or methodSorter is specified and isn't marked as a singleton, the - // ClassIntrospector can't be shared/cached as those objects could contain a back-reference to the - // BeansWrapper. - return new ClassIntrospector(this, new Object(), true, false); - } - } - -} \ 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/beans/CollectionAdapter.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionAdapter.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionAdapter.java deleted file mode 100644 index 74db4cb..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionAdapter.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.util.AbstractCollection; -import java.util.Collection; -import java.util.Iterator; - -import org.apache.freemarker.core.model.TemplateCollectionModel; -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 TemplateCollectionModel} to {@link Collection}. - */ -class CollectionAdapter extends AbstractCollection implements TemplateModelAdapter { - private final BeansWrapper wrapper; - private final TemplateCollectionModel model; - - CollectionAdapter(TemplateCollectionModel model, BeansWrapper wrapper) { - this.model = model; - this.wrapper = wrapper; - } - - @Override - public TemplateModel getTemplateModel() { - return model; - } - - @Override - public int size() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterator iterator() { - try { - return new Iterator() { - final TemplateModelIterator i = model.iterator(); - - @Override - public boolean hasNext() { - try { - return i.hasNext(); - } catch (TemplateModelException e) { - throw new UndeclaredThrowableException(e); - } - } - - @Override - public Object next() { - try { - return wrapper.unwrap(i.next()); - } catch (TemplateModelException e) { - throw new UndeclaredThrowableException(e); - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } catch (TemplateModelException e) { - throw new UndeclaredThrowableException(e); - } - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java deleted file mode 100644 index d27a56d..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/CollectionModel.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.util.Collection; -import java.util.List; - -import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.TemplateCollectionModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.model.TemplateSequenceModel; - -/** - * <p>A special case of {@link BeanModel} that can wrap Java collections - * and that implements the {@link TemplateCollectionModel} in order to be usable - * in a <tt><#list></tt> block.</p> - */ -public class CollectionModel -extends - StringModel -implements - TemplateCollectionModel, - TemplateSequenceModel { - static final ModelFactory FACTORY = - new ModelFactory() - { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new CollectionModel((Collection) object, (BeansWrapper) wrapper); - } - }; - - - /** - * Creates a new model that wraps the specified collection object. - * @param collection the collection object to wrap into a model. - * @param wrapper the {@link BeansWrapper} associated with this model. - * Every model has to have an associated {@link BeansWrapper} instance. The - * model gains many attributes from its wrapper, including the caching - * behavior, method exposure level, method-over-item shadowing policy etc. - */ - public CollectionModel(Collection collection, BeansWrapper wrapper) { - super(collection, wrapper); - } - - /** - * Retrieves the i-th object from the collection, wrapped as a TemplateModel. - * @throws TemplateModelException if the index is out of bounds, or the - * underlying collection is not a List. - */ - @Override - public TemplateModel get(int index) - throws TemplateModelException { - // Don't forget to keep getSupportsIndexedAccess in sync with this! - if (object instanceof List) { - try { - return wrap(((List) object).get(index)); - } catch (IndexOutOfBoundsException e) { - return null; -// throw new TemplateModelException("Index out of bounds: " + index); - } - } else { - throw new TemplateModelException("Underlying collection is not a list, it's " + object.getClass().getName()); - } - } - - /** - * Tells if {@link #get(int)} will always fail for this object. - * As this object implements {@link TemplateSequenceModel}, - * {@link #get(int)} should always work, but due to a design flaw, for - * non-{@link List} wrapped objects {@link #get(int)} will always fail. - * This method exists to ease working this problem around. - * - * @since 2.3.17 - */ - public boolean getSupportsIndexedAccess() { - return object instanceof List; - } - - @Override - public TemplateModelIterator iterator() { - return new IteratorModel(((Collection) object).iterator(), wrapper); - } - - @Override - public int size() { - return ((Collection) object).size(); - } - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java deleted file mode 100644 index 55419cd..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/DateModel.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -import java.util.Date; - -import org.apache.freemarker.core.model.ObjectWrapper; -import org.apache.freemarker.core.model.TemplateDateModel; -import org.apache.freemarker.core.model.TemplateModel; - -/** - * Wraps arbitrary subclass of {@link java.util.Date} into a reflective model. - * Beside acting as a {@link TemplateDateModel}, you can call all Java methods - * on these objects as well. - */ -public class DateModel -extends - BeanModel -implements - TemplateDateModel { - static final ModelFactory FACTORY = - new ModelFactory() - { - @Override - public TemplateModel create(Object object, ObjectWrapper wrapper) { - return new DateModel((Date) object, (BeansWrapper) wrapper); - } - }; - - private final int type; - - /** - * Creates a new model that wraps the specified date object. - * @param date the date object to wrap into a model. - * @param wrapper the {@link BeansWrapper} associated with this model. - * Every model has to have an associated {@link BeansWrapper} instance. The - * model gains many attributes from its wrapper, including the caching - * behavior, method exposure level, method-over-item shadowing policy etc. - */ - public DateModel(Date date, BeansWrapper wrapper) { - super(date, wrapper); - if (date instanceof java.sql.Date) { - type = DATE; - } else if (date instanceof java.sql.Time) { - type = TIME; - } else if (date instanceof java.sql.Timestamp) { - type = DATETIME; - } else { - type = wrapper.getDefaultDateType(); - } - } - - @Override - public Date getAsDate() { - return (Date) object; - } - - @Override - public int getDateType() { - return type; - } -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/051a0822/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java deleted file mode 100644 index d0e6b8e..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyCallableMemberDescriptor.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -/** - * 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/beans/EmptyMemberAndArguments.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyMemberAndArguments.java b/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyMemberAndArguments.java deleted file mode 100644 index 2e078c9..0000000 --- a/src/main/java/org/apache/freemarker/core/model/impl/beans/EmptyMemberAndArguments.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model.impl.beans; - -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; - } - -}