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);
}
}