/* * Copyright 2012 Jason Miller * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jj.document; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import jj.engine.DoCallFunction; import jj.engine.DoInvokeFunction; import jj.engine.EngineAPI; import jj.event.Publisher; import jj.script.RhinoContext; import jj.script.module.ScriptResource; import org.mozilla.javascript.ScriptableObject; /** * just a simple extraction of compilation duties from the document script resource, * to keep the classes relatively focused * * * @author jason * */ @Singleton class ScriptCompiler { private final Provider<RhinoContext> contextProvider; private final Publisher publisher; @Inject ScriptCompiler(final Provider<RhinoContext> contextProvider, final Publisher publisher) { this.contextProvider = contextProvider; this.publisher = publisher; } void compile( final ScriptableObject scope, final ScriptResource clientScript, final ScriptResource sharedScript, final String serverScriptPath ) { try (RhinoContext context = contextProvider.get()) { if (sharedScript != null) { publisher.publish(new EvaluatingSharedScript(sharedScript.path().toString())); try { context.evaluateString( scope, sharedScript.source(), sharedScript.path().toString() ); } catch (RuntimeException e) { publisher.publish(new ErrorEvaluatingSharedScript(sharedScript.path().toString(), e)); throw e; } } if (clientScript != null) { String clientStub = extractClientStubs(clientScript); publisher.publish(new EvaluatingClientStub(clientScript.path().toString(), clientStub)); try { context.evaluateString(scope, clientStub, "client stub for " + serverScriptPath); } catch (RuntimeException e) { publisher.publish(new ErrorEvaluatingClientStub(clientScript.path().toString(), e)); throw e; } } } } private static final Pattern COUNT_PATTERN = Pattern.compile("\\r?\\n", Pattern.MULTILINE); private static final Pattern TOP_LEVEL_FUNCTION_SIGNATURE_PATTERN = Pattern.compile("^function[\\s]*([^\\(]+)\\([^\\)]*\\)[\\s]*\\{[\\s]*$"); /** * Pulls the client stubs out of the client script according to some simple rules * which have been detailed... somewhere. in the test! * * will throw an NPE if the client script is null * @param clientScript * @return */ private String extractClientStubs(final ScriptResource clientScript) { StringBuilder stubs = new StringBuilder(); final String[] lines = COUNT_PATTERN.split(clientScript.source()); Matcher lastMatcher = null; String previousLine = null; for (String line : lines) { if (lastMatcher == null) { Matcher matcher = TOP_LEVEL_FUNCTION_SIGNATURE_PATTERN.matcher(line); if (matcher.matches()) { lastMatcher = matcher; } } else if ("}".equals(line) && lastMatcher != null) { boolean hasReturn = previousLine.trim().startsWith("return "); stubs.append("function ") .append(lastMatcher.group(1)) .append("(){") .append(hasReturn ? "return " : "") .append("global['") .append(hasReturn ? DoInvokeFunction.PROP_DO_INVOKE : DoCallFunction.PROP_DO_CALL) .append("']('") .append(lastMatcher.group(1)) .append("',global['") .append(EngineAPI.PROP_CONVERT_ARGS) .append("'](arguments))") .append(";}\n"); //log.trace("found {}, {} return", lastMatcher.group(1), hasReturn ? "with" : "no"); lastMatcher = null; } previousLine = line; } return stubs.toString(); } }