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:
019: package org.apache.tools.ant;
020:
021: import java.util.Hashtable;
022: import java.util.Vector;
023: import java.util.Enumeration;
024:
025: /* ISSUES:
026: - ns param. It could be used to provide "namespaces" for properties, which
027: may be more flexible.
028: - Object value. In ant1.5 String is used for Properties - but it would be nice
029: to support generic Objects (the property remains immutable - you can't change
030: the associated object). This will also allow JSP-EL style setting using the
031: Object if an attribute contains only the property (name="${property}" could
032: avoid Object->String->Object conversion)
033: - Currently we "chain" only for get and set property (probably most users
034: will only need that - if they need more they can replace the top helper).
035: Need to discuss this and find if we need more.
036: */
037:
038: /** NOT FINAL. API MAY CHANGE
039: *
040: * Deals with properties - substitution, dynamic properties, etc.
041: *
042: * This is the same code as in Ant1.5. The main addition is the ability
043: * to chain multiple PropertyHelpers and to replace the default.
044: *
045: * @since Ant 1.6
046: */
047: public class PropertyHelper {
048:
049: private Project project;
050: private PropertyHelper next;
051:
052: /** Project properties map (usually String to String). */
053: private Hashtable properties = new Hashtable();
054:
055: /**
056: * Map of "user" properties (as created in the Ant task, for example).
057: * Note that these key/value pairs are also always put into the
058: * project properties, so only the project properties need to be queried.
059: * Mapping is String to String.
060: */
061: private Hashtable userProperties = new Hashtable();
062:
063: /**
064: * Map of inherited "user" properties - that are those "user"
065: * properties that have been created by tasks and not been set
066: * from the command line or a GUI tool.
067: * Mapping is String to String.
068: */
069: private Hashtable inheritedProperties = new Hashtable();
070:
071: /**
072: * Default constructor.
073: */
074: protected PropertyHelper() {
075: }
076:
077: //override facility for subclasses to put custom hashtables in
078:
079: // -------------------- Hook management --------------------
080:
081: /**
082: * Set the project for which this helper is performing property resolution
083: *
084: * @param p the project instance.
085: */
086: public void setProject(Project p) {
087: this .project = p;
088: }
089:
090: /** There are 2 ways to hook into property handling:
091: * - you can replace the main PropertyHelper. The replacement is required
092: * to support the same semantics (of course :-)
093: *
094: * - you can chain a property helper capable of storing some properties.
095: * Again, you are required to respect the immutability semantics (at
096: * least for non-dynamic properties)
097: *
098: * @param next the next property helper in the chain.
099: */
100: public void setNext(PropertyHelper next) {
101: this .next = next;
102: }
103:
104: /**
105: * Get the next property helper in the chain.
106: *
107: * @return the next property helper.
108: */
109: public PropertyHelper getNext() {
110: return next;
111: }
112:
113: /**
114: * Factory method to create a property processor.
115: * Users can provide their own or replace it using "ant.PropertyHelper"
116: * reference. User tasks can also add themselves to the chain, and provide
117: * dynamic properties.
118: *
119: * @param project the project fro which the property helper is required.
120: *
121: * @return the project's property helper.
122: */
123: public static synchronized PropertyHelper getPropertyHelper(
124: Project project) {
125: PropertyHelper helper = (PropertyHelper) project
126: .getReference(MagicNames.REFID_PROPERTY_HELPER);
127: if (helper != null) {
128: return helper;
129: }
130: helper = new PropertyHelper();
131: helper.setProject(project);
132:
133: project.addReference(MagicNames.REFID_PROPERTY_HELPER, helper);
134: return helper;
135: }
136:
137: // -------------------- Methods to override --------------------
138:
139: /**
140: * Sets a property. Any existing property of the same name
141: * is overwritten, unless it is a user property. Will be called
142: * from setProperty().
143: *
144: * If all helpers return false, the property will be saved in
145: * the default properties table by setProperty.
146: *
147: * @param ns The namespace that the property is in (currently
148: * not used.
149: * @param name The name of property to set.
150: * Must not be <code>null</code>.
151: * @param value The new value of the property.
152: * Must not be <code>null</code>.
153: * @param inherited True if this property is inherited (an [sub]ant[call] property).
154: * @param user True if this property is a user property.
155: * @param isNew True is this is a new property.
156: * @return true if this helper has stored the property, false if it
157: * couldn't. Each helper should delegate to the next one (unless it
158: * has a good reason not to).
159: */
160: public boolean setPropertyHook(String ns, String name,
161: Object value, boolean inherited, boolean user, boolean isNew) {
162: if (getNext() != null) {
163: boolean subst = getNext().setPropertyHook(ns, name, value,
164: inherited, user, isNew);
165: // If next has handled the property
166: if (subst) {
167: return true;
168: }
169: }
170: return false;
171: }
172:
173: /** Get a property. If all hooks return null, the default
174: * tables will be used.
175: *
176: * @param ns namespace of the sought property.
177: * @param name name of the sought property.
178: * @param user True if this is a user property.
179: * @return The property, if returned by a hook, or null if none.
180: */
181: public Object getPropertyHook(String ns, String name, boolean user) {
182: if (getNext() != null) {
183: Object o = getNext().getPropertyHook(ns, name, user);
184: if (o != null) {
185: return o;
186: }
187: }
188: // Experimental/Testing, will be removed
189: if (name.startsWith("toString:")) {
190: name = name.substring("toString:".length());
191: Object v = project.getReference(name);
192: return (v == null) ? null : v.toString();
193: }
194: return null;
195: }
196:
197: // -------------------- Optional methods --------------------
198: // You can override those methods if you want to optimize or
199: // do advanced things (like support a special syntax).
200: // The methods do not chain - you should use them when embedding ant
201: // (by replacing the main helper)
202:
203: /**
204: * Parses a string containing <code>${xxx}</code> style property
205: * references into two lists. The first list is a collection
206: * of text fragments, while the other is a set of string property names.
207: * <code>null</code> entries in the first list indicate a property
208: * reference from the second list.
209: *
210: * It can be overridden with a more efficient or customized version.
211: *
212: * @param value Text to parse. Must not be <code>null</code>.
213: * @param fragments List to add text fragments to.
214: * Must not be <code>null</code>.
215: * @param propertyRefs List to add property names to.
216: * Must not be <code>null</code>.
217: *
218: * @exception BuildException if the string contains an opening
219: * <code>${</code> without a closing
220: * <code>}</code>
221: */
222: public void parsePropertyString(String value, Vector fragments,
223: Vector propertyRefs) throws BuildException {
224: parsePropertyStringDefault(value, fragments, propertyRefs);
225: }
226:
227: /**
228: * Replaces <code>${xxx}</code> style constructions in the given value
229: * with the string value of the corresponding data types.
230: *
231: * @param ns The namespace for the property.
232: * @param value The string to be scanned for property references.
233: * May be <code>null</code>, in which case this
234: * method returns immediately with no effect.
235: * @param keys Mapping (String to String) of property names to their
236: * values. If <code>null</code>, only project properties will
237: * be used.
238: *
239: * @exception BuildException if the string contains an opening
240: * <code>${</code> without a closing
241: * <code>}</code>
242: * @return the original string with the properties replaced, or
243: * <code>null</code> if the original string is <code>null</code>.
244: */
245: public String replaceProperties(String ns, String value,
246: Hashtable keys) throws BuildException {
247: if (value == null || value.indexOf('$') == -1) {
248: return value;
249: }
250: Vector fragments = new Vector();
251: Vector propertyRefs = new Vector();
252: parsePropertyString(value, fragments, propertyRefs);
253:
254: StringBuffer sb = new StringBuffer();
255: Enumeration i = fragments.elements();
256: Enumeration j = propertyRefs.elements();
257:
258: while (i.hasMoreElements()) {
259: String fragment = (String) i.nextElement();
260: if (fragment == null) {
261: String propertyName = (String) j.nextElement();
262: Object replacement = null;
263:
264: // try to get it from the project or keys
265: // Backward compatibility
266: if (keys != null) {
267: replacement = keys.get(propertyName);
268: }
269: if (replacement == null) {
270: replacement = getProperty(ns, propertyName);
271: }
272:
273: if (replacement == null) {
274: project.log("Property \"" + propertyName
275: + "\" has not been set",
276: Project.MSG_VERBOSE);
277: }
278: fragment = (replacement != null) ? replacement
279: .toString() : "${" + propertyName + "}";
280: }
281: sb.append(fragment);
282: }
283: return sb.toString();
284: }
285:
286: // -------------------- Default implementation --------------------
287: // Methods used to support the default behavior and provide backward
288: // compatibility. Some will be deprecated, you should avoid calling them.
289:
290: /** Default implementation of setProperty. Will be called from Project.
291: * This is the original 1.5 implementation, with calls to the hook
292: * added.
293: * @param ns The namespace for the property (currently not used).
294: * @param name The name of the property.
295: * @param value The value to set the property to.
296: * @param verbose If this is true output extra log messages.
297: * @return true if the property is set.
298: */
299: public synchronized boolean setProperty(String ns, String name,
300: Object value, boolean verbose) {
301: // user (CLI) properties take precedence
302: if (null != userProperties.get(name)) {
303: if (verbose) {
304: project.log("Override ignored for user property \""
305: + name + "\"", Project.MSG_VERBOSE);
306: }
307: return false;
308: }
309:
310: boolean done = setPropertyHook(ns, name, value, false, false,
311: false);
312: if (done) {
313: return true;
314: }
315:
316: if (null != properties.get(name) && verbose) {
317: project.log("Overriding previous definition of property \""
318: + name + "\"", Project.MSG_VERBOSE);
319: }
320:
321: if (verbose) {
322: project.log("Setting project property: " + name + " -> "
323: + value, Project.MSG_DEBUG);
324: }
325: properties.put(name, value);
326: return true;
327: }
328:
329: /**
330: * Sets a property if no value currently exists. If the property
331: * exists already, a message is logged and the method returns with
332: * no other effect.
333: *
334: * @param ns The namespace for the property (currently not used).
335: * @param name The name of property to set.
336: * Must not be <code>null</code>.
337: * @param value The new value of the property.
338: * Must not be <code>null</code>.
339: * @since Ant 1.6
340: */
341: public synchronized void setNewProperty(String ns, String name,
342: Object value) {
343: if (null != properties.get(name)) {
344: project.log("Override ignored for property \"" + name
345: + "\"", Project.MSG_VERBOSE);
346: return;
347: }
348:
349: boolean done = setPropertyHook(ns, name, value, false, false,
350: true);
351: if (done) {
352: return;
353: }
354:
355: project.log("Setting project property: " + name + " -> "
356: + value, Project.MSG_DEBUG);
357: if (name != null && value != null) {
358: properties.put(name, value);
359: }
360: }
361:
362: /**
363: * Sets a user property, which cannot be overwritten by
364: * set/unset property calls. Any previous value is overwritten.
365: * @param ns The namespace for the property (currently not used).
366: * @param name The name of property to set.
367: * Must not be <code>null</code>.
368: * @param value The new value of the property.
369: * Must not be <code>null</code>.
370: */
371: public synchronized void setUserProperty(String ns, String name,
372: Object value) {
373: project.log("Setting ro project property: " + name + " -> "
374: + value, Project.MSG_DEBUG);
375: userProperties.put(name, value);
376:
377: boolean done = setPropertyHook(ns, name, value, false, true,
378: false);
379: if (done) {
380: return;
381: }
382: properties.put(name, value);
383: }
384:
385: /**
386: * Sets an inherited user property, which cannot be overwritten by set/unset
387: * property calls. Any previous value is overwritten. Also marks
388: * these properties as properties that have not come from the
389: * command line.
390: *
391: * @param ns The namespace for the property (currently not used).
392: * @param name The name of property to set.
393: * Must not be <code>null</code>.
394: * @param value The new value of the property.
395: * Must not be <code>null</code>.
396: */
397: public synchronized void setInheritedProperty(String ns,
398: String name, Object value) {
399: inheritedProperties.put(name, value);
400:
401: project.log("Setting ro project property: " + name + " -> "
402: + value, Project.MSG_DEBUG);
403: userProperties.put(name, value);
404:
405: boolean done = setPropertyHook(ns, name, value, true, false,
406: 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:
431: Object o = getPropertyHook(ns, name, false);
432: if (o != null) {
433: return o;
434: }
435:
436: return properties.get(name);
437: }
438:
439: /**
440: * Returns the value of a user property, if it is set.
441: *
442: * @param ns The namespace for the property (currently not used).
443: * @param name The name of the property.
444: * May be <code>null</code>, in which case
445: * the return value is also <code>null</code>.
446: * @return the property value, or <code>null</code> for no match
447: * or if a <code>null</code> name is provided.
448: */
449: public synchronized Object getUserProperty(String ns, String name) {
450: if (name == null) {
451: return null;
452: }
453: Object o = getPropertyHook(ns, name, true);
454: if (o != null) {
455: return o;
456: }
457: return userProperties.get(name);
458: }
459:
460: // -------------------- Access to property tables --------------------
461: // This is used to support ant call and similar tasks. It should be
462: // deprecated, it is possible to use a better (more efficient)
463: // mechanism to preserve the context.
464:
465: /**
466: * Returns a copy of the properties table.
467: * @return a hashtable containing all properties
468: * (including user properties).
469: */
470: public Hashtable getProperties() {
471: return new Hashtable(properties);
472: // There is a better way to save the context. This shouldn't
473: // delegate to next, it's for backward compatibility only.
474: }
475:
476: /**
477: * Returns a copy of the user property hashtable
478: * @return a hashtable containing just the user properties
479: */
480: public Hashtable getUserProperties() {
481: return new Hashtable(userProperties);
482: }
483:
484: /**
485: * special back door for subclasses, internal access to
486: * the hashtables
487: * @return the live hashtable of all properties
488: */
489: protected Hashtable getInternalProperties() {
490: return properties;
491: }
492:
493: /**
494: * special back door for subclasses, internal access to
495: * 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
505: * the hashtables
506: *
507: * @return the live hashtable inherited properties
508: */
509: protected Hashtable getInternalInheritedProperties() {
510: return inheritedProperties;
511: }
512:
513: /**
514: * Copies all user properties that have not been set on the
515: * command line or a GUI tool from this instance to the Project
516: * instance given as the argument.
517: *
518: * <p>To copy all "user" properties, you will also have to call
519: * {@link #copyUserProperties copyUserProperties}.</p>
520: *
521: * @param other the project to copy the properties to. Must not be null.
522: *
523: * @since Ant 1.6
524: */
525: public void copyInheritedProperties(Project other) {
526: Enumeration e = inheritedProperties.keys();
527: while (e.hasMoreElements()) {
528: String arg = e.nextElement().toString();
529: if (other.getUserProperty(arg) != null) {
530: continue;
531: }
532: Object value = inheritedProperties.get(arg);
533: other.setInheritedProperty(arg, value.toString());
534: }
535: }
536:
537: /**
538: * Copies all user properties that have been set on the command
539: * line or a GUI tool from this instance to the Project instance
540: * given as the argument.
541: *
542: * <p>To copy all "user" properties, you will also have to call
543: * {@link #copyInheritedProperties copyInheritedProperties}.</p>
544: *
545: * @param other the project to copy the properties to. Must not be null.
546: *
547: * @since Ant 1.6
548: */
549: public void copyUserProperties(Project other) {
550: Enumeration e = userProperties.keys();
551: while (e.hasMoreElements()) {
552: Object arg = e.nextElement();
553: if (inheritedProperties.containsKey(arg)) {
554: continue;
555: }
556: Object value = userProperties.get(arg);
557: other.setUserProperty(arg.toString(), value.toString());
558: }
559: }
560:
561: // -------------------- Property parsing --------------------
562: // Moved from ProjectHelper. You can override the static method -
563: // this is used for backward compatibility (for code that calls
564: // the parse method in ProjectHelper).
565:
566: /** Default parsing method. It is here only to support backward compatibility
567: * for the static ProjectHelper.parsePropertyString().
568: */
569: static void parsePropertyStringDefault(String value,
570: Vector fragments, Vector propertyRefs)
571: throws BuildException {
572: int prev = 0;
573: int pos;
574: //search for the next instance of $ from the 'prev' position
575: while ((pos = value.indexOf("$", prev)) >= 0) {
576:
577: //if there was any text before this, add it as a fragment
578: //TODO, this check could be modified to go if pos>prev;
579: //seems like this current version could stick empty strings
580: //into the list
581: if (pos > 0) {
582: fragments.addElement(value.substring(prev, pos));
583: }
584: //if we are at the end of the string, we tack on a $
585: //then move past it
586: if (pos == (value.length() - 1)) {
587: fragments.addElement("$");
588: prev = pos + 1;
589: } else if (value.charAt(pos + 1) != '{') {
590: //peek ahead to see if the next char is a property or not
591: //not a property: insert the char as a literal
592: /*
593: fragments.addElement(value.substring(pos + 1, pos + 2));
594: prev = pos + 2;
595: */
596: if (value.charAt(pos + 1) == '$') {
597: //backwards compatibility two $ map to one mode
598: fragments.addElement("$");
599: prev = pos + 2;
600: } else {
601: //new behaviour: $X maps to $X for all values of X!='$'
602: fragments.addElement(value.substring(pos, pos + 2));
603: prev = pos + 2;
604: }
605:
606: } else {
607: //property found, extract its name or bail on a typo
608: int endName = value.indexOf('}', pos);
609: if (endName < 0) {
610: throw new BuildException(
611: "Syntax error in property: " + value);
612: }
613: String propertyName = value.substring(pos + 2, endName);
614: fragments.addElement(null);
615: propertyRefs.addElement(propertyName);
616: prev = endName + 1;
617: }
618: }
619: //no more $ signs found
620: //if there is any tail to the file, append it
621: if (prev < value.length()) {
622: fragments.addElement(value.substring(prev));
623: }
624: }
625:
626: }
|