| /* |
| * 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 freemarker.ext.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 freemarker.template.Version; |
| |
| final class ClassIntrospectorBuilder implements Cloneable { |
| |
| private final boolean bugfixed; |
| |
| 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) { |
| bugfixed = ci.bugfixed; |
| 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. |
| bugfixed = BeansWrapper.is2321Bugfixed(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 + (bugfixed ? 1231 : 1237); |
| 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 (bugfixed != other.bugfixed) return false; |
| if (exposeFields != other.exposeFields) return false; |
| if (exposureLevel != other.exposureLevel) return false; |
| if (methodAppearanceFineTuner != other.methodAppearanceFineTuner) return false; |
| if (methodSorter != other.methodSorter) return false; |
| |
| return true; |
| } |
| |
| 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); |
| } |
| } |
| |
| public boolean isBugfixed() { |
| return bugfixed; |
| } |
| |
| } |