1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.scxml2.env.groovy;
18
19 import java.lang.reflect.Array;
20 import java.util.Collection;
21 import java.util.Map;
22
23 import org.apache.commons.scxml2.Builtin;
24 import org.apache.commons.scxml2.SCXMLExpressionException;
25 import org.apache.commons.scxml2.XPathBuiltin;
26
27 import groovy.lang.Binding;
28 import groovy.lang.MissingPropertyException;
29 import groovy.lang.Script;
30
31 /**
32 * Groovy {@link Script} base class for SCXML, providing the standard 'builtin' functions {@link #In(String)},
33 * {@link #Data(String)} and {@link #Location(String)}, as well as JEXL like convenience functions
34 * {@link #empty(Object)} and {@link #var(String)}.
35 */
36 public abstract class GroovySCXMLScript extends Script {
37
38 GroovyContext context;
39 GroovyContextBinding binding;
40
41 protected GroovySCXMLScript() {
42 super(null);
43 }
44
45 @Override
46 public void setBinding(final Binding binding) {
47 super.setBinding(binding);
48 this.binding = (GroovyContextBinding)binding;
49 this.context = this.binding.getContext();
50 }
51
52 /**
53 * Implements the In() predicate for SCXML documents ( see Builtin#isMember )
54 * @param state The State ID to compare with
55 * @return Whether this State belongs to this Set
56 */
57 public boolean In(final String state) {
58 return Builtin.isMember(context, state);
59 }
60
61 /**
62 * Implements the Data() predicate for SCXML documents.
63 * @param expression the XPath expression
64 * @return the data matching the expression
65 */
66 public Object Data(final String expression) throws SCXMLExpressionException {
67 return XPathBuiltin.eval(context, expression);
68 }
69
70 /**
71 * Implements the Location() predicate for SCXML documents.
72 * @param location the XPath expression
73 * @return the location list for the location expression
74 */
75 public Object Location(final String location) throws SCXMLExpressionException {
76 return XPathBuiltin.evalLocation(context, location);
77 }
78
79 /**
80 * The var function can be used to check if a variable is defined,
81 * <p>
82 * In the Groovy language (implementation) you cannot check for an undefined variable directly:
83 * Groovy will raise a MissingPropertyException before you get the chance.
84 * </p>
85 * <p>
86 * The var function works around this by indirectly looking up the variable, which you therefore have to specify as a String.
87 * </p>
88 * <p>
89 * So, use <code>var('name')</code>, not <code>var(name)</code>
90 * </p>
91 * <p>
92 * Note: this function doesn't support object navigation, like <code>var('name.property')</code>.<br/>
93 * Instead, once you established a variable 'name' exists, you <em>thereafter</em> can use the standard Groovy
94 * Safe Navigation operator (?.), like so: <code>name?.property</code>.<br/>
95 * See for more information: <a href="http://docs.codehaus.org/display/GROOVY/Operators#Operators-SafeNavigationOperator(?.)">Groovy SafeNavigationOperator</a>
96 * </p>
97 */
98 public boolean var(String property) {
99 if (!context.has(property)) {
100 try {
101 getMetaClass().getProperty(this, property);
102 } catch (MissingPropertyException e) {
103 return false;
104 }
105 }
106 return true;
107 }
108
109 /**
110 * The empty function mimics the behavior of the JEXL empty function, in that it returns true if the parameter is:
111 * <ul>
112 * <li>null, or</li>
113 * <li>an empty String, or</li>
114 * <li>an zero length Array, or</li>
115 * <li>an empty Collection, or</li>
116 * <li>an empty Map</li>
117 * </ul>
118 * <p>
119 * Note: one difference with the JEXL language is that Groovy doesn't allow checking for undefined variables.<br/>
120 * Before being able to check, Groovy will already have raised an MissingPropertyException if the variable cannot be found.<br/>
121 * To work around this, the custom {@link #var(String)} function is available.
122 * </p>
123 */
124 public boolean empty(Object obj) {
125 return obj == null ||
126 (obj instanceof String && ((String)obj).isEmpty()) ||
127 ((obj.getClass().isArray() && Array.getLength(obj)==0)) ||
128 (obj instanceof Collection && ((Collection)obj).size()==0) ||
129 (obj instanceof Map && ((Map)obj).isEmpty());
130 }
131 }