@Test public void codeCacheTestOpt() throws ScriptException, IOException { System.setProperty("nashorn.persistent.code.cache", codeCache); final NashornScriptEngineFactory fac = new NashornScriptEngineFactory(); final ScriptEngine e = fac.getScriptEngine(ENGINE_OPTIONS_OPT); final Path codeCachePath = getCodeCachePath(true); e.eval(code1); e.eval(code2); e.eval(code3); // less than minimum size for storing // adding code1 and code2. final DirectoryStream<Path> stream = Files.newDirectoryStream(codeCachePath); checkCompiledScripts(stream, 4); }
@Test public void changeUserDirTest() throws ScriptException, IOException { System.setProperty("nashorn.persistent.code.cache", codeCache); final NashornScriptEngineFactory fac = new NashornScriptEngineFactory(); final ScriptEngine e = fac.getScriptEngine(ENGINE_OPTIONS_NOOPT); final Path codeCachePath = getCodeCachePath(false); final String newUserDir = "build/newUserDir"; // Now changing current working directory System.setProperty("user.dir", System.getProperty("user.dir") + File.separator + newUserDir); try { // Check that a new compiled script is stored in existing code cache e.eval(code1); final DirectoryStream<Path> stream = Files.newDirectoryStream(codeCachePath); checkCompiledScripts(stream, 1); // Setting to default current working dir } finally { System.setProperty("user.dir", oldUserDir); } }
@Test public void pathHandlingTest() { System.setProperty("nashorn.persistent.code.cache", codeCache); final NashornScriptEngineFactory fac = new NashornScriptEngineFactory(); fac.getScriptEngine(ENGINE_OPTIONS_NOOPT); final Path expectedCodeCachePath = FileSystems.getDefault().getPath(oldUserDir + File.separator + codeCache); final Path actualCodeCachePath = FileSystems.getDefault() .getPath(System.getProperty("nashorn.persistent.code.cache")) .toAbsolutePath(); // Check that nashorn code cache is created in current working directory assertEquals(actualCodeCachePath, expectedCodeCachePath); // Check that code cache dir exists and it's not empty final File file = new File(actualCodeCachePath.toUri()); assertTrue(file.exists(), "No code cache directory was created!"); assertTrue(file.isDirectory(), "Code cache location is not a directory!"); assertFalse(file.list().length == 0, "Code cache directory is empty!"); }
/** @author vv, mg */ public class Scripts { private static final NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); private static final NashornScriptEngine engine = (NashornScriptEngine) factory.getScriptEngine(); protected static final String PLATYPUS_JS_FILENAME = "platypus.js"; public static final String STRING_TYPE_NAME = "String"; // NOI18N public static final String NUMBER_TYPE_NAME = "Number"; // NOI18N public static final String DATE_TYPE_NAME = "Date"; // NOI18N public static final String BOOLEAN_TYPE_NAME = "Boolean"; // NOI18N public static final String GEOMETRY_TYPE_NAME = "Geometry"; // NOI18N public static final String THIS_KEYWORD = "this"; // NOI18N protected static volatile Path absoluteApiPath; protected static volatile URL platypusJsUrl; public static NashornScriptEngine getEngine() { return engine; } private static final ThreadLocal<LocalContext> contextRef = new ThreadLocal<>(); public static Space getSpace() { return getContext() != null ? getContext().getSpace() : null; } public static LocalContext getContext() { return contextRef.get(); } public static void setContext(LocalContext aContext) { if (aContext != null) { contextRef.set(aContext); } else { contextRef.remove(); } } public static class LocalContext { protected Object request; protected Object response; protected Object principal; protected Object session; protected Integer asyncsCount; protected Scripts.Space space; public Object getSession() { return session; } public void setSession(Object aValue) { session = aValue; } public Object getRequest() { return request; } public void setRequest(Object aRequest) { request = aRequest; } public Object getResponse() { return response; } public void setResponse(Object aResponse) { response = aResponse; } public Object getPrincipal() { return principal; } public void setPrincipal(Object aSession) { principal = aSession; } public int getAsyncsCount() { return asyncsCount != null ? asyncsCount : 0; } public void incAsyncsCount() { if (asyncsCount != null) { asyncsCount++; } } public void initAsyncs(Integer aSeed) { asyncsCount = aSeed; } public Space getSpace() { return space; } public void setSpace(Space aValue) { space = aValue; } } public static LocalContext createContext(Scripts.Space aSpace) { LocalContext res = new LocalContext(); res.setSpace(aSpace); return res; } public static class Pending { protected Consumer<Void> onLoad; protected Consumer<Exception> onError; public Pending(Consumer<Void> aOnLoad, Consumer<Exception> aOnError) { super(); onLoad = aOnLoad; onError = aOnError; } public void loaded() { onLoad.accept(null); } public void failed(Exception ex) { onError.accept(ex); } } public static class Space { protected ScriptContext scriptContext; protected Object global; protected Map<String, JSObject> publishers = new HashMap<>(); protected Set<String> required = new HashSet<>(); protected Set<String> executed = new HashSet<>(); protected Map<String, List<Pending>> pending = new HashMap<>(); protected Space() { this(null); global = new Object(); } public Space(ScriptContext aScriptContext) { super(); scriptContext = aScriptContext; } public Set<String> getRequired() { return required; } public Set<String> getExecuted() { return executed; } public Map<String, List<Pending>> getPending() { return pending; } protected JSObject loadFunc; protected JSObject toPrimitiveFunc; protected JSObject lookupInGlobalFunc; protected JSObject putInGlobalFunc; protected JSObject toDateFunc; protected JSObject parseJsonFunc; protected JSObject parseJsonWithDatesFunc; protected JSObject writeJsonFunc; protected JSObject extendFunc; protected JSObject scalarDefFunc; protected JSObject collectionDefFunc; protected JSObject isArrayFunc; protected JSObject makeObjFunc; protected JSObject makeArrayFunc; protected JSObject listenFunc; protected JSObject listenElementsFunc; protected JSObject copyObjectFunc; public void setGlobal(Object aValue) { if (global == null) { global = aValue; } else { throw new IllegalStateException("Scripts space should be initialized only once."); } } public void putPublisher(String aClassName, JSObject aPublisher) { publishers.put(aClassName, aPublisher); } public JSObject getPublisher(String aClassName) { return publishers.get(aClassName); } public JSObject getLoadFunc() { assert loadFunc != null : SCRIPT_NOT_INITIALIZED; return loadFunc; } public void setLoadFunc(JSObject aValue) { assert loadFunc == null; loadFunc = aValue; } public JSObject getToPrimitiveFunc() { assert toPrimitiveFunc != null : SCRIPT_NOT_INITIALIZED; return toPrimitiveFunc; } public void setToPrimitiveFunc(JSObject aValue) { assert toPrimitiveFunc == null; toPrimitiveFunc = aValue; } public void setLookupInGlobalFunc(JSObject aValue) { assert lookupInGlobalFunc == null; lookupInGlobalFunc = aValue; } public void setPutInGlobalFunc(JSObject aValue) { assert putInGlobalFunc == null; putInGlobalFunc = aValue; } public JSObject getToDateFunc() { assert toDateFunc != null; return toDateFunc; } public void setToDateFunc(JSObject aValue) { assert toDateFunc == null; toDateFunc = aValue; } public void setParseJsonFunc(JSObject aValue) { assert parseJsonFunc == null; parseJsonFunc = aValue; } public void setParseJsonWithDatesFunc(JSObject aValue) { assert parseJsonWithDatesFunc == null; parseJsonWithDatesFunc = aValue; } public void setWriteJsonFunc(JSObject aValue) { assert writeJsonFunc == null; writeJsonFunc = aValue; } public void setExtendFunc(JSObject aValue) { assert extendFunc == null; extendFunc = aValue; } public void setScalarDefFunc(JSObject aValue) { assert scalarDefFunc == null; scalarDefFunc = aValue; } public void setCollectionDefFunc(JSObject aValue) { assert collectionDefFunc == null; collectionDefFunc = aValue; } public void setIsArrayFunc(JSObject aValue) { assert isArrayFunc == null; isArrayFunc = aValue; } public void setMakeObjFunc(JSObject aValue) { assert makeObjFunc == null; makeObjFunc = aValue; } public void setMakeArrayFunc(JSObject aValue) { assert makeArrayFunc == null; makeArrayFunc = aValue; } public void setListenFunc(JSObject aValue) { assert listenFunc == null; listenFunc = aValue; } public void setListenElementsFunc(JSObject aValue) { assert listenElementsFunc == null; listenElementsFunc = aValue; } public void setCopyObjectFunc(JSObject aValue) { assert copyObjectFunc == null; copyObjectFunc = aValue; } public JSObject getCopyObjectFunc() { return copyObjectFunc; } public Object toJava(Object aValue) { if (aValue instanceof ScriptObject) { aValue = ScriptUtils.wrap((ScriptObject) aValue); } if (aValue instanceof JSObject) { assert toPrimitiveFunc != null : SCRIPT_NOT_INITIALIZED; aValue = toPrimitiveFunc.call(null, new Object[] {aValue}); } else if (aValue == ScriptRuntime.UNDEFINED) { return null; } return aValue; } public Object toJs(Object aValue) { if (aValue instanceof Date) { // force js boxing of date, because of absence js literal of date value assert toDateFunc != null : SCRIPT_NOT_INITIALIZED; return toDateFunc.call(null, aValue); } else if (aValue instanceof HasPublished) { return ((HasPublished) aValue).getPublished(); } else { return aValue; } } public Object[] toJs(Object[] aArray) { Object[] publishedArgs = new Object[aArray.length]; for (int i = 0; i < aArray.length; i++) { publishedArgs[i] = toJs(aArray[i]); } return publishedArgs; } public Object parseJson(String json) { assert parseJsonFunc != null : SCRIPT_NOT_INITIALIZED; return parseJsonFunc.call(null, new Object[] {json}); } public Object parseJsonWithDates(String json) { assert parseJsonWithDatesFunc != null : SCRIPT_NOT_INITIALIZED; return parseJsonWithDatesFunc.call(null, new Object[] {json}); } public String toJson(Object aObj) { assert writeJsonFunc != null : SCRIPT_NOT_INITIALIZED; if (aObj instanceof Undefined) { // nashorn JSON parser could not work with undefined. aObj = null; } if (aObj instanceof JSObject || aObj instanceof CharSequence || aObj instanceof Number || aObj instanceof Boolean || aObj instanceof ScriptObject || aObj == null) { return JSType.toString(writeJsonFunc.call(null, new Object[] {aObj})); } else { throw new IllegalArgumentException("Java object couldn't be converted to JSON!"); } } public void extend(JSObject aChild, JSObject aParent) { assert extendFunc != null : SCRIPT_NOT_INITIALIZED; extendFunc.call(null, new Object[] {aChild, aParent}); } public JSObject scalarPropertyDefinition( JSObject targetEntity, String targetFieldName, String sourceFieldName) { assert scalarDefFunc != null : SCRIPT_NOT_INITIALIZED; return (JSObject) scalarDefFunc.newObject(new Object[] {targetEntity, targetFieldName, sourceFieldName}); } public JSObject collectionPropertyDefinition( JSObject sourceEntity, String targetFieldName, String sourceFieldName) { assert collectionDefFunc != null : SCRIPT_NOT_INITIALIZED; return (JSObject) collectionDefFunc.newObject( new Object[] {sourceEntity, targetFieldName, sourceFieldName}); } public boolean isArrayDeep(JSObject aInstance) { assert isArrayFunc != null : SCRIPT_NOT_INITIALIZED; Object oResult = isArrayFunc.call(null, new Object[] {aInstance}); return Boolean.TRUE.equals(oResult); } public JSObject makeObj() { assert makeObjFunc != null : SCRIPT_NOT_INITIALIZED; Object oResult = makeObjFunc.call(null, new Object[] {}); return (JSObject) oResult; } public JSObject makeArray() { assert makeArrayFunc != null : SCRIPT_NOT_INITIALIZED; Object oResult = makeArrayFunc.call(null, new Object[] {}); return (JSObject) oResult; } private static class Wrapper { public Object value; } public Object makeCopy(Object aSource) { assert copyObjectFunc != null : SCRIPT_NOT_INITIALIZED; Wrapper w = new Wrapper(); copyObjectFunc.call( null, new Object[] { aSource, new AbstractJSObject() { @Override public Object call(Object thiz, Object... args) { w.value = args.length > 0 ? args[0] : null; return null; } } }); return w.value; } public JSObject listen(JSObject aTarget, String aPath, JSObject aCallback) { assert listenFunc != null : SCRIPT_NOT_INITIALIZED; Object oResult = listenFunc.call(null, new Object[] {aTarget, aPath, aCallback}); return (JSObject) oResult; } public JSObject listenElements(JSObject aTarget, JSObject aCallback) { assert listenElementsFunc != null : SCRIPT_NOT_INITIALIZED; Object oResult = listenElementsFunc.call(null, new Object[] {aTarget, aCallback}); return (JSObject) oResult; } public JSObject createModule(String aModuleName) { assert lookupInGlobalFunc != null : SCRIPT_NOT_INITIALIZED; Object oConstructor = lookupInGlobalFunc.call(null, new Object[] {aModuleName}); if (oConstructor instanceof JSObject && ((JSObject) oConstructor).isFunction()) { JSObject jsConstructor = (JSObject) oConstructor; return (JSObject) jsConstructor.newObject(new Object[] {}); } else { return null; } } public JSObject lookupInGlobal(String aName) { assert lookupInGlobalFunc != null : SCRIPT_NOT_INITIALIZED; Object res = lookupInGlobalFunc.call(null, new Object[] {aName}); return res instanceof JSObject ? (JSObject) res : null; } public void putInGlobal(String aName, JSObject aValue) { assert putInGlobalFunc != null : SCRIPT_NOT_INITIALIZED; putInGlobalFunc.call(null, new Object[] {aName, aValue}); } public Object exec(String aSourceName, URL aSourcePlace) throws ScriptException, URISyntaxException { scriptContext.setAttribute(ScriptEngine.FILENAME, aSourceName, ScriptContext.ENGINE_SCOPE); return engine.eval(new URLReader(aSourcePlace), scriptContext); } public Object exec(String aSource) throws ScriptException, URISyntaxException { assert scriptContext != null : SCRIPT_NOT_INITIALIZED; return engine.eval(aSource, scriptContext); } public void schedule(JSObject aJsTask, long aTimeout) { Scripts.LocalContext context = Scripts.getContext(); bio.submit( () -> { try { Thread.sleep(aTimeout); Scripts.setContext(context); try { process( () -> { aJsTask.call(null, new Object[] {}); }); } finally { Scripts.setContext(null); } } catch (InterruptedException ex) { Logger.getLogger(Scripts.class.getName()).log(Level.SEVERE, null, ex); } }); } public void enqueue(JSObject aJsTask) { process( () -> { aJsTask.call(null, new Object[] {}); }); } protected Queue<Runnable> queue = new ConcurrentLinkedQueue<>(); protected AtomicInteger queueVersion = new AtomicInteger(); protected AtomicReference worker = new AtomicReference(null); public void process(Runnable aTask) { Scripts.LocalContext context = Scripts.getContext(); Runnable taskWrapper = () -> { Scripts.setContext(context); try { aTask.run(); } finally { Scripts.setContext(null); } }; queue.offer(taskWrapper); offerTask( () -> { // Runnable processedTask = taskWrapper; int version; int newVersion; Thread thisThread = Thread.currentThread(); do { version = queueVersion.get(); // Zombie counter ... newVersion = version + 1; if (newVersion == Integer.MAX_VALUE) { newVersion = 0; } /* moved to top of body if (processedTask != null) {//Single attempt to offer aTask. queue.offer(processedTask); processedTask = null; } */ if (worker.compareAndSet(null, thisThread)) { // Worker electing. try { // already single threaded environment if (global == null) { Scripts.Space.this.initSpaceGlobal(); } // Zombie processing ... Runnable task = queue.poll(); while (task != null) { task.run(); task = queue.poll(); } } catch (Throwable t) { Logger.getLogger(Scripts.class.getName()).log(Level.SEVERE, null, t); } finally { boolean setted = worker.compareAndSet(thisThread, null); assert setted : "Worker electing assumption failed"; // Always successfull CAS. } } } while (!queueVersion.compareAndSet(version, newVersion)); }); } void initSpaceGlobal() { Bindings bindings = engine.createBindings(); scriptContext.setBindings(bindings, ScriptContext.ENGINE_SCOPE); bindings.put("space", this); try { Scripts.LocalContext ctx = Scripts.createContext(Scripts.Space.this); Scripts.setContext(ctx); try { scriptContext.setAttribute( ScriptEngine.FILENAME, PLATYPUS_JS_FILENAME, ScriptContext.ENGINE_SCOPE); engine.eval(new URLReader(platypusJsUrl), scriptContext); } finally { Scripts.setContext(null); } } catch (ScriptException ex) { Logger.getLogger(Scripts.class.getName()).log(Level.SEVERE, null, ex); } } public JSObject readJsArray(Collection<Map<String, Object>> aCollection) { JSObject result = makeArray(); JSObject jsPush = (JSObject) result.getMember("push"); aCollection.forEach( (Map<String, Object> aItem) -> { JSObject jsItem = makeObj(); aItem .entrySet() .forEach( (Map.Entry<String, Object> aItemContent) -> { jsItem.setMember(aItemContent.getKey(), toJs(aItemContent.getValue())); }); jsPush.call(result, new Object[] {jsItem}); }); return result; } } protected static Consumer<Runnable> tasks; // bio thread pool protected static ThreadPoolExecutor bio; public static void init(Path aAbsoluteApiPath) throws MalformedURLException { absoluteApiPath = aAbsoluteApiPath; platypusJsUrl = absoluteApiPath.resolve(PLATYPUS_JS_FILENAME).toUri().toURL(); } public static Path getAbsoluteApiPath() { return absoluteApiPath; } public static void initTasks(Consumer<Runnable> aTasks) { assert tasks == null : "Scripts tasks are already initialized"; tasks = aTasks; } public static void offerTask(Runnable aTask) { assert tasks != null : "Scripts tasks are not initialized"; Scripts.getContext().incAsyncsCount(); tasks.accept(aTask); } public static void initBIO(int aMaxThreads) { bio = new ThreadPoolExecutor( aMaxThreads, aMaxThreads, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new DeamonThreadFactory("platypus-abio-", false)); bio.allowCoreThreadTimeOut(true); } public static void shutdown() { if (bio != null) { bio.shutdownNow(); } } public static void startBIO(Runnable aBioTask) { LocalContext context = getContext(); context.incAsyncsCount(); bio.submit( () -> { setContext(context); try { aBioTask.run(); } finally { setContext(null); } }); } public static Space createSpace() throws ScriptException { Space space = new Space(new SimpleScriptContext()); return space; } public static Space createQueue() throws ScriptException { Space space = new Space(); return space; } public static boolean isInitialized() { Space space = getContext() != null ? getContext().getSpace() : null; return space != null && space.listenElementsFunc != null && space.listenFunc != null && space.scalarDefFunc != null && space.collectionDefFunc != null; } public static boolean isValidJsIdentifier(final String aName) { if (aName != null && !aName.trim().isEmpty()) { try { FunctionNode astRoot = parseJs(String.format("function %s() {}", aName)); return astRoot != null && !astRoot.getBody().getStatements().isEmpty(); } catch (Exception ex) { return false; } } return false; } public static FunctionNode parseJs(String aJsContent) { Source source = Source.sourceFor("", aJsContent); // NOI18N Options options = new Options(null); ScriptEnvironment env = new ScriptEnvironment(options, null, null); ErrorManager errors = new ErrorManager(); Parser p = new Parser(env, source, errors); return p.parse(); } /** * Extracts the comments tokens from a JavaScript source. * * @param aSource a source * @return a list of comment tokens */ public static List<Long> getCommentsTokens(String aSource) { TokenStream tokens = new TokenStream(); Lexer lexer = new Lexer(Source.sourceFor("", aSource), tokens); // NOI18N long t; TokenType tt = TokenType.EOL; int i = 0; List<Long> commentsTokens = new ArrayList<>(); while (tt != TokenType.EOF) { // Get next token in nashorn's parser way while (i > tokens.last()) { if (tokens.isFull()) { tokens.grow(); } lexer.lexify(); } t = tokens.get(i++); tt = Token.descType(t); if (tt == TokenType.COMMENT) { commentsTokens.add(t); } } return commentsTokens; } /** * Removes all commentaries from some JavaScript code. * * @param text a source * @return comments-free JavaScript code */ public static String removeComments(String text) { StringBuilder sb = new StringBuilder(); int i = 0; for (Long t : getCommentsTokens(text)) { int offset = Token.descPosition(t); int lenght = Token.descLength(t); sb.append(text.substring(i, offset)); for (int j = 0; j < lenght; j++) { sb.append(" "); // NOI18N } i = offset + lenght; } sb.append(text.substring(i)); return sb.toString(); } /** * Searches for all <code>this</code> aliases in a constructor. * * @param moduleConstructor a constructor to search in * @return a set of aliases including <code>this</code> itself */ public static Set<String> getThisAliases(final FunctionNode moduleConstructor) { final Set<String> aliases = new HashSet<>(); if (moduleConstructor != null && moduleConstructor.getBody() != null) { aliases.add(THIS_KEYWORD); LexicalContext lc = new LexicalContext(); moduleConstructor.accept( new NodeOperatorVisitor<LexicalContext>(lc) { @Override public boolean enterVarNode(VarNode varNode) { if (lc.getCurrentFunction() == moduleConstructor) { if (varNode.getAssignmentSource() instanceof IdentNode) { IdentNode in = (IdentNode) varNode.getAssignmentSource(); if (THIS_KEYWORD.equals(in.getName())) { aliases.add(varNode.getAssignmentDest().getName()); } } } return super.enterVarNode(varNode); } }); } return aliases; } protected static final String SCRIPT_NOT_INITIALIZED = "Platypus script functions are not initialized."; public static void unlisten(JSObject aCookie) { JSObject unlisten = (JSObject) aCookie.getMember("unlisten"); unlisten.call(null, new Object[] {}); } public static boolean isInNode(Node node, int offset) { return node.getStart() <= offset && offset <= node.getFinish() + 1; } public static boolean isInNode(Node outerNode, Node innerNode) { return outerNode.getStart() <= innerNode.getStart() && innerNode.getFinish() <= outerNode.getFinish(); } public static Node getOffsetNode(Node node, final int offset) { GetOffsetNodeVisitorSupport vs = new GetOffsetNodeVisitorSupport(node, offset); Node offsetNode = vs.getOffsetNode(); return offsetNode != null ? offsetNode : node; } private static class GetOffsetNodeVisitorSupport { private final Node root; private final int offset; private Node offsetNode; public GetOffsetNodeVisitorSupport(Node root, int offset) { this.root = root; this.offset = offset; } public Node getOffsetNode() { final LexicalContext lc = new LexicalContext(); root.accept( new NodeVisitor<LexicalContext>(lc) { @Override protected boolean enterDefault(Node node) { if (isInNode(node, offset)) { offsetNode = node; return true; } return false; } }); return offsetNode; } } }