KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > Ostermiller > util > UberProperties


1 /*
2  * A souped up version of the Java Properties format which can
3  * handle multiple properties with the same name.
4  * Copyright (C) 2002-2003 Stephen Ostermiller
5  * http://ostermiller.org/contact.pl?regarding=Java+Utilities
6  *
7  * Copyright (C) 2003 Carlo Magnaghi <software at tecnosoft dot net>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * See COPYING.TXT for details.
20  */

21 package com.Ostermiller.util;
22
23 import java.io.IOException JavaDoc;
24 import java.io.InputStream JavaDoc;
25 import java.io.FileInputStream JavaDoc;
26 import java.io.InputStreamReader JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.io.FileOutputStream JavaDoc;
29 import java.io.FileNotFoundException JavaDoc;
30 import java.io.File JavaDoc;
31 import java.util.ArrayList JavaDoc;
32 import java.util.HashMap JavaDoc;
33 import java.util.HashSet JavaDoc;
34 import java.util.Set JavaDoc;
35 import java.util.Arrays JavaDoc;
36 import java.io.ByteArrayOutputStream JavaDoc;
37 import java.io.UnsupportedEncodingException JavaDoc;
38
39 /**
40  * The Properties class represents a persistent set of properties. The
41  * Properties can be saved to a stream or loaded from a stream. Each key and
42  * its corresponding value in the property list is a string.
43  * More information about this class is available from <a target="_top" HREF=
44  * "http://ostermiller.org/utils/UberProperties.html">ostermiller.org</a>.
45  * <p>
46  * A property list can contain another property list as its "defaults"; this
47  * second property list is searched if the property key is not found in the
48  * original property list.
49  * <p>
50  * When saving properties to a stream or loading them from a stream, the ISO
51  * 8859-1 character encoding is used. For characters that cannot be directly
52  * represented in this encoding, Unicode escapes are used; however, only a
53  * single 'u' character is allowed in an escape sequence. The native2ascii tool
54  * can be used to convert property files to and from other character encodings.
55  * <p>
56  * Unlike the java.util.Properties, UberProperties does not inherit from
57  * java.util.Hashtable, so Objects other than strings cannot be stored in it.
58  * Also, comments from a files are preserved, and there can be several
59  * properties for a given name.
60  * <p>
61  * This class is not synchronized, so it should not be used in a
62  * multi-threaded environment without external synchronization.
63  * <p>
64  * The file format that UberProperties uses is as follows:
65  * <blockquote>
66  * The file is assumed to be using the ISO 8859-1 character encoding. All of the
67  * comment lines (starting with a '#' or '!') at the beginning of the file before the
68  * first line that is not a comment, are the comment associated with the file.
69  * After that, each comment will be associated with the next property. If there
70  * is more than one property with the same name, the first comment will be the
71  * only one that is loaded.
72  * <p>
73  * Every property occupies one line of the input stream. Each line is terminated
74  * by a line terminator (\n or \r or \r\n).
75  * <p>
76  * A line that contains only whitespace or whose first non-whitespace character
77  * is an ASCII # or ! is ignored (thus, # or ! indicate comment lines).
78  * <p>
79  * Every line other than a blank line or a comment line describes one property
80  * to be added to the table (except that if a line ends with \, then the
81  * following line, if it exists, is treated as a continuation line,
82  * as described below). The key consists of all the characters in the line
83  * starting with the first non-whitespace character and up to, but not
84  * including, the first ASCII =, :, or whitespace character. All of the key
85  * termination characters may be included in the key by preceding them with a \.
86  * Any whitespace after the key is skipped; if the first non-whitespace
87  * character after the key is = or :, then it is ignored and any whitespace
88  * characters after it are also skipped. All remaining characters on the line
89  * become part of the associated element string. Within the element string, the
90  * ASCII escape sequences \t, \n, \r, \\, \", \', \ (a backslash and a space),
91  * and \\uxxxx are recognized and converted to single characters. Moreover, if
92  * the last character on the line is \, then the next line is treated as a
93  * continuation of the current line; the \ and line terminator are simply
94  * discarded, and any leading whitespace characters on the continuation line are
95  * also discarded and are not part of the element string.
96  * <p>
97  * As an example, each of the following four lines specifies the key "Truth"
98  * and the associated element value "Beauty":<br>
99  * <pre>Truth = Beauty
100  * Truth:Beauty
101  * Truth :Beauty</pre>
102  * <p>
103  * As another example, the following three lines specify a single property:<br>
104  * <pre>fruits apple, banana, pear, \
105  * cantaloupe, watermelon, \
106  * kiwi, mango</pre>
107  * <p>
108  * The key is "fruits" and the associated element is:<br>
109  * "apple,&nbsp;banana,&nbsp;pear,&nbsp;cantaloupe,&nbsp;watermelon,&nbsp;kiwi,&nbsp;mango"<br>
110  * Note that a space appears before each \ so that a space will appear after
111  * each comma in the final result; the \, line terminator, and leading
112  * whitespace on the continuation line are merely discarded and are not replaced
113  * by one or more other characters.
114  * <p>
115  * As a third example, the line:<br>
116  * cheeses<br>
117  * specifies that the key is "cheeses" and the associated element is the empty
118  * string.
119  * </blockquote>
120  *
121  * @author Stephen Ostermiller http://ostermiller.org/contact.pl?regarding=Java+Utilities
122  * @since ostermillerutils 1.00.00
123  */

