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