View Javadoc
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 }