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 }