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
52 import java.util.List;
53 import java.util.Map;
54
55 import org.codehaus.groovy.runtime.InvokerHelper;
56
57 /***
58 * An abstract base class for creating arbitrary nested trees of objects
59 * or events
60 *
61 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
62 * @version $Revision: 1.7 $
63 */
64 public abstract class BuilderSupport extends GroovyObjectSupport {
65
66 private Object current;
67 private Closure nameMappingClosure;
68 private BuilderSupport proxyBuilder;
69
70 public BuilderSupport() {
71 this.proxyBuilder = this;
72 }
73
74 public BuilderSupport(BuilderSupport proxyBuilder) {
75 this(null, proxyBuilder);
76 }
77
78 public BuilderSupport(Closure nameMappingClosure, BuilderSupport proxyBuilder) {
79 this.nameMappingClosure = nameMappingClosure;
80 this.proxyBuilder = proxyBuilder;
81 }
82
83 public Object invokeMethod(String methodName, Object args) {
84 Object name = getName(methodName);
85 return doInvokeMethod(methodName, name, args);
86 }
87
88 protected Object doInvokeMethod(String methodName, Object name, Object args) {
89 Object node = null;
90 Closure closure = null;
91 List list = InvokerHelper.asList(args);
92
93
94
95 switch (list.size()) {
96 case 0:
97 break;
98 case 1:
99 {
100 Object object = list.get(0);
101 if (object instanceof Map) {
102 node = proxyBuilder.createNode(name, (Map) object);
103 } else if (object instanceof Closure) {
104 closure = (Closure) object;
105 node = proxyBuilder.createNode(name);
106 } else {
107 node = proxyBuilder.createNode(name, object);
108 }
109 }
110 break;
111 case 2:
112 {
113 Object object1 = list.get(0);
114 Object object2 = list.get(1);
115 if (object1 instanceof Map) {
116 if (object2 instanceof Closure) {
117 closure = (Closure) object2;
118 node = proxyBuilder.createNode(name, (Map) object1);
119 } else {
120 node = proxyBuilder.createNode(name, (Map) object1, object2);
121 }
122 } else {
123 if (object2 instanceof Closure) {
124 closure = (Closure) object2;
125 node = proxyBuilder.createNode(name, object1);
126 }
127 }
128 }
129 break;
130 case 3:
131 {
132 Object attributes = list.get(0);
133 Object value = list.get(1);
134 closure = (Closure) list.get(2);
135 node = proxyBuilder.createNode(name, (Map) attributes, value);
136 }
137 break;
138 }
139
140 if (node == null) {
141 node = proxyBuilder.createNode(name);
142 }
143
144 if (current != null) {
145 proxyBuilder.setParent(current, node);
146 }
147
148 if (closure != null) {
149
150 Object oldCurrent = current;
151 current = node;
152
153
154 setClosureDelegate(closure, node);
155 closure.call();
156
157 current = oldCurrent;
158 }
159
160 proxyBuilder.nodeCompleted(current, node);
161 return node;
162 }
163
164 /***
165 * A strategy method to allow derived builders to use
166 * builder-trees and switch in different kinds of builders.
167 * This method should call the setDelegate() method on the closure
168 * which by default passes in this but if node is-a builder
169 * we could pass that in instead (or do something wacky too)
170 *
171 * @param closure the closure on which to call setDelegate()
172 * @param node the node value that we've just created, which could be
173 * a builder
174 */
175 protected void setClosureDelegate(Closure closure, Object node) {
176 closure.setDelegate(this);
177 }
178
179 protected abstract void setParent(Object parent, Object child);
180 protected abstract Object createNode(Object name);
181 protected abstract Object createNode(Object name, Object value);
182 protected abstract Object createNode(Object name, Map attributes);
183 protected abstract Object createNode(Object name, Map attributes, Object value);
184
185 /***
186 * A hook to allow names to be converted into some other object
187 * such as a QName in XML or ObjectName in JMX
188 * @param methodName
189 * @return
190 */
191 protected Object getName(String methodName) {
192 if (nameMappingClosure != null) {
193 return nameMappingClosure.call(methodName);
194 }
195 return methodName;
196 }
197
198
199 /***
200 * A hook to allow nodes to be processed once they have had all of their
201 * children applied
202 */
203 protected void nodeCompleted(Object parent, Object node) {
204 }
205
206 protected Object getCurrent() {
207 return current;
208 }
209
210 protected void setCurrent(Object current) {
211 this.current = current;
212 }
213 }