/*******************************************************************************
* Copyright (c) 2011 Subgraph.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Subgraph - initial API and implementation
******************************************************************************/
package com.subgraph.vega.impl.scanner.modules.scripting;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.ImporterTopLevel;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import com.subgraph.vega.api.scanner.modules.ModuleScriptType;
import com.subgraph.vega.impl.scanner.modules.scripting.ModuleValidator.ModuleValidationException;
import com.subgraph.vega.impl.scanner.modules.scripting.ScriptFile.CompileStatus;
public class ScriptLoader {
private final Logger logger = Logger.getLogger("script-loader");
private final Scriptable globalScope;
private final File moduleRoot;
private final PreludeLoader preludeLoader;
private final ScriptCompiler moduleCompiler;
private final boolean preludeLoadFailed;
private final Map<File, ScriptedModule> modulePathMap = new HashMap<File, ScriptedModule>();
/* Script files are tracked separately from modules mainly so that we can track files have failed to compile. */
private final Map<File, ScriptFile> scriptPathMap = new HashMap<File, ScriptFile>();
private final FileFilter scriptFilter = new FileFilter() {
public boolean accept(File pathname) {
return pathname.isFile() && pathname.getName().endsWith(".js");
}
};
private final FileFilter directoryFilter = new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory();
}
};
public ScriptLoader(File moduleRoot) {
logger.info("Loading scripts from "+ moduleRoot.getAbsolutePath());
this.moduleRoot = moduleRoot;
enableDynamicScope();
globalScope = createGlobalScope();
preludeLoader = new PreludeLoader(new File(moduleRoot, "prelude"), globalScope);
preludeLoadFailed = (preludeLoader.load() == false);
if(preludeLoadFailed)
moduleCompiler = null;
else
moduleCompiler = new ScriptCompiler(preludeLoader.getPreludeScope());
}
private void enableDynamicScope() {
ContextFactory.initGlobal(new ContextFactory() {
@Override
protected boolean hasFeature(Context cx, int featureIndex) {
if(featureIndex == Context.FEATURE_DYNAMIC_SCOPE)
return true;
else
return super.hasFeature(cx, featureIndex);
}
});
}
private Scriptable createGlobalScope() {
try {
final Context cx = Context.enter();
final ScriptableObject importer = new ImporterTopLevel(cx, true);
return cx.initStandardObjects(importer, true);
} finally {
Context.exit();
}
}
public List<ScriptedModule> getAllModulesByType(ModuleScriptType type) {
final List<ScriptedModule> result = new ArrayList<ScriptedModule>();
synchronized(modulePathMap) {
for(ScriptedModule m: modulePathMap.values()) {
if(!m.isDisabled() && (type == null || type == m.getModuleType())) {
result.add(m);
}
}
}
return result;
}
public List<ScriptedModule> getAllModules() {
return getAllModulesByType(null);
}
public Scriptable getPreludeScope() {
return preludeLoader.getPreludeScope();
}
private ScriptedModule compileModuleScript(ScriptFile scriptFile) {
final ModuleValidator validator = compileAndValidate(scriptFile);
if(validator == null) {
return null;
}
return new ScriptedModule(scriptFile, validator);
}
private boolean recompileModule(ScriptedModule module) {
final ModuleValidator validator = compileAndValidate(module.getScriptFile());
if(validator == null)
return false;
module.updateFromValidator(validator);
return true;
}
private ModuleValidator compileAndValidate(ScriptFile scriptFile) {
if(!moduleCompiler.compile(scriptFile) || scriptFile.getCompileStatus() != CompileStatus.COMPILE_SUCCEEDED) {
logger.warning(scriptFile.getCompileFailureMessage());
return null;
}
return validateModule(scriptFile.getCompiledScript(), scriptFile.getPath());
}
private ModuleValidator validateModule(Scriptable module, String modulePath) {
final ModuleValidator validator = new ModuleValidator(module);
try {
validator.validate();
return validator;
} catch (ModuleValidationException e) {
logger.warning("Failed to validate module script "+ modulePath +" :"+ e.getMessage());
return null;
}
}
private List<File> allScriptPaths() {
final File scriptRoot = new File(moduleRoot, "modules");
final List<File> scriptFiles = new ArrayList<File>();
crawlDirectory(scriptRoot, scriptFiles);
return scriptFiles;
}
private void crawlDirectory(File dir, List<File> files) {
for(File f: dir.listFiles(scriptFilter)) {
files.add(f);
}
for(File d: dir.listFiles(directoryFilter)) {
crawlDirectory(d, files);
}
}
public boolean reloadModules() {
boolean somethingChanged = false;
if(preludeLoadFailed) {
return false;
}
synchronized(modulePathMap) {
synchronizeScriptPaths();
for(Map.Entry<File, ScriptFile> entry: scriptPathMap.entrySet()) {
if(compileScriptFileIfNeeded(entry.getKey(), entry.getValue())) {
somethingChanged = true;
}
}
}
return somethingChanged;
}
private void synchronizeScriptPaths() {
final Set<File> pathSet = new HashSet<File>();
for(File path: allScriptPaths()) {
pathSet.add(path);
if(!scriptPathMap.containsKey(path)) {
scriptPathMap.put(path, new ScriptFile(path));
}
}
final List<File> keys = new ArrayList<File>(scriptPathMap.keySet());
for(File path: keys) {
if(!pathSet.contains(path)) {
modulePathMap.remove(path);
scriptPathMap.remove(path);
}
}
}
private boolean compileScriptFileIfNeeded(File path, ScriptFile scriptFile) {
if(!isCompileNeeded(scriptFile)) {
return false;
}
if(modulePathMap.containsKey(path)) {
if(!recompileModule(modulePathMap.get(path))) {
modulePathMap.remove(path);
}
} else {
final ScriptedModule module = compileModuleScript(scriptFile);
if(module != null) {
modulePathMap.put(path, module);
}
}
return true;
}
private boolean isCompileNeeded(ScriptFile scriptFile) {
return ((scriptFile.getCompileStatus() == CompileStatus.NOT_COMPILED) || scriptFile.hasFileChanged());
}
}