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 }