Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.commons.scxml2.env.groovy; import groovy.lang.Script; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.scxml2.Context; import org.apache.commons.scxml2.Evaluator; import org.apache.commons.scxml2.EvaluatorProvider; import org.apache.commons.scxml2.SCXMLExpressionException; import org.apache.commons.scxml2.SCXMLSystemContext; import org.apache.commons.scxml2.env.AbstractBaseEvaluator; import org.apache.commons.scxml2.env.EffectiveContextMap; import org.apache.commons.scxml2.model.SCXML; /** * Evaluator implementation enabling use of Groovy expressions in SCXML documents. * <P> * This implementation itself is thread-safe, so you can keep singleton for efficiency. * </P> */ public class GroovyEvaluator extends AbstractBaseEvaluator { /** Serial version UID. */ private static final long serialVersionUID = 1L; /** * Unique context variable name used for temporary reference to assign data (thus must be a valid variable name) */ private static final String ASSIGN_VARIABLE_NAME = "a" + UUID.randomUUID().toString().replace('-', 'x'); public static final String SUPPORTED_DATA_MODEL = "groovy"; public static class GroovyEvaluatorProvider implements EvaluatorProvider { @Override public String getSupportedDatamodel() { return SUPPORTED_DATA_MODEL; } @Override public Evaluator getEvaluator() { return new GroovyEvaluator(); } @Override public Evaluator getEvaluator(final SCXML document) { return new GroovyEvaluator(); } } /** Error message if evaluation context is not a GroovyContext. */ private static final String ERR_CTX_TYPE = "Error evaluating Groovy " + "expression, Context must be a org.apache.commons.scxml2.env.groovy.GroovyContext"; protected static final GroovyExtendableScriptCache.ScriptPreProcessor scriptPreProcessor = new GroovyExtendableScriptCache.ScriptPreProcessor() { /** * Pattern for case-sensitive matching of the Groovy operator aliases, delimited by whitespace */ public final Pattern GROOVY_OPERATOR_ALIASES_PATTERN = Pattern .compile("(?<=\\s)(and|or|not|eq|lt|le|ne|gt|ge)(?=\\s)"); /** * Groovy operator aliases mapped to their underlying Groovy operator */ public final Map<String, String> GROOVY_OPERATOR_ALIASES = Collections .unmodifiableMap(new HashMap<String, String>() { { put("and", "&& "); put("or", "||"); put("not", " ! "); put("eq", "=="); put("lt", "< "); put("le", "<="); put("ne", "!="); put("gt", "> "); put("ge", ">="); } }); @Override public String preProcess(final String script) { if (script == null || script.length() == 0) { return script; } StringBuffer sb = null; Matcher m = GROOVY_OPERATOR_ALIASES_PATTERN.matcher(script); while (m.find()) { if (sb == null) { sb = new StringBuffer(); } m.appendReplacement(sb, GROOVY_OPERATOR_ALIASES.get(m.group())); } if (sb != null) { m.appendTail(sb); return sb.toString(); } return script; } }; private final boolean useInitialScriptAsBaseScript; private final GroovyExtendableScriptCache scriptCache; public GroovyEvaluator() { this(false); } public GroovyEvaluator(boolean useInitialScriptAsBaseScript) { this.useInitialScriptAsBaseScript = useInitialScriptAsBaseScript; this.scriptCache = newScriptCache(); } /** * Overridable factory method to create the GroovyExtendableScriptCache for this GroovyEvaluator. * <p> * The default implementation configures the scriptCache to use the {@link #scriptPreProcessor GroovyEvaluator scriptPreProcessor} * and the {@link GroovySCXMLScript} as script base class. * </p> * * @return GroovyExtendableScriptCache for this GroovyEvaluator */ protected GroovyExtendableScriptCache newScriptCache() { GroovyExtendableScriptCache scriptCache = new GroovyExtendableScriptCache(); scriptCache.setScriptPreProcessor(getScriptPreProcessor()); scriptCache.setScriptBaseClass(GroovySCXMLScript.class.getName()); return scriptCache; } @SuppressWarnings("unchecked") protected Script getScript(GroovyContext groovyContext, String scriptBaseClassName, String scriptSource) { Script script = scriptCache.getScript(scriptBaseClassName, scriptSource); script.setBinding(groovyContext.getBinding()); return script; } @SuppressWarnings("unused") public void clearCache() { scriptCache.clearCache(); } public GroovyExtendableScriptCache.ScriptPreProcessor getScriptPreProcessor() { return scriptPreProcessor; } /* SCXMLEvaluator implementation methods */ @Override public String getSupportedDatamodel() { return SUPPORTED_DATA_MODEL; } @Override public boolean requiresGlobalContext() { return false; } /** * Evaluate an expression. * * @param ctx variable context * @param expr expression * @return a result of the evaluation * @throws SCXMLExpressionException For a malformed expression * @see Evaluator#eval(Context, String) */ @Override public Object eval(final Context ctx, final String expr) throws SCXMLExpressionException { if (expr == null) { return null; } if (!(ctx instanceof GroovyContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } final GroovyContext groovyCtx = (GroovyContext) ctx; if (groovyCtx.getGroovyEvaluator() == null) { groovyCtx.setGroovyEvaluator(this); } try { return getScript(getEffectiveContext(groovyCtx), groovyCtx.getScriptBaseClass(), expr).run(); } catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("eval('" + expr + "'): " + exMessage, e); } } /** * @see Evaluator#evalCond(Context, String) */ @Override public Boolean evalCond(final Context ctx, final String expr) throws SCXMLExpressionException { if (expr == null) { return null; } if (!(ctx instanceof GroovyContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } final GroovyContext groovyCtx = (GroovyContext) ctx; if (groovyCtx.getGroovyEvaluator() == null) { groovyCtx.setGroovyEvaluator(this); } try { final Object result = getScript(getEffectiveContext(groovyCtx), groovyCtx.getScriptBaseClass(), expr) .run(); return result == null ? Boolean.FALSE : (Boolean) result; } catch (Exception e) { String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("evalCond('" + expr + "'): " + exMessage, e); } } /** * @see Evaluator#evalAssign(Context, String, Object, AssignType, String) */ public void evalAssign(final Context ctx, final String location, final Object data, final AssignType type, final String attr) throws SCXMLExpressionException { final StringBuilder sb = new StringBuilder(location).append("=").append(ASSIGN_VARIABLE_NAME); try { ctx.getVars().put(ASSIGN_VARIABLE_NAME, data); eval(ctx, sb.toString()); } finally { ctx.getVars().remove(ASSIGN_VARIABLE_NAME); } } /** * @see Evaluator#evalScript(Context, String) */ @Override public Object evalScript(final Context ctx, final String scriptSource) throws SCXMLExpressionException { if (scriptSource == null) { return null; } if (!(ctx instanceof GroovyContext)) { throw new SCXMLExpressionException(ERR_CTX_TYPE); } final GroovyContext groovyCtx = (GroovyContext) ctx; if (groovyCtx.getGroovyEvaluator() == null) { groovyCtx.setGroovyEvaluator(this); } try { final GroovyContext effective = getEffectiveContext(groovyCtx); final boolean inGlobalContext = groovyCtx.getParent() instanceof SCXMLSystemContext; final Script script = getScript(effective, groovyCtx.getScriptBaseClass(), scriptSource); final Object result = script.run(); if (inGlobalContext && useInitialScriptAsBaseScript) { groovyCtx.setScriptBaseClass(script.getClass().getName()); } return result; } catch (Exception e) { final String exMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getCanonicalName(); throw new SCXMLExpressionException("evalScript('" + scriptSource + "'): " + exMessage, e); } } protected ClassLoader getGroovyClassLoader() { return scriptCache.getGroovyClassLoader(); } /** * Create a new child context. * * @param parent parent context * @return new child context * @see Evaluator#newContext(Context) */ @Override public Context newContext(final Context parent) { return new GroovyContext(parent, this); } /** * Create a new context which is the summation of contexts from the * current state to document root, child has priority over parent * in scoping rules. * * @param nodeCtx The GroovyContext for this state. * @return The effective GroovyContext for the path leading up to * document root. */ protected GroovyContext getEffectiveContext(final GroovyContext nodeCtx) { return new GroovyContext(nodeCtx, new EffectiveContextMap(nodeCtx), this); } }