SoftCachingModuleScriptProvider.java

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript.commonjs.module.provider;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.commonjs.module.ModuleScript;

/**
 * A module script provider that uses a module source provider to load modules and caches the loaded
 * modules. It softly references the loaded modules' Rhino {@link Script} objects, thus a module
 * once loaded can become eligible for garbage collection if it is otherwise unused under memory
 * pressure. Instances of this class are thread safe.
 *
 * @author Attila Szegedi
 * @version $Id: SoftCachingModuleScriptProvider.java,v 1.3 2011/04/07 20:26:12 hannes%helma.at Exp
 *     $
 */
public class SoftCachingModuleScriptProvider extends CachingModuleScriptProviderBase {
    private static final long serialVersionUID = 1L;
    private transient ReferenceQueue<Script> scriptRefQueue = new ReferenceQueue<>();
    private transient ConcurrentMap<String, ScriptReference> scripts =
            new ConcurrentHashMap<>(16, .75f, getConcurrencyLevel());

    /**
     * Creates a new module provider with the specified module source provider.
     *
     * @param moduleSourceProvider provider for modules' source code
     */
    public SoftCachingModuleScriptProvider(ModuleSourceProvider moduleSourceProvider) {
        super(moduleSourceProvider);
    }

    @Override
    public ModuleScript getModuleScript(
            Context cx, String moduleId, URI uri, URI base, Scriptable paths) throws Exception {
        // Overridden to clear the reference queue before retrieving the
        // script.
        for (; ; ) {
            ScriptReference ref = (ScriptReference) scriptRefQueue.poll();
            if (ref == null) {
                break;
            }
            scripts.remove(ref.getModuleId(), ref);
        }
        return super.getModuleScript(cx, moduleId, uri, base, paths);
    }

    @Override
    protected CachedModuleScript getLoadedModule(String moduleId) {
        final ScriptReference scriptRef = scripts.get(moduleId);
        return scriptRef != null ? scriptRef.getCachedModuleScript() : null;
    }

    @Override
    protected void putLoadedModule(String moduleId, ModuleScript moduleScript, Object validator) {
        scripts.put(
                moduleId,
                new ScriptReference(
                        moduleScript.getScript(),
                        moduleId,
                        moduleScript.getUri(),
                        moduleScript.getBase(),
                        validator,
                        scriptRefQueue));
    }

    private static class ScriptReference extends SoftReference<Script> {
        private final String moduleId;
        private final URI uri;
        private final URI base;
        private final Object validator;

        ScriptReference(
                Script script,
                String moduleId,
                URI uri,
                URI base,
                Object validator,
                ReferenceQueue<Script> refQueue) {
            super(script, refQueue);
            this.moduleId = moduleId;
            this.uri = uri;
            this.base = base;
            this.validator = validator;
        }

        CachedModuleScript getCachedModuleScript() {
            final Script script = get();
            if (script == null) {
                return null;
            }
            return new CachedModuleScript(new ModuleScript(script, uri, base), validator);
        }

        String getModuleId() {
            return moduleId;
        }
    }

    @SuppressWarnings("unchecked")
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        scriptRefQueue = new ReferenceQueue<>();
        scripts = new ConcurrentHashMap<>();
        final Map<String, CachedModuleScript> serScripts = (Map) in.readObject();
        for (Map.Entry<String, CachedModuleScript> entry : serScripts.entrySet()) {
            final CachedModuleScript cachedModuleScript = entry.getValue();
            putLoadedModule(
                    entry.getKey(),
                    cachedModuleScript.getModule(),
                    cachedModuleScript.getValidator());
        }
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        final Map<String, CachedModuleScript> serScripts = new HashMap<>();
        for (Map.Entry<String, ScriptReference> entry : scripts.entrySet()) {
            final CachedModuleScript cachedModuleScript = entry.getValue().getCachedModuleScript();
            if (cachedModuleScript != null) {
                serScripts.put(entry.getKey(), cachedModuleScript);
            }
        }
        out.writeObject(serScripts);
    }
}