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