View Javadoc

1   // Copyright (C) 2000 Paco Gomez
2   // Copyright (C) 2000 - 2012 Philip Aston
3   // All rights reserved.
4   //
5   // This file is part of The Grinder software distribution. Refer to
6   // the file LICENSE which is part of The Grinder distribution for
7   // licensing details. The Grinder distribution is available on the
8   // Internet at http://grinder.sourceforge.net/
9   //
10  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
11  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
12  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
13  // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
14  // COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
15  // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
16  // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
17  // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18  // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
19  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
20  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
21  // OF THE POSSIBILITY OF SUCH DAMAGE.
22  
23  package net.grinder.common;
24  
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.InputStream;
29  import java.io.IOException;
30  import java.io.OutputStream;
31  import java.io.PrintWriter;
32  import java.util.Enumeration;
33  import java.util.Properties;
34  
35  
36  
37  /**
38   * Extend {@link java.util.Properties} to add type safe accessors.
39   * Has an optional associated file.
40   *
41   * @author Philip Aston
42   * @see net.grinder.script.Grinder.ScriptContext#getProperties
43   */
44  public class GrinderProperties extends Properties {
45    private static final long serialVersionUID = 1;
46  
47    /**
48     * Key to use for the script property.
49     */
50    public static final String SCRIPT = "grinder.script";
51  
52    /**
53     * Key to use for the log directory property.
54     */
55    public static final String LOG_DIRECTORY = "grinder.logDirectory";
56  
57    /**
58     * Key to use for the console host property.
59     */
60    public static final String CONSOLE_HOST = "grinder.consoleHost";
61  
62    /**
63     * Key to use for the console host property.
64     */
65    public static final String CONSOLE_PORT = "grinder.consolePort";
66  
67    /**
68     * Default file name for properties.
69     */
70    public static final File DEFAULT_PROPERTIES = new File("grinder.properties");
71  
72    /**
73     * Default script file name.
74     */
75    public static final File DEFAULT_SCRIPT = new File("grinder.py");
76  
77    private transient PrintWriter m_errorWriter =
78      new PrintWriter(System.err, true);
79  
80    /** @serial Associated file. */
81    private File m_file;
82  
83    /**
84     * Construct an empty GrinderProperties with no associated file.
85     */
86    public GrinderProperties() {
87      m_file = null;
88    }
89  
90    /**
91     * Construct a GrinderProperties, reading initial values from the
92     * specified file. System properties beginning with
93     * "<code>grinder.</code>"are also added to allow values to be
94     * overridden on the command line.
95     * @param file The file to read the properties from.
96     *
97     * @exception PersistenceException If an error occurs reading from
98     * the file.
99     * @see #DEFAULT_PROPERTIES
100    */
101   public GrinderProperties(File file) throws PersistenceException {
102     setAssociatedFile(file);
103 
104     if (m_file.exists()) {
105       InputStream propertiesInputStream = null;
106       try {
107         propertiesInputStream = new FileInputStream(m_file);
108         load(propertiesInputStream);
109       }
110       catch (IOException e) {
111         UncheckedInterruptedException.ioException(e);
112         throw new PersistenceException(
113           "Error loading properties file '" + m_file.getPath() + '\'', e);
114       }
115       finally {
116         Closer.close(propertiesInputStream);
117       }
118     }
119 
120     final Enumeration<?> systemProperties =
121       System.getProperties().propertyNames();
122 
123     while (systemProperties.hasMoreElements()) {
124       final String name = (String)systemProperties.nextElement();
125 
126       if (name.startsWith("grinder.")) {
127         put(name, System.getProperty(name));
128       }
129     }
130   }
131 
132   /**
133    * Get the associated file.
134    *
135    * @return The associated file, or <code>null</code> if none.
136    */
137   public final File getAssociatedFile() {
138     return m_file;
139   }
140 
141   /**
142    * Set the associated file. Does not cause the properties to be re-read from
143    * the new file.
144    *
145    * @param file
146    *            The associated file, or <code>null</code> if none.
147    */
148   public final void setAssociatedFile(File file) {
149     m_file = file;
150   }
151 
152   /**
153    * Save our properties to our file.
154    *
155    * @exception PersistenceException If there is no file associated
156    * with this {@link GrinderProperties} or an I/O exception occurs..
157    */
158   public final void save() throws PersistenceException {
159 
160     if (m_file == null) {
161       throw new PersistenceException("No associated file");
162     }
163 
164     OutputStream outputStream = null;
165 
166     try {
167       outputStream = new FileOutputStream(m_file);
168       store(outputStream, generateFileHeader());
169     }
170     catch (IOException e) {
171       UncheckedInterruptedException.ioException(e);
172       throw new PersistenceException(
173         "Error writing properties file '" + m_file.getPath() + '\'', e);
174     }
175     finally {
176       Closer.close(outputStream);
177     }
178   }
179 
180   /**
181    * Save a single property to our file.
182    *
183    * @param name Property name.
184    * @exception PersistenceException If there is no file associated with this
185    * {@link GrinderProperties} or an I/O exception occurs.
186    */
187   public final void saveSingleProperty(String name)
188     throws PersistenceException {
189 
190     if (m_file == null) {
191       throw new PersistenceException("No associated file");
192     }
193 
194     try {
195       final Properties properties = new Properties();
196 
197       InputStream inputStream = null;
198 
199       try {
200         inputStream = new FileInputStream(m_file);
201         properties.load(inputStream);
202       }
203       catch (IOException e) {
204         // Can't read the file, maybe its not there. Ignore.
205         UncheckedInterruptedException.ioException(e);
206       }
207       finally {
208         Closer.close(inputStream);
209       }
210 
211       OutputStream outputStream = null;
212 
213       try {
214         outputStream = new FileOutputStream(m_file);
215 
216         final String value = getProperty(name);
217 
218         if (value != null) {
219           properties.setProperty(name, value);
220         }
221         else {
222           properties.remove(name);
223         }
224 
225         properties.store(outputStream, generateFileHeader());
226       }
227       finally {
228         Closer.close(outputStream);
229       }
230     }
231     catch (IOException e) {
232       UncheckedInterruptedException.ioException(e);
233       throw new PersistenceException(
234         "Error writing properties file '" + m_file.getPath() + '\'', e);
235     }
236   }
237 
238   private String generateFileHeader() {
239     return "Properties file for The Grinder";
240   }
241 
242   /**
243    * Set a writer to report warnings to.
244    *
245    * @param writer The writer.
246    */
247   public final void setErrorWriter(PrintWriter writer) {
248     m_errorWriter = writer;
249   }
250 
251   /**
252    * Return a new GrinderProperties that contains the subset of our
253    * Properties which begin with the specified prefix.
254    * @param prefix The prefix.
255    *
256    * @return The subset.
257    */
258   public final synchronized GrinderProperties
259     getPropertySubset(String prefix) {
260     final GrinderProperties result = new GrinderProperties();
261 
262     final Enumeration<?> propertyNames = propertyNames();
263 
264     while (propertyNames.hasMoreElements()) {
265       final String name = (String)propertyNames.nextElement();
266 
267       if (name.startsWith(prefix)) {
268         result.setProperty(name.substring(prefix.length()),
269                            getProperty(name));
270       }
271     }
272 
273     return result;
274   }
275 
276   /**
277    * Get the value of the property with the given name, return the
278    * value as an <code>int</code>.
279    * @param propertyName The property name.
280    * @param defaultValue The value to return if a property with the
281    * given name does not exist or is not an integer.
282    *
283    * @return The value.
284    */
285   public final int getInt(String propertyName, int defaultValue) {
286     final String s = getProperty(propertyName);
287 
288     if (s != null) {
289       try {
290         return Integer.parseInt(s.trim());
291       }
292       catch (NumberFormatException e) {
293         m_errorWriter.println("Warning, property '" + propertyName +
294                               "' does not specify an integer value");
295       }
296     }
297 
298     return defaultValue;
299   }
300 
301   /**
302    * Set the property with the given name to an <code>int</code>
303    * value.
304    * @param propertyName The property name.
305    * @param value The value to set.
306    */
307   public final void setInt(String propertyName, int value) {
308     setProperty(propertyName, Integer.toString(value));
309   }
310 
311 
312   /**
313    * Get the value of the property with the given name, return the
314    * value as a <code>long</code>.
315    * @param propertyName The property name.
316    * @param defaultValue The value to return if a property with the
317    * given name does not exist or is not a long.
318    *
319    * @return The value.
320    */
321   public final long getLong(String propertyName, long defaultValue) {
322     final String s = getProperty(propertyName);
323 
324     if (s != null) {
325       try {
326         return Long.parseLong(s.trim());
327       }
328       catch (NumberFormatException e) {
329         m_errorWriter.println("Warning, property '" + propertyName +
330                               "' does not specify an integer value");
331       }
332     }
333 
334     return defaultValue;
335   }
336 
337   /**
338    * Set the property with the given name to a <code>long</code>
339    * value.
340    * @param propertyName The property name.
341    * @param value The value to set.
342    */
343   public final void setLong(String propertyName, long value) {
344     setProperty(propertyName, Long.toString(value));
345   }
346 
347   /**
348    * Get the value of the property with the given name, return the
349    * value as a <code>short</code>.
350    * @param propertyName The property name.
351    * @param defaultValue The value to return if a property with the
352    * given name does not exist or is not a short.
353    *
354    * @return The value.
355    */
356   public final short getShort(String propertyName, short defaultValue) {
357     final String s = getProperty(propertyName);
358 
359     if (s != null) {
360       try {
361         return Short.parseShort(s.trim());
362       }
363       catch (NumberFormatException e) {
364         m_errorWriter.println("Warning, property '" + propertyName +
365                               "' does not specify a short value");
366       }
367     }
368 
369     return defaultValue;
370   }
371 
372   /**
373    * Set the property with the given name to a <code>short</code>
374    * value.
375    * @param propertyName The property name.
376    * @param value The value to set.
377    */
378   public final void setShort(String propertyName, short value) {
379     setProperty(propertyName, Short.toString(value));
380   }
381 
382   /**
383    * Get the value of the property with the given name, return the
384    * value as a <code>double</code>.
385    * @param propertyName The property name.
386    * @param defaultValue The value to return if a property with the
387    * given name does not exist or is not a double.
388    *
389    * @return The value.
390    */
391   public final double getDouble(String propertyName, double defaultValue) {
392     final String s = getProperty(propertyName);
393 
394     if (s != null) {
395       try {
396         return Double.parseDouble(s.trim());
397       }
398       catch (NumberFormatException e) {
399         m_errorWriter.println("Warning, property '" + propertyName +
400                               "' does not specify a double value");
401       }
402     }
403 
404     return defaultValue;
405   }
406 
407   /**
408    * Set the property with the given name to a <code>double</code>
409    * value.
410    * @param propertyName The property name.
411    * @param value The value to set.
412    */
413   public final void setDouble(String propertyName, double value) {
414     setProperty(propertyName, Double.toString(value));
415   }
416 
417   /**
418    * Get the value of the property with the given name, return the
419    * value as a <code>boolean</code>.
420    * @param propertyName The property name.
421    * @param defaultValue The value to return if a property with the
422    * given name does not exist.
423    *
424    * @return The value.
425    */
426   public final boolean getBoolean(String propertyName, boolean defaultValue) {
427     final String s = getProperty(propertyName);
428 
429     if (s != null) {
430       return Boolean.valueOf(s).booleanValue();
431     }
432 
433     return defaultValue;
434   }
435 
436   /**
437    * Set the property with the given name to a <code>boolean</code>
438    * value.
439    * @param propertyName The property name.
440    * @param value The value to set.
441    */
442   public final void setBoolean(String propertyName, boolean value) {
443     setProperty(propertyName, String.valueOf(value));
444   }
445 
446   /**
447    * Get the value of the property with the given name, return the value as a
448    * <code>File</code>.
449    *
450    * @param propertyName
451    *            The property name.
452    * @param defaultValue
453    *            The value to return if a property with the given name does not
454    *            exist.
455    * @return The value.
456    */
457   public final File getFile(String propertyName, File defaultValue) {
458     final String s = getProperty(propertyName);
459 
460     if (s != null) {
461       return new File(s);
462     }
463 
464     return defaultValue;
465   }
466 
467   /**
468    * Returns a {@link File} representing the combined path of our associated
469    * property file directory and the passed <code>file</code>.
470    *
471    * <p>
472    * If <code>file</code> is absolute, or this <code>GrinderProperties</code>
473    * has no associated property file, return <code>file</code>.
474    * </p>
475    *
476    * @param file
477    *            The file.
478    * @return If possible, <code>file</code> resolved relative to the directory
479    *         of our associated properties file, or <code>file</code>.
480    */
481   public final File resolveRelativeFile(File file) {
482 
483     if (m_file != null && file != null && !file.isAbsolute()) {
484       return new File(m_file.getParentFile(), file.getPath());
485     }
486 
487     return file;
488   }
489 
490   /**
491    * Set the property with the given name to a <code>File</code>
492    * value.
493    * @param propertyName The property name.
494    * @param value The value to set.
495    */
496   public final void setFile(String propertyName, File value) {
497     setProperty(propertyName, value.getPath());
498   }
499 
500   /**
501    * Exception indicating a problem in persisting properties.
502    */
503   public static final class PersistenceException extends GrinderException {
504     private PersistenceException(String message) {
505       super(message);
506     }
507 
508     private PersistenceException(String message, Throwable t) {
509       super(message, t);
510     }
511   }
512 
513   /**
514    * Override to restore error writer to <code>System.err</code>.
515    */
516   private void readObject(java.io.ObjectInputStream in)
517     throws IOException, ClassNotFoundException {
518     in.defaultReadObject();
519     setErrorWriter(new PrintWriter(System.err, true));
520   }
521 }