org.htmlunit.corejs.javascript.commonjs.module.provider.CachingModuleScriptProviderBase Maven / Gradle / Ivy
/* 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.htmlunit.corejs.javascript.commonjs.module.provider;
import java.io.Reader;
import java.io.Serializable;
import java.net.URI;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.Scriptable;
import org.htmlunit.corejs.javascript.commonjs.module.ModuleScript;
import org.htmlunit.corejs.javascript.commonjs.module.ModuleScriptProvider;
/**
* Abstract base class that implements caching of loaded module scripts. It uses a {@link
* ModuleSourceProvider} to obtain the source text of the scripts. It supports a cache revalidation
* mechanism based on validator objects returned from the {@link ModuleSourceProvider}. Instances of
* this class and its subclasses are thread safe (and written to perform decently under concurrent
* access).
*
* @author Attila Szegedi
* @version $Id: CachingModuleScriptProviderBase.java,v 1.3 2011/04/07 20:26:12 hannes%helma.at Exp
* $
*/
public abstract class CachingModuleScriptProviderBase
implements ModuleScriptProvider, Serializable {
private static final long serialVersionUID = -1L;
private static final int loadConcurrencyLevel = Runtime.getRuntime().availableProcessors() * 8;
private static final int loadLockShift;
private static final int loadLockMask;
private static final int loadLockCount;
static {
int sshift = 0;
int ssize = 1;
while (ssize < loadConcurrencyLevel) {
++sshift;
ssize <<= 1;
}
loadLockShift = 32 - sshift;
loadLockMask = ssize - 1;
loadLockCount = ssize;
}
private final Object[] loadLocks = new Object[loadLockCount];
{
for (int i = 0; i < loadLocks.length; ++i) {
loadLocks[i] = new Object();
}
}
private final ModuleSourceProvider moduleSourceProvider;
/**
* Creates a new module script provider with the specified source.
*
* @param moduleSourceProvider provider for modules' source code
*/
protected CachingModuleScriptProviderBase(ModuleSourceProvider moduleSourceProvider) {
this.moduleSourceProvider = moduleSourceProvider;
}
@Override
public ModuleScript getModuleScript(
Context cx, String moduleId, URI moduleUri, URI baseUri, Scriptable paths)
throws Exception {
final CachedModuleScript cachedModule1 = getLoadedModule(moduleId);
final Object validator1 = getValidator(cachedModule1);
final ModuleSource moduleSource =
(moduleUri == null)
? moduleSourceProvider.loadSource(moduleId, paths, validator1)
: moduleSourceProvider.loadSource(moduleUri, baseUri, validator1);
if (moduleSource == ModuleSourceProvider.NOT_MODIFIED) {
return cachedModule1.getModule();
}
if (moduleSource == null) {
return null;
}
try (Reader reader = moduleSource.getReader()) {
final int idHash = moduleId.hashCode();
synchronized (loadLocks[(idHash >>> loadLockShift) & loadLockMask]) {
final CachedModuleScript cachedModule2 = getLoadedModule(moduleId);
if (cachedModule2 != null) {
if (!equal(validator1, getValidator(cachedModule2))) {
return cachedModule2.getModule();
}
}
final URI sourceUri = moduleSource.getUri();
final ModuleScript moduleScript =
new ModuleScript(
cx.compileReader(
reader,
sourceUri.toString(),
1,
moduleSource.getSecurityDomain()),
sourceUri,
moduleSource.getBase());
putLoadedModule(moduleId, moduleScript, moduleSource.getValidator());
return moduleScript;
}
}
}
/**
* Store a loaded module script for later retrieval using {@link #getLoadedModule(String)}.
*
* @param moduleId the ID of the module
* @param moduleScript the module script
* @param validator the validator for the module's source text entity
*/
protected abstract void putLoadedModule(
String moduleId, ModuleScript moduleScript, Object validator);
/**
* Retrieves an already loaded moduleScript stored using {@link #putLoadedModule(String,
* ModuleScript, Object)}.
*
* @param moduleId the ID of the module
* @return a cached module script, or null if the module is not loaded.
*/
protected abstract CachedModuleScript getLoadedModule(String moduleId);
/**
* Instances of this class represent a loaded and cached module script.
*
* @author Attila Szegedi
* @version $Id: CachingModuleScriptProviderBase.java,v 1.3 2011/04/07 20:26:12 hannes%helma.at
* Exp $
*/
public static class CachedModuleScript {
private final ModuleScript moduleScript;
private final Object validator;
/**
* Creates a new cached module script.
*
* @param moduleScript the module script itself
* @param validator a validator for the moduleScript's source text entity.
*/
public CachedModuleScript(ModuleScript moduleScript, Object validator) {
this.moduleScript = moduleScript;
this.validator = validator;
}
/**
* Returns the module script.
*
* @return the module script.
*/
ModuleScript getModule() {
return moduleScript;
}
/**
* Returns the validator for the module script's source text entity.
*
* @return the validator for the module script's source text entity.
*/
Object getValidator() {
return validator;
}
}
private static Object getValidator(CachedModuleScript cachedModule) {
return cachedModule == null ? null : cachedModule.getValidator();
}
private static boolean equal(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
/**
* Returns the internal concurrency level utilized by caches in this JVM.
*
* @return the internal concurrency level utilized by caches in this JVM.
*/
protected static int getConcurrencyLevel() {
return loadLockCount;
}
}