Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/lutris/util/ConfigFile.java


1   /*
2    * Enhydra Java Application Server Project
3    * 
4    * The contents of this file are subject to the Enhydra Public License
5    * Version 1.1 (the "License"); you may not use this file except in
6    * compliance with the License. You may obtain a copy of the License on
7    * the Enhydra web site ( http://www.enhydra.org/ ).
8    * 
9    * Software distributed under the License is distributed on an "AS IS"
10   * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
11   * the License for the specific terms governing rights and limitations
12   * under the License.
13   * 
14   * The Initial Developer of the Enhydra Application Server is Lutris
15   * Technologies, Inc. The Enhydra Application Server and portions created
16   * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17   * All Rights Reserved.
18   * 
19   * Contributor(s):
20   * 
21   * $Id: ConfigFile.java,v 1.21.2.1.2.1 2000/10/19 17:58:53 jasona Exp $
22   */
23  
24  package com.lutris.util;
25  
26  import java.io.*;
27  import java.util.*;
28  
29  /**
30   * ConfigFile is used to manipulate Multiserver configuration
31   * (.conf) files.
32   *
33   * Presents all configuration elements in the form of a keyed
34   * table.  Configuration elements are grouped by string "keys" according
35   * to the function they configure.  The syntax is described more formally
36   * below.
37   * <PRE>
38   *   stream ::= entry | stream entry
39   *   entry ::= key "=" value_list | comment | blank
40   *   value_list ::= value | value_list "," value
41   *   value ::= fragment | value "+" fragment
42   *   fragment ::= key | quoted_string
43   *   quoted_string ::= (C/C++ style quoted string)
44   *   key ::= (A string matching [A-Za-z_\./][A-Za-z0-9_-\./]*)
45   *   comment ::= "#" (any text up to a newline)
46   *   blank ::= (A line containing only white space)
47   * </PRE>
48   * In addition to the above syntax, some additional semantic rules apply.
49   * The operator "+" concatenates the fragment immediately to the left
50   * and to the right of it.  Thus <code>ab + cd</code> results in
51   * <code>abcd</code>.  The operator "," terminates one element and begins
52   * the next element.  Thus, the line <code>val = hello, world</code>
53   * creates a configuration entry with the key "val" and the two elements
54   * "hello", and "world".  If the characters "+", ",", or "\" occur at
55   * the end of a line, then the entry is continued on the next line.
56   * Trailing and leading whitespaces are ignored, and multiple whitespace
57   * characters are converted by default into a single space.  The "+"
58   * operator leaves no whitespace between operands.
59   * Finally, within quoted strings, C-style backslash escapes are
60   * recognized.  These include "\n" for newline, "\t" for tab, etc.
61   * An example configuration input file is given below.
62   * <PRE>
63   *  #==============================================================
64   *  # Sample.config
65   *  #==============================================================
66   *  LDAP_SERVERS[] =  "server1.ldap.gov:338",
67   *        "server2.ldap.gov:1000"
68   *  USER_TIMEOUT =  3600  # seconds.  Comments can follow on same line.
69   *  STRANGE_ENTRY = "This is a long long long long long " +
70   *      "long long line.  Notice how the \"+\" " +
71   *      "operator is used to break this line up " +
72   *      "into more than one line in the config file." ,
73   *      "And this is the second element."
74   *  # etc.
75   * </PRE>
76   *
77   * @see Config
78   * @author Shawn McMurdo
79   * @version $Revision: 1.21.2.1.2.1 $
80   */
81  public class ConfigFile {
82      private Config config;
83      private Vector order;
84      private Hashtable comments;
85  
86      /** The associated config file (if any) */
87      private File file = null;
88  
89      /**
90       * The key under which the trailing comment is stored.
91       */
92      public static final String TRAILING_COMMENT = "ConfigFileTrailingComment";
93  
94      /**
95       * Default constructor for an empty config file.
96       */
97      public ConfigFile() {
98    config = new Config();
99    order = new Vector();
100   comments = new Hashtable();
101     }
102 
103     /**
104      * Constructor from an InputStream.
105      *
106      * @param inputStream
107      *    The input stream from which to parse the config file.
108      */
109     public ConfigFile(InputStream inputStream) throws ConfigException {
110   config = new Config();
111   order = new Vector();
112   comments = new Hashtable();
113   ConfigParser parser = new ConfigParser(inputStream);
114   try {
115       parser.process(this);
116   } catch (ParseException e) {
117       throw new ConfigException(ConfigException.SYNTAX, e.getMessage());
118   }
119     }
120 
121     /**
122      * Constructor from a File. Allows to later write back the
123      * configuration to the same file.
124      *
125      * @param configFile
126      *    The name of a local file to parse.
127      */
128     public ConfigFile(File configFile) throws ConfigException, IOException {
129         this(new FileInputStream(configFile));
130         this.file = configFile;
131         config.setConfigFile(this);
132     }
133 
134     /**
135      * Constructor from a KeywordValueTable.
136      *
137      * @param kvt
138      *    A KeywordValueTable from which to populate the config file.
139      */
140     public ConfigFile(KeywordValueTable kvt) throws ConfigException {
141   config = new Config(kvt);
142   order = new Vector();
143   comments = new Hashtable();
144     }
145 
146     /**
147      * Returns the Config object representing the config data in the file.
148      *
149      * @return The Config object for this ConfigFile.
150      */
151     public Config getConfig() {
152   return config;
153     }
154 
155     /**
156      * Returns the comment associated with a given key, or <code>null</code>
157      * if there is no comment. Pass in
158      * <code>ConfigFile.TRAILING_COMMENT</code> to get the trailing comment.
159      *
160      * @param key the key to look up.
161      *
162      * @return the associated comment or <code>null</code>
163      */
164     public String getComment(String key) {
165   return (String)comments.get(key);
166     }
167 
168     /**
169      * Add an entry to the config file.
170      *
171      * @param key
172      *    The config element name.
173      * @param values
174      *    A string array of values.
175      * @param comment
176      *    A string containing a properly commented config
177      *    file comment (i.e. beginning with "#")
178      */
179     public void addEntry(String key, String[] values, String comment)
180       throws KeywordValueException {
181 
182   // Don't add an actual config entry for the trailing comment
183   if (!key.equals(TRAILING_COMMENT)) {
184       config.set(key, values);
185       if (!order.contains(key)) {
186           order.addElement(key);
187       }
188   }
189   comments.put(key, comment);
190     }
191 
192     /**
193      * Add an entry to the config file.
194      *
195      * @param key
196      *    The config element name.
197      * @param value
198      *    A String value.
199      * @param comment
200      *    A string containing a properly commented config
201      *    file comment (i.e. beginning with "#")
202      */
203     public void addEntry(String key, String value, String comment)
204       throws KeywordValueException {
205 
206   // Don't add an actual config entry for the trailing comment
207   if (!key.equals(TRAILING_COMMENT)) {
208       config.set(key, value);
209       if (!order.contains(key)) {
210           order.addElement(key);
211       }
212   }
213   comments.put(key, comment);
214     }
215 
216     /**
217      * Remove an entry from the config file.
218      *
219      * @param key
220      *    The config element name.
221      */
222     public void removeEntry(String key) throws KeywordValueException {
223   // There is no config entry for the trailing comment
224   if (!key.equals(TRAILING_COMMENT)) {
225       config.remove(key);
226       order.removeElement(key);
227   }
228   comments.remove(key);
229     }
230 
231     /**
232      * Get the associated file. If no file is associated with this
233      *  config, <code>null</code> is returned.
234      */
235     public File getFile() {
236         return file;
237     }
238 
239     /**
240      * Writes this config to the associated file. If no file
241      * is associated with this config, throws a
242      * <code>FileNotFoundException</code>
243      */
244     public void write() throws IOException, FileNotFoundException {
245         if (file == null) {
246             throw new FileNotFoundException("No file associated with this object");
247         }
248         FileOutputStream out = new FileOutputStream(file);
249         write(out);
250         out.close();
251     }
252 
253     /**
254      * Writes out a config file to the OutputStream specified.
255      * Note that Objects other than String or String[] will
256      * be converted into a String.
257      *
258      * @param outputStream
259      *    The output stream on which to write the config file.
260      */
261     public void write(OutputStream outputStream) {
262   PrintWriter out = new PrintWriter(outputStream, true);
263   boolean isArray = false;
264   String key;
265   String comment;
266   String[] values;
267   Hashtable remaining = new Hashtable();
268   String[] remainingkeys = config.leafKeys();
269 
270   // Set up the remaining keys list
271   // The value doesn't matter, just the key
272   for (int i = 0; i < remainingkeys.length; i++) {
273       remaining.put(remainingkeys[i], "X");
274   }
275 
276   // Do all the entries for which we have comments
277   for (int i = 0; i < order.size(); i++) {
278       key = (String) order.elementAt(i);
279       comment = (String) comments.get(key);
280       isArray = false;
281       try {
282           Object o = config.get(key);
283                 if (o == null) {
284                     continue;
285     }
286     isArray = o.getClass().isArray();
287     if (isArray) {
288         Object[] oa = (Object[])o;
289                     if ((oa.length > 0) && (oa[0] instanceof java.lang.String)) {
290                         values = (String[]) o;
291         } else {
292             values = new String[oa.length];
293       for (int k = 0; k < oa.length; k++) {
294           values[k] = oa[k].toString();
295       }
296         }
297     } else {
298                     values = new String[1];
299                     if (o instanceof java.lang.String) {
300                         values[0] = (String) o;
301         } else {
302       values[0] = o.toString();
303         }
304     }
305       } catch (KeywordValueException e) {
306     values = null;
307       }
308       // write out entry
309       if ((values == null) || (values.length == 0)) {
310     if ((comment != null) && !(comment.equals(""))) {
311         if (comment.endsWith("\n")) {
312             out.print(comment);
313         } else {
314             out.println(comment);
315         }
316     }
317     out.print(key);
318     if (isArray) out.print("[]");
319     out.println(" =");
320       } else {
321     if ((comment != null) && !(comment.equals(""))) {
322         if (comment.endsWith("\n")) {
323             out.print(comment);
324         } else {
325             out.println(comment);
326         }
327     }
328     if (isArray) {
329         out.print(key + "[] = " + quoteStr(values[0]));
330     } else {
331         out.print(key + " = " + quoteStr(values[0]));
332     }
333     for (int j = 1; j < values.length; j++) {
334         out.print(", " + quoteStr(values[j]));
335     }
336     out.println();
337       }
338       remaining.remove(key);
339   }
340 
341   // Do the trailing comment
342   comment = (String) comments.get(TRAILING_COMMENT);
343   if ((comment != null) && !(comment.equals(""))) {
344       if (comment.endsWith("\n")) {
345           out.print(comment);
346       } else {
347           out.println(comment);
348       }
349   }
350   remaining.remove(TRAILING_COMMENT);
351 
352 
353         // The new keys are in a hash table with no order
354         // sort them here so that they will be grouped correctly
355         // in the conf file. It makes it easier to read.
356   // XXX This is dependent on JDK 1.2 so comment it out
357   // XXX until we don't support JDK 1.1 anymore.
358   /* XXX
359         java.util.Arrays.sort(remainingkeys,new Comparator() {
360            public int compare(Object o1, Object o2) {
361               String s1 = (String) o1;
362               return s1.compareTo((String) o2);
363            }
364         });
365   XXX */
366 
367 
368         int i=0;
369         String lastWord = "";
370         while (i != remainingkeys.length) {
371           key = remainingkeys[i];
372           i++;
373     // Do the remaining config entries
374           if (remaining.get(key) != null) {
375         isArray = false;
376             if (!key.startsWith(lastWord)) {
377               out.println("");
378             }
379             int dot = key.indexOf('.');
380             if (dot == -1)
381               dot = key.length();
382             lastWord = key.substring(0,dot);
383 
384       try {
385           Object o = config.get(key);
386                 if (o == null) {
387                     continue;
388     }
389     isArray = o.getClass().isArray();
390     if (isArray) {
391         Object[] oa = (Object[])o;
392                     if (oa[0] instanceof java.lang.String) {
393                         values = (String[]) o;
394         } else {
395             values = new String[oa.length];
396       for (int k = 0; k < oa.length; k++) {
397           values[k] = oa[k].toString();
398       }
399         }
400     } else {
401                     values = new String[1];
402                     if (o instanceof java.lang.String) {
403                         values[0] = (String) o;
404         } else {
405       values[0] = o.toString();
406         }
407     }
408       } catch (KeywordValueException e) {
409     values = null;
410       }
411       // write out entry
412       if ((values == null) || (values.length == 0)) {
413     out.println(key + " =");
414       } else {
415     if (isArray) {
416         out.print(key + "[] = " + quoteStr(values[0]));
417     } else {
418         out.print(key + " = " + quoteStr(values[0]));
419     }
420 
421     for (int j = 1; j < values.length; j++) {
422         out.print(key + ", " + quoteStr(values[j]));
423     }
424     out.println();
425       }
426     }
427         }
428     }
429 
430 
431     private boolean containsWhiteSpace(String str) {
432   if (str.indexOf(" ") != -1) {
433       return true;
434   } else if (str.indexOf("\t") != -1) {
435       return true;
436   }
437   return false;
438     }
439 
440     private static final String quoteStr(String s) {
441   if ((s == null) || (s.length() < 1)) return "";
442   char[] chars = s.toCharArray();
443   StringBuffer sb = new StringBuffer();
444   boolean needQuotes = false;
445   for (int i=0; i<chars.length; i++) {
446       switch (chars[i]) {
447     // Chars that get special backquotes
448     case '\n':
449         needQuotes = true;
450         sb.append("\\n");
451         break;
452     case '\b':
453         needQuotes = true;
454         sb.append("\\b");
455         break;
456     case '\r':
457         needQuotes = true;
458         sb.append("\\r");
459         break;
460     case '\f':
461         needQuotes = true;
462         sb.append("\\f");
463         break;
464     case '"':
465         needQuotes = true;
466         sb.append("\\\"");
467         break;
468     case '\\':
469         needQuotes = true;
470         sb.append("\\\\");
471         break;
472 
473     // Chars that cause the string to be enclosed in
474     // double quotes.
475     case '\t': case ' ': case '!': case '#': case '$':
476     case '%': case '&': case '\'': case '(': case ')':
477     case '*': case '+': case ',': case '/': case ':':
478     case ';': case '<': case '=': case '>': case '?':
479     case '[': case ']': case '^': case '`': case '{':
480     case '|': case '}': case '~':
481         needQuotes = true;
482         sb.append(chars[i]);
483         break;
484 
485     // All other characters.
486     default:
487         if ((chars[i] < ' ') || (chars[i] == 0x7f)) {
488       needQuotes = true;
489       int ival = (int) chars[i];
490       sb.append('\\');
491       sb.append(digits[(ival & 0xc0) >> 6]);
492       sb.append(digits[(ival & 0x38) >> 3]);
493       sb.append(digits[(ival & 0x07)]);
494         } else if (chars[i] > 0x7f) {
495       needQuotes = true;
496       int ival = (int) chars[i];
497       sb.append("\\u");
498       sb.append(digits[(ival & 0xf000) >> 12]);
499       sb.append(digits[(ival & 0x0f00) >> 8]);
500       sb.append(digits[(ival & 0x00f0) >> 4]);
501       sb.append(digits[(ival & 0x000f)]);
502         } else {
503       sb.append(chars[i]);
504         }
505       }
506   }
507   if (needQuotes) {
508       return( "\"" + sb.toString() + "\"");
509   }
510   return sb.toString();
511     }
512 
513     private static final char[] digits = {
514   '0', '1', '2', '3', '4', '5', '6', '7',
515   '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
516     };
517 
518 }
519