1. /*
  2. * Copyright 2000-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;
  18. import java.io.File;
  19. import java.io.FileInputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.net.URL;
  23. import java.util.Enumeration;
  24. import java.util.Properties;
  25. import java.util.Stack;
  26. import java.util.Vector;
  27. import org.apache.tools.ant.BuildException;
  28. import org.apache.tools.ant.Project;
  29. import org.apache.tools.ant.ProjectHelper;
  30. import org.apache.tools.ant.Task;
  31. import org.apache.tools.ant.types.Path;
  32. import org.apache.tools.ant.types.Reference;
  33. /**
  34. * Sets a property by name, or set of properties (from file or
  35. * resource) in the project. </p>
  36. * Properties are immutable: whoever sets a property first freezes it for the
  37. * rest of the build; they are most definitely not variable.
  38. * <p>There are five ways to set properties:</p>
  39. * <ul>
  40. * <li>By supplying both the <i>name</i> and <i>value</i> attribute.</li>
  41. * <li>By supplying both the <i>name</i> and <i>refid</i> attribute.</li>
  42. * <li>By setting the <i>file</i> attribute with the filename of the property
  43. * file to load. This property file has the format as defined by the file used
  44. * in the class java.util.Properties.</li>
  45. * <li>By setting the <i>resource</i> attribute with the resource name of the
  46. * property file to load. This property file has the format as defined by the
  47. * file used in the class java.util.Properties.</li>
  48. * <li>By setting the <i>environment</i> attribute with a prefix to use.
  49. * Properties will be defined for every environment variable by
  50. * prefixing the supplied name and a period to the name of the variable.</li>
  51. * </ul>
  52. * <p>Although combinations of these ways are possible, only one should be used
  53. * at a time. Problems might occur with the order in which properties are set, for
  54. * instance.</p>
  55. * <p>The value part of the properties being set, might contain references to other
  56. * properties. These references are resolved at the time these properties are set.
  57. * This also holds for properties loaded from a property file.</p>
  58. * Properties are case sensitive.
  59. *
  60. * @since Ant 1.1
  61. *
  62. * @ant.attribute.group name="name" description="One of these, when using the name attribute"
  63. * @ant.attribute.group name="noname" description="One of these, when not using the name attribute"
  64. */
  65. public class Property extends Task {
  66. protected String name;
  67. protected String value;
  68. protected File file;
  69. protected URL url;
  70. protected String resource;
  71. protected Path classpath;
  72. protected String env;
  73. protected Reference ref;
  74. protected String prefix;
  75. private Project fallback;
  76. protected boolean userProperty; // set read-only properties
  77. public Property() {
  78. this(false);
  79. }
  80. /**
  81. * @since Ant 1.5
  82. */
  83. protected Property(boolean userProperty) {
  84. this(userProperty, null);
  85. }
  86. /**
  87. * @since Ant 1.5
  88. */
  89. protected Property(boolean userProperty, Project fallback) {
  90. this.userProperty = userProperty;
  91. this.fallback = fallback;
  92. }
  93. /**
  94. * The name of the property to set.
  95. * @param name property name
  96. */
  97. public void setName(String name) {
  98. this.name = name;
  99. }
  100. public String getName() {
  101. return name;
  102. }
  103. /**
  104. * Sets the property to the absolute filename of the
  105. * given file. If the value of this attribute is an absolute path, it
  106. * is left unchanged (with / and \ characters converted to the
  107. * current platforms conventions). Otherwise it is taken as a path
  108. * relative to the project's basedir and expanded.
  109. * @param location path to set
  110. *
  111. * @ant.attribute group="name"
  112. */
  113. public void setLocation(File location) {
  114. setValue(location.getAbsolutePath());
  115. }
  116. /**
  117. * The value of the property.
  118. * @param value value to assign
  119. *
  120. * @ant.attribute group="name"
  121. */
  122. public void setValue(String value) {
  123. this.value = value;
  124. }
  125. public String getValue() {
  126. return value;
  127. }
  128. /**
  129. * Filename of a property file to load.
  130. * @param file filename
  131. *
  132. * @ant.attribute group="noname"
  133. */
  134. public void setFile(File file) {
  135. this.file = file;
  136. }
  137. public File getFile() {
  138. return file;
  139. }
  140. /**
  141. * The url from which to load properties.
  142. * @param url url string
  143. *
  144. * @ant.attribute group="noname"
  145. */
  146. public void setUrl(URL url) {
  147. this.url = url;
  148. }
  149. public URL getUrl() {
  150. return url;
  151. }
  152. /**
  153. * Prefix to apply to properties loaded using <code>file</code>
  154. * or <code>resource</code>.
  155. * A "." is appended to the prefix if not specified.
  156. * @param prefix prefix string
  157. * @since Ant 1.5
  158. */
  159. public void setPrefix(String prefix) {
  160. this.prefix = prefix;
  161. if (!prefix.endsWith(".")) {
  162. this.prefix += ".";
  163. }
  164. }
  165. /**
  166. * @since Ant 1.5
  167. */
  168. public String getPrefix() {
  169. return prefix;
  170. }
  171. /**
  172. * Sets a reference to an Ant datatype
  173. * declared elsewhere.
  174. * Only yields reasonable results for references
  175. * PATH like structures or properties.
  176. * @param ref reference
  177. *
  178. * @ant.attribute group="name"
  179. */
  180. public void setRefid(Reference ref) {
  181. this.ref = ref;
  182. }
  183. public Reference getRefid() {
  184. return ref;
  185. }
  186. /**
  187. * The resource name of a property file to load
  188. * @param resource resource on classpath
  189. *
  190. * @ant.attribute group="noname"
  191. */
  192. public void setResource(String resource) {
  193. this.resource = resource;
  194. }
  195. public String getResource() {
  196. return resource;
  197. }
  198. /**
  199. * Prefix to use when retrieving environment variables.
  200. * Thus if you specify environment="myenv"
  201. * you will be able to access OS-specific
  202. * environment variables via property names "myenv.PATH" or
  203. * "myenv.TERM".
  204. * <p>
  205. * Note that if you supply a property name with a final
  206. * "." it will not be doubled. ie environment="myenv." will still
  207. * allow access of environment variables through "myenv.PATH" and
  208. * "myenv.TERM". This functionality is currently only implemented
  209. * on select platforms. Feel free to send patches to increase the number of platforms
  210. * this functionality is supported on ;).<br>
  211. * Note also that properties are case sensitive, even if the
  212. * environment variables on your operating system are not, e.g. it
  213. * will be ${env.Path} not ${env.PATH} on Windows 2000.
  214. * @param env prefix
  215. *
  216. * @ant.attribute group="noname"
  217. */
  218. public void setEnvironment(String env) {
  219. this.env = env;
  220. }
  221. /**
  222. * @since Ant 1.5
  223. */
  224. public String getEnvironment() {
  225. return env;
  226. }
  227. /**
  228. * The classpath to use when looking up a resource.
  229. * @param classpath to add to any existing classpath
  230. */
  231. public void setClasspath(Path classpath) {
  232. if (this.classpath == null) {
  233. this.classpath = classpath;
  234. } else {
  235. this.classpath.append(classpath);
  236. }
  237. }
  238. /**
  239. * The classpath to use when looking up a resource.
  240. */
  241. public Path createClasspath() {
  242. if (this.classpath == null) {
  243. this.classpath = new Path(getProject());
  244. }
  245. return this.classpath.createPath();
  246. }
  247. /**
  248. * the classpath to use when looking up a resource,
  249. * given as reference to a <path> defined elsewhere
  250. */
  251. public void setClasspathRef(Reference r) {
  252. createClasspath().setRefid(r);
  253. }
  254. /**
  255. * @since Ant 1.5
  256. */
  257. public Path getClasspath() {
  258. return classpath;
  259. }
  260. /**
  261. * @deprecated This was never a supported feature and has been
  262. * deprecated without replacement
  263. * @ant.attribute ignore="true"
  264. */
  265. public void setUserProperty(boolean userProperty) {
  266. log("DEPRECATED: Ignoring request to set user property in Property"
  267. + " task.", Project.MSG_WARN);
  268. }
  269. /**
  270. * get the value of this property
  271. * @return the current value or the empty string
  272. */
  273. public String toString() {
  274. return value == null ? "" : value;
  275. }
  276. /**
  277. * set the property in the project to the value.
  278. * if the task was give a file, resource or env attribute
  279. * here is where it is loaded
  280. */
  281. public void execute() throws BuildException {
  282. if (getProject() == null) {
  283. throw new IllegalStateException("project has not been set");
  284. }
  285. if (name != null) {
  286. if (value == null && ref == null) {
  287. throw new BuildException("You must specify value, location or "
  288. + "refid with the name attribute",
  289. getLocation());
  290. }
  291. } else {
  292. if (url == null && file == null && resource == null && env == null) {
  293. throw new BuildException("You must specify url, file, resource or "
  294. + "environment when not using the "
  295. + "name attribute", getLocation());
  296. }
  297. }
  298. if (url == null && file == null && resource == null && prefix != null) {
  299. throw new BuildException("Prefix is only valid when loading from "
  300. + "a url, file or resource", getLocation());
  301. }
  302. if ((name != null) && (value != null)) {
  303. addProperty(name, value);
  304. }
  305. if (file != null) {
  306. loadFile(file);
  307. }
  308. if (url != null) {
  309. loadUrl(url);
  310. }
  311. if (resource != null) {
  312. loadResource(resource);
  313. }
  314. if (env != null) {
  315. loadEnvironment(env);
  316. }
  317. if ((name != null) && (ref != null)) {
  318. try {
  319. addProperty(name,
  320. ref.getReferencedObject(getProject()).toString());
  321. } catch (BuildException be) {
  322. if (fallback != null) {
  323. addProperty(name,
  324. ref.getReferencedObject(fallback).toString());
  325. } else {
  326. throw be;
  327. }
  328. }
  329. }
  330. }
  331. /**
  332. * load properties from a url
  333. * @param url url to load from
  334. */
  335. protected void loadUrl(URL url) throws BuildException {
  336. Properties props = new Properties();
  337. log("Loading " + url, Project.MSG_VERBOSE);
  338. try {
  339. InputStream is = url.openStream();
  340. try {
  341. props.load(is);
  342. } finally {
  343. if (is != null) {
  344. is.close();
  345. }
  346. }
  347. addProperties(props);
  348. } catch (IOException ex) {
  349. throw new BuildException(ex, getLocation());
  350. }
  351. }
  352. /**
  353. * load properties from a file
  354. * @param file file to load
  355. */
  356. protected void loadFile(File file) throws BuildException {
  357. Properties props = new Properties();
  358. log("Loading " + file.getAbsolutePath(), Project.MSG_VERBOSE);
  359. try {
  360. if (file.exists()) {
  361. FileInputStream fis = new FileInputStream(file);
  362. try {
  363. props.load(fis);
  364. } finally {
  365. if (fis != null) {
  366. fis.close();
  367. }
  368. }
  369. addProperties(props);
  370. } else {
  371. log("Unable to find property file: " + file.getAbsolutePath(),
  372. Project.MSG_VERBOSE);
  373. }
  374. } catch (IOException ex) {
  375. throw new BuildException(ex, getLocation());
  376. }
  377. }
  378. /**
  379. * load properties from a resource in the current classpath
  380. * @param name name of resource to load
  381. */
  382. protected void loadResource(String name) {
  383. Properties props = new Properties();
  384. log("Resource Loading " + name, Project.MSG_VERBOSE);
  385. InputStream is = null;
  386. try {
  387. ClassLoader cL = null;
  388. if (classpath != null) {
  389. cL = getProject().createClassLoader(classpath);
  390. } else {
  391. cL = this.getClass().getClassLoader();
  392. }
  393. if (cL == null) {
  394. is = ClassLoader.getSystemResourceAsStream(name);
  395. } else {
  396. is = cL.getResourceAsStream(name);
  397. }
  398. if (is != null) {
  399. props.load(is);
  400. addProperties(props);
  401. } else {
  402. log("Unable to find resource " + name, Project.MSG_WARN);
  403. }
  404. } catch (IOException ex) {
  405. throw new BuildException(ex, getLocation());
  406. } finally {
  407. if (is != null) {
  408. try {
  409. is.close();
  410. } catch (IOException e) {
  411. // ignore
  412. }
  413. }
  414. }
  415. }
  416. /**
  417. * load the environment values
  418. * @param prefix prefix to place before them
  419. */
  420. protected void loadEnvironment(String prefix) {
  421. Properties props = new Properties();
  422. if (!prefix.endsWith(".")) {
  423. prefix += ".";
  424. }
  425. log("Loading Environment " + prefix, Project.MSG_VERBOSE);
  426. Vector osEnv = Execute.getProcEnvironment();
  427. for (Enumeration e = osEnv.elements(); e.hasMoreElements();) {
  428. String entry = (String) e.nextElement();
  429. int pos = entry.indexOf('=');
  430. if (pos == -1) {
  431. log("Ignoring: " + entry, Project.MSG_WARN);
  432. } else {
  433. props.put(prefix + entry.substring(0, pos),
  434. entry.substring(pos + 1));
  435. }
  436. }
  437. addProperties(props);
  438. }
  439. /**
  440. * iterate through a set of properties,
  441. * resolve them then assign them
  442. */
  443. protected void addProperties(Properties props) {
  444. resolveAllProperties(props);
  445. Enumeration e = props.keys();
  446. while (e.hasMoreElements()) {
  447. String name = (String) e.nextElement();
  448. String value = props.getProperty(name);
  449. String v = getProject().replaceProperties(value);
  450. if (prefix != null) {
  451. name = prefix + name;
  452. }
  453. addProperty(name, v);
  454. }
  455. }
  456. /**
  457. * add a name value pair to the project property set
  458. * @param n name of property
  459. * @param v value to set
  460. */
  461. protected void addProperty(String n, String v) {
  462. if (userProperty) {
  463. if (getProject().getUserProperty(n) == null) {
  464. getProject().setInheritedProperty(n, v);
  465. } else {
  466. log("Override ignored for " + n, Project.MSG_VERBOSE);
  467. }
  468. } else {
  469. getProject().setNewProperty(n, v);
  470. }
  471. }
  472. /**
  473. * resolve properties inside a properties hashtable
  474. * @param props properties object to resolve
  475. */
  476. private void resolveAllProperties(Properties props) throws BuildException {
  477. for (Enumeration e = props.keys(); e.hasMoreElements();) {
  478. String name = (String) e.nextElement();
  479. Stack referencesSeen = new Stack();
  480. resolve(props, name, referencesSeen);
  481. }
  482. }
  483. /**
  484. * Recursively expand the named property using the project's
  485. * reference table and the given set of properties - fail if a
  486. * circular definition is detected.
  487. *
  488. * @param props properties object to resolve
  489. * @param name of the property to resolve
  490. * @param referencesSeen stack of all property names that have
  491. * been tried to expand before coming here.
  492. */
  493. private void resolve(Properties props, String name, Stack referencesSeen)
  494. throws BuildException {
  495. if (referencesSeen.contains(name)) {
  496. throw new BuildException("Property " + name + " was circularly "
  497. + "defined.");
  498. }
  499. String value = props.getProperty(name);
  500. Vector fragments = new Vector();
  501. Vector propertyRefs = new Vector();
  502. ProjectHelper.parsePropertyString(value, fragments, propertyRefs);
  503. if (propertyRefs.size() != 0) {
  504. referencesSeen.push(name);
  505. StringBuffer sb = new StringBuffer();
  506. Enumeration i = fragments.elements();
  507. Enumeration j = propertyRefs.elements();
  508. while (i.hasMoreElements()) {
  509. String fragment = (String) i.nextElement();
  510. if (fragment == null) {
  511. String propertyName = (String) j.nextElement();
  512. fragment = getProject().getProperty(propertyName);
  513. if (fragment == null) {
  514. if (props.containsKey(propertyName)) {
  515. resolve(props, propertyName, referencesSeen);
  516. fragment = props.getProperty(propertyName);
  517. } else {
  518. fragment = "${" + propertyName + "}";
  519. }
  520. }
  521. }
  522. sb.append(fragment);
  523. }
  524. value = sb.toString();
  525. props.put(name, value);
  526. referencesSeen.pop();
  527. }
  528. }
  529. }