001 /*
002 $Id: BuilderSupport.java 4247 2006-11-19 19:00:19Z mcspanky $
003
004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005
006 Redistribution and use of this software and associated documentation
007 ("Software"), with or without modification, are permitted provided
008 that the following conditions are met:
009
010 1. Redistributions of source code must retain copyright
011 statements and notices. Redistributions must also contain a
012 copy of this document.
013
014 2. Redistributions in binary form must reproduce the
015 above copyright notice, this list of conditions and the
016 following disclaimer in the documentation and/or other
017 materials provided with the distribution.
018
019 3. The name "groovy" must not be used to endorse or promote
020 products derived from this Software without prior written
021 permission of The Codehaus. For written permission,
022 please contact info@codehaus.org.
023
024 4. Products derived from this Software may not be called "groovy"
025 nor may "groovy" appear in their names without prior written
026 permission of The Codehaus. "groovy" is a registered
027 trademark of The Codehaus.
028
029 5. Due credit should be given to The Codehaus -
030 http://groovy.codehaus.org/
031
032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043 OF THE POSSIBILITY OF SUCH DAMAGE.
044
045 */
046 package groovy.util;
047
048
049 import groovy.lang.Closure;
050 import groovy.lang.GroovyObjectSupport;
051 import groovy.lang.MissingMethodException;
052
053 import java.util.List;
054 import java.util.Map;
055
056 import org.codehaus.groovy.runtime.InvokerHelper;
057
058 /**
059 * An abstract base class for creating arbitrary nested trees of objects
060 * or events
061 *
062 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
063 * @version $Revision: 4247 $
064 */
065 public abstract class BuilderSupport extends GroovyObjectSupport {
066
067 private Object current;
068 private Closure nameMappingClosure;
069 private BuilderSupport proxyBuilder;
070
071 public BuilderSupport() {
072 this.proxyBuilder = this;
073 }
074
075 public BuilderSupport(BuilderSupport proxyBuilder) {
076 this(null, proxyBuilder);
077 }
078
079 public BuilderSupport(Closure nameMappingClosure, BuilderSupport proxyBuilder) {
080 this.nameMappingClosure = nameMappingClosure;
081 this.proxyBuilder = proxyBuilder;
082 }
083
084 /**
085 * Convenience method when no arguments are required
086 * @return the result of the call
087 * @param methodName the name of the method to invoke
088 */
089 public Object invokeMethod(String methodName) {
090 return invokeMethod(methodName, null);
091 }
092
093 public Object invokeMethod(String methodName, Object args) {
094 Object name = getName(methodName);
095 return doInvokeMethod(methodName, name, args);
096 }
097
098 protected Object doInvokeMethod(String methodName, Object name, Object args) {
099 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 }