Repository: incubator-freemarker Updated Branches: refs/heads/3 dceec32ed -> c04f5b517
Cleaned up DefaultObjectWrapper TCCL singleton caching Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/c04f5b51 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/c04f5b51 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/c04f5b51 Branch: refs/heads/3 Commit: c04f5b5173046463cb8e9f14bd73d9c3fd045768 Parents: dceec32 Author: ddekany <[email protected]> Authored: Sat Mar 18 11:05:27 2017 +0100 Committer: ddekany <[email protected]> Committed: Sat Mar 18 11:05:27 2017 +0100 ---------------------------------------------------------------------- .../core/model/impl/DefaultObjectWrapper.java | 179 +++++++++++-------- .../DefaultObjectWrapperTCCLSingletonUtil.java | 129 +++++++++++++ .../model/impl/RestrictedObjectWrapper.java | 29 ++- .../freemarker/core/model/impl/_ModelAPI.java | 95 ---------- 4 files changed, 260 insertions(+), 172 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c04f5b51/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java index da6c675..3e31739 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java @@ -1281,11 +1281,27 @@ public class DefaultObjectWrapper implements RichObjectWrapper { * a singleton that is also in use elsewhere. */ public DefaultObjectWrapper build() { - return _ModelAPI.getDefaultObjectWrapperSubclassSingleton( + return DefaultObjectWrapperTCCLSingletonUtil.getSingleton( this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, ConstructorInvoker.INSTANCE); } /** + * Calls {@link ExtendableBuilder#hashCodeForCacheKey(ExtendableBuilder)}. + */ + @Override + public int hashCode() { + return hashCodeForCacheKey(this); + } + + /** + * Calls {@link ExtendableBuilder#equalsForCacheKey(ExtendableBuilder, Object)}. + */ + @Override + public boolean equals(Object obj) { + return equalsForCacheKey(this, obj); + } + + /** * For unit testing only */ static Map<ClassLoader, Map<Builder, WeakReference<DefaultObjectWrapper>>> getInstanceCache() { @@ -1293,7 +1309,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper { } private static class ConstructorInvoker - implements _ModelAPI._ConstructorInvoker<DefaultObjectWrapper, Builder> { + implements DefaultObjectWrapperTCCLSingletonUtil._ConstructorInvoker<DefaultObjectWrapper, Builder> { private static final ConstructorInvoker INSTANCE = new ConstructorInvoker(); @@ -1318,10 +1334,10 @@ public class DefaultObjectWrapper implements RichObjectWrapper { private final Version incompatibleImprovements; - ClassIntrospector.Builder classIntrospectorBuilder; + // Can't be final because deep cloning must replace it + private ClassIntrospector.Builder classIntrospectorBuilder; // Properties and their *defaults*: - private boolean simpleMapWrapper; private int defaultDateType = TemplateDateModel.UNKNOWN; private ObjectWrapper outerIdentity; private boolean strict; @@ -1335,37 +1351,37 @@ public class DefaultObjectWrapper implements RichObjectWrapper { /** * @param incompatibleImprovements - * Sets which of the non-backward-compatible improvements should be enabled. Not {@code null}. This version number - * is the same as the FreeMarker version number with which the improvements were implemented. - * - * <p>For new projects, it's recommended to set this to the FreeMarker version that's used during the development. - * For released products that are still actively developed it's a low risk change to increase the 3rd - * version number further as FreeMarker is updated, but of course you should always check the list of effects - * below. Increasing the 2nd or 1st version number possibly mean substantial changes with higher risk of breaking - * the application, but again, see the list of effects below. - * - * <p>The reason it's separate from {@link Configuration#setIncompatibleImprovements(Version)} is that - * {@link ObjectWrapper} objects are often shared among multiple {@link Configuration}-s, so the two version - * numbers are technically independent. But it's recommended to keep those two version numbers the same. - * - * <p>The changes enabled by {@code incompatibleImprovements} are: - * <ul> - * <li> - * <p>3.0.0: No changes; this is the starting point, the version used in older projects. - * </li> - * </ul> - * - * <p>Note that the version will be normalized to the lowest version where the same incompatible - * {@link DefaultObjectWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might returns - * a lower version than what you have specified. + * Sets which of the non-backward-compatible improvements should be enabled. Not {@code null}. This + * version number is the same as the FreeMarker version number with which the improvements were + * implemented. + * <p> + * For new projects, it's recommended to set this to the FreeMarker version that's used during the + * development. For released products that are still actively developed it's a low risk change to + * increase the 3rd version number further as FreeMarker is updated, but of course you should always + * check the list of effects below. Increasing the 2nd or 1st version number possibly mean substantial + * changes with higher risk of breaking the application, but again, see the list of effects below. + * <p> + * The reason it's separate from {@link Configuration#setIncompatibleImprovements(Version)} is that + * {@link ObjectWrapper} objects are often shared among multiple {@link Configuration}-s, so the two + * version numbers are technically independent. But it's recommended to keep those two version numbers + * the same. + * <p> + * The changes enabled by {@code incompatibleImprovements} are: + * <ul> + * <li><p>3.0.0: No changes; this is the starting point, the version used in older projects.</li> + * </ul> + * <p> + * Note that the version will be normalized to the lowest version where the same incompatible {@link + * DefaultObjectWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} + * might returns a lower version than what you have specified. * @param isIncompImprsAlreadyNormalized - * Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> value. - * This parameter meant to be {@code true} when the class that extends {@link DefaultObjectWrapper} needs to - * add additional breaking versions over those of {@link DefaultObjectWrapper}. Thus, if this parameter is - * {@code true}, the versions where {@link DefaultObjectWrapper} had breaking changes must be already - * factored into the {@code incompatibleImprovements} parameter value, as no more normalization will happen. - * (You can use {@link DefaultObjectWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover - * those.) + * Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> + * value. This parameter meant to be {@code true} when the class that extends {@link + * DefaultObjectWrapper} needs to add additional breaking versions over those of {@link + * DefaultObjectWrapper}. Thus, if this parameter is {@code true}, the versions where {@link + * DefaultObjectWrapper} had breaking changes must be already factored into the {@code + * incompatibleImprovements} parameter value, as no more normalization will happen. (You can use {@link + * DefaultObjectWrapper#normalizeIncompatibleImprovementsVersion(Version)} to discover those.) */ protected ExtendableBuilder(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) { _CoreAPI.checkVersionNotNullAndSupported(incompatibleImprovements); @@ -1379,62 +1395,77 @@ public class DefaultObjectWrapper implements RichObjectWrapper { } /** - * Properly implementing this method is important if the builder is used as a cache key; if you override - * {@link ExtendableBuilder} and add new fields, don't forget to override it! + * Calculate a content-based hash that could be used when looking up the product object that {@link #build()} + * returns from a cache. If you override {@link ExtendableBuilder} and add new fields, don't forget to take + * those into account too! + * + * <p>{@link Builder#hashCode()} is delegated to this. + * + * @see #equalsForCacheKey(ExtendableBuilder, Object) + * @see #cloneForCacheKey() */ - // TODO Move this to Builder and a static helper method - @Override - public int hashCode() { + protected static int hashCodeForCacheKey(ExtendableBuilder<?, ?> builder) { 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 + (usePrivateCaches ? 1231 : 1237); - result = prime * result + classIntrospectorBuilder.hashCode(); + result = prime * result + builder.getIncompatibleImprovements().hashCode(); + result = prime * result + builder.getDefaultDateType(); + result = prime * result + (builder.getOuterIdentity() != null ? builder.getOuterIdentity().hashCode() : 0); + result = prime * result + (builder.isStrict() ? 1231 : 1237); + result = prime * result + (builder.getUseModelCache() ? 1231 : 1237); + result = prime * result + (builder.getUsePrivateCaches() ? 1231 : 1237); + result = prime * result + builder.classIntrospectorBuilder.hashCode(); return result; } /** - * Two {@link ExtendableBuilder}-s are equal exactly if their classes are identical ({@code ==}), and their - * field values are equal. Properly implementing this method is important if the builder is used as a cache key; - * if you override {@link ExtendableBuilder} and add new fields, don't forget to override it! + * A content-based {@link Object#equals(Object)} that could be used to look up the product object that + * {@link #build()} returns from a cache. If you override {@link ExtendableBuilder} and add new fields, don't + * forget to take those into account too! + * + * <p> + * {@link Builder#equals(Object)} is delegated to this. + * + * @see #hashCodeForCacheKey(ExtendableBuilder) + * @see #cloneForCacheKey() */ - // TODO Move this to Builder and a static helper method - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - ExtendableBuilder other = (ExtendableBuilder) 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; - if (usePrivateCaches != other.usePrivateCaches) return false; - return classIntrospectorBuilder.equals(other.classIntrospectorBuilder); + protected static boolean equalsForCacheKey(ExtendableBuilder<?, ?> thisBuilder, Object thatObj) { + if (thisBuilder == thatObj) return true; + if (thatObj == null) return false; + if (thisBuilder.getClass() != thatObj.getClass()) return false; + ExtendableBuilder<?, ?> thatBuilder = (ExtendableBuilder<?, ?>) thatObj; + + if (!thisBuilder.getIncompatibleImprovements().equals(thatBuilder.getIncompatibleImprovements())) { + return false; + } + if (thisBuilder.getDefaultDateType() != thatBuilder.getDefaultDateType()) return false; + if (thisBuilder.getOuterIdentity() != thatBuilder.getOuterIdentity()) return false; + if (thisBuilder.isStrict() != thatBuilder.isStrict()) return false; + if (thisBuilder.getUseModelCache() != thatBuilder.getUseModelCache()) return false; + if (thisBuilder.getUsePrivateCaches() != thatBuilder.getUsePrivateCaches()) return false; + return thisBuilder.classIntrospectorBuilder.equals(thatBuilder.classIntrospectorBuilder); } /** - * In case the builder is used as a cache key, this is used to clone it before it's actually used as a key; if - * you override {@link ExtendableBuilder} and add new fields that needs deep cloning, don't forget to - * override it! Calls {@link Object#clone()} internally (among others), so newly added fields are automatically - * copied, but again, that's not enough if the field value is mutable. + * If the builder is used as a cache key, this is used to clone it before it's stored in the cache as a key, so + * that further changes in the original builder won't change the key (aliasing). It calls {@link Object#clone()} + * internally, so all fields are automatically copied, but it will also individually clone field values that are + * both mutable and has a content-based equals method (deep cloning). + * <p> + * If you extend {@link ExtendableBuilder} with new fields with mutable values that have a content-based equals + * method, and you will also cache product instances, you need to clone those values manually to prevent + * aliasing problems, so don't forget to override this method! + * + * @see #equalsForCacheKey(ExtendableBuilder, Object) + * @see #hashCodeForCacheKey(ExtendableBuilder) */ - // TODO Move this to Builder and DeepCloneableBuilder - protected SelfT deepClone() { + protected SelfT cloneForCacheKey() { try { @SuppressWarnings("unchecked") SelfT clone = (SelfT) super.clone(); - clone.classIntrospectorBuilder = (ClassIntrospector.Builder) classIntrospectorBuilder.clone(); + ((ExtendableBuilder<?, ?>) clone).classIntrospectorBuilder = (ClassIntrospector.Builder) + classIntrospectorBuilder.clone(); return clone; } catch (CloneNotSupportedException e) { - throw new RuntimeException("Failed to deepClone ExtendableBuilder", e); + throw new RuntimeException("Failed to deepClone Builder", e); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c04f5b51/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java b/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTCCLSingletonUtil.java new file mode 100644 index 0000000..3945770 --- /dev/null +++ b/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.BuilderBase; + +/** + * 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 BuilderBase#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/c04f5b51/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java b/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java index eeec18f..e456dc6 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java @@ -19,6 +19,11 @@ package org.apache.freemarker.core.model.impl; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.WeakHashMap; + import org.apache.freemarker.core.Version; import org.apache.freemarker.core.model.TemplateHashModel; import org.apache.freemarker.core.model.TemplateModel; @@ -31,8 +36,8 @@ import org.apache.freemarker.core.model.TemplateModelException; */ public class RestrictedObjectWrapper extends DefaultObjectWrapper { - protected RestrictedObjectWrapper(Builder builder) { - super(builder, true); + protected RestrictedObjectWrapper(Builder builder, boolean finalizeConstruction) { + super(builder, finalizeConstruction); } /** @@ -62,14 +67,32 @@ public class RestrictedObjectWrapper extends DefaultObjectWrapper { public static final class Builder extends ExtendableBuilder<RestrictedObjectWrapper, Builder> { + private final static Map<ClassLoader, Map<Builder, WeakReference<RestrictedObjectWrapper>>> + INSTANCE_CACHE = new WeakHashMap<>(); + + private final static ReferenceQueue<RestrictedObjectWrapper> INSTANCE_CACHE_REF_QUEUE = new ReferenceQueue<>(); + public Builder(Version incompatibleImprovements) { super(incompatibleImprovements, false); } @Override public RestrictedObjectWrapper build() { - return new RestrictedObjectWrapper(this); + return DefaultObjectWrapperTCCLSingletonUtil.getSingleton( + this, INSTANCE_CACHE, INSTANCE_CACHE_REF_QUEUE, ConstructorInvoker.INSTANCE); + } + + private static class ConstructorInvoker + implements DefaultObjectWrapperTCCLSingletonUtil._ConstructorInvoker<RestrictedObjectWrapper, Builder> { + + private static final ConstructorInvoker INSTANCE = new ConstructorInvoker(); + + @Override + public RestrictedObjectWrapper invoke(Builder builder) { + return new RestrictedObjectWrapper(builder, true); + } } + } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/c04f5b51/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java b/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java index 6e09e9b..fecb0b0 100644 --- a/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java +++ b/src/main/java/org/apache/freemarker/core/model/impl/_ModelAPI.java @@ -19,20 +19,13 @@ package org.apache.freemarker.core.model.impl; -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.Map; import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.util.BuilderBase; import org.apache.freemarker.core.util._CollectionUtil; /** @@ -125,93 +118,5 @@ public class _ModelAPI { return constrDesc.invokeConstructor(ow, packedArgs); } - - /** - * 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. - */ - // [FM3] Generalize and publish this functionality - public static < - ObjectWrapperT extends DefaultObjectWrapper, - BuilderT extends DefaultObjectWrapper.ExtendableBuilder<ObjectWrapperT, BuilderT>> - ObjectWrapperT getDefaultObjectWrapperSubclassSingleton( - 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.deepClone(); // 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 BuilderBase#build()} would be infinite recursion in such cases. - */ - public interface _ConstructorInvoker<ProductT, BuilderT> { - - ProductT invoke(BuilderT builder); - } - }
