001    /*
002     $Id: BuilderSupport.java,v 1.8 2005/02/05 16:02:22 mcspanky Exp $
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: 1.8 $
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        public Object invokeMethod(String methodName, Object args) {
085            Object name = getName(methodName);
086            return doInvokeMethod(methodName, name, args);
087        }
088    
089        protected Object doInvokeMethod(String methodName, Object name, Object args) {
090            Object node = null;
091            Closure closure = null;
092            List list = InvokerHelper.asList(args);
093    
094            //System.out.println("Called invokeMethod with name: " + name + " arguments: " + list);
095    
096            switch (list.size()) {
097                            case 0:
098                                node = proxyBuilder.createNode(name);
099                                break;
100                            case 1:
101                            {
102                                    Object object = list.get(0);
103                                    if (object instanceof Map) {
104                                        node = proxyBuilder.createNode(name, (Map) object);
105                                    } else if (object instanceof Closure) {
106                                        closure = (Closure) object;
107                                        node = proxyBuilder.createNode(name);
108                                    } else {
109                                        node = proxyBuilder.createNode(name, object);
110                                    }
111                            }
112                            break;
113                            case 2:
114                            {
115                                Object object1 = list.get(0);
116                            Object object2 = list.get(1);
117                                if (object1 instanceof Map) {
118                                    if (object2 instanceof Closure) {
119                                        closure = (Closure) object2;
120                                        node = proxyBuilder.createNode(name, (Map) object1);
121                                    } else {
122                                        node = proxyBuilder.createNode(name, (Map) object1, object2);
123                                    }
124                                } else {
125                                    if (object2 instanceof Closure) {
126                                        closure = (Closure) object2;
127                                        node = proxyBuilder.createNode(name, object1);
128                                    } else if (object2 instanceof Map) {
129                                        node = proxyBuilder.createNode(name, (Map) object2, object1);
130                                    } else {
131                                        throw new MissingMethodException(name.toString(), getClass(), list.toArray());
132                                    }
133                                }
134                            }
135                            break;
136                            case 3:
137                            {
138                                Object arg0 = list.get(0);
139                                Object arg1 = list.get(1);
140                                Object arg2 = list.get(2);
141                                if (arg0 instanceof Map && arg2 instanceof Closure) {
142                                    closure = (Closure) arg2;
143                                    node = proxyBuilder.createNode(name, (Map) arg0, arg1);
144                                } else if (arg1 instanceof Map && arg2 instanceof Closure) {
145                                    closure = (Closure) arg2;
146                                    node = proxyBuilder.createNode(name, (Map) arg1, arg0);
147                                } else {
148                                    throw new MissingMethodException(name.toString(), getClass(), list.toArray());
149                               }
150                            }
151                            break;
152                            default:
153                            {
154                                throw new MissingMethodException(name.toString(), getClass(), list.toArray());
155                            }
156    
157            }
158    
159            if (current != null) {
160                proxyBuilder.setParent(current, node);
161            }
162    
163            if (closure != null) {
164                // push new node on stack
165                Object oldCurrent = current;
166                current = node;
167    
168                // lets register the builder as the delegate
169                setClosureDelegate(closure, node);
170                closure.call();
171    
172                current = oldCurrent;
173            }
174    
175            proxyBuilder.nodeCompleted(current, node);
176            return node;
177        }
178    
179        /**
180         * A strategy method to allow derived builders to use
181         * builder-trees and switch in different kinds of builders.
182         * This method should call the setDelegate() method on the closure
183         * which by default passes in this but if node is-a builder
184         * we could pass that in instead (or do something wacky too)
185         *
186         * @param closure the closure on which to call setDelegate()
187         * @param node the node value that we've just created, which could be
188         * a builder
189         */
190        protected void setClosureDelegate(Closure closure, Object node) {
191            closure.setDelegate(this);
192        }
193    
194        protected abstract void setParent(Object parent, Object child);
195        protected abstract Object createNode(Object name);
196        protected abstract Object createNode(Object name, Object value);
197        protected abstract Object createNode(Object name, Map attributes);
198        protected abstract Object createNode(Object name, Map attributes, Object value);
199    
200        /**
201         * A hook to allow names to be converted into some other object
202         * such as a QName in XML or ObjectName in JMX
203         * @param methodName
204         * @return
205         */
206        protected Object getName(String methodName) {
207            if (nameMappingClosure != null) {
208                return nameMappingClosure.call(methodName);
209            }
210            return methodName;
211        }
212    
213    
214        /**
215         * A hook to allow nodes to be processed once they have had all of their
216         * children applied
217         */
218        protected void nodeCompleted(Object parent, Object node) {
219        }
220    
221        protected Object getCurrent() {
222            return current;
223        }
224    
225        protected void setCurrent(Object current) {
226            this.current = current;
227        }
228    }