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: import groovy.lang.Closure;
049: import groovy.lang.GroovyObjectSupport;
050: import groovy.lang.MissingMethodException;
051:
052: import java.util.List;
053: import java.util.Map;
054:
055: import org.codehaus.groovy.runtime.InvokerHelper;
056:
057: /**
058: * An abstract base class for creating arbitrary nested trees of objects
059: * or events
060: *
061: * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
062: * @version $Revision: 4247 $
063: */
064: public abstract class BuilderSupport extends GroovyObjectSupport {
065:
066: private Object current;
067: private Closure nameMappingClosure;
068: private BuilderSupport proxyBuilder;
069:
070: public BuilderSupport() {
071: this .proxyBuilder = this ;
072: }
073:
074: public BuilderSupport(BuilderSupport proxyBuilder) {
075: this (null, proxyBuilder);
076: }
077:
078: public BuilderSupport(Closure nameMappingClosure,
079: 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,
099: Object args) {
100: Object node = null;
101: Closure closure = null;
102: List list = InvokerHelper.asList(args);
103:
104: //System.out.println("Called invokeMethod with name: " + name + " arguments: " + list);
105:
106: switch (list.size()) {
107: case 0:
108: node = proxyBuilder.createNode(name);
109: break;
110: case 1: {
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: Object object1 = list.get(0);
124: Object object2 = list.get(1);
125: if (object1 instanceof Map) {
126: if (object2 instanceof Closure) {
127: closure = (Closure) object2;
128: node = proxyBuilder.createNode(name, (Map) object1);
129: } else {
130: node = proxyBuilder.createNode(name, (Map) object1,
131: 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,
139: object1);
140: } else {
141: throw new MissingMethodException(name.toString(),
142: getClass(), list.toArray(), false);
143: }
144: }
145: }
146: break;
147: case 3: {
148: Object arg0 = list.get(0);
149: Object arg1 = list.get(1);
150: Object arg2 = list.get(2);
151: if (arg0 instanceof Map && arg2 instanceof Closure) {
152: closure = (Closure) arg2;
153: node = proxyBuilder.createNode(name, (Map) arg0, arg1);
154: } else if (arg1 instanceof Map && arg2 instanceof Closure) {
155: closure = (Closure) arg2;
156: node = proxyBuilder.createNode(name, (Map) arg1, arg0);
157: } else {
158: throw new MissingMethodException(name.toString(),
159: getClass(), list.toArray(), false);
160: }
161: }
162: break;
163: default: {
164: throw new MissingMethodException(name.toString(),
165: getClass(), list.toArray(), false);
166: }
167:
168: }
169:
170: if (current != null) {
171: proxyBuilder.setParent(current, node);
172: }
173:
174: if (closure != null) {
175: // push new node on stack
176: Object oldCurrent = current;
177: current = node;
178:
179: // lets register the builder as the delegate
180: setClosureDelegate(closure, node);
181: closure.call();
182:
183: current = oldCurrent;
184: }
185:
186: proxyBuilder.nodeCompleted(current, node);
187: return node;
188: }
189:
190: /**
191: * A strategy method to allow derived builders to use
192: * builder-trees and switch in different kinds of builders.
193: * This method should call the setDelegate() method on the closure
194: * which by default passes in this but if node is-a builder
195: * we could pass that in instead (or do something wacky too)
196: *
197: * @param closure the closure on which to call setDelegate()
198: * @param node the node value that we've just created, which could be
199: * a builder
200: */
201: protected void setClosureDelegate(Closure closure, Object node) {
202: closure.setDelegate(this );
203: }
204:
205: protected abstract void setParent(Object parent, Object child);
206:
207: protected abstract Object createNode(Object name);
208:
209: protected abstract Object createNode(Object name, Object value);
210:
211: protected abstract Object createNode(Object name, Map attributes);
212:
213: protected abstract Object createNode(Object name, Map attributes,
214: Object value);
215:
216: /**
217: * A hook to allow names to be converted into some other object
218: * such as a QName in XML or ObjectName in JMX
219: * @param methodName
220: */
221: protected Object getName(String methodName) {
222: if (nameMappingClosure != null) {
223: return nameMappingClosure.call(methodName);
224: }
225: return methodName;
226: }
227:
228: /**
229: * A hook to allow nodes to be processed once they have had all of their
230: * children applied
231: */
232: protected void nodeCompleted(Object parent, Object node) {
233: }
234:
235: protected Object getCurrent() {
236: return current;
237: }
238:
239: protected void setCurrent(Object current) {
240: this.current = current;
241: }
242: }
|