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    }