001    /*
002     $Id: AntBuilder.java 4077 2006-09-26 19:51:42Z glaforge $
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 java.util.Collections;
050    import java.util.Iterator;
051    import java.util.Map;
052    import java.util.logging.Level;
053    import java.util.logging.Logger;
054    
055    import org.apache.tools.ant.BuildLogger;
056    import org.apache.tools.ant.NoBannerLogger;
057    import org.apache.tools.ant.Project;
058    import org.apache.tools.ant.RuntimeConfigurable;
059    import org.apache.tools.ant.Target;
060    import org.apache.tools.ant.Task;
061    import org.apache.tools.ant.UnknownElement;
062    import org.apache.tools.ant.helper.AntXMLContext;
063    import org.apache.tools.ant.helper.ProjectHelper2;
064    import org.codehaus.groovy.ant.FileScanner;
065    import org.xml.sax.Attributes;
066    import org.xml.sax.Locator;
067    import org.xml.sax.SAXParseException;
068    import org.xml.sax.helpers.AttributesImpl;
069    import groovy.xml.QName;
070    
071    /**
072     * Allows Ant tasks to be used with GroovyMarkup 
073     * 
074     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>, changes by Dierk Koenig (dk)
075     * @version $Revision: 4077 $
076     */
077    public class AntBuilder extends BuilderSupport {
078    
079        private static final Class[] addTaskParamTypes = { String.class };
080    
081        private Logger log = Logger.getLogger(getClass().getName());
082        private Project project;
083        private final AntXMLContext antXmlContext;
084        private final ProjectHelper2.ElementHandler antElementHandler = new ProjectHelper2.ElementHandler();
085        private final Target collectorTarget;
086        private Object lastCompletedNode;
087    
088    
089    
090        public AntBuilder() {
091            this(createProject());
092        }
093    
094        public AntBuilder(final Project project) {
095            this(project, new Target());
096        }
097    
098        public AntBuilder(final Project project, final Target owningTarget) {
099            this.project = project;
100    
101            collectorTarget = owningTarget;
102            
103            antXmlContext = new AntXMLContext(project);
104            collectorTarget.setProject(project);
105            antXmlContext.setCurrentTarget(collectorTarget);
106            antXmlContext.setLocator(new AntBuilderLocator());
107            
108            // FileScanner is a Groovy hack (utility?)
109            project.addDataTypeDefinition("fileScanner", FileScanner.class);
110        }
111    
112        // dk: introduced for convenience in subclasses
113        protected Project getProject() {
114            return project;
115        }
116    
117        /**
118         * @return Factory method to create new Project instances
119         */
120        protected static Project createProject() {
121            Project project = new Project();
122            BuildLogger logger = new NoBannerLogger();
123    
124            logger.setMessageOutputLevel(org.apache.tools.ant.Project.MSG_INFO);
125            logger.setOutputPrintStream(System.out);
126            logger.setErrorPrintStream(System.err);
127    
128            project.addBuildListener(logger);
129    
130            project.init();
131            project.getBaseDir();
132            return project;
133        }
134    
135        protected void setParent(Object parent, Object child) {
136        }
137    
138        
139        /**
140         * We don't want to return the node as created in {@link #createNode(Object, Map, Object)}
141         * but the one made ready by {@link #nodeCompleted(Object, Object)}
142         * @see groovy.util.BuilderSupport#doInvokeMethod(java.lang.String, java.lang.Object, java.lang.Object)
143         */
144        protected Object doInvokeMethod(String methodName, Object name, Object args) {
145            super.doInvokeMethod(methodName, name, args);
146            
147    
148            // return the completed node
149            return lastCompletedNode;
150        }
151    
152        /**
153         * Determines, when the ANT Task that is represented by the "node" should perform.
154         * Node must be an ANT Task or no "perform" is called.
155         * If node is an ANT Task, it performs right after complete contstruction.
156         * If node is nested in a TaskContainer, calling "perform" is delegated to that
157         * TaskContainer.
158         * @param parent note: null when node is root
159         * @param node the node that now has all its children applied
160         */
161        protected void nodeCompleted(final Object parent, final Object node) {
162    
163            antElementHandler.onEndElement(null, null, antXmlContext);
164    
165            lastCompletedNode = node;
166            if (parent != null) {
167                log.finest("parent is not null: no perform on nodeCompleted");
168                return; // parent will care about when children perform
169            }
170            
171            // as in Target.execute()
172            if (node instanceof Task) {
173                Object task = node;
174                // "Unwrap" the UnknownElement to return the real task to the calling code
175                if (node instanceof UnknownElement) {
176                    final UnknownElement unknownElement = (UnknownElement) node;
177                    unknownElement.maybeConfigure();
178                    task = unknownElement.getRealThing();
179                }
180                
181                lastCompletedNode = task;
182                // UnknownElement may wrap everything: task, path, ...
183                if (task instanceof Task) {
184                    ((Task) task).perform();
185                }
186            }
187            else {
188                final RuntimeConfigurable r = (RuntimeConfigurable) node;
189                r.maybeConfigure(project);
190            }
191        }
192    
193        protected Object createNode(Object tagName) {
194            return createNode(tagName, Collections.EMPTY_MAP);
195        }
196    
197        protected Object createNode(Object name, Object value) {
198            Object task = createNode(name);
199            setText(task, value.toString());
200            return task;
201        }
202    
203        protected Object createNode(Object name, Map attributes, Object value) {
204            Object task = createNode(name, attributes);
205            setText(task, value.toString());
206            return task;
207        }
208        
209        /**
210         * Builds an {@link Attributes} from a {@link Map}
211         * @param attributes the attributes to wrap
212         */
213        protected static Attributes buildAttributes(final Map attributes) {
214            final AttributesImpl attr = new AttributesImpl();
215            for (final Iterator iter=attributes.entrySet().iterator(); iter.hasNext(); ) {
216                    final Map.Entry entry = (Map.Entry) iter.next();
217                    final String attributeName = (String) entry.getKey();
218                    final String attributeValue = String.valueOf(entry.getValue());
219                    attr.addAttribute(null, attributeName, attributeName, "CDATA", attributeValue);
220            }
221            return attr;
222        }
223    
224        protected Object createNode(final Object name, final Map attributes) {
225    
226            String tagName = name.toString();
227            String ns = "";
228    
229            if(name instanceof QName) {
230                QName q = (QName)name;
231                tagName = q.getLocalPart();
232                ns = q.getNamespaceURI();
233            }
234    
235            try
236                    {
237                            antElementHandler.onStartElement(ns, tagName, tagName, buildAttributes(attributes), antXmlContext);
238                    }
239                    catch (final SAXParseException e)
240                    {
241                log.log(Level.SEVERE, "Caught: " + e, e);
242                    }
243            
244                    final RuntimeConfigurable wrapper = (RuntimeConfigurable) antXmlContext.getWrapperStack().lastElement();
245            return wrapper.getProxy();
246        }
247    
248        protected void setText(Object task, String text) {
249            final char[] characters = text.toCharArray();
250            try {
251                    antElementHandler.characters(characters, 0, characters.length, antXmlContext);
252            }
253            catch (final SAXParseException e) {
254                log.log(Level.WARNING, "SetText failed: " + task + ". Reason: " + e, e);
255            }
256        }
257    
258        public Project getAntProject() {
259            return project;
260        }
261    }
262    
263    /**
264     * Would be nice to retrieve location information (from AST?).
265     * In a first time, without info
266     */
267    class AntBuilderLocator implements Locator {
268            public int getColumnNumber()
269            {
270                    return 0;
271            }
272            public int getLineNumber()
273            {
274                    return 0;
275            }
276            public String getPublicId()
277            {
278                    return "";
279            }
280            public String getSystemId()
281            {
282                    return "";
283            }
284    }