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: package org.apache.tools.ant.taskdefs.optional;
019:
020: import java.io.ByteArrayOutputStream;
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.OutputStream;
027: import java.io.OutputStreamWriter;
028: import java.io.Writer;
029: import java.util.Enumeration;
030: import java.util.Hashtable;
031: import java.util.Properties;
032: import java.util.Vector;
033: import java.util.List;
034: import java.util.ArrayList;
035: import java.util.Comparator;
036: import java.util.Map;
037: import java.util.Set;
038: import java.util.TreeSet;
039: import java.util.Collections;
040: import java.util.Iterator;
041: import javax.xml.parsers.DocumentBuilder;
042: import javax.xml.parsers.DocumentBuilderFactory;
043: import org.apache.tools.ant.BuildException;
044: import org.apache.tools.ant.Project;
045: import org.apache.tools.ant.Task;
046: import org.apache.tools.ant.types.EnumeratedAttribute;
047: import org.apache.tools.ant.types.PropertySet;
048: import org.apache.tools.ant.util.CollectionUtils;
049: import org.apache.tools.ant.util.DOMElementWriter;
050: import org.apache.tools.ant.util.FileUtils;
051: import org.apache.tools.ant.util.JavaEnvUtils;
052: import org.w3c.dom.Document;
053: import org.w3c.dom.Element;
054:
055: /**
056: * Displays all the current properties in the build. The output can be sent to
057: * a file if desired. <P>
058: *
059: * Attribute "destfile" defines a file to send the properties to. This can be
060: * processed as a standard property file later. <P>
061: *
062: * Attribute "prefix" defines a prefix which is used to filter the properties
063: * only those properties starting with this prefix will be echoed. <P>
064: *
065: * By default, the "failonerror" attribute is enabled. If an error occurs while
066: * writing the properties to a file, and this attribute is enabled, then a
067: * BuildException will be thrown. If disabled, then IO errors will be reported
068: * as a log statement, but no error will be thrown. <P>
069: *
070: * Examples: <pre>
071: * <echoproperties />
072: * </pre> Report the current properties to the log. <P>
073: *
074: * <pre>
075: * <echoproperties destfile="my.properties" />
076: * </pre> Report the current properties to the file "my.properties", and will
077: * fail the build if the file could not be created or written to. <P>
078: *
079: * <pre>
080: * <echoproperties destfile="my.properties" failonerror="false"
081: * prefix="ant" />
082: * </pre> Report all properties beginning with 'ant' to the file
083: * "my.properties", and will log a message if the file could not be created or
084: * written to, but will still allow the build to continue.
085: *
086: *@since Ant 1.5
087: */
088: public class EchoProperties extends Task {
089:
090: /**
091: * the properties element.
092: */
093: private static final String PROPERTIES = "properties";
094:
095: /**
096: * the property element.
097: */
098: private static final String PROPERTY = "property";
099:
100: /**
101: * name attribute for property, testcase and testsuite elements.
102: */
103: private static final String ATTR_NAME = "name";
104:
105: /**
106: * value attribute for property elements.
107: */
108: private static final String ATTR_VALUE = "value";
109:
110: /**
111: * the input file.
112: */
113: private File inFile = null;
114:
115: /**
116: * File object pointing to the output file. If this is null, then
117: * we output to the project log, not to a file.
118: */
119: private File destfile = null;
120:
121: /**
122: * If this is true, then errors generated during file output will become
123: * build errors, and if false, then such errors will be logged, but not
124: * thrown.
125: */
126: private boolean failonerror = true;
127:
128: private Vector propertySets = new Vector();
129:
130: private String format = "text";
131:
132: private String prefix;
133:
134: /**
135: * @since Ant 1.7
136: */
137: private String regex;
138:
139: /**
140: * Sets the input file.
141: *
142: * @param file the input file
143: */
144: public void setSrcfile(File file) {
145: inFile = file;
146: }
147:
148: /**
149: * Set a file to store the property output. If this is never specified,
150: * then the output will be sent to the Ant log.
151: *
152: *@param destfile file to store the property output
153: */
154: public void setDestfile(File destfile) {
155: this .destfile = destfile;
156: }
157:
158: /**
159: * If true, the task will fail if an error occurs writing the properties
160: * file, otherwise errors are just logged.
161: *
162: *@param failonerror <tt>true</tt> if IO exceptions are reported as build
163: * exceptions, or <tt>false</tt> if IO exceptions are ignored.
164: */
165: public void setFailOnError(boolean failonerror) {
166: this .failonerror = failonerror;
167: }
168:
169: /**
170: * If the prefix is set, then only properties which start with this
171: * prefix string will be recorded. If regex is not set and if this
172: * is never set, or it is set to an empty string or <tt>null</tt>,
173: * then all properties will be recorded. <P>
174: *
175: * For example, if the attribute is set as:
176: * <PRE><echoproperties prefix="ant." /></PRE>
177: * then the property "ant.home" will be recorded, but "ant-example"
178: * will not.
179: *
180: * @param prefix The new prefix value
181: */
182: public void setPrefix(String prefix) {
183: if (prefix != null && prefix.length() != 0) {
184: this .prefix = prefix;
185: PropertySet ps = new PropertySet();
186: ps.setProject(getProject());
187: ps.appendPrefix(prefix);
188: addPropertyset(ps);
189: }
190: }
191:
192: /**
193: * If the regex is set, then only properties whose names match it
194: * will be recorded. If prefix is not set and if this is never set,
195: * or it is set to an empty string or <tt>null</tt>, then all
196: * properties will be recorded.<P>
197: *
198: * For example, if the attribute is set as:
199: * <PRE><echoproperties prefix=".*ant.*" /></PRE>
200: * then the properties "ant.home" and "user.variant" will be recorded,
201: * but "ant-example" will not.
202: *
203: * @param regex The new regex value
204: *
205: * @since Ant 1.7
206: */
207: public void setRegex(String regex) {
208: if (regex != null && regex.length() != 0) {
209: this .regex = regex;
210: PropertySet ps = new PropertySet();
211: ps.setProject(getProject());
212: ps.appendRegex(regex);
213: addPropertyset(ps);
214: }
215: }
216:
217: /**
218: * A set of properties to write.
219: * @param ps the property set to write
220: * @since Ant 1.6
221: */
222: public void addPropertyset(PropertySet ps) {
223: propertySets.addElement(ps);
224: }
225:
226: /**
227: * Set the output format - xml or text.
228: * @param ea an enumerated <code>FormatAttribute</code> value
229: */
230: public void setFormat(FormatAttribute ea) {
231: format = ea.getValue();
232: }
233:
234: /**
235: * A enumerated type for the format attribute.
236: * The values are "xml" and "text".
237: */
238: public static class FormatAttribute extends EnumeratedAttribute {
239: private String[] formats = new String[] { "xml", "text" };
240:
241: /**
242: * @see EnumeratedAttribute#getValues()
243: * @return accepted values
244: */
245: public String[] getValues() {
246: return formats;
247: }
248: }
249:
250: /**
251: * Run the task.
252: *
253: *@exception BuildException trouble, probably file IO
254: */
255: public void execute() throws BuildException {
256: if (prefix != null && regex != null) {
257: throw new BuildException("Please specify either prefix"
258: + " or regex, but not both", getLocation());
259: }
260: //copy the properties file
261: Hashtable allProps = new Hashtable();
262:
263: /* load properties from file if specified, otherwise
264: use Ant's properties */
265: if (inFile == null && propertySets.size() == 0) {
266: // add ant properties
267: allProps.putAll(getProject().getProperties());
268: } else if (inFile != null) {
269: if (inFile.exists() && inFile.isDirectory()) {
270: String message = "srcfile is a directory!";
271: if (failonerror) {
272: throw new BuildException(message, getLocation());
273: } else {
274: log(message, Project.MSG_ERR);
275: }
276: return;
277: }
278:
279: if (inFile.exists() && !inFile.canRead()) {
280: String message = "Can not read from the specified srcfile!";
281: if (failonerror) {
282: throw new BuildException(message, getLocation());
283: } else {
284: log(message, Project.MSG_ERR);
285: }
286: return;
287: }
288:
289: FileInputStream in = null;
290: try {
291: in = new FileInputStream(inFile);
292: Properties props = new Properties();
293: props.load(in);
294: allProps.putAll(props);
295: } catch (FileNotFoundException fnfe) {
296: String message = "Could not find file "
297: + inFile.getAbsolutePath();
298: if (failonerror) {
299: throw new BuildException(message, fnfe,
300: getLocation());
301: } else {
302: log(message, Project.MSG_WARN);
303: }
304: return;
305: } catch (IOException ioe) {
306: String message = "Could not read file "
307: + inFile.getAbsolutePath();
308: if (failonerror) {
309: throw new BuildException(message, ioe,
310: getLocation());
311: } else {
312: log(message, Project.MSG_WARN);
313: }
314: return;
315: } finally {
316: FileUtils.close(in);
317: }
318: }
319:
320: Enumeration e = propertySets.elements();
321: while (e.hasMoreElements()) {
322: PropertySet ps = (PropertySet) e.nextElement();
323: allProps.putAll(ps.getProperties());
324: }
325:
326: OutputStream os = null;
327: try {
328: if (destfile == null) {
329: os = new ByteArrayOutputStream();
330: saveProperties(allProps, os);
331: log(os.toString(), Project.MSG_INFO);
332: } else {
333: if (destfile.exists() && destfile.isDirectory()) {
334: String message = "destfile is a directory!";
335: if (failonerror) {
336: throw new BuildException(message, getLocation());
337: } else {
338: log(message, Project.MSG_ERR);
339: }
340: return;
341: }
342:
343: if (destfile.exists() && !destfile.canWrite()) {
344: String message = "Can not write to the specified destfile!";
345: if (failonerror) {
346: throw new BuildException(message, getLocation());
347: } else {
348: log(message, Project.MSG_ERR);
349: }
350: return;
351: }
352: os = new FileOutputStream(this .destfile);
353: saveProperties(allProps, os);
354: }
355: } catch (IOException ioe) {
356: if (failonerror) {
357: throw new BuildException(ioe, getLocation());
358: } else {
359: log(ioe.getMessage(), Project.MSG_INFO);
360: }
361: } finally {
362: if (os != null) {
363: try {
364: os.close();
365: } catch (IOException ex) {
366: //ignore
367: }
368: }
369: }
370: }
371:
372: /**
373: * Send the key/value pairs in the hashtable to the given output stream.
374: * Only those properties matching the <tt>prefix</tt> constraint will be
375: * sent to the output stream.
376: * The output stream will be closed when this method returns.
377: *
378: * @param allProps propfile to save
379: * @param os output stream
380: * @throws IOException on output errors
381: * @throws BuildException on other errors
382: */
383: protected void saveProperties(Hashtable allProps, OutputStream os)
384: throws IOException, BuildException {
385: final List keyList = new ArrayList(allProps.keySet());
386: Collections.sort(keyList);
387: Properties props = new Properties() {
388: public Enumeration keys() {
389: return CollectionUtils
390: .asEnumeration(keyList.iterator());
391: }
392:
393: public Set entrySet() {
394: Set result = super .entrySet();
395: if (JavaEnvUtils.isKaffe()) {
396: TreeSet t = new TreeSet(new Comparator() {
397: public int compare(Object o1, Object o2) {
398: String key1 = (String) ((Map.Entry) o1)
399: .getKey();
400: String key2 = (String) ((Map.Entry) o2)
401: .getKey();
402: return key1.compareTo(key2);
403: }
404: });
405: t.addAll(result);
406: result = t;
407: }
408: return result;
409: }
410: };
411: for (int i = 0; i < keyList.size(); i++) {
412: String name = keyList.get(i).toString();
413: String value = allProps.get(name).toString();
414: props.setProperty(name, value);
415: }
416: if ("text".equals(format)) {
417: jdkSaveProperties(props, os, "Ant properties");
418: } else if ("xml".equals(format)) {
419: xmlSaveProperties(props, os);
420: }
421: }
422:
423: /**
424: * a tuple for the sort list.
425: */
426: private static class Tuple implements Comparable {
427: private String key;
428: private String value;
429:
430: private Tuple(String key, String value) {
431: this .key = key;
432: this .value = value;
433: }
434:
435: /**
436: * Compares this object with the specified object for order.
437: * @param o the Object to be compared.
438: * @return a negative integer, zero, or a positive integer as this object is
439: * less than, equal to, or greater than the specified object.
440: * @throws ClassCastException if the specified object's type prevents it
441: * from being compared to this Object.
442: */
443: public int compareTo(Object o) {
444: Tuple that = (Tuple) o;
445: return key.compareTo(that.key);
446: }
447: }
448:
449: private List sortProperties(Properties props) {
450: //sort the list. Makes SCM and manual diffs easier.
451: List sorted = new ArrayList(props.size());
452: Enumeration e = props.propertyNames();
453: while (e.hasMoreElements()) {
454: String name = (String) e.nextElement();
455: sorted.add(new Tuple(name, props.getProperty(name)));
456: }
457: Collections.sort(sorted);
458: return sorted;
459: }
460:
461: /**
462: * Output the properties as xml output.
463: * @param props the properties to save
464: * @param os the output stream to write to (Note this gets closed)
465: * @throws IOException on error in writing to the stream
466: */
467: protected void xmlSaveProperties(Properties props, OutputStream os)
468: throws IOException {
469: // create XML document
470: Document doc = getDocumentBuilder().newDocument();
471: Element rootElement = doc.createElement(PROPERTIES);
472:
473: List sorted = sortProperties(props);
474:
475: // output properties
476: Iterator iten = sorted.iterator();
477: while (iten.hasNext()) {
478: Tuple tuple = (Tuple) iten.next();
479: Element propElement = doc.createElement(PROPERTY);
480: propElement.setAttribute(ATTR_NAME, tuple.key);
481: propElement.setAttribute(ATTR_VALUE, tuple.value);
482: rootElement.appendChild(propElement);
483: }
484:
485: Writer wri = null;
486: try {
487: wri = new OutputStreamWriter(os, "UTF8");
488: wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
489: (new DOMElementWriter()).write(rootElement, wri, 0, "\t");
490: wri.flush();
491: } catch (IOException ioe) {
492: throw new BuildException("Unable to write XML file", ioe);
493: } finally {
494: FileUtils.close(wri);
495: }
496: }
497:
498: /**
499: * JDK 1.2 allows for the safer method
500: * <tt>Properties.store(OutputStream, String)</tt>, which throws an
501: * <tt>IOException</tt> on an output error.
502: *
503: *@param props the properties to record
504: *@param os record the properties to this output stream
505: *@param header prepend this header to the property output
506: *@exception IOException on an I/O error during a write.
507: */
508: protected void jdkSaveProperties(Properties props, OutputStream os,
509: String header) throws IOException {
510: try {
511: props.store(os, header);
512:
513: } catch (IOException ioe) {
514: throw new BuildException(ioe, getLocation());
515: } finally {
516: if (os != null) {
517: try {
518: os.close();
519: } catch (IOException ioex) {
520: log("Failed to close output stream");
521: }
522: }
523: }
524: }
525:
526: /**
527: * Uses the DocumentBuilderFactory to get a DocumentBuilder instance.
528: *
529: * @return The DocumentBuilder instance
530: */
531: private static DocumentBuilder getDocumentBuilder() {
532: try {
533: return DocumentBuilderFactory.newInstance()
534: .newDocumentBuilder();
535: } catch (Exception e) {
536: throw new ExceptionInInitializerError(e);
537: }
538: }
539: }
|