124 public class UberProperties {
125
126     /**
127      * A hash map that contains all the properties.
128      * This should never be null, but may be empty.
129      * This should hold objects of type Property.
130      *
131      * @since ostermillerutils 1.00.00
132      */

133     private HashMap JavaDoc<String JavaDoc,Property> properties = new HashMap JavaDoc<String JavaDoc,Property>();
134
135     /**
136      * Comment for this set of properties.
137      * This may be either null or empty.
138      *
139      * @since ostermillerutils 1.00.00
140      */

141     private String JavaDoc comment = null;
142
143     /**
144      * The object type that goes in the HashMap.
145      *
146      * @since ostermillerutils 1.00.00
147      */

148     private class Property {
149
150         /**
151          * List of values for this property.
152          * This should never be null or empty.
153          *
154          * @since ostermillerutils 1.00.00
155          */

156         private ArrayList JavaDoc<String JavaDoc> list;
157
158         /**
159          * Comment for this set of properties.
160          * This may be either null or empty.
161          *
162          * @since ostermillerutils 1.00.00
163          */

164         private String JavaDoc comment = null;
165
166         /**
167          * Set the comment associated with this property.
168          *
169          * @param comment the comment for this property, or null to clear.
170          *
171          * @since ostermillerutils 1.00.00
172          */

173         public void setComment(String JavaDoc comment){
174             this.comment = comment;
175         }
176
177         /**
178          * Get the comment associated with this property.
179          *
180          * @return comment for this property, or null if none is set.
181          *
182          * @since ostermillerutils 1.00.00
183          */

184         public String JavaDoc getComment(){
185             return this.comment;
186         }
187
188         /**
189          * Construct a new property with the given value.
190          *
191          * @param value initial value for this property.
192          *
193          * @since ostermillerutils 1.00.00
194          */

195         public Property(String JavaDoc value){
196             list = new ArrayList JavaDoc<String JavaDoc>(1);
197             add(value);
198         }
199
200         /**
201          * Construct a new property with the given values.
202          *
203          * @param values initial values for this property.
204          *
205          * @since ostermillerutils 1.00.00
206          */

207         public Property(String JavaDoc[] values){
208             list = new ArrayList JavaDoc<String JavaDoc>(values.length);
209             add(values);
210         }
211
212         /**
213          * Set this property to have this single value.
214          *
215          * @param value lone value for this property.
216          *
217          * @since ostermillerutils 1.00.00
218          */

219         public void set(String JavaDoc value){
220             list.clear();
221             add(value);
222         }
223
224         /**
225          * Set this property to have only these values.
226          *
227          * @param values lone values for this property.
228          *
229          * @since ostermillerutils 1.00.00
230          */

231         public void set(String JavaDoc[] values){
232             list.clear();
233             add(values);
234         }
235
236         /**
237          * Add this value to the list of values for this property.
238          *
239          * @param value another value for this property.
240          *
241          * @since ostermillerutils 1.00.00
242          */

243         public void add(String JavaDoc value){
244             list.add(value);
245         }
246
247         /**
248          * Add these values to the list of values for this property.
249          *
250          * @param values other values for this property.
251          *
252          * @since ostermillerutils 1.00.00
253          */

254         public void add(String JavaDoc[] values){
255             list.ensureCapacity(list.size() + values.length);
256             for (int i=0; i<values.length; i++){
257                 add(values[i]);
258             }
259         }
260
261         /**
262          * Get the last value for this property.
263          *
264          * @return the last value.
265          *
266          * @since ostermillerutils 1.00.00
267          */

268         public String JavaDoc getValue(){
269             return (String JavaDoc)list.get(list.size() - 1);
270         }
271
272         /**
273          * Get all the values for this property.
274          *
275          * @return a list of all the values.
276          *
277          * @since ostermillerutils 1.00.00
278          */

279         public String JavaDoc[] getValues(){
280             return (String JavaDoc[])list.toArray(new String JavaDoc[list.size()]);
281         }
282     }
283
284     /**
285      * Creates an empty property list with no default values.
286      *
287      * @since ostermillerutils 1.00.00
288      */

289     public UberProperties(){
290     }
291
292     /**
293      * Creates an empty property list with the specified defaults.
294      *
295      * @param defaults the defaults.
296      * @throws NullPointerException if defaults is null.
297      *
298      * @since ostermillerutils 1.00.00
299      */

300     public UberProperties(UberProperties defaults){
301         merge(defaults);
302     }
303
304     /**
305      * Put all the properties from the defaults in this.
306      * Calling this from a constructor will clone (deep)
307      * the default properties.
308      *
309      * @since ostermillerutils 1.00.00
310      */

311     private void merge(UberProperties defaults){
312         setComment(defaults.getComment());
313         String JavaDoc[] names = defaults.propertyNames();
314         for (int i=0; i<names.length; i++){
315             setProperties(names[i], defaults.getProperties(names[i]));
316             setComment(names[i], defaults.getComment(names[i]));
317         }
318     }
319
320     /**
321      * Test to see if a property with the given name exists.
322      *
323      * @param name the name of the property.
324      * @return true if the property existed and was removed, false if it did not exist.
325      * @throws NullPointerException if name is null.
326      *
327      * @since ostermillerutils 1.00.00
328      */

329     public boolean contains(String JavaDoc name){
330         if (name == null) throw new NullPointerException JavaDoc();
331         return properties.containsKey(name);
332     }
333
334     /**
335      * Remove any property with the given name.
336      *
337      * @param name the name of the property.
338      * @return true if the property existed and was removed, false if it did not exist.
339      * @throws NullPointerException if name is null.
340      *
341      * @since ostermillerutils 1.00.00
342      */

343     public boolean remove(String JavaDoc name){
344         if (!contains(name)) return false;
345         properties.remove(name);
346         return true;
347     }
348
349     /**
350      * Replaces all properties of the given name with
351      * a single property with the given value.
352      *
353      * @param name the name of the property.
354      * @param value the value of the property, or null to remove it.
355      * @throws NullPointerException if name is null.
356      *
357      * @since ostermillerutils 1.00.00
358      */

359     public void setProperty(String JavaDoc name, String JavaDoc value){
360         if (name == null) throw new NullPointerException JavaDoc();
361         if (value == null){
362             properties.remove(name);
363         } else {
364             Property property;
365             if (properties.containsKey(name)){
366                 property = (Property)properties.get(name);
367                 property.set(value);
368             } else {
369                 property = new Property(value);
370                 properties.put(name, property);
371             }
372         }
373     }
374
375     /**
376      * Replaces all properties of the given name with
377      * properties with the given values.
378      *
379      * @param name the name of the property.
380      * @param values for the property.
381      * @throws NullPointerException if name is null.
382      * @throws NullPointerException if values is null.
383      * @throws IllegalArgumentException if values is empty.
384      *
385      * @since ostermillerutils 1.00.00
386      */

387     public void setProperties(String JavaDoc name, String JavaDoc[] values){
388         if (name == null) throw new NullPointerException JavaDoc();
389         if (values.length == 0) throw new IllegalArgumentException JavaDoc();
390         Property property;
391         if (properties.containsKey(name)){
392             property = (Property)properties.get(name);
393             property.set(values);
394         } else {
395             property = new Property(values);
396             properties.put(name, property);
397         }
398     }
399
400     /**
401      * Replaces all properties of the given name with
402      * a single property with the given value.
403      *
404      * @param name the name of the property.
405      * @param value the value of the property or null to remove it.
406      * @param comment the comment for the property, or null to remove it.
407      * @throws NullPointerException if name is null.
408      * @throws NullPointerException if comment is null.
409      *
410      * @since ostermillerutils 1.00.00
411      */

412     public void setProperty(String JavaDoc name, String JavaDoc value, String JavaDoc comment){
413         if (name == null) throw new NullPointerException JavaDoc();
414         if (value == null){
415             properties.remove(name);
416         } else {
417             setProperty(name, value);
418             setComment(name, comment);
419         }
420     }
421
422     /**
423      * Replaces all properties of the given name with
424      * properties with the given values.
425      *
426      * @param name the name of the property.
427      * @param values value of the property.
428      * @param comment the comment for the property, or null to remove it.
429      * @throws NullPointerException if name is null.
430      * @throws NullPointerException if values is null.
431      * @throws IllegalArgumentException if values is empty.
432      *
433      * @since ostermillerutils 1.00.00
434      */

435     public void setProperties(String JavaDoc name, String JavaDoc[] values, String JavaDoc comment){
436         if (name == null) throw new NullPointerException JavaDoc();
437         if (values.length == 0) throw new IllegalArgumentException JavaDoc();
438         setProperties(name, values);
439         setComment(name, comment);
440     }
441
442     /**
443      * Set the comment on the property of the given name.
444      * The property must exist before this method is called.
445      *
446      * @param name the name of the property.
447      * @param comment the comment for the property.
448      * @param comment the comment for the property, or null to remove it.
449      * @throws NullPointerException if name is null.
450      * @throws IllegalArgumentException if name is not a known key.
451      *
452      * @since ostermillerutils 1.00.00
453      */

454     private void setComment(String JavaDoc name, String JavaDoc comment){
455         if (name == null) throw new NullPointerException JavaDoc();
456         if (!properties.containsKey(name)) throw new IllegalArgumentException JavaDoc();
457         ((Property)properties.get(name)).setComment(comment);
458     }
459
460     /**
461      * Adds a value to the list of properties with the
462      * given name.
463      *
464      * @param name the name of the property.
465      * @param value the values for the property, or null to remove.
466      * @param comment the comment for the property, or null to remove it.
467      * @throws NullPointerException if name is null.
468      * @throws NullPointerException if value is null.
469      *
470      * @since ostermillerutils 1.00.00
471      */

472     public void addProperty(String JavaDoc name, String JavaDoc value, String JavaDoc comment){
473         if (name == null) throw new NullPointerException JavaDoc();
474         if (value == null) throw new NullPointerException JavaDoc();
475         addProperty(name, value);
476         setComment(name, comment);
477     }
478
479     /**
480      * Adds the values to the list of properties with the
481      * given name.
482      *
483      * @param name the name of the property.
484      * @param values the values for the property.
485      * @param comment the comment for the property, or null to remove it.
486      * @throws NullPointerException if name is null.
487      * @throws NullPointerException if values is null.
488      *
489      * @since ostermillerutils 1.00.00
490      */

491     public void addProperties(String JavaDoc name, String JavaDoc[] values, String JavaDoc comment){
492         if (name == null) throw new NullPointerException JavaDoc();
493         if (values == null) throw new NullPointerException JavaDoc();
494         addProperties(name, values);
495         setComment(name, comment);
496     }
497
498     /**
499      * Adds a value to the list of properties with the
500      * given name.
501      *
502      * @param name the name of the property.
503      * @param value the values for the property.
504      * @throws NullPointerException if name is null.
505      * @throws NullPointerException if value is null.
506      *
507      * @since ostermillerutils 1.00.00
508      */

509     public void addProperty(String JavaDoc name, String JavaDoc value){
510         if (name == null) throw new NullPointerException JavaDoc();
511         if (value == null) throw new NullPointerException JavaDoc();
512         Property property;
513         if (properties.containsKey(name)){
514             property = (Property)properties.get(name);
515             property.add(value);
516         } else {
517             property = new Property(value);
518             properties.put(name, property);
519         }
520     }
521
522     /**
523      * Adds the values to the list of properties with the
524      * given name.
525      *
526      * @param name the name of the property.
527      * @param values the values for the property.
528      * @throws NullPointerException if name is null.
529      * @throws NullPointerException if values is null.
530      *
531      * @since ostermillerutils 1.00.00
532      */

533     public void addProperties(String JavaDoc name, String JavaDoc[] values){
534         if (name == null) throw new NullPointerException JavaDoc();
535         if (values == null) throw new NullPointerException JavaDoc();
536         Property property;
537         if (properties.containsKey(name)){
538             property = (Property)properties.get(name);
539             property.add(values);
540         } else {
541             property = new Property(values);
542             properties.put(name, property);
543         }
544     }
545
546     private static int hexDigitValue(char c){
547         switch (c){
548             case '0': return 0;
549             case '1': return 1;
550             case '2': return 2;
551             case '3': return 3;
552             case '4': return 4;
553             case '5': return 5;
554             case '6': return 6;
555             case '7': return 7;
556             case '8': return 8;
557             case '9': return 9;
558             case 'a': case 'A': return 10;
559             case 'b': case 'B': return 11;
560             case 'c': case 'C': return 12;
561             case 'd': case 'D': return 13;
562             case 'e': case 'E': return 14;
563             case 'f': case 'F': return 15;
564             default: return -1;
565         }
566     }
567
568     private static String JavaDoc unescape(String JavaDoc s){
569         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(s.length());
570         for(int i=0; i<s.length(); i++){
571             char c = s.charAt(i);
572             if (c == '\\'){
573                 i++;
574                 if (i < s.length()){
575                     c = s.charAt(i);
576                     switch (c){
577                         case 'n': {
578                             sb.append('\n');
579                         } break;
580                         case 'r': {
581                             sb.append('\r');
582                         } break;
583                         case 't': {
584                                 sb.append('\t');
585                         } break;
586                         case 'f': {
587                             sb.append('\f');
588                         } break;
589                         case 'u': {
590                             boolean foundUnicode = false;
591                             if (i+4 < s.length()){
592                                 int unicodeValue = 0;
593                                 for (int j = 3; unicodeValue >= 0 && j >= 0; j--){
594                                     int val = hexDigitValue(s.charAt(i+(4-j)));
595                                     if (val == -1){
596                                         unicodeValue = -1;
597                                     } else {
598                                         unicodeValue |= (val << (j << 2));
599                                     }
600                                 }
601                                 if (unicodeValue >= 0) {
602                                     i+=4;
603                                         foundUnicode = true;
604                                         sb.append((char)unicodeValue);
605                                 }
606                             }
607                             if (!foundUnicode) sb.append(c);
608                         } break;
609                         default: {
610                             sb.append(c);
611                         } break;
612                     }
613                 }
614             } else {
615                 sb.append(c);
616             }
617         }
618         return sb.toString();
619     }
620
621     /**
622      * Load these properties from a user file with default properties
623      * from a system resource.
624      * <p>
625      * Ex:
626      * <pre>load(
627      * new String(){".java","tld","company","package","component.properties"}
628          * "tld/company/package/component.properties",
629      * )</pre>
630      * This will load the properties file relative to the classpath as the
631      * defaults and the file &lt;%userhome%&gt;/.java/tld/company/package/component.properties
632      * if the file exists. The .java directory is recommended as it is a common,
633      * possibly hidden, directory in the users home directory commonly used by
634      * Java programs.
635      *
636      * This method is meant to be used with the save(String systemResource) method
637      * which will save modified properties back to the user directory.
638      *
639      * @param userFile array of Strings representing a path and file name relative to the user home directory.
640      * @param systemResource name relative to classpath of default properties, or null to ignore.
641      * @throws IOException if an error occurs when reading.
642      * @throws NullPointerException if userFile is null.
643      * @throws IllegalArgumentException if userFile is empty.
644      *
645      * @since ostermillerutils 1.00.00
646      */

647     public void load(String JavaDoc[] userFile, String JavaDoc systemResource) throws IOException JavaDoc {
648         int length = userFile.length;
649         if (userFile.length == 0) throw new IllegalArgumentException JavaDoc();
650         InputStream JavaDoc in = ClassLoader.getSystemResourceAsStream(systemResource);
651         if (in==null) throw new FileNotFoundException JavaDoc(systemResource);
652         if (systemResource != null) load(in);
653         File JavaDoc f = new File JavaDoc(System.getProperty("user.home"));
654         for (int i=0; f.exists() && i<length; i++){
655             f = new File JavaDoc(f, userFile[i]);
656         }
657         if (f.exists()) load(new FileInputStream JavaDoc(f));
658     }
659
660     /**
661      * Add the properties from the input stream to this
662      * UberProperties.
663      *
664      * @param in InputStream containing properties.
665      * @param add whether parameters should add to parameters with the same name or replace them.
666      * @throws IOException if an error occurs when reading.
667      *
668      * @since ostermillerutils 1.00.00
669      */

670     public void load(InputStream JavaDoc in, boolean add) throws IOException JavaDoc {
671         PropertiesLexer lex = new PropertiesLexer(new InputStreamReader JavaDoc(in, "ISO-8859-1"));
672         PropertiesToken t;
673         HashSet JavaDoc<String JavaDoc> names = new HashSet JavaDoc<String JavaDoc>();
674         StringBuffer JavaDoc comment = new StringBuffer JavaDoc();
675         boolean foundComment = false;
676         StringBuffer JavaDoc name = new StringBuffer JavaDoc();
677         StringBuffer JavaDoc value = new StringBuffer JavaDoc();
678         int last = TYPE_COMMENT;
679         boolean atStart = true;
680         String JavaDoc lastSeparator = null;
681         while ((t = lex.getNextToken()) != null){
682             if (t.getID() == PropertiesToken.COMMENT){
683                 int start = 1;
684                 String JavaDoc commentText = t.getContents();
685                 if (commentText.startsWith("# ")) start = 2;
686                 comment.append(commentText.substring(start, commentText.length()));
687                 comment.append("\n");
688                 lex.getNextToken();
689                 foundComment = true;
690             } else if (t.getID() == PropertiesToken.NAME){
691                 if (atStart){
692                     setComment(comment.toString());
693                     comment.setLength(0);
694                     atStart = false;
695                 }
696                 name.append(t.getContents());
697             } else if (t.getID() == PropertiesToken.VALUE){
698                 if (atStart){
699                     setComment(comment.toString());
700                     comment.setLength(0);
701                     atStart = false;
702                 }
703                 value.append(t.getContents());
704             } else if (t.getID() == PropertiesToken.SEPARATOR){
705                 lastSeparator = t.getContents();
706             } else if (t.getID() == PropertiesToken.END_LINE_WHITE_SPACE){
707                 if (atStart){
708                     setComment(comment.toString());
709                     comment.setLength(0);
710                     atStart = false;
711                 }
712                 String JavaDoc stName = unescape(name.toString());
713                 String JavaDoc stValue = unescape(value.toString());
714                 if (lastSeparator != null || stName.length() > 0 || stValue.length() > 0 ){
715                     if (add || names.contains(stName)){
716                         addProperty(stName, stValue);
717                     } else {
718                         setProperty(stName, stValue);
719                         names.add(stName);
720                     }
721                     if (foundComment) setComment(stName, unescape(comment.toString()));
722                 }
723                 comment.setLength(0);
724                 name.setLength(0);
725                 value.setLength(0);
726                 foundComment = false;
727                 lastSeparator = null;
728             }
729         }
730     }
731
732     /**
733      * Add the properties from the input stream to this
734      * UberProperties.
735      * <p>
736      * Properties that are found replace any properties that
737      * were there before.
738      *
739      * @param in InputStream containing properties.
740      * @throws IOException if an error occurs when reading.
741      *
742      * @since ostermillerutils 1.00.00
743      */

744     public void load(InputStream JavaDoc in) throws IOException JavaDoc {
745         load(in, false);
746     }
747
748     /**
749      * Save these properties from a user file.
750      * <p>
751      * Ex:
752      * <pre>save(
753      * new String(){"tld","company","package","component.properties"}
754      * )</pre>
755      * This will save the properties file relative to the user directory:
756      * &lt;%userhome%&gt;/tld/company/package/component.properties
757      * Directories will be created as needed.
758      *
759      * @param userFile array of Strings representing a path and file name relative to the user home directory.
760      * @throws IOException if an error occurs when reading.
761      * @throws NullPointerException if userFile is null.
762      * @throws IllegalArgumentException if userFile is empty.
763      *
764      * @since ostermillerutils 1.00.00
765      */

766     public void save(String JavaDoc[] userFile) throws IOException JavaDoc {
767         int length = userFile.length;
768         if (length == 0) throw new IllegalArgumentException JavaDoc();
769         File JavaDoc f = new File JavaDoc(System.getProperty("user.home"));
770         for (int i=0; i<length; i++){
771             f = new File JavaDoc(f, userFile[i]);
772             if (i == length - 2 && !f.exists()){
773                 f.mkdirs();
774             }
775         }
776         OutputStream JavaDoc out = new FileOutputStream JavaDoc(f);
777         save(out);
778         out.close();
779     }
780
781     /**
782      * Save these properties to the given stream.
783      *
784      * @param out OutputStream to which these properties should be written.
785      * @throws IOException if an error occurs when writing.
786      *
787      * @since ostermillerutils 1.00.00
788      */

789     public void save(OutputStream JavaDoc out) throws IOException JavaDoc {
790         writeComment(out, comment);
791         out.write('\n');
792         String JavaDoc[] names = propertyNames();
793         Arrays.sort(names);
794         for (int i=0; i<names.length; i++){
795             writeComment(out, getComment(names[i]));
796             String JavaDoc[] values = getProperties(names[i]);
797             for (int j=0; j<values.length; j++){
798                 writeProperty(out, names[i], values[j]);
799             }
800         }
801         out.flush();
802     }
803
804     private static void writeProperty(OutputStream JavaDoc out, String JavaDoc name, String JavaDoc value) throws IOException JavaDoc {
805         writeEscapedISO88591(out, name, TYPE_NAME);
806         out.write('=');
807         writeEscapedISO88591(out, value, TYPE_VALUE);
808         out.write('\n');
809
810     }
811
812     private static void writeComment(OutputStream JavaDoc out, String JavaDoc comment) throws IOException JavaDoc {
813         if (comment != null){
814             java.util.StringTokenizer JavaDoc tok = new java.util.StringTokenizer JavaDoc(comment, "\r\n");
815             while (tok.hasMoreTokens()){
816                 out.write('#');
817                 out.write(' ');
818                 writeEscapedISO88591(out, tok.nextToken(), TYPE_COMMENT);
819                 out.write('\n');
820                         }
821         }
822     }
823
824     private static final int TYPE_COMMENT = 0;
825     private static final int TYPE_NAME = 1;
826     private static final int TYPE_VALUE = 2;
827
828     private static void writeEscapedISO88591(OutputStream JavaDoc out, String JavaDoc s, int type) throws IOException JavaDoc {
829         for (int i=0; i<s.length(); i++){
830             int c = (int)s.charAt(i);
831             if (c < 0x100){
832                 boolean escape = false;
833                 if (c == '\r' || c == '\n' || c == '\\'){
834                     escape = true;
835                 } else if (c == ' ' || c == '\t' || c == '\f'){
836                     if(type == TYPE_NAME){
837                         escape = true;
838                     } else if (type == TYPE_VALUE && (i==0 || i == s.length() - 1)){
839                         escape = true;
840                     }
841                 } else if (type == TYPE_NAME && (c == '=' || c == ':')){
842                     escape = true;
843                 }
844                 if (escape){
845                     switch (c){
846                         case '\n': {
847                             switch (type){
848                                 case TYPE_COMMENT: {
849                                     out.write('\n');
850                                     out.write('#');
851                                     out.write(' ');
852                                 } break;
853                                 case TYPE_NAME: {
854                                     out.write('\\');
855                                     out.write('n');
856                                     out.write('\\');
857                                     out.write('\n');
858                                     out.write('\t');
859                                 } break;
860                                 case TYPE_VALUE: {
861                                     out.write('\\');
862                                     out.write('n');
863                                     out.write('\\');
864                                     out.write('\n');
865                                     out.write('\t');
866                                     out.write('\t');
867                                 } break;
868                             }
869                         } break;
870                         case '\\': {
871                             out.write('\\');
872                             out.write('\\');
873                         } break;
874                         case '\r': {
875                             out.write('\\');
876                             out.write('r');
877                         } break;
878                         case '\t': {
879                             out.write('\\');
880                             out.write('t');
881                         } break;
882                         case '\f': {
883                             out.write('\\');
884                             out.write('f');
885                         } break;
886                         default : {
887                             out.write('\\');
888                             out.write((byte)c);
889                         } break;
890                     }
891                 } else {
892                     out.write((byte)c);
893                 }
894             } else {
895                 out.write('\\');
896                 out.write('u');
897                 out.write(StringHelper.prepad(Integer.toHexString(c), 4, '0').getBytes("ISO-8859-1"));
898             }
899         }
900     }
901
902     /**
903      * Get the first property with the given name.
904      * If the property is not specified in this UberProperties
905      * but it is in the default UberProperties, the default is
906      * used. If no default is found, null is returned.
907      *
908      * @return the first value of this property, or null if the property does not exist.
909      *
910      * @since ostermillerutils 1.00.00
911      */

912     public String JavaDoc getProperty(String JavaDoc name){
913         String JavaDoc value = null;
914         if (properties.containsKey(name)){
915             value = ((Property)properties.get(name)).getValue();
916         }
917         return value;
918     }
919
920     /**
921      * Get the first property with the given name.
922      * If the property is not specified in this UberProperties
923      * but it is in the default UberProperties, the default
924      * UberProperties is consulted, otherwise, the supplied
925      * default is used.
926      *
927      * @return the first value of this property.
928      *
929      * @since ostermillerutils 1.00.00
930      */

931     public String JavaDoc getProperty(String JavaDoc name, String JavaDoc defaultValue){
932         String JavaDoc value = getProperty(name);
933         if (value == null) value = defaultValue;
934         return value;
935     }
936
937     /**
938      * Get the values for a property.
939      * Properties returned in the same order in which
940      * they were added.
941      * <p>
942      * If the property is not specified in this UberProperties
943      * but it is in the default UberProperties, the default is
944      * used. If no default is found, null is returned.
945      *
946      * @return all the values associated with the given key, or null if the property does not exist.
947      *
948      * @since ostermillerutils 1.00.00
949      */

950     public String JavaDoc[] getProperties(String JavaDoc name){
951         String JavaDoc[] values = null;
952         if (properties.containsKey(name)){
953             values = ((Property)properties.get(name)).getValues();
954         }
955         return values;
956     }
957
958     /**
959      * Get the values for a property.
960      * Properties returned in the same order in which
961      * they were added.
962      * <p>
963      * If the property is not specified in this UberProperties
964      * but it is in the default UberProperties, the default
965      * UberProperties is consulted, otherwise, the supplied
966      * defaults are used.
967      *
968      * @return all the values associated with the given key, or null if the property does not exist.
969      *
970      * @since ostermillerutils 1.00.00
971      */

972     public String JavaDoc[] getProperties(String JavaDoc name, String JavaDoc[] defaultValues){
973         String JavaDoc[] values = getProperties(name);
974         if (values == null) values = defaultValues;
975         return values;
976     }
977
978     /**
979      * Get the comment associated with this property.
980      * <p>
981      * If the property is not specified in this UberProperties
982      * but it is in the default UberProperties, the default is
983      * used. If no default is found, null is returned.
984      *
985      * @return the comment for this property, or null if there is no comment or the property does not exist.
986      *
987      * @since ostermillerutils 1.00.00
988      */

989     public String JavaDoc getComment(String JavaDoc name){
990         String JavaDoc comment = null;
991         if (properties.containsKey(name)){
992             comment = ((Property)properties.get(name)).getComment();
993         }
994         return comment;
995     }
996
997     /**
998      * Returns an enumeration of all the keys in this property list, including
999      * distinct keys in the default property list if a key of the same name has
1000     * not already been found from the main properties list.
1001     *
1002     * @return an enumeration of all the keys in this property list, including the keys in the default property list.
1003     *
1004     * @since ostermillerutils 1.00.00
1005     */

1006    public String JavaDoc[] propertyNames(){
1007        Set JavaDoc<String JavaDoc> names = properties.keySet();
1008        return (String JavaDoc[])names.toArray(new String JavaDoc[names.size()]);
1009    }
1010
1011    /**
1012     * Set the comment associated with this set of properties.
1013     *
1014     * @param comment the comment for entire set of properties, or null to clear.
1015     *
1016     * @since ostermillerutils 1.00.00
1017     */

1018    public void setComment(String JavaDoc comment){
1019        this.comment = comment;
1020    }
1021
1022    /**
1023     * Get the comment associated with this set of properties.
1024     *
1025     * @return comment for entire set of properties, or null if there is no comment.
1026     *
1027     * @since ostermillerutils 1.00.00
1028     */

1029    public String JavaDoc getComment(){
1030        return this.comment;
1031    }
1032
1033    /**
1034     * Get the number of unique names for properties stored
1035     * in this UberProperties.
1036     *
1037     * @return number of names.
1038     *
1039     * @since ostermillerutils 1.00.00
1040     */

1041    public int getPropertyNameCount(){
1042        return properties.keySet().size();
1043    }
1044
1045    /**
1046     * Save these properties to a string.
1047     *
1048     * @return Serialized String version of these properties.
1049     *
1050     * @since ostermillerutils 1.02.23
1051     */

1052    public String JavaDoc toString(){
1053        ByteArrayOutputStream JavaDoc out = new ByteArrayOutputStream JavaDoc();
1054        try {
1055            this.save(out);
1056        } catch (IOException JavaDoc iox){
1057            throw new Error JavaDoc("IO constructed on memory, this shouldn't happen.", iox);
1058        }
1059        String JavaDoc s = null;
1060        try {
1061            s = new String JavaDoc(out.toByteArray(), "ISO-8859-1");
1062        } catch (UnsupportedEncodingException JavaDoc uee){
1063            throw new Error JavaDoc("ISO-8859-1 should be recognized.", uee);
1064        }
1065        return s;
1066    }
1067}
1068
Popular Tags