001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.FileOutputStream;
023: import java.io.IOException;
024: import java.io.PrintStream;
025: import java.lang.reflect.Method;
026: import java.util.Enumeration;
027: import java.util.Hashtable;
028: import java.util.Iterator;
029: import java.util.Vector;
030: import java.util.Set;
031: import java.util.HashSet;
032: import org.apache.tools.ant.BuildException;
033: import org.apache.tools.ant.BuildListener;
034: import org.apache.tools.ant.DefaultLogger;
035: import org.apache.tools.ant.Project;
036: import org.apache.tools.ant.ProjectComponent;
037: import org.apache.tools.ant.ProjectHelper;
038: import org.apache.tools.ant.Target;
039: import org.apache.tools.ant.Task;
040: import org.apache.tools.ant.MagicNames;
041: import org.apache.tools.ant.Main;
042: import org.apache.tools.ant.types.PropertySet;
043: import org.apache.tools.ant.util.FileUtils;
044:
045: /**
046: * Build a sub-project.
047: *
048: * <pre>
049: * <target name="foo" depends="init">
050: * <ant antfile="build.xml" target="bar" >
051: * <property name="property1" value="aaaaa" />
052: * <property name="foo" value="baz" />
053: * </ant></span>
054: * </target></span>
055: *
056: * <target name="bar" depends="init">
057: * <echo message="prop is ${property1} ${foo}" />
058: * </target>
059: * </pre>
060: *
061: *
062: * @since Ant 1.1
063: *
064: * @ant.task category="control"
065: */
066: public class Ant extends Task {
067:
068: private static final FileUtils FILE_UTILS = FileUtils
069: .getFileUtils();
070:
071: /** the basedir where is executed the build file */
072: private File dir = null;
073:
074: /**
075: * the build.xml file (can be absolute) in this case dir will be
076: * ignored
077: */
078: private String antFile = null;
079:
080: /** the output */
081: private String output = null;
082:
083: /** should we inherit properties from the parent ? */
084: private boolean inheritAll = true;
085:
086: /** should we inherit references from the parent ? */
087: private boolean inheritRefs = false;
088:
089: /** the properties to pass to the new project */
090: private Vector properties = new Vector();
091:
092: /** the references to pass to the new project */
093: private Vector references = new Vector();
094:
095: /** the temporary project created to run the build file */
096: private Project newProject;
097:
098: /** The stream to which output is to be written. */
099: private PrintStream out = null;
100:
101: /** the sets of properties to pass to the new project */
102: private Vector propertySets = new Vector();
103:
104: /** the targets to call on the new project */
105: private Vector targets = new Vector();
106:
107: /** whether the target attribute was specified **/
108: private boolean targetAttributeSet = false;
109:
110: /**
111: * simple constructor
112: */
113: public Ant() {
114: //default
115: }
116:
117: /**
118: * create a task bound to its creator
119: * @param owner owning task
120: */
121: public Ant(Task owner) {
122: bindToOwner(owner);
123: }
124:
125: /**
126: * If true, pass all properties to the new Ant project.
127: * Defaults to true.
128: * @param value if true pass all properties to the new Ant project.
129: */
130: public void setInheritAll(boolean value) {
131: inheritAll = value;
132: }
133:
134: /**
135: * If true, pass all references to the new Ant project.
136: * Defaults to false.
137: * @param value if true, pass all references to the new Ant project
138: */
139: public void setInheritRefs(boolean value) {
140: inheritRefs = value;
141: }
142:
143: /**
144: * Creates a Project instance for the project to call.
145: */
146: public void init() {
147: newProject = getProject().createSubProject();
148: newProject.setJavaVersionProperty();
149: }
150:
151: /**
152: * Called in execute or createProperty (via getNewProject())
153: * if newProject is null.
154: *
155: * <p>This can happen if the same instance of this task is run
156: * twice as newProject is set to null at the end of execute (to
157: * save memory and help the GC).</p>
158: * <p>calls init() again</p>
159: *
160: */
161: private void reinit() {
162: init();
163: }
164:
165: /**
166: * Attaches the build listeners of the current project to the new
167: * project, configures a possible logfile, transfers task and
168: * data-type definitions, transfers properties (either all or just
169: * the ones specified as user properties to the current project,
170: * depending on inheritall), transfers the input handler.
171: */
172: private void initializeProject() {
173: newProject.setInputHandler(getProject().getInputHandler());
174:
175: Iterator iter = getBuildListeners();
176: while (iter.hasNext()) {
177: newProject.addBuildListener((BuildListener) iter.next());
178: }
179:
180: if (output != null) {
181: File outfile = null;
182: if (dir != null) {
183: outfile = FILE_UTILS.resolveFile(dir, output);
184: } else {
185: outfile = getProject().resolveFile(output);
186: }
187: try {
188: out = new PrintStream(new FileOutputStream(outfile));
189: DefaultLogger logger = new DefaultLogger();
190: logger.setMessageOutputLevel(Project.MSG_INFO);
191: logger.setOutputPrintStream(out);
192: logger.setErrorPrintStream(out);
193: newProject.addBuildListener(logger);
194: } catch (IOException ex) {
195: log("Ant: Can't set output to " + output);
196: }
197: }
198: // set user-defined properties
199: getProject().copyUserProperties(newProject);
200:
201: if (!inheritAll) {
202: // set Java built-in properties separately,
203: // b/c we won't inherit them.
204: newProject.setSystemProperties();
205:
206: } else {
207: // set all properties from calling project
208: addAlmostAll(getProject().getProperties());
209: }
210:
211: Enumeration e = propertySets.elements();
212: while (e.hasMoreElements()) {
213: PropertySet ps = (PropertySet) e.nextElement();
214: addAlmostAll(ps.getProperties());
215: }
216: }
217:
218: /**
219: * Handles output.
220: * Send it the the new project if is present, otherwise
221: * call the super class.
222: * @param outputToHandle The string output to output.
223: * @see Task#handleOutput(String)
224: * @since Ant 1.5
225: */
226: public void handleOutput(String outputToHandle) {
227: if (newProject != null) {
228: newProject.demuxOutput(outputToHandle, false);
229: } else {
230: super .handleOutput(outputToHandle);
231: }
232: }
233:
234: /**
235: * Handles input.
236: * Deleate to the created project, if present, otherwise
237: * call the super class.
238: * @param buffer the buffer into which data is to be read.
239: * @param offset the offset into the buffer at which data is stored.
240: * @param length the amount of data to read.
241: *
242: * @return the number of bytes read.
243: *
244: * @exception IOException if the data cannot be read.
245: * @see Task#handleInput(byte[], int, int)
246: * @since Ant 1.6
247: */
248: public int handleInput(byte[] buffer, int offset, int length)
249: throws IOException {
250: if (newProject != null) {
251: return newProject.demuxInput(buffer, offset, length);
252: }
253: return super .handleInput(buffer, offset, length);
254: }
255:
256: /**
257: * Handles output.
258: * Send it the the new project if is present, otherwise
259: * call the super class.
260: * @param toFlush The string to output.
261: * @see Task#handleFlush(String)
262: * @since Ant 1.5.2
263: */
264: public void handleFlush(String toFlush) {
265: if (newProject != null) {
266: newProject.demuxFlush(toFlush, false);
267: } else {
268: super .handleFlush(toFlush);
269: }
270: }
271:
272: /**
273: * Handle error output.
274: * Send it the the new project if is present, otherwise
275: * call the super class.
276: * @param errorOutputToHandle The string to output.
277: *
278: * @see Task#handleErrorOutput(String)
279: * @since Ant 1.5
280: */
281: public void handleErrorOutput(String errorOutputToHandle) {
282: if (newProject != null) {
283: newProject.demuxOutput(errorOutputToHandle, true);
284: } else {
285: super .handleErrorOutput(errorOutputToHandle);
286: }
287: }
288:
289: /**
290: * Handle error output.
291: * Send it the the new project if is present, otherwise
292: * call the super class.
293: * @param errorOutputToFlush The string to output.
294: * @see Task#handleErrorFlush(String)
295: * @since Ant 1.5.2
296: */
297: public void handleErrorFlush(String errorOutputToFlush) {
298: if (newProject != null) {
299: newProject.demuxFlush(errorOutputToFlush, true);
300: } else {
301: super .handleErrorFlush(errorOutputToFlush);
302: }
303: }
304:
305: /**
306: * Do the execution.
307: * @throws BuildException if a target tries to call itself;
308: * probably also if a BuildException is thrown by the new project.
309: */
310: public void execute() throws BuildException {
311: File savedDir = dir;
312: String savedAntFile = antFile;
313: Vector locals = new Vector(targets);
314: try {
315: getNewProject();
316:
317: if (dir == null && inheritAll) {
318: dir = getProject().getBaseDir();
319: }
320:
321: initializeProject();
322:
323: if (dir != null) {
324: newProject.setBaseDir(dir);
325: if (savedDir != null) {
326: // has been set explicitly
327: newProject.setInheritedProperty(
328: MagicNames.PROJECT_BASEDIR, dir
329: .getAbsolutePath());
330: }
331: } else {
332: dir = getProject().getBaseDir();
333: }
334:
335: overrideProperties();
336:
337: if (antFile == null) {
338: antFile = Main.DEFAULT_BUILD_FILENAME;
339: }
340:
341: File file = FILE_UTILS.resolveFile(dir, antFile);
342: antFile = file.getAbsolutePath();
343:
344: log("calling target(s) "
345: + ((locals.size() > 0) ? locals.toString()
346: : "[default]") + " in build file "
347: + antFile, Project.MSG_VERBOSE);
348: newProject.setUserProperty(MagicNames.ANT_FILE, antFile);
349:
350: String this AntFile = getProject().getProperty(
351: MagicNames.ANT_FILE);
352: // Are we trying to call the target in which we are defined (or
353: // the build file if this is a top level task)?
354: if (this AntFile != null
355: && file.equals(getProject()
356: .resolveFile(this AntFile))
357: && getOwningTarget() != null) {
358:
359: if (getOwningTarget().getName().equals("")) {
360: if (getTaskName().equals("antcall")) {
361: throw new BuildException(
362: "antcall must not be used at"
363: + " the top level.");
364: }
365: throw new BuildException(getTaskName()
366: + " task at the"
367: + " top level must not invoke"
368: + " its own build file.");
369: }
370: }
371:
372: try {
373: ProjectHelper.configureProject(newProject, file);
374: } catch (BuildException ex) {
375: throw ProjectHelper.addLocationToBuildException(ex,
376: getLocation());
377: }
378:
379: if (locals.size() == 0) {
380: String defaultTarget = newProject.getDefaultTarget();
381: if (defaultTarget != null) {
382: locals.add(defaultTarget);
383: }
384: }
385:
386: if (newProject.getProperty(MagicNames.ANT_FILE).equals(
387: getProject().getProperty(MagicNames.ANT_FILE))
388: && getOwningTarget() != null) {
389:
390: String owningTargetName = getOwningTarget().getName();
391:
392: if (locals.contains(owningTargetName)) {
393: throw new BuildException(getTaskName()
394: + " task calling "
395: + "its own parent target.");
396: }
397: boolean circular = false;
398: for (Iterator it = locals.iterator(); !circular
399: && it.hasNext();) {
400: Target other = (Target) (getProject().getTargets()
401: .get(it.next()));
402: circular |= (other != null && other
403: .dependsOn(owningTargetName));
404: }
405: if (circular) {
406: throw new BuildException(getTaskName()
407: + " task calling a target"
408: + " that depends on"
409: + " its parent target \'"
410: + owningTargetName + "\'.");
411: }
412: }
413:
414: addReferences();
415:
416: if (locals.size() > 0
417: && !(locals.size() == 1 && "".equals(locals.get(0)))) {
418: BuildException be = null;
419: try {
420: log("Entering " + antFile + "...",
421: Project.MSG_VERBOSE);
422: newProject.fireSubBuildStarted();
423: newProject.executeTargets(locals);
424: } catch (BuildException ex) {
425: be = ProjectHelper.addLocationToBuildException(ex,
426: getLocation());
427: throw be;
428: } finally {
429: log("Exiting " + antFile + ".", Project.MSG_VERBOSE);
430: newProject.fireSubBuildFinished(be);
431: }
432: }
433: } finally {
434: // help the gc
435: newProject = null;
436: Enumeration e = properties.elements();
437: while (e.hasMoreElements()) {
438: Property p = (Property) e.nextElement();
439: p.setProject(null);
440: }
441:
442: if (output != null && out != null) {
443: try {
444: out.close();
445: } catch (final Exception ex) {
446: //ignore
447: }
448: }
449: dir = savedDir;
450: antFile = savedAntFile;
451: }
452: }
453:
454: /**
455: * Override the properties in the new project with the one
456: * explicitly defined as nested elements here.
457: * @throws BuildException under unknown circumstances.
458: */
459: private void overrideProperties() throws BuildException {
460: // remove duplicate properties - last property wins
461: // Needed for backward compatibility
462: Set set = new HashSet();
463: for (int i = properties.size() - 1; i >= 0; --i) {
464: Property p = (Property) properties.get(i);
465: if (p.getName() != null && !p.getName().equals("")) {
466: if (set.contains(p.getName())) {
467: properties.remove(i);
468: } else {
469: set.add(p.getName());
470: }
471: }
472: }
473: Enumeration e = properties.elements();
474: while (e.hasMoreElements()) {
475: Property p = (Property) e.nextElement();
476: p.setProject(newProject);
477: p.execute();
478: }
479: getProject().copyInheritedProperties(newProject);
480: }
481:
482: /**
483: * Add the references explicitly defined as nested elements to the
484: * new project. Also copy over all references that don't override
485: * existing references in the new project if inheritrefs has been
486: * requested.
487: * @throws BuildException if a reference does not have a refid.
488: */
489: private void addReferences() throws BuildException {
490: Hashtable this References = (Hashtable) getProject()
491: .getReferences().clone();
492: Hashtable newReferences = newProject.getReferences();
493: Enumeration e;
494: if (references.size() > 0) {
495: for (e = references.elements(); e.hasMoreElements();) {
496: Reference ref = (Reference) e.nextElement();
497: String refid = ref.getRefId();
498: if (refid == null) {
499: throw new BuildException(
500: "the refid attribute is required"
501: + " for reference elements");
502: }
503: if (!this References.containsKey(refid)) {
504: log(
505: "Parent project doesn't contain any reference '"
506: + refid + "'", Project.MSG_WARN);
507: continue;
508: }
509:
510: this References.remove(refid);
511: String toRefid = ref.getToRefid();
512: if (toRefid == null) {
513: toRefid = refid;
514: }
515: copyReference(refid, toRefid);
516: }
517: }
518:
519: // Now add all references that are not defined in the
520: // subproject, if inheritRefs is true
521: if (inheritRefs) {
522: for (e = this References.keys(); e.hasMoreElements();) {
523: String key = (String) e.nextElement();
524: if (newReferences.containsKey(key)) {
525: continue;
526: }
527: copyReference(key, key);
528: newProject.inheritIDReferences(getProject());
529: }
530: }
531: }
532:
533: /**
534: * Try to clone and reconfigure the object referenced by oldkey in
535: * the parent project and add it to the new project with the key newkey.
536: *
537: * <p>If we cannot clone it, copy the referenced object itself and
538: * keep our fingers crossed.</p>
539: * @param oldKey the reference id in the current project.
540: * @param newKey the reference id in the new project.
541: */
542: private void copyReference(String oldKey, String newKey) {
543: Object orig = getProject().getReference(oldKey);
544: if (orig == null) {
545: log("No object referenced by " + oldKey
546: + ". Can't copy to " + newKey, Project.MSG_WARN);
547: return;
548: }
549:
550: Class c = orig.getClass();
551: Object copy = orig;
552: try {
553: Method cloneM = c.getMethod("clone", new Class[0]);
554: if (cloneM != null) {
555: copy = cloneM.invoke(orig, new Object[0]);
556: log("Adding clone of reference " + oldKey,
557: Project.MSG_DEBUG);
558: }
559: } catch (Exception e) {
560: // not Clonable
561: }
562:
563: if (copy instanceof ProjectComponent) {
564: ((ProjectComponent) copy).setProject(newProject);
565: } else {
566: try {
567: Method setProjectM = c.getMethod("setProject",
568: new Class[] { Project.class });
569: if (setProjectM != null) {
570: setProjectM.invoke(copy,
571: new Object[] { newProject });
572: }
573: } catch (NoSuchMethodException e) {
574: // ignore this if the class being referenced does not have
575: // a set project method.
576: } catch (Exception e2) {
577: String msg = "Error setting new project instance for "
578: + "reference with id " + oldKey;
579: throw new BuildException(msg, e2, getLocation());
580: }
581: }
582: newProject.addReference(newKey, copy);
583: }
584:
585: /**
586: * Copies all properties from the given table to the new project -
587: * omitting those that have already been set in the new project as
588: * well as properties named basedir or ant.file.
589: * @param props properties <code>Hashtable</code> to copy to the
590: * new project.
591: * @since Ant 1.6
592: */
593: private void addAlmostAll(Hashtable props) {
594: Enumeration e = props.keys();
595: while (e.hasMoreElements()) {
596: String key = e.nextElement().toString();
597: if (MagicNames.PROJECT_BASEDIR.equals(key)
598: || MagicNames.ANT_FILE.equals(key)) {
599: // basedir and ant.file get special treatment in execute()
600: continue;
601: }
602:
603: String value = props.get(key).toString();
604: // don't re-set user properties, avoid the warning message
605: if (newProject.getProperty(key) == null) {
606: // no user property
607: newProject.setNewProperty(key, value);
608: }
609: }
610: }
611:
612: /**
613: * The directory to use as a base directory for the new Ant project.
614: * Defaults to the current project's basedir, unless inheritall
615: * has been set to false, in which case it doesn't have a default
616: * value. This will override the basedir setting of the called project.
617: * @param dir new directory as <code>File</code>.
618: */
619: public void setDir(File dir) {
620: this .dir = dir;
621: }
622:
623: /**
624: * The build file to use. Defaults to "build.xml". This file is expected
625: * to be a filename relative to the dir attribute given.
626: * @param antFile the <code>String</code> build file name.
627: */
628: public void setAntfile(String antFile) {
629: // @note: it is a string and not a file to handle relative/absolute
630: // otherwise a relative file will be resolved based on the current
631: // basedir.
632: this .antFile = antFile;
633: }
634:
635: /**
636: * The target of the new Ant project to execute.
637: * Defaults to the new project's default target.
638: * @param targetToAdd the name of the target to invoke.
639: */
640: public void setTarget(String targetToAdd) {
641: if (targetToAdd.equals("")) {
642: throw new BuildException(
643: "target attribute must not be empty");
644: }
645: targets.add(targetToAdd);
646: targetAttributeSet = true;
647: }
648:
649: /**
650: * Set the filename to write the output to. This is relative to the value
651: * of the dir attribute if it has been set or to the base directory of the
652: * current project otherwise.
653: * @param outputFile the name of the file to which the output should go.
654: */
655: public void setOutput(String outputFile) {
656: this .output = outputFile;
657: }
658:
659: /**
660: * Property to pass to the new project.
661: * The property is passed as a 'user property'.
662: * @return the created <code>Property</code> object.
663: */
664: public Property createProperty() {
665: Property p = new Property(true, getProject());
666: p.setProject(getNewProject());
667: p.setTaskName("property");
668: properties.addElement(p);
669: return p;
670: }
671:
672: /**
673: * Add a Reference element identifying a data type to carry
674: * over to the new project.
675: * @param ref <code>Reference</code> to add.
676: */
677: public void addReference(Reference ref) {
678: references.addElement(ref);
679: }
680:
681: /**
682: * Add a target to this Ant invocation.
683: * @param t the <code>TargetElement</code> to add.
684: * @since Ant 1.6.3
685: */
686: public void addConfiguredTarget(TargetElement t) {
687: if (targetAttributeSet) {
688: throw new BuildException(
689: "nested target is incompatible with the target attribute");
690: }
691: String name = t.getName();
692: if (name.equals("")) {
693: throw new BuildException("target name must not be empty");
694: }
695: targets.add(name);
696: }
697:
698: /**
699: * Add a set of properties to pass to the new project.
700: *
701: * @param ps <code>PropertySet</code> to add.
702: * @since Ant 1.6
703: */
704: public void addPropertyset(PropertySet ps) {
705: propertySets.addElement(ps);
706: }
707:
708: /*
709: * Get the (sub)-Project instance currently in use.
710: * @return Project
711: * @since Ant 1.7
712: */
713: protected Project getNewProject() {
714: if (newProject == null) {
715: reinit();
716: }
717: return newProject;
718: }
719:
720: /**
721: * @since Ant 1.6.2
722: */
723: private Iterator getBuildListeners() {
724: return getProject().getBuildListeners().iterator();
725: }
726:
727: /**
728: * Helper class that implements the nested <reference>
729: * element of <ant> and <antcall>.
730: */
731: public static class Reference extends
732: org.apache.tools.ant.types.Reference {
733:
734: /** Creates a reference to be configured by Ant. */
735: public Reference() {
736: super ();
737: }
738:
739: private String targetid = null;
740:
741: /**
742: * Set the id that this reference to be stored under in the
743: * new project.
744: *
745: * @param targetid the id under which this reference will be passed to
746: * the new project. */
747: public void setToRefid(String targetid) {
748: this .targetid = targetid;
749: }
750:
751: /**
752: * Get the id under which this reference will be stored in the new
753: * project.
754: *
755: * @return the id of the reference in the new project.
756: */
757: public String getToRefid() {
758: return targetid;
759: }
760: }
761:
762: /**
763: * Helper class that implements the nested <target>
764: * element of <ant> and <antcall>.
765: * @since Ant 1.6.3
766: */
767: public static class TargetElement {
768: private String name;
769:
770: /**
771: * Default constructor.
772: */
773: public TargetElement() {
774: //default
775: }
776:
777: /**
778: * Set the name of this TargetElement.
779: * @param name the <code>String</code> target name.
780: */
781: public void setName(String name) {
782: this .name = name;
783: }
784:
785: /**
786: * Get the name of this TargetElement.
787: * @return <code>String</code>.
788: */
789: public String getName() {
790: return name;
791: }
792: }
793: }
|