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 }