1. /*
  2. * Copyright 2002-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional;
  18. import java.io.ByteArrayOutputStream;
  19. import java.io.File;
  20. import java.io.FileInputStream;
  21. import java.io.FileNotFoundException;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.OutputStream;
  25. import java.io.OutputStreamWriter;
  26. import java.io.Writer;
  27. import java.util.Enumeration;
  28. import java.util.Hashtable;
  29. import java.util.Properties;
  30. import java.util.Vector;
  31. import javax.xml.parsers.DocumentBuilder;
  32. import javax.xml.parsers.DocumentBuilderFactory;
  33. import org.apache.tools.ant.BuildException;
  34. import org.apache.tools.ant.Project;
  35. import org.apache.tools.ant.Task;
  36. import org.apache.tools.ant.types.EnumeratedAttribute;
  37. import org.apache.tools.ant.types.PropertySet;
  38. import org.apache.tools.ant.util.CollectionUtils;
  39. import org.apache.tools.ant.util.DOMElementWriter;
  40. import org.w3c.dom.Document;
  41. import org.w3c.dom.Element;
  42. /**
  43. * Displays all the current properties in the build. The output can be sent to
  44. * a file if desired. <P>
  45. *
  46. * Attribute "destfile" defines a file to send the properties to. This can be
  47. * processed as a standard property file later. <P>
  48. *
  49. * Attribute "prefix" defines a prefix which is used to filter the properties
  50. * only those properties starting with this prefix will be echoed. <P>
  51. *
  52. * By default, the "failonerror" attribute is enabled. If an error occurs while
  53. * writing the properties to a file, and this attribute is enabled, then a
  54. * BuildException will be thrown. If disabled, then IO errors will be reported
  55. * as a log statement, but no error will be thrown. <P>
  56. *
  57. * Examples: <pre>
  58. * <echoproperties />
  59. * </pre> Report the current properties to the log. <P>
  60. *
  61. * <pre>
  62. * <echoproperties destfile="my.properties" />
  63. * </pre> Report the current properties to the file "my.properties", and will
  64. * fail the build if the file could not be created or written to. <P>
  65. *
  66. * <pre>
  67. * <echoproperties destfile="my.properties" failonerror="false"
  68. * prefix="ant" />
  69. * </pre> Report all properties beginning with 'ant' to the file
  70. * "my.properties", and will log a message if the file could not be created or
  71. * written to, but will still allow the build to continue.
  72. *
  73. *@since Ant 1.5
  74. */
  75. public class EchoProperties extends Task {
  76. /**
  77. * the properties element.
  78. */
  79. private static final String PROPERTIES = "properties";
  80. /**
  81. * the property element.
  82. */
  83. private static final String PROPERTY = "property";
  84. /**
  85. * name attribute for property, testcase and testsuite elements.
  86. */
  87. private static final String ATTR_NAME = "name";
  88. /**
  89. * value attribute for property elements.
  90. */
  91. private static final String ATTR_VALUE = "value";
  92. /**
  93. * the input file.
  94. */
  95. private File inFile = null;
  96. /**
  97. * File object pointing to the output file. If this is null, then
  98. * we output to the project log, not to a file.
  99. */
  100. private File destfile = null;
  101. /**
  102. * If this is true, then errors generated during file output will become
  103. * build errors, and if false, then such errors will be logged, but not
  104. * thrown.
  105. */
  106. private boolean failonerror = true;
  107. private Vector propertySets = new Vector();
  108. private String format = "text";
  109. /**
  110. * Sets the input file.
  111. *
  112. * @param file the input file
  113. */
  114. public void setSrcfile(File file) {
  115. inFile = file;
  116. }
  117. /**
  118. * Set a file to store the property output. If this is never specified,
  119. * then the output will be sent to the Ant log.
  120. *
  121. *@param destfile file to store the property output
  122. */
  123. public void setDestfile(File destfile) {
  124. this.destfile = destfile;
  125. }
  126. /**
  127. * If true, the task will fail if an error occurs writing the properties
  128. * file, otherwise errors are just logged.
  129. *
  130. *@param failonerror <tt>true</tt> if IO exceptions are reported as build
  131. * exceptions, or <tt>false</tt> if IO exceptions are ignored.
  132. */
  133. public void setFailOnError(boolean failonerror) {
  134. this.failonerror = failonerror;
  135. }
  136. /**
  137. * If the prefix is set, then only properties which start with this
  138. * prefix string will be recorded. If this is never set, or it is set
  139. * to an empty string or <tt>null</tt>, then all properties will be
  140. * recorded. <P>
  141. *
  142. * For example, if the property is set as:
  143. * <PRE><echoproperties prefix="ant." /></PRE>
  144. * then the property "ant.home" will be recorded, but "ant-example"
  145. * will not.
  146. *
  147. *@param prefix The new prefix value
  148. */
  149. public void setPrefix(String prefix) {
  150. PropertySet ps = new PropertySet();
  151. ps.setProject(getProject());
  152. ps.appendPrefix(prefix);
  153. addPropertyset(ps);
  154. }
  155. /**
  156. * A set of properties to write.
  157. *
  158. * @since Ant 1.6
  159. */
  160. public void addPropertyset(PropertySet ps) {
  161. propertySets.addElement(ps);
  162. }
  163. public void setFormat(FormatAttribute ea) {
  164. format = ea.getValue();
  165. }
  166. public static class FormatAttribute extends EnumeratedAttribute {
  167. private String [] formats = new String[]{"xml", "text"};
  168. public String[] getValues() {
  169. return formats;
  170. }
  171. }
  172. /**
  173. * Run the task.
  174. *
  175. *@exception BuildException trouble, probably file IO
  176. */
  177. public void execute() throws BuildException {
  178. //copy the properties file
  179. Hashtable allProps = new Hashtable();
  180. /* load properties from file if specified, otherwise
  181. use Ant's properties */
  182. if (inFile == null && propertySets.size() == 0) {
  183. // add ant properties
  184. CollectionUtils.putAll(allProps, getProject().getProperties());
  185. } else if (inFile != null) {
  186. if (inFile.exists() && inFile.isDirectory()) {
  187. String message = "srcfile is a directory!";
  188. if (failonerror) {
  189. throw new BuildException(message, getLocation());
  190. } else {
  191. log(message, Project.MSG_ERR);
  192. }
  193. return;
  194. }
  195. if (inFile.exists() && !inFile.canRead()) {
  196. String message = "Can not read from the specified srcfile!";
  197. if (failonerror) {
  198. throw new BuildException(message, getLocation());
  199. } else {
  200. log(message, Project.MSG_ERR);
  201. }
  202. return;
  203. }
  204. FileInputStream in = null;
  205. try {
  206. in = new FileInputStream(inFile);
  207. Properties props = new Properties();
  208. props.load(in);
  209. CollectionUtils.putAll(allProps, props);
  210. } catch (FileNotFoundException fnfe) {
  211. String message =
  212. "Could not find file " + inFile.getAbsolutePath();
  213. if (failonerror) {
  214. throw new BuildException(message, fnfe, getLocation());
  215. } else {
  216. log(message, Project.MSG_WARN);
  217. }
  218. return;
  219. } catch (IOException ioe) {
  220. String message =
  221. "Could not read file " + inFile.getAbsolutePath();
  222. if (failonerror) {
  223. throw new BuildException(message, ioe, getLocation());
  224. } else {
  225. log(message, Project.MSG_WARN);
  226. }
  227. return;
  228. } finally {
  229. try {
  230. if (null != in) {
  231. in.close();
  232. }
  233. } catch (IOException ioe) {
  234. //ignore
  235. }
  236. }
  237. }
  238. Enumeration e = propertySets.elements();
  239. while (e.hasMoreElements()) {
  240. PropertySet ps = (PropertySet) e.nextElement();
  241. CollectionUtils.putAll(allProps, ps.getProperties());
  242. }
  243. OutputStream os = null;
  244. try {
  245. if (destfile == null) {
  246. os = new ByteArrayOutputStream();
  247. saveProperties(allProps, os);
  248. log(os.toString(), Project.MSG_INFO);
  249. } else {
  250. if (destfile.exists() && destfile.isDirectory()) {
  251. String message = "destfile is a directory!";
  252. if (failonerror) {
  253. throw new BuildException(message, getLocation());
  254. } else {
  255. log(message, Project.MSG_ERR);
  256. }
  257. return;
  258. }
  259. if (destfile.exists() && !destfile.canWrite()) {
  260. String message =
  261. "Can not write to the specified destfile!";
  262. if (failonerror) {
  263. throw new BuildException(message, getLocation());
  264. } else {
  265. log(message, Project.MSG_ERR);
  266. }
  267. return;
  268. }
  269. os = new FileOutputStream(this.destfile);
  270. saveProperties(allProps, os);
  271. }
  272. } catch (IOException ioe) {
  273. if (failonerror) {
  274. throw new BuildException(ioe, getLocation());
  275. } else {
  276. log(ioe.getMessage(), Project.MSG_INFO);
  277. }
  278. } finally {
  279. if (os != null) {
  280. try {
  281. os.close();
  282. } catch (IOException ex) {
  283. //ignore
  284. }
  285. }
  286. }
  287. }
  288. /**
  289. * Send the key/value pairs in the hashtable to the given output stream.
  290. * Only those properties matching the <tt>prefix</tt> constraint will be
  291. * sent to the output stream.
  292. * The output stream will be closed when this method returns.
  293. *
  294. *@param allProps propfile to save
  295. *@param os output stream
  296. *@exception IOException trouble
  297. */
  298. protected void saveProperties(Hashtable allProps, OutputStream os)
  299. throws IOException, BuildException {
  300. Properties props = new Properties();
  301. Enumeration e = allProps.keys();
  302. while (e.hasMoreElements()) {
  303. String name = e.nextElement().toString();
  304. String value = allProps.get(name).toString();
  305. props.put(name, value);
  306. }
  307. if ("text".equals(format)) {
  308. jdkSaveProperties(props, os, "Ant properties");
  309. } else if ("xml".equals(format)) {
  310. xmlSaveProperties(props, os);
  311. }
  312. }
  313. protected void xmlSaveProperties(Properties props,
  314. OutputStream os) throws IOException {
  315. // create XML document
  316. Document doc = getDocumentBuilder().newDocument();
  317. Element rootElement = doc.createElement(PROPERTIES);
  318. // output properties
  319. String name;
  320. Enumeration e = props.propertyNames();
  321. while (e.hasMoreElements()) {
  322. name = (String) e.nextElement();
  323. Element propElement = doc.createElement(PROPERTY);
  324. propElement.setAttribute(ATTR_NAME, name);
  325. propElement.setAttribute(ATTR_VALUE, props.getProperty(name));
  326. rootElement.appendChild(propElement);
  327. }
  328. Writer wri = null;
  329. try {
  330. wri = new OutputStreamWriter(os, "UTF8");
  331. wri.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  332. (new DOMElementWriter()).write(rootElement, wri, 0, "\t");
  333. wri.flush();
  334. } catch (IOException ioe) {
  335. throw new BuildException("Unable to write XML file", ioe);
  336. } finally {
  337. if (wri != null) {
  338. wri.close();
  339. }
  340. }
  341. }
  342. /**
  343. * JDK 1.2 allows for the safer method
  344. * <tt>Properties.store(OutputStream, String)</tt>, which throws an
  345. * <tt>IOException</tt> on an output error.
  346. *
  347. *@param props the properties to record
  348. *@param os record the properties to this output stream
  349. *@param header prepend this header to the property output
  350. *@exception IOException on an I/O error during a write.
  351. */
  352. protected void jdkSaveProperties(Properties props, OutputStream os,
  353. String header) throws IOException {
  354. try {
  355. props.store(os, header);
  356. } catch (IOException ioe) {
  357. throw new BuildException(ioe, getLocation());
  358. } finally {
  359. if (os != null) {
  360. try {
  361. os.close();
  362. } catch (IOException ioex) {
  363. log("Failed to close output stream");
  364. }
  365. }
  366. }
  367. }
  368. /**
  369. * Uses the DocumentBuilderFactory to get a DocumentBuilder instance.
  370. *
  371. * @return The DocumentBuilder instance
  372. */
  373. private static DocumentBuilder getDocumentBuilder() {
  374. try {
  375. return DocumentBuilderFactory.newInstance().newDocumentBuilder();
  376. } catch (Exception e) {
  377. throw new ExceptionInInitializerError(e);
  378. }
  379. }
  380. }