package com.eas.script; import com.eas.concurrent.PlatypusThreadFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.nio.channels.CompletionHandler; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptException; import javax.script.SimpleScriptContext; import jdk.nashorn.api.scripting.JSObject; import jdk.nashorn.api.scripting.NashornScriptEngine; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import jdk.nashorn.api.scripting.ScriptUtils; import jdk.nashorn.api.scripting.URLReader; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.parser.Lexer; import jdk.nashorn.internal.parser.Parser; import jdk.nashorn.internal.parser.Token; import jdk.nashorn.internal.parser.TokenStream; import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.ErrorManager; import jdk.nashorn.internal.runtime.JSType; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.Undefined; import jdk.nashorn.internal.runtime.options.Options; /** * * @author vv, mg */ public class Scripts { private static final NashornScriptEngineFactory SCRIPT_FACTORY = new NashornScriptEngineFactory(); private static final NashornScriptEngine SCRIPT_ENGINE = (NashornScriptEngine) SCRIPT_FACTORY.getScriptEngine(); protected static final String INTERNALS_MODULENAME = "internals"; public static final String INTERNALS_JS_FILENAME = INTERNALS_MODULENAME + ".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 public static /*final*/ URL internalsUrl; public static /*final*/ boolean globalAPI; public static volatile Path absoluteApiPath; public static NashornScriptEngine getEngine() { return SCRIPT_ENGINE; } private static final ThreadLocal<LocalContext> contextRef = new ThreadLocal<>(); private static final ThreadLocal<Space> spaceRef = new ThreadLocal<>(); private static Space onlySpace; // for single threaded environment public static boolean isGlobalAPI() { return globalAPI; } public static Space getSpace() { return onlySpace != null ? onlySpace : spaceRef.get(); } public static void setSpace(Space aSpace) { if (aSpace != null) { spaceRef.set(aSpace); } else { spaceRef.remove(); } } /** * Warning! Use it only once and only in single threaded environment * * @param aSpace */ public static void setOnlySpace(Space aSpace) { onlySpace = aSpace; } 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 final Object request; protected final Object response; protected final Object principal; protected final Object session; protected Integer asyncsCount; public LocalContext(Object aPrincipal, Object aSession) { this(null, null, aPrincipal, aSession); } public LocalContext(Object aRequest, Object aResponse, Object aPrincipal, Object aSession) { super(); request = aRequest; response = aResponse; principal = aPrincipal; session = aSession; } public Object getSession() { return session; } public Object getRequest() { return request; } public Object getResponse() { return response; } public Object getPrincipal() { return principal; } public int getAsyncsCount() { return asyncsCount != null ? asyncsCount : 0; } public void incAsyncsCount() { if (asyncsCount != null) { asyncsCount++; } } public void initAsyncs(Integer aSeed) { asyncsCount = aSeed; } } public static class Pending { protected Consumer<Void> onLoad; protected Consumer<Exception> onError; protected LocalContext context = getContext(); public Pending(Consumer<Void> aOnLoad, Consumer<Exception> aOnError) { super(); onLoad = aOnLoad; onError = aOnError; } public void loaded() { LocalContext oldContext = getContext(); setContext(context); try { onLoad.accept(null); } finally { setContext(oldContext); } } public void failed(Exception ex) { LocalContext oldContext = getContext(); setContext(context); try { onError.accept(ex); } finally { setContext(oldContext); } } } public static class AmdDefine { protected String moduleName; protected String[] amdDependencies; protected JSObject moduleDefiner; public AmdDefine(String aModuleName, String[] aAmdDependencies, JSObject aModuleDefiner) { super(); moduleName = aModuleName; amdDependencies = aAmdDependencies; moduleDefiner = aModuleDefiner; } public String getModuleName() { return moduleName; } public String[] getAmdDependencies() { return amdDependencies; } public JSObject getModuleDefiner() { return moduleDefiner; } } public static class Space { protected ScriptContext scriptContext; protected Object global; protected Map<String, JSObject> publishers = new HashMap<>(); protected Collection<AmdDefine> amdDefines = new ArrayList<>(); // script files alredy executed within this script space protected Map<URL, Set<String>> executed = new HashMap<>(); protected Map<String, List<Pending>> pending = new HashMap<>(); protected Map<String, JSObject> defined = new HashMap<>(); protected Space() { this(null); global = new Object(); } public Space(ScriptContext aScriptContext) { super(); scriptContext = aScriptContext; } /** * This method is used by crazy designer only * * @return */ public String getFileNameFromContext() { return (String) scriptContext.getAttribute(ScriptEngine.FILENAME); } public void pendOn(String aModuleName, Scripts.Pending aPending) { List<Scripts.Pending> pends = pending.get(aModuleName); if (pends == null) { pends = new ArrayList<>(); pending.put(aModuleName, pends); } pends.add(aPending); } public void notifyLoaded(String aModuleName) { List<Scripts.Pending> pendings = pending.remove(aModuleName); if (pendings != null) { Scripts.Pending[] pend = pendings.toArray(new Scripts.Pending[]{}); pendings.clear(); for (Scripts.Pending p : pend) { p.loaded(); } } } public void notifyFailed(String aModuleName, Exception ex) { List<Scripts.Pending> pendings = pending.remove(aModuleName); if (pendings != null) { Scripts.Pending[] pend = pendings.toArray(new Scripts.Pending[]{}); pendings.clear(); for (Scripts.Pending p : pend) { p.failed(ex); } } } public Map<URL, Set<String>> getExecuted() { return Collections.unmodifiableMap(executed); } public Map<String, List<Pending>> getPending() { return pending; } public Map<String, JSObject> getDefined() { return defined; } public void addAmdDefine(String aModuleName, String[] aAmdDependencies, JSObject aModuleDefiner) { amdDefines.add(new AmdDefine(aModuleName, aAmdDependencies, aModuleDefiner)); } public Collection<AmdDefine> consumeAmdDefines() { Collection<AmdDefine> res = amdDefines; amdDefines = new ArrayList<>(); return res; } 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; protected JSObject restoreObjectFunc; public void setGlobal(Object aValue) { if (global == null) { global = aValue; } else { throw new IllegalStateException("Scripts space should be initialized only once."); } } public Object getGlobal() { return global; } public Object getUndefined() { return Undefined.getUndefined(); } 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 void setRestoreObjectFunc(JSObject aValue) { assert restoreObjectFunc == null; restoreObjectFunc = aValue; } public JSObject getRestoreObjectFunc() { return restoreObjectFunc; } 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 Number) { return ((Number) aValue).doubleValue(); } else if (aValue instanceof HasPublished) { return ((HasPublished) aValue).getPublished(); } else { return aValue; } } public JSObject toJsArray(List aArray) { JSObject published = makeArray(); JSObject push = (JSObject) published.getMember("push"); for (int i = 0; i < aArray.size(); i++) { push.call(published, toJs(aArray.get(i))); } return published; } 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; } public Object makeCopy(Object aSource) { assert copyObjectFunc != null : SCRIPT_NOT_INITIALIZED; return copyObjectFunc.call(null, new Object[]{aSource}); } public Object restoreCopy(Object aSource) { assert restoreObjectFunc != null : SCRIPT_NOT_INITIALIZED; return restoreObjectFunc.call(null, new Object[]{aSource}); } 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; JSObject jsConstructor = lookup(aModuleName); if (jsConstructor != null && jsConstructor.isFunction()) { return (JSObject) jsConstructor.newObject(new Object[]{}); } else { return null; } } public JSObject lookup(String aName) { JSObject amd = defined.get(aName); if (amd != null) { return amd; } else { return lookupInGlobal(aName); } } public JSObject lookupInGlobal(String aName) { assert lookupInGlobalFunc != null : SCRIPT_NOT_INITIALIZED; Object res = aName != null && !aName.isEmpty() ? lookupInGlobalFunc.call(null, new Object[]{aName}) : null; 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.toLowerCase().endsWith(".js") ? aSourceName.substring(0, aSourceName.length() - 3) : aSourceName, ScriptContext.ENGINE_SCOPE); Object result = SCRIPT_ENGINE.eval(new URLReader(aSourcePlace), scriptContext); executed.put(aSourcePlace, new HashSet<>()); return result; } public Object exec(String aSource) throws ScriptException, URISyntaxException { assert scriptContext != null : SCRIPT_NOT_INITIALIZED; return SCRIPT_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(); process(context, aTask); } public void process(Scripts.LocalContext context, Runnable aTask) { Runnable taskWrapper = () -> { setContext(context); try { setSpace(Space.this); try { aTask.run(); } finally { setSpace(null); } } finally { 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) { 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)); }); } /** * Public only for tests */ public void initSpaceGlobal() { Bindings bindings = SCRIPT_ENGINE.createBindings(); scriptContext.setBindings(bindings, ScriptContext.ENGINE_SCOPE); try { setSpace(Space.this); try { scriptContext.setAttribute(ScriptEngine.FILENAME, INTERNALS_MODULENAME, ScriptContext.ENGINE_SCOPE); SCRIPT_ENGINE.eval(new URLReader(internalsUrl), scriptContext); } finally { setSpace(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, boolean aGlobalAPI) throws MalformedURLException { globalAPI = aGlobalAPI; internalsUrl = aAbsoluteApiPath.resolve(INTERNALS_JS_FILENAME).toUri().toURL(); absoluteApiPath = aAbsoluteApiPath; } 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 initTasks(ExecutorService aExecutor) { class TasksExecutor implements Consumer<Runnable>, Function<Void, ExecutorService> { @Override public void accept(Runnable aTask) { aExecutor.submit(aTask); } @Override public ExecutorService apply(Void v) { return aExecutor; } } initTasks(new TasksExecutor()); } public static ExecutorService getTasksExecutorIfPresent() { assert tasks != null : "Scripts tasks are not initialized"; if (tasks instanceof Callable<?>) { return ((Function<Void, ExecutorService>) tasks).apply(null); } else { return null; } } public static void offerTask(Runnable aTask) { assert tasks != null : "Scripts tasks are not initialized"; if (Scripts.getContext() != null) { Scripts.getContext().incAsyncsCount(); } tasks.accept(aTask); } public static void initBIO(int aMaxThreads) { bio = new ThreadPoolExecutor(aMaxThreads, aMaxThreads, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new PlatypusThreadFactory("platypus-abio-", false)); bio.allowCoreThreadTimeOut(true); } public static void shutdown() { if (bio != null) { bio.shutdownNow(); } } public static void startBIO(Runnable aBioTask) { LocalContext context = getContext(); if (context != null) { 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; } /** * If scripts are initialized, then Platypua.ja system will work in full * manner. Otherwise it will not perform some actions, such as data binding. * * @return True if scripts are fully initialized, false otherwise. */ public static boolean isInitialized() { Space space = getSpace(); return space != null && space.copyObjectFunc != null && space.restoreObjectFunc != null; } public static boolean isValidJsIdentifier(final String aName) { if (aName != null && !aName.trim().isEmpty()) { try { FunctionNode astRoot = parseJs(String.format("function %s() {}", aName)).getAst(); return astRoot != null && !astRoot.getBody().getStatements().isEmpty(); } catch (Exception ex) { return false; } } return false; } public static ParsedJs 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(); // Map<Long, Long> prevComments = new HashMap<>(); Parser p = new Parser(env, source, errors) { @Override public FunctionNode parse(String scriptName, int startPos, int len, boolean allowPropertyFunction) { prevComments.clear(); stream = new TokenStream() { protected long prevToken; @Override public void put(long token) { if (Token.descType(token) != TokenType.EOL) { if (Token.descType(prevToken) == TokenType.COMMENT) { prevComments.put(token, prevToken); } prevToken = token; } super.put(token); } }; lexer = new Lexer(source, stream, false); // Set up first token (skips opening EOL.) k = -1; next(); // Begin parse. try { Method program = Parser.class.getDeclaredMethod("program", new Class[]{String.class, boolean.class}); program.setAccessible(true); return (FunctionNode) program.invoke(this, new Object[]{scriptName, true}); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Scripts.class.getName()).log(Level.WARNING, null, ex); return null; } } }; FunctionNode jsAst = p.parse(); return jsAst != null ? new ParsedJs(jsAst, prevComments) : null; } /** * 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(); } protected static final String SCRIPT_NOT_INITIALIZED = "Platypus script functions are not initialized."; public static void unlisten(JSObject aCookie) { if (aCookie != null) { JSObject unlisten = (JSObject) aCookie.getMember("unlisten"); unlisten.call(null, new Object[]{}); } } public static CompletionHandler<?, ?> asCompletionHandler(JSObject aOnSuccess, JSObject aOnFailure) { final Space callingSpace = getSpace(); final LocalContext callingContext = getContext(); return new CompletionHandler<Integer, Object>() { @Override public void completed(Integer result, Object attachment) { callingSpace.process(callingContext, () -> { if (aOnSuccess != null) { aOnSuccess.call(null, new Object[]{result}); } }); } @Override public void failed(Throwable exc, Object attachment) { callingSpace.process(callingContext, () -> { if (aOnFailure != null) { aOnFailure.call(null, new Object[]{exc.toString()}); } }); } }; } /** * For external API. * * @param aWrapped * @return BiConsumer<Object, Throwable> Object - result instance, * Throwable - exception raised while an operation. */ public static BiConsumer<Object, Throwable> inContext(BiConsumer<Object, Throwable> aWrapped) { Space callingSpace = getSpace(); LocalContext callingContext = getContext(); return (Object aResult, Throwable aReason) -> { callingSpace.process(callingContext, () -> { aWrapped.accept(aResult, aReason); }); }; } 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; } } }