001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.scxml2.env.groovy; 018 019import java.lang.reflect.Array; 020import java.util.Collection; 021import java.util.Map; 022 023import org.apache.commons.scxml2.Builtin; 024import org.apache.commons.scxml2.SCXMLExpressionException; 025import org.apache.commons.scxml2.XPathBuiltin; 026 027import groovy.lang.Binding; 028import groovy.lang.MissingPropertyException; 029import groovy.lang.Script; 030 031/** 032 * Groovy {@link Script} base class for SCXML, providing the standard 'builtin' functions {@link #In(String)}, 033 * {@link #Data(String)} and {@link #Location(String)}, as well as JEXL like convenience functions 034 * {@link #empty(Object)} and {@link #var(String)}. 035 */ 036public abstract class GroovySCXMLScript extends Script { 037 038 GroovyContext context; 039 GroovyContextBinding binding; 040 041 protected GroovySCXMLScript() { 042 super(null); 043 } 044 045 @Override 046 public void setBinding(final Binding binding) { 047 super.setBinding(binding); 048 this.binding = (GroovyContextBinding)binding; 049 this.context = this.binding.getContext(); 050 } 051 052 /** 053 * Implements the In() predicate for SCXML documents ( see Builtin#isMember ) 054 * @param state The State ID to compare with 055 * @return Whether this State belongs to this Set 056 */ 057 public boolean In(final String state) { 058 return Builtin.isMember(context, state); 059 } 060 061 /** 062 * Implements the Data() predicate for SCXML documents. 063 * @param expression the XPath expression 064 * @return the data matching the expression 065 */ 066 public Object Data(final String expression) throws SCXMLExpressionException { 067 return XPathBuiltin.eval(context, expression); 068 } 069 070 /** 071 * Implements the Location() predicate for SCXML documents. 072 * @param location the XPath expression 073 * @return the location list for the location expression 074 */ 075 public Object Location(final String location) throws SCXMLExpressionException { 076 return XPathBuiltin.evalLocation(context, location); 077 } 078 079 /** 080 * The var function can be used to check if a variable is defined, 081 * <p> 082 * In the Groovy language (implementation) you cannot check for an undefined variable directly: 083 * Groovy will raise a MissingPropertyException before you get the chance. 084 * </p> 085 * <p> 086 * The var function works around this by indirectly looking up the variable, which you therefore have to specify as a String. 087 * </p> 088 * <p> 089 * So, use <code>var('name')</code>, not <code>var(name)</code> 090 * </p> 091 * <p> 092 * Note: this function doesn't support object navigation, like <code>var('name.property')</code>.<br/> 093 * Instead, once you established a variable 'name' exists, you <em>thereafter</em> can use the standard Groovy 094 * Safe Navigation operator (?.), like so: <code>name?.property</code>.<br/> 095 * See for more information: <a href="http://docs.codehaus.org/display/GROOVY/Operators#Operators-SafeNavigationOperator(?.)">Groovy SafeNavigationOperator</a> 096 * </p> 097 */ 098 public boolean var(String property) { 099 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}