View Javadoc

1   /*
2    $Id: BuilderSupport.java 4247 2006-11-19 19:00:19Z mcspanky $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.util;
47  
48  
49  import groovy.lang.Closure;
50  import groovy.lang.GroovyObjectSupport;
51  import groovy.lang.MissingMethodException;
52  
53  import java.util.List;
54  import java.util.Map;
55  
56  import org.codehaus.groovy.runtime.InvokerHelper;
57  
58  /***
59   * An abstract base class for creating arbitrary nested trees of objects
60   * or events
61   *
62   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
63   * @version $Revision: 4247 $
64   */
65  public abstract class BuilderSupport extends GroovyObjectSupport {
66  
67      private Object current;
68      private Closure nameMappingClosure;
69      private BuilderSupport proxyBuilder;
70  
71      public BuilderSupport() {
72          this.proxyBuilder = this;
73      }
74  
75      public BuilderSupport(BuilderSupport proxyBuilder) {
76          this(null, proxyBuilder);
77      }
78  
79      public BuilderSupport(Closure nameMappingClosure, BuilderSupport proxyBuilder) {
80          this.nameMappingClosure = nameMappingClosure;
81          this.proxyBuilder = proxyBuilder;
82      }
83  
84      /***
85       * Convenience method when no arguments are required
86       * @return the result of the call
87       * @param methodName the name of the method to invoke
88       */
89      public Object invokeMethod(String methodName) {
90          return invokeMethod(methodName, null);
91      }
92  
93      public Object invokeMethod(String methodName, Object args) {
94          Object name = getName(methodName);
95          return doInvokeMethod(methodName, name, args);
96      }
97  
98      protected Object doInvokeMethod(String methodName, Object name, Object args) {
99          Object node = null;
100         Closure closure = null;
101         List list = InvokerHelper.asList(args);
102 
103         //System.out.println("Called invokeMethod with name: " + name + " arguments: " + list);
104 
105         switch (list.size()) {
106         		case 0:
107 	    	            node = proxyBuilder.createNode(name);
108         		    break;
109         	    	case 1:
110         	    	{
111         	    	    	Object object = list.get(0);
112         	    	    	if (object instanceof Map) {
113         	    	    	    node = proxyBuilder.createNode(name, (Map) object);
114         	    	    	} else if (object instanceof Closure) {
115         	    	    	    closure = (Closure) object;
116         	    	    	    node = proxyBuilder.createNode(name);
117         	    	    	} else {
118         	    	    	    node = proxyBuilder.createNode(name, object);
119         	    	    	}
120         	    	}
121         	    	break;
122         	    	case 2:
123         	    	{
124         	    	    Object object1 = list.get(0);
125     	    	        Object object2 = list.get(1);
126         	    	    if (object1 instanceof Map) {
127         	    	        if (object2 instanceof Closure) {
128         	    	            closure = (Closure) object2;
129         	    	            node = proxyBuilder.createNode(name, (Map) object1);
130         	    	        } else {
131         	    	            node = proxyBuilder.createNode(name, (Map) object1, object2);
132         	    	        }
133         	    	    } else {
134         	    	        if (object2 instanceof Closure) {
135         	    	            closure = (Closure) object2;
136         	    	            node = proxyBuilder.createNode(name, object1);
137 				} else if (object2 instanceof Map) {
138 				    node = proxyBuilder.createNode(name, (Map) object2, object1);
139         	    	        } else {
140 				    throw new MissingMethodException(name.toString(), getClass(), list.toArray(), false);
141 				}
142         	    	    }
143         	    	}
144         	    	break;
145         	    	case 3:
146         	    	{
147         	    	    Object arg0 = list.get(0);
148         	    	    Object arg1 = list.get(1);
149         	    	    Object arg2 = list.get(2);
150         	    	    if (arg0 instanceof Map && arg2 instanceof Closure) {
151         	    	        closure = (Closure) arg2;
152         	    	        node = proxyBuilder.createNode(name, (Map) arg0, arg1);
153 			    } else if (arg1 instanceof Map && arg2 instanceof Closure) {
154         	    	        closure = (Closure) arg2;
155         	    	        node = proxyBuilder.createNode(name, (Map) arg1, arg0);
156 			    } else {
157 				throw new MissingMethodException(name.toString(), getClass(), list.toArray(), false);
158 			   }
159         	    	}
160         	    	break;
161         	    	default:
162         	    	{
163 			    throw new MissingMethodException(name.toString(), getClass(), list.toArray(), false);
164 			}
165 
166         }
167 
168         if (current != null) {
169             proxyBuilder.setParent(current, node);
170         }
171 
172         if (closure != null) {
173             // push new node on stack
174             Object oldCurrent = current;
175             current = node;
176 
177             // lets register the builder as the delegate
178             setClosureDelegate(closure, node);
179             closure.call();
180 
181             current = oldCurrent;
182         }
183 
184         proxyBuilder.nodeCompleted(current, node);
185         return node;
186     }
187 
188     /***
189      * A strategy method to allow derived builders to use
190      * builder-trees and switch in different kinds of builders.
191      * This method should call the setDelegate() method on the closure
192      * which by default passes in this but if node is-a builder
193      * we could pass that in instead (or do something wacky too)
194      *
195      * @param closure the closure on which to call setDelegate()
196      * @param node the node value that we've just created, which could be
197      * a builder
198      */
199     protected void setClosureDelegate(Closure closure, Object node) {
200         closure.setDelegate(this);
201     }
202 
203     protected abstract void setParent(Object parent, Object child);
204     protected abstract Object createNode(Object name);
205     protected abstract Object createNode(Object name, Object value);
206     protected abstract Object createNode(Object name, Map attributes);
207     protected abstract Object createNode(Object name, Map attributes, Object value);
208 
209     /***
210      * A hook to allow names to be converted into some other object
211      * such as a QName in XML or ObjectName in JMX
212      * @param methodName
213      */
214     protected Object getName(String methodName) {
215         if (nameMappingClosure != null) {
216             return nameMappingClosure.call(methodName);
217         }
218         return methodName;
219     }
220 
221 
222     /***
223      * A hook to allow nodes to be processed once they have had all of their
224      * children applied
225      */
226     protected void nodeCompleted(Object parent, Object node) {
227     }
228 
229     protected Object getCurrent() {
230         return current;
231     }
232 
233     protected void setCurrent(Object current) {
234         this.current = current;
235     }
236 }