/*
 * 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.util;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.IdentityHashMap;
import java.util.Map;

import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelAdapter;

Internally used by various wrapper implementations to implement model caching.
/** * Internally used by various wrapper implementations to implement model * caching. */
public abstract class ModelCache { private boolean useCache = false; private Map<Object, ModelReference> modelCache = null; private ReferenceQueue<TemplateModel> refQueue = null; protected ModelCache() { }
Sets whether this wrapper caches model instances. Default is false. When set to true, calling getInstance(Object) multiple times for the same object will return the same model.
/** * Sets whether this wrapper caches model instances. Default is false. * When set to true, calling {@link #getInstance(Object)} * multiple times for the same object will return the same model. */
public synchronized void setUseCache(boolean useCache) { this.useCache = useCache; if (useCache) { modelCache = new IdentityHashMap<Object, ModelReference>(); refQueue = new ReferenceQueue<TemplateModel>(); } else { modelCache = null; refQueue = null; } }
Since:2.3.21
/** * @since 2.3.21 */
public synchronized boolean getUseCache() { return useCache; } public TemplateModel getInstance(Object object) { if (object instanceof TemplateModel) { return (TemplateModel) object; } if (object instanceof TemplateModelAdapter) { return ((TemplateModelAdapter) object).getTemplateModel(); } if (useCache && isCacheable(object)) { TemplateModel model = lookup(object); if (model == null) { model = create(object); register(model, object); } return model; } else { return create(object); } } protected abstract TemplateModel create(Object object); protected abstract boolean isCacheable(Object object); public void clearCache() { if (modelCache != null) { synchronized (modelCache) { modelCache.clear(); } } } private final TemplateModel lookup(Object object) { ModelReference ref = null; // NOTE: we're doing minimal synchronizations -- which can lead to // duplicate wrapper creation. However, this has no harmful side-effects and // is a lesser performance hit. synchronized (modelCache) { ref = modelCache.get(object); } if (ref != null) return ref.getModel(); return null; } private final void register(TemplateModel model, Object object) { synchronized (modelCache) { // Remove cleared references for (; ; ) { ModelReference queuedRef = (ModelReference) refQueue.poll(); if (queuedRef == null) { break; } modelCache.remove(queuedRef.object); } // Register new reference modelCache.put(object, new ModelReference(model, object, refQueue)); } }
A special soft reference that is registered in the modelCache. When it gets cleared (that is, the model became unreachable) it will remove itself from the model cache.
/** * A special soft reference that is registered in the modelCache. * When it gets cleared (that is, the model became unreachable) * it will remove itself from the model cache. */
private static final class ModelReference extends SoftReference<TemplateModel> { Object object; ModelReference(TemplateModel ref, Object object, ReferenceQueue<TemplateModel> refQueue) { super(ref, refQueue); this.object = object; } TemplateModel getModel() { return (TemplateModel) this.get(); } } }