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.taskdefs;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.net.URL;
026: import java.util.Enumeration;
027: import java.util.Properties;
028: import java.util.Stack;
029: import java.util.Vector;
030:
031: import org.apache.tools.ant.BuildException;
032: import org.apache.tools.ant.Project;
033: import org.apache.tools.ant.PropertyHelper;
034: import org.apache.tools.ant.Task;
035: import org.apache.tools.ant.types.Path;
036: import org.apache.tools.ant.types.Reference;
037:
038: /**
039: * Sets a property by name, or set of properties (from file or
040: * resource) in the project. </p>
041: * Properties are immutable: whoever sets a property first freezes it for the
042: * rest of the build; they are most definitely not variable.
043: * <p>There are five ways to set properties:</p>
044: * <ul>
045: * <li>By supplying both the <i>name</i> and <i>value</i> attribute.</li>
046: * <li>By supplying both the <i>name</i> and <i>refid</i> attribute.</li>
047: * <li>By setting the <i>file</i> attribute with the filename of the property
048: * file to load. This property file has the format as defined by the file used
049: * in the class java.util.Properties.</li>
050: * <li>By setting the <i>resource</i> attribute with the resource name of the
051: * property file to load. This property file has the format as defined by the
052: * file used in the class java.util.Properties.</li>
053: * <li>By setting the <i>environment</i> attribute with a prefix to use.
054: * Properties will be defined for every environment variable by
055: * prefixing the supplied name and a period to the name of the variable.</li>
056: * </ul>
057: * <p>Although combinations of these ways are possible, only one should be used
058: * at a time. Problems might occur with the order in which properties are set, for
059: * instance.</p>
060: * <p>The value part of the properties being set, might contain references to other
061: * properties. These references are resolved at the time these properties are set.
062: * This also holds for properties loaded from a property file.</p>
063: * Properties are case sensitive.
064: *
065: * @since Ant 1.1
066: *
067: * @ant.attribute.group name="name" description="One of these, when using the name attribute"
068: * @ant.attribute.group name="noname" description="One of these, when not using the name attribute"
069: * @ant.task category="property"
070: */
071: public class Property extends Task {
072:
073: // CheckStyle:VisibilityModifier OFF - bc
074: protected String name;
075: protected String value;
076: protected File file;
077: protected URL url;
078: protected String resource;
079: protected Path classpath;
080: protected String env;
081: protected Reference ref;
082: protected String prefix;
083: private Project fallback;
084:
085: protected boolean userProperty; // set read-only properties
086:
087: // CheckStyle:VisibilityModifier ON
088:
089: /**
090: * Constructor for Property.
091: */
092: public Property() {
093: this (false);
094: }
095:
096: /**
097: * Constructor for Property.
098: * @param userProperty if true this is a user property
099: * @since Ant 1.5
100: */
101: protected Property(boolean userProperty) {
102: this (userProperty, null);
103: }
104:
105: /**
106: * Constructor for Property.
107: * @param userProperty if true this is a user property
108: * @param fallback a project to use to look for references if the reference is
109: * not in the current project
110: * @since Ant 1.5
111: */
112: protected Property(boolean userProperty, Project fallback) {
113: this .userProperty = userProperty;
114: this .fallback = fallback;
115: }
116:
117: /**
118: * The name of the property to set.
119: * @param name property name
120: */
121: public void setName(String name) {
122: this .name = name;
123: }
124:
125: /**
126: * Get the property name.
127: * @return the property name
128: */
129: public String getName() {
130: return name;
131: }
132:
133: /**
134: * Sets the property to the absolute filename of the
135: * given file. If the value of this attribute is an absolute path, it
136: * is left unchanged (with / and \ characters converted to the
137: * current platforms conventions). Otherwise it is taken as a path
138: * relative to the project's basedir and expanded.
139: * @param location path to set
140: *
141: * @ant.attribute group="name"
142: */
143: public void setLocation(File location) {
144: setValue(location.getAbsolutePath());
145: }
146:
147: /**
148: * The value of the property.
149: * @param value value to assign
150: *
151: * @ant.attribute group="name"
152: */
153: public void setValue(String value) {
154: this .value = value;
155: }
156:
157: /**
158: * Get the property value.
159: * @return the property value
160: */
161: public String getValue() {
162: return value;
163: }
164:
165: /**
166: * Filename of a property file to load.
167: * @param file filename
168: *
169: * @ant.attribute group="noname"
170: */
171: public void setFile(File file) {
172: this .file = file;
173: }
174:
175: /**
176: * Get the file attribute.
177: * @return the file attribute
178: */
179: public File getFile() {
180: return file;
181: }
182:
183: /**
184: * The url from which to load properties.
185: * @param url url string
186: *
187: * @ant.attribute group="noname"
188: */
189: public void setUrl(URL url) {
190: this .url = url;
191: }
192:
193: /**
194: * Get the url attribute.
195: * @return the url attribute
196: */
197: public URL getUrl() {
198: return url;
199: }
200:
201: /**
202: * Prefix to apply to properties loaded using <code>file</code>
203: * or <code>resource</code>.
204: * A "." is appended to the prefix if not specified.
205: * @param prefix prefix string
206: * @since Ant 1.5
207: */
208: public void setPrefix(String prefix) {
209: this .prefix = prefix;
210: if (!prefix.endsWith(".")) {
211: this .prefix += ".";
212: }
213: }
214:
215: /**
216: * Get the prefix attribute.
217: * @return the prefix attribute
218: * @since Ant 1.5
219: */
220: public String getPrefix() {
221: return prefix;
222: }
223:
224: /**
225: * Sets a reference to an Ant datatype
226: * declared elsewhere.
227: * Only yields reasonable results for references
228: * PATH like structures or properties.
229: * @param ref reference
230: *
231: * @ant.attribute group="name"
232: */
233: public void setRefid(Reference ref) {
234: this .ref = ref;
235: }
236:
237: /**
238: * Get the refid attribute.
239: * @return the refid attribute
240: */
241: public Reference getRefid() {
242: return ref;
243: }
244:
245: /**
246: * The resource name of a property file to load
247: * @param resource resource on classpath
248: *
249: * @ant.attribute group="noname"
250: */
251: public void setResource(String resource) {
252: this .resource = resource;
253: }
254:
255: /**
256: * Get the resource attribute.
257: * @return the resource attribute
258: */
259: public String getResource() {
260: return resource;
261: }
262:
263: /**
264: * Prefix to use when retrieving environment variables.
265: * Thus if you specify environment="myenv"
266: * you will be able to access OS-specific
267: * environment variables via property names "myenv.PATH" or
268: * "myenv.TERM".
269: * <p>
270: * Note that if you supply a property name with a final
271: * "." it will not be doubled. ie environment="myenv." will still
272: * allow access of environment variables through "myenv.PATH" and
273: * "myenv.TERM". This functionality is currently only implemented
274: * on select platforms. Feel free to send patches to increase the number of platforms
275: * this functionality is supported on ;).<br>
276: * Note also that properties are case sensitive, even if the
277: * environment variables on your operating system are not, e.g. it
278: * will be ${env.Path} not ${env.PATH} on Windows 2000.
279: * @param env prefix
280: *
281: * @ant.attribute group="noname"
282: */
283: public void setEnvironment(String env) {
284: this .env = env;
285: }
286:
287: /**
288: * Get the environment attribute.
289: * @return the environment attribute
290: * @since Ant 1.5
291: */
292: public String getEnvironment() {
293: return env;
294: }
295:
296: /**
297: * The classpath to use when looking up a resource.
298: * @param classpath to add to any existing classpath
299: */
300: public void setClasspath(Path classpath) {
301: if (this .classpath == null) {
302: this .classpath = classpath;
303: } else {
304: this .classpath.append(classpath);
305: }
306: }
307:
308: /**
309: * The classpath to use when looking up a resource.
310: * @return a path to be configured
311: */
312: public Path createClasspath() {
313: if (this .classpath == null) {
314: this .classpath = new Path(getProject());
315: }
316: return this .classpath.createPath();
317: }
318:
319: /**
320: * the classpath to use when looking up a resource,
321: * given as reference to a <path> defined elsewhere
322: * @param r a reference to a classpath
323: */
324: public void setClasspathRef(Reference r) {
325: createClasspath().setRefid(r);
326: }
327:
328: /**
329: * Get the classpath used when looking up a resource.
330: * @return the classpath
331: * @since Ant 1.5
332: */
333: public Path getClasspath() {
334: return classpath;
335: }
336:
337: /**
338: * @param userProperty ignored
339: * @deprecated since 1.5.x.
340: * This was never a supported feature and has been
341: * deprecated without replacement.
342: * @ant.attribute ignore="true"
343: */
344: public void setUserProperty(boolean userProperty) {
345: log(
346: "DEPRECATED: Ignoring request to set user property in Property"
347: + " task.", Project.MSG_WARN);
348: }
349:
350: /**
351: * get the value of this property
352: * @return the current value or the empty string
353: */
354: public String toString() {
355: return value == null ? "" : value;
356: }
357:
358: /**
359: * set the property in the project to the value.
360: * if the task was give a file, resource or env attribute
361: * here is where it is loaded
362: * @throws BuildException on error
363: */
364: public void execute() throws BuildException {
365: if (getProject() == null) {
366: throw new IllegalStateException("project has not been set");
367: }
368:
369: if (name != null) {
370: if (value == null && ref == null) {
371: throw new BuildException(
372: "You must specify value, location or "
373: + "refid with the name attribute",
374: getLocation());
375: }
376: } else {
377: if (url == null && file == null && resource == null
378: && env == null) {
379: throw new BuildException(
380: "You must specify url, file, resource or "
381: + "environment when not using the "
382: + "name attribute", getLocation());
383: }
384: }
385:
386: if (url == null && file == null && resource == null
387: && prefix != null) {
388: throw new BuildException(
389: "Prefix is only valid when loading from "
390: + "a url, file or resource", getLocation());
391: }
392:
393: if ((name != null) && (value != null)) {
394: addProperty(name, value);
395: }
396:
397: if (file != null) {
398: loadFile(file);
399: }
400:
401: if (url != null) {
402: loadUrl(url);
403: }
404:
405: if (resource != null) {
406: loadResource(resource);
407: }
408:
409: if (env != null) {
410: loadEnvironment(env);
411: }
412:
413: if ((name != null) && (ref != null)) {
414: try {
415: addProperty(name, ref.getReferencedObject(getProject())
416: .toString());
417: } catch (BuildException be) {
418: if (fallback != null) {
419: addProperty(name, ref.getReferencedObject(fallback)
420: .toString());
421: } else {
422: throw be;
423: }
424: }
425: }
426: }
427:
428: /**
429: * load properties from a url
430: * @param url url to load from
431: * @throws BuildException on error
432: */
433: protected void loadUrl(URL url) throws BuildException {
434: Properties props = new Properties();
435: log("Loading " + url, Project.MSG_VERBOSE);
436: try {
437: InputStream is = url.openStream();
438: try {
439: props.load(is);
440: } finally {
441: if (is != null) {
442: is.close();
443: }
444: }
445: addProperties(props);
446: } catch (IOException ex) {
447: throw new BuildException(ex, getLocation());
448: }
449: }
450:
451: /**
452: * load properties from a file
453: * @param file file to load
454: * @throws BuildException on error
455: */
456: protected void loadFile(File file) throws BuildException {
457: Properties props = new Properties();
458: log("Loading " + file.getAbsolutePath(), Project.MSG_VERBOSE);
459: try {
460: if (file.exists()) {
461: FileInputStream fis = new FileInputStream(file);
462: try {
463: props.load(fis);
464: } finally {
465: if (fis != null) {
466: fis.close();
467: }
468: }
469: addProperties(props);
470: } else {
471: log("Unable to find property file: "
472: + file.getAbsolutePath(), Project.MSG_VERBOSE);
473: }
474: } catch (IOException ex) {
475: throw new BuildException(ex, getLocation());
476: }
477: }
478:
479: /**
480: * load properties from a resource in the current classpath
481: * @param name name of resource to load
482: */
483: protected void loadResource(String name) {
484: Properties props = new Properties();
485: log("Resource Loading " + name, Project.MSG_VERBOSE);
486: InputStream is = null;
487: try {
488: ClassLoader cL = null;
489:
490: if (classpath != null) {
491: cL = getProject().createClassLoader(classpath);
492: } else {
493: cL = this .getClass().getClassLoader();
494: }
495:
496: if (cL == null) {
497: is = ClassLoader.getSystemResourceAsStream(name);
498: } else {
499: is = cL.getResourceAsStream(name);
500: }
501:
502: if (is != null) {
503: props.load(is);
504: addProperties(props);
505: } else {
506: log("Unable to find resource " + name, Project.MSG_WARN);
507: }
508: } catch (IOException ex) {
509: throw new BuildException(ex, getLocation());
510: } finally {
511: if (is != null) {
512: try {
513: is.close();
514: } catch (IOException e) {
515: // ignore
516: }
517: }
518: }
519:
520: }
521:
522: /**
523: * load the environment values
524: * @param prefix prefix to place before them
525: */
526: protected void loadEnvironment(String prefix) {
527: Properties props = new Properties();
528: if (!prefix.endsWith(".")) {
529: prefix += ".";
530: }
531: log("Loading Environment " + prefix, Project.MSG_VERBOSE);
532: Vector osEnv = Execute.getProcEnvironment();
533: for (Enumeration e = osEnv.elements(); e.hasMoreElements();) {
534: String entry = (String) e.nextElement();
535: int pos = entry.indexOf('=');
536: if (pos == -1) {
537: log("Ignoring: " + entry, Project.MSG_WARN);
538: } else {
539: props.put(prefix + entry.substring(0, pos), entry
540: .substring(pos + 1));
541: }
542: }
543: addProperties(props);
544: }
545:
546: /**
547: * iterate through a set of properties,
548: * resolve them then assign them
549: * @param props the properties to iterate over
550: */
551: protected void addProperties(Properties props) {
552: resolveAllProperties(props);
553: Enumeration e = props.keys();
554: while (e.hasMoreElements()) {
555: String propertyName = (String) e.nextElement();
556: String propertyValue = props.getProperty(propertyName);
557:
558: String v = getProject().replaceProperties(propertyValue);
559:
560: if (prefix != null) {
561: propertyName = prefix + propertyName;
562: }
563:
564: addProperty(propertyName, v);
565: }
566: }
567:
568: /**
569: * add a name value pair to the project property set
570: * @param n name of property
571: * @param v value to set
572: */
573: protected void addProperty(String n, String v) {
574: if (userProperty) {
575: if (getProject().getUserProperty(n) == null) {
576: getProject().setInheritedProperty(n, v);
577: } else {
578: log("Override ignored for " + n, Project.MSG_VERBOSE);
579: }
580: } else {
581: getProject().setNewProperty(n, v);
582: }
583: }
584:
585: /**
586: * resolve properties inside a properties hashtable
587: * @param props properties object to resolve
588: */
589: private void resolveAllProperties(Properties props)
590: throws BuildException {
591: for (Enumeration e = props.keys(); e.hasMoreElements();) {
592: String propertyName = (String) e.nextElement();
593: Stack referencesSeen = new Stack();
594: resolve(props, propertyName, referencesSeen);
595: }
596: }
597:
598: /**
599: * Recursively expand the named property using the project's
600: * reference table and the given set of properties - fail if a
601: * circular definition is detected.
602: *
603: * @param props properties object to resolve
604: * @param name of the property to resolve
605: * @param referencesSeen stack of all property names that have
606: * been tried to expand before coming here.
607: */
608: private void resolve(Properties props, String name,
609: Stack referencesSeen) throws BuildException {
610: if (referencesSeen.contains(name)) {
611: throw new BuildException("Property " + name
612: + " was circularly " + "defined.");
613: }
614:
615: String propertyValue = props.getProperty(name);
616: Vector fragments = new Vector();
617: Vector propertyRefs = new Vector();
618: PropertyHelper.getPropertyHelper(this .getProject())
619: .parsePropertyString(propertyValue, fragments,
620: propertyRefs);
621:
622: if (propertyRefs.size() != 0) {
623: referencesSeen.push(name);
624: StringBuffer sb = new StringBuffer();
625: Enumeration i = fragments.elements();
626: Enumeration j = propertyRefs.elements();
627: while (i.hasMoreElements()) {
628: String fragment = (String) i.nextElement();
629: if (fragment == null) {
630: String propertyName = (String) j.nextElement();
631: fragment = getProject().getProperty(propertyName);
632: if (fragment == null) {
633: if (props.containsKey(propertyName)) {
634: resolve(props, propertyName, referencesSeen);
635: fragment = props.getProperty(propertyName);
636: } else {
637: fragment = "${" + propertyName + "}";
638: }
639: }
640: }
641: sb.append(fragment);
642: }
643: propertyValue = sb.toString();
644: props.put(name, propertyValue);
645: referencesSeen.pop();
646: }
647: }
648: }
|