1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
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
174 Object oldCurrent = current;
175 current = node;
176
177
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 }