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;
  18. import java.util.Hashtable;
  19. import java.util.Vector;
  20. import java.util.Enumeration;
  21. /* ISSUES:
  22. - ns param. It could be used to provide "namespaces" for properties, which
  23. may be more flexible.
  24. - Object value. In ant1.5 String is used for Properties - but it would be nice
  25. to support generic Objects (the property remains immutable - you can't change
  26. the associated object). This will also allow JSP-EL style setting using the
  27. Object if an attribute contains only the property (name="${property}" could
  28. avoid Object->String->Object conversion)
  29. - Currently we "chain" only for get and set property (probably most users
  30. will only need that - if they need more they can replace the top helper).
  31. Need to discuss this and find if we need more.
  32. */
  33. /** NOT FINAL. API MAY CHANGE
  34. *
  35. * Deals with properties - substitution, dynamic properties, etc.
  36. *
  37. * This is the same code as in Ant1.5. The main addition is the ability
  38. * to chain multiple PropertyHelpers and to replace the default.
  39. *
  40. * @since Ant 1.6
  41. */
  42. public class PropertyHelper {
  43. private Project project;
  44. private PropertyHelper next;
  45. /** Project properties map (usually String to String). */
  46. private Hashtable properties = new Hashtable();
  47. /**
  48. * Map of "user" properties (as created in the Ant task, for example).
  49. * Note that these key/value pairs are also always put into the
  50. * project properties, so only the project properties need to be queried.
  51. * Mapping is String to String.
  52. */
  53. private Hashtable userProperties = new Hashtable();
  54. /**
  55. * Map of inherited "user" properties - that are those "user"
  56. * properties that have been created by tasks and not been set
  57. * from the command line or a GUI tool.
  58. * Mapping is String to String.
  59. */
  60. private Hashtable inheritedProperties = new Hashtable();
  61. /**
  62. * Default constructor.
  63. */
  64. protected PropertyHelper() {
  65. }
  66. // -------------------- Hook management --------------------
  67. /**
  68. * Set the project for which this helper is performing property resolution
  69. *
  70. * @param p the project instance.
  71. */
  72. public void setProject(Project p) {
  73. this.project = p;
  74. }
  75. /** There are 2 ways to hook into property handling:
  76. * - you can replace the main PropertyHelper. The replacement is required
  77. * to support the same semantics (of course :-)
  78. *
  79. * - you can chain a property helper capable of storing some properties.
  80. * Again, you are required to respect the immutability semantics (at
  81. * least for non-dynamic properties)
  82. *
  83. * @param next the next property helper in the chain.
  84. */
  85. public void setNext(PropertyHelper next) {
  86. this.next = next;
  87. }
  88. /**
  89. * Get the next property helper in the chain.
  90. *
  91. * @return the next property helper.
  92. */
  93. public PropertyHelper getNext() {
  94. return next;
  95. }
  96. /**
  97. * Factory method to create a property processor.
  98. * Users can provide their own or replace it using "ant.PropertyHelper"
  99. * reference. User tasks can also add themselves to the chain, and provide
  100. * dynamic properties.
  101. *
  102. * @param project the project fro which the property helper is required.
  103. *
  104. * @return the project's property helper.
  105. */
  106. public static synchronized
  107. PropertyHelper getPropertyHelper(Project project) {
  108. PropertyHelper helper
  109. = (PropertyHelper) project.getReference("ant.PropertyHelper");
  110. if (helper != null) {
  111. return helper;
  112. }
  113. helper = new PropertyHelper();
  114. helper.setProject(project);
  115. project.addReference("ant.PropertyHelper", helper);
  116. return helper;
  117. }
  118. // -------------------- Methods to override --------------------
  119. /**
  120. * Sets a property. Any existing property of the same name
  121. * is overwritten, unless it is a user property. Will be called
  122. * from setProperty().
  123. *
  124. * If all helpers return false, the property will be saved in
  125. * the default properties table by setProperty.
  126. *
  127. * @param name The name of property to set.
  128. * Must not be <code>null</code>.
  129. * @param value The new value of the property.
  130. * Must not be <code>null</code>.
  131. * @return true if this helper has stored the property, false if it
  132. * couldn't. Each helper should delegate to the next one (unless it
  133. * has a good reason not to).
  134. */
  135. public boolean setPropertyHook(String ns, String name,
  136. Object value,
  137. boolean inherited, boolean user,
  138. boolean isNew) {
  139. if (getNext() != null) {
  140. boolean subst = getNext().setPropertyHook(ns, name, value,
  141. inherited, user, isNew);
  142. // If next has handled the property
  143. if (subst) {
  144. return true;
  145. }
  146. }
  147. return false;
  148. }
  149. /** Get a property. If all hooks return null, the default
  150. * tables will be used.
  151. *
  152. * @param ns namespace of the sought property
  153. * @param name name of the sought property
  154. * @return The property, if returned by a hook, or null if none.
  155. */
  156. public Object getPropertyHook(String ns, String name, boolean user) {
  157. if (getNext() != null) {
  158. Object o = getNext().getPropertyHook(ns, name, user);
  159. if (o != null) {
  160. return o;
  161. }
  162. }
  163. // Experimental/Testing, will be removed
  164. if (name.startsWith("toString:")) {
  165. name = name.substring("toString:".length());
  166. Object v = project.getReference(name);
  167. if (v == null) {
  168. return null;
  169. }
  170. return v.toString();
  171. }
  172. return null;
  173. }
  174. // -------------------- Optional methods --------------------
  175. // You can override those methods if you want to optimize or
  176. // do advanced things (like support a special syntax).
  177. // The methods do not chain - you should use them when embedding ant
  178. // (by replacing the main helper)
  179. /**
  180. * Parses a string containing <code>${xxx}</code> style property
  181. * references into two lists. The first list is a collection
  182. * of text fragments, while the other is a set of string property names.
  183. * <code>null</code> entries in the first list indicate a property
  184. * reference from the second list.
  185. *
  186. * It can be overridden with a more efficient or customized version.
  187. *
  188. * @param value Text to parse. Must not be <code>null</code>.
  189. * @param fragments List to add text fragments to.
  190. * Must not be <code>null</code>.
  191. * @param propertyRefs List to add property names to.
  192. * Must not be <code>null</code>.
  193. *
  194. * @exception BuildException if the string contains an opening
  195. * <code>${</code> without a closing
  196. * <code>}</code>
  197. */
  198. public void parsePropertyString(String value, Vector fragments,
  199. Vector propertyRefs)
  200. throws BuildException {
  201. parsePropertyStringDefault(value, fragments, propertyRefs);
  202. }
  203. /**
  204. * Replaces <code>${xxx}</code> style constructions in the given value
  205. * with the string value of the corresponding data types.
  206. *
  207. * @param value The string to be scanned for property references.
  208. * May be <code>null</code>, in which case this
  209. * method returns immediately with no effect.
  210. * @param keys Mapping (String to String) of property names to their
  211. * values. If <code>null</code>, only project properties will
  212. * be used.
  213. *
  214. * @exception BuildException if the string contains an opening
  215. * <code>${</code> without a closing
  216. * <code>}</code>
  217. * @return the original string with the properties replaced, or
  218. * <code>null</code> if the original string is <code>null</code>.
  219. */
  220. public String replaceProperties(String ns, String value,
  221. Hashtable keys)
  222. throws BuildException {
  223. if (value == null) {
  224. return null;
  225. }
  226. Vector fragments = new Vector();
  227. Vector propertyRefs = new Vector();
  228. parsePropertyString(value, fragments, propertyRefs);
  229. StringBuffer sb = new StringBuffer();
  230. Enumeration i = fragments.elements();
  231. Enumeration j = propertyRefs.elements();
  232. while (i.hasMoreElements()) {
  233. String fragment = (String) i.nextElement();
  234. if (fragment == null) {
  235. String propertyName = (String) j.nextElement();
  236. Object replacement = null;
  237. // try to get it from the project or keys
  238. // Backward compatibility
  239. if (keys != null) {
  240. replacement = keys.get(propertyName);
  241. }
  242. if (replacement == null) {
  243. replacement = getProperty(ns, propertyName);
  244. }
  245. if (replacement == null) {
  246. project.log("Property ${" + propertyName
  247. + "} has not been set", Project.MSG_VERBOSE);
  248. }
  249. fragment = (replacement != null)
  250. ? replacement.toString()
  251. : "${" + propertyName + "}";
  252. }
  253. sb.append(fragment);
  254. }
  255. return sb.toString();
  256. }
  257. // -------------------- Default implementation --------------------
  258. // Methods used to support the default behavior and provide backward
  259. // compatibility. Some will be deprecated, you should avoid calling them.
  260. /** Default implementation of setProperty. Will be called from Project.
  261. * This is the original 1.5 implementation, with calls to the hook
  262. * added.
  263. */
  264. public synchronized boolean setProperty(String ns, String name,
  265. Object value, boolean verbose) {
  266. // user (CLI) properties take precedence
  267. if (null != userProperties.get(name)) {
  268. if (verbose) {
  269. project.log("Override ignored for user property " + name,
  270. Project.MSG_VERBOSE);
  271. }
  272. return false;
  273. }
  274. boolean done = setPropertyHook(ns, name, value, false, false, false);
  275. if (done) {
  276. return true;
  277. }
  278. if (null != properties.get(name) && verbose) {
  279. project.log("Overriding previous definition of property " + name,
  280. Project.MSG_VERBOSE);
  281. }
  282. if (verbose) {
  283. project.log("Setting project property: " + name + " -> "
  284. + value, Project.MSG_DEBUG);
  285. }
  286. properties.put(name, value);
  287. return true;
  288. }
  289. /**
  290. * Sets a property if no value currently exists. If the property
  291. * exists already, a message is logged and the method returns with
  292. * no other effect.
  293. *
  294. * @param name The name of property to set.
  295. * Must not be <code>null</code>.
  296. * @param value The new value of the property.
  297. * Must not be <code>null</code>.
  298. * @since Ant 1.6
  299. */
  300. public synchronized void setNewProperty(String ns, String name,
  301. Object value) {
  302. if (null != properties.get(name)) {
  303. project.log("Override ignored for property " + name,
  304. Project.MSG_VERBOSE);
  305. return;
  306. }
  307. boolean done = setPropertyHook(ns, name, value, false, false, true);
  308. if (done) {
  309. return;
  310. }
  311. project.log("Setting project property: " + name + " -> "
  312. + value, Project.MSG_DEBUG);
  313. if (name != null && value != null) {
  314. properties.put(name, value);
  315. }
  316. }
  317. /**
  318. * Sets a user property, which cannot be overwritten by
  319. * set/unset property calls. Any previous value is overwritten.
  320. * @param name The name of property to set.
  321. * Must not be <code>null</code>.
  322. * @param value The new value of the property.
  323. * Must not be <code>null</code>.
  324. */
  325. public synchronized void setUserProperty(String ns, String name,
  326. Object value) {
  327. project.log("Setting ro project property: " + name + " -> "
  328. + value, Project.MSG_DEBUG);
  329. userProperties.put(name, value);
  330. boolean done = setPropertyHook(ns, name, value, false, true, false);
  331. if (done) {
  332. return;
  333. }
  334. properties.put(name, value);
  335. }
  336. /**
  337. * Sets a user property, which cannot be overwritten by set/unset
  338. * property calls. Any previous value is overwritten. Also marks
  339. * these properties as properties that have not come from the
  340. * command line.
  341. *
  342. * @param name The name of property to set.
  343. * Must not be <code>null</code>.
  344. * @param value The new value of the property.
  345. * Must not be <code>null</code>.
  346. */
  347. public synchronized void setInheritedProperty(String ns, String name,
  348. Object value) {
  349. inheritedProperties.put(name, value);
  350. project.log("Setting ro project property: " + name + " -> "
  351. + value, Project.MSG_DEBUG);
  352. userProperties.put(name, value);
  353. boolean done = setPropertyHook(ns, name, value, true, false, false);
  354. if (done) {
  355. return;
  356. }
  357. properties.put(name, value);
  358. }
  359. // -------------------- Getting properties --------------------
  360. /**
  361. * Returns the value of a property, if it is set. You can override
  362. * this method in order to plug your own storage.
  363. *
  364. * @param name The name of the property.
  365. * May be <code>null</code>, in which case
  366. * the return value is also <code>null</code>.
  367. * @return the property value, or <code>null</code> for no match
  368. * or if a <code>null</code> name is provided.
  369. */
  370. public synchronized Object getProperty(String ns, String name) {
  371. if (name == null) {
  372. return null;
  373. }
  374. Object o = getPropertyHook(ns, name, false);
  375. if (o != null) {
  376. return o;
  377. }
  378. return properties.get(name);
  379. }
  380. /**
  381. * Returns the value of a user property, if it is set.
  382. *
  383. * @param name The name of the property.
  384. * May be <code>null</code>, in which case
  385. * the return value is also <code>null</code>.
  386. * @return the property value, or <code>null</code> for no match
  387. * or if a <code>null</code> name is provided.
  388. */
  389. public synchronized Object getUserProperty(String ns, String name) {
  390. if (name == null) {
  391. return null;
  392. }
  393. Object o = getPropertyHook(ns, name, true);
  394. if (o != null) {
  395. return o;
  396. }
  397. return userProperties.get(name);
  398. }
  399. // -------------------- Access to property tables --------------------
  400. // This is used to support ant call and similar tasks. It should be
  401. // deprecated, it is possible to use a better (more efficient)
  402. // mechanism to preserve the context.
  403. // TODO: do we need to delegate ?
  404. /**
  405. * Returns a copy of the properties table.
  406. * @return a hashtable containing all properties
  407. * (including user properties).
  408. */
  409. public Hashtable getProperties() {
  410. Hashtable propertiesCopy = new Hashtable();
  411. Enumeration e = properties.keys();
  412. while (e.hasMoreElements()) {
  413. Object name = e.nextElement();
  414. Object value = properties.get(name);
  415. propertiesCopy.put(name, value);
  416. }
  417. // There is a better way to save the context. This shouldn't
  418. // delegate to next, it's for backward compatibility only.
  419. return propertiesCopy;
  420. }
  421. /**
  422. * Returns a copy of the user property hashtable
  423. * @return a hashtable containing just the user properties
  424. */
  425. public Hashtable getUserProperties() {
  426. Hashtable propertiesCopy = new Hashtable();
  427. Enumeration e = userProperties.keys();
  428. while (e.hasMoreElements()) {
  429. Object name = e.nextElement();
  430. Object value = properties.get(name);
  431. propertiesCopy.put(name, value);
  432. }
  433. return propertiesCopy;
  434. }
  435. /**
  436. * Copies all user properties that have not been set on the
  437. * command line or a GUI tool from this instance to the Project
  438. * instance given as the argument.
  439. *
  440. * <p>To copy all "user" properties, you will also have to call
  441. * {@link #copyUserProperties copyUserProperties}.</p>
  442. *
  443. * @param other the project to copy the properties to. Must not be null.
  444. *
  445. * @since Ant 1.6
  446. */
  447. public void copyInheritedProperties(Project other) {
  448. Enumeration e = inheritedProperties.keys();
  449. while (e.hasMoreElements()) {
  450. String arg = e.nextElement().toString();
  451. if (other.getUserProperty(arg) != null) {
  452. continue;
  453. }
  454. Object value = inheritedProperties.get(arg);
  455. other.setInheritedProperty(arg, value.toString());
  456. }
  457. }
  458. /**
  459. * Copies all user properties that have been set on the command
  460. * line or a GUI tool from this instance to the Project instance
  461. * given as the argument.
  462. *
  463. * <p>To copy all "user" properties, you will also have to call
  464. * {@link #copyInheritedProperties copyInheritedProperties}.</p>
  465. *
  466. * @param other the project to copy the properties to. Must not be null.
  467. *
  468. * @since Ant 1.6
  469. */
  470. public void copyUserProperties(Project other) {
  471. Enumeration e = userProperties.keys();
  472. while (e.hasMoreElements()) {
  473. Object arg = e.nextElement();
  474. if (inheritedProperties.containsKey(arg)) {
  475. continue;
  476. }
  477. Object value = userProperties.get(arg);
  478. other.setUserProperty(arg.toString(), value.toString());
  479. }
  480. }
  481. // -------------------- Property parsing --------------------
  482. // Moved from ProjectHelper. You can override the static method -
  483. // this is used for backward compatibility (for code that calls
  484. // the parse method in ProjectHelper).
  485. /** Default parsing method. It is here only to support backward compatibility
  486. * for the static ProjectHelper.parsePropertyString().
  487. */
  488. static void parsePropertyStringDefault(String value, Vector fragments,
  489. Vector propertyRefs)
  490. throws BuildException {
  491. int prev = 0;
  492. int pos;
  493. //search for the next instance of $ from the 'prev' position
  494. while ((pos = value.indexOf("$", prev)) >= 0) {
  495. //if there was any text before this, add it as a fragment
  496. //TODO, this check could be modified to go if pos>prev;
  497. //seems like this current version could stick empty strings
  498. //into the list
  499. if (pos > 0) {
  500. fragments.addElement(value.substring(prev, pos));
  501. }
  502. //if we are at the end of the string, we tack on a $
  503. //then move past it
  504. if (pos == (value.length() - 1)) {
  505. fragments.addElement("$");
  506. prev = pos + 1;
  507. } else if (value.charAt(pos + 1) != '{') {
  508. //peek ahead to see if the next char is a property or not
  509. //not a property: insert the char as a literal
  510. /*
  511. fragments.addElement(value.substring(pos + 1, pos + 2));
  512. prev = pos + 2;
  513. */
  514. if (value.charAt(pos + 1) == '$') {
  515. //backwards compatibility two $ map to one mode
  516. fragments.addElement("$");
  517. prev = pos + 2;
  518. } else {
  519. //new behaviour: $X maps to $X for all values of X!='$'
  520. fragments.addElement(value.substring(pos, pos + 2));
  521. prev = pos + 2;
  522. }
  523. } else {
  524. //property found, extract its name or bail on a typo
  525. int endName = value.indexOf('}', pos);
  526. if (endName < 0) {
  527. throw new BuildException("Syntax error in property: "
  528. + value);
  529. }
  530. String propertyName = value.substring(pos + 2, endName);
  531. fragments.addElement(null);
  532. propertyRefs.addElement(propertyName);
  533. prev = endName + 1;
  534. }
  535. }
  536. //no more $ signs found
  537. //if there is any tail to the file, append it
  538. if (prev < value.length()) {
  539. fragments.addElement(value.substring(prev));
  540. }
  541. }
  542. }