Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.compat.cdc / org.gvsig.i18n / utils / java / src / org / gvsig / i18n / utils / OrderedProperties.java @ 40559

History | View | Annotate | Download (21.6 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24

    
25
package org.gvsig.i18n.utils;
26

    
27
import java.io.BufferedReader;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.io.InputStreamReader;
31
import java.io.OutputStream;
32
import java.io.OutputStreamWriter;
33
import java.io.PrintStream;
34
import java.io.PrintWriter;
35
import java.util.Calendar;
36
import java.util.Collections;
37
import java.util.Comparator;
38
import java.util.Enumeration;
39
import java.util.HashSet;
40
import java.util.Iterator;
41
import java.util.PropertyResourceBundle;
42
import java.util.Set;
43
import java.util.TreeMap;
44
import java.util.Map.Entry;
45

    
46
/**
47
* A set of persistent properties, which can be saved or loaded from a stream.
48
* A property list may also contain defaults, searched if the main list
49
* does not contain a property for a given key.
50
*
51
* An example of a properties file for the german language is given
52
* here.  This extends the example given in ListResourceBundle.
53
* Create a file MyResource_de.properties with the following contents
54
* and put it in the CLASSPATH.  (The character
55
* <code>\</code><code>u00e4</code> is the german umlaut)
56
*
57
* 
58
<pre>s1=3
59
s2=MeineDisk
60
s3=3. M\<code></code>u00e4rz 96
61
s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}.
62
s5=0
63
s6=keine Dateien
64
s7=1
65
s8=eine Datei
66
s9=2
67
s10={0,number} Dateien
68
s11=Das Formatieren schlug fehl mit folgender Exception: {0}
69
s12=FEHLER
70
s13=Ergebnis
71
s14=Dialog
72
s15=Auswahlkriterium
73
s16=1,3</pre>
74
*
75
* <p>Although this is a sub class of a hash table, you should never
76
* insert anything other than strings to this property, or several
77
* methods, that need string keys and values, will fail.  To ensure
78
* this, you should use the <code>get/setProperty</code> method instead
79
* of <code>get/put</code>.
80
*
81
* Properties are saved in the specified encoding. If no encoding is
82
* specified, then the ISO 8859-1 encoding is used, with Unicode escapes with
83
* a single <code>u</code> for any character which cannot be represented.
84
*
85
* @author Jochen Hoenicke
86
* @author Eric Blake (ebb9@email.byu.edu)
87
* @author Cesar Martinez Izquierdo (cesar.martinez@iver.es)
88
* @see PropertyResourceBundle
89
* @status updated to 1.4
90
*/
91
public class OrderedProperties extends TreeMap
92
{
93
        
94
/**
95
* The property list that contains default values for any keys not
96
* in this property list.
97
*
98
* @serial the default properties
99
*/
100
protected OrderedProperties defaults;
101

    
102
/**
103
* Compatible with JDK 1.0+.
104
*/
105
private static final long serialVersionUID = -5087876565919950510L;
106

    
107
/**
108
* Creates a new empty property list with no default values.
109
*/
110
public OrderedProperties()
111
{
112
        super(new StringComparator());
113
}
114

    
115
/**
116
* Create a new empty property list with the specified default values.
117
*
118
* @param defaults a Properties object containing the default values
119
*/
120
public OrderedProperties(OrderedProperties defaults)
121
{
122
        super(new StringComparator());
123
        this.defaults = defaults;
124
}
125

    
126
/**
127
* Adds the given key/value pair to this properties.  This calls
128
* the hashtable method put.
129
*
130
* @param key the key for this property
131
* @param value the value for this property
132
* @return The old value for the given key
133
* @see #getProperty(String)
134
* @since 1.2
135
*/
136
public Object setProperty(String key, String value)
137
{
138
 return put(key, value);
139
}
140

    
141
/**
142
* Reads a property list from an input stream.  The stream should
143
* have the following format: <br>
144
*
145
* An empty line or a line starting with <code>#</code> or
146
* <code>!</code> is ignored.  An backslash (<code>\</code>) at the
147
* end of the line makes the line continueing on the next line
148
* (but make sure there is no whitespace after the backslash).
149
* Otherwise, each line describes a key/value pair. <br>
150
*
151
* The chars up to the first whitespace, = or : are the key.  You
152
* can include these caracters in the key, if you precede them with
153
* a backslash (<code>\</code>). The key is followed by optional
154
* whitespaces, optionally one <code>=</code> or <code>:</code>,
155
* and optionally some more whitespaces.  The rest of the line is
156
* the resource belonging to the key. <br>
157
*
158
* Escape sequences <code>\t, \n, \r, \\, \", \', \!, \#, \ </code>(a
159
* space), and unicode characters with the
160
* <code>\\u</code><em>xxxx</em> notation are detected, and
161
* converted to the corresponding single character. <br>
162
*
163
* 
164
<pre># This is a comment
165
key     = value
166
k\:5      \ a string starting with space and ending with newline\n
167
# This is a multiline specification; note that the value contains
168
# no white space.
169
weekdays: Sunday,Monday,Tuesday,Wednesday,\\
170
       Thursday,Friday,Saturday
171
# The safest way to include a space at the end of a value:
172
label   = Name:\\u0020</pre>
173
*
174
* @param inStream the input stream
175
* @throws IOException if an error occurred when reading the input
176
* @throws NullPointerException if in is null
177
*/
178
public void load(InputStream inStream) throws IOException
179
{
180
 // The spec says that the file must be encoded using ISO-8859-1.
181
 BufferedReader reader =
182
   new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1"));
183
 String line;
184

    
185
 while ((line = reader.readLine()) != null)
186
   {
187
     char c = 0;
188
     int pos = 0;
189
        // Leading whitespaces must be deleted first.
190
     while (pos < line.length()
191
            && Character.isWhitespace(c = line.charAt(pos)))
192
       pos++;
193

    
194
     // If empty line or begins with a comment character, skip this line.
195
     if ((line.length() - pos) == 0
196
            || line.charAt(pos) == '#' || line.charAt(pos) == '!')
197
       continue;
198

    
199
     // The characters up to the next Whitespace, ':', or '='
200
     // describe the key.  But look for escape sequences.
201
        // Try to short-circuit when there is no escape char.
202
        int start = pos;
203
        boolean needsEscape = line.indexOf('\\', pos) != -1;
204
     StringBuffer key = needsEscape ? new StringBuffer() : null;
205
     while (pos < line.length()
206
            && ! Character.isWhitespace(c = line.charAt(pos++))
207
            && c != '=' && c != ':')
208
       {
209
         if (needsEscape && c == '\\')
210
           {
211
             if (pos == line.length())
212
               {
213
                 // The line continues on the next line.  If there
214
                 // is no next line, just treat it as a key with an
215
                 // empty value.
216
                 line = reader.readLine();
217
                    if (line == null)
218
                      line = "";
219
                 pos = 0;
220
                 while (pos < line.length()
221
                        && Character.isWhitespace(c = line.charAt(pos)))
222
                   pos++;
223
               }
224
             else
225
               {
226
                 c = line.charAt(pos++);
227
                 switch (c)
228
                   {
229
                   case 'n':
230
                     key.append('\n');
231
                     break;
232
                   case 't':
233
                     key.append('\t');
234
                     break;
235
                   case 'r':
236
                     key.append('\r');
237
                     break;
238
                   case 'u':
239
                     if (pos + 4 <= line.length())
240
                       {
241
                         char uni = (char) Integer.parseInt
242
                           (line.substring(pos, pos + 4), 16);
243
                         key.append(uni);
244
                         pos += 4;
245
                       }        // else throw exception?
246
                     break;
247
                   default:
248
                     key.append(c);
249
                     break;
250
                   }
251
               }
252
           }
253
         else if (needsEscape)
254
           key.append(c);
255
       }
256

    
257
     boolean isDelim = (c == ':' || c == '=');
258

    
259
        String keyString;
260
        if (needsEscape)
261
          keyString = key.toString();
262
        else if (isDelim || Character.isWhitespace(c))
263
          keyString = line.substring(start, pos - 1);
264
        else
265
          keyString = line.substring(start, pos);
266

    
267
     while (pos < line.length()
268
            && Character.isWhitespace(c = line.charAt(pos)))
269
       pos++;
270

    
271
     if (! isDelim && (c == ':' || c == '='))
272
       {
273
         pos++;
274
         while (pos < line.length()
275
                && Character.isWhitespace(c = line.charAt(pos)))
276
           pos++;
277
       }
278

    
279
        // Short-circuit if no escape chars found.
280
        if (!needsEscape)
281
          {
282
            put(keyString, line.substring(pos));
283
            continue;
284
          }
285

    
286
        // Escape char found so iterate through the rest of the line.
287
     StringBuffer element = new StringBuffer(line.length() - pos);
288
     while (pos < line.length())
289
       {
290
         c = line.charAt(pos++);
291
         if (c == '\\')
292
           {
293
             if (pos == line.length())
294
               {
295
                 // The line continues on the next line.
296
                 line = reader.readLine();
297

    
298
                    // We might have seen a backslash at the end of
299
                    // the file.  The JDK ignores the backslash in
300
                    // this case, so we follow for compatibility.
301
                    if (line == null)
302
                      break;
303

    
304
                 pos = 0;
305
                 while (pos < line.length()
306
                        && Character.isWhitespace(c = line.charAt(pos)))
307
                   pos++;
308
                 element.ensureCapacity(line.length() - pos +
309
                                        element.length());
310
               }
311
             else
312
               {
313
                 c = line.charAt(pos++);
314
                 switch (c)
315
                   {
316
                   case 'n':
317
                     element.append('\n');
318
                     break;
319
                   case 't':
320
                     element.append('\t');
321
                     break;
322
                   case 'r':
323
                     element.append('\r');
324
                     break;
325
                   case 'u':
326
                     if (pos + 4 <= line.length())
327
                       {
328
                         char uni = (char) Integer.parseInt
329
                           (line.substring(pos, pos + 4), 16);
330
                         element.append(uni);
331
                         pos += 4;
332
                       }        // else throw exception?
333
                     break;
334
                   default:
335
                     element.append(c);
336
                     break;
337
                   }
338
               }
339
           }
340
         else
341
           element.append(c);
342
       }
343
     put(keyString, element.toString());
344
   }
345
}
346

    
347
/**
348
* Reads a property list from an input stream, using the
349
* provided encoding. The provided stream should be
350
* correctly encoded as specified, otherwise an IOException
351
* error will be thrown. No escape sequences are accepted
352
* to represent any character, and thus this method has some
353
* limitations and the
354
* format of the accepted property files is slightly
355
* different from
356
* the standard Java property files.
357
* 
358
* The main differences are:
359
* <ul><li>whitespaces, = or : cannot be present in
360
* the key.</li>
361
* <li>each pair key/value must be contained in a single line</li>
362
* </ul>
363
* 
364
* The stream should have the following format: <br>
365
*
366
* An empty line or a line starting with <code>#</code> or
367
* <code>!</code> is ignored.
368
* Otherwise, each line describes a key/value pair. <br>
369
*
370
* The chars up to the first whitespace, = or : are the key.  You
371
* cannot include these caracters in the key. The key is followed by optional
372
* whitespaces, optionally one <code>=</code> or <code>:</code>,
373
* and optionally some more whitespaces.  The rest of the line is
374
* the resource belonging to the key. <br>
375
*
376
* @param inStream the input stream
377
* @throws IOException if an error occurred when reading the input
378
* @throws NullPointerException if in is null
379
*/
380
public void load(InputStream inStream, String encoding) throws IOException {
381
        BufferedReader reader = new BufferedReader(new InputStreamReader(inStream, encoding));
382

    
383
        String line;
384
        while ((line = reader.readLine()) != null) {
385
                int pos = 0;
386
                // Leading whitespaces must be deleted first.
387
                while (pos < line.length()
388
                         && Character.isWhitespace(line.charAt(pos)))
389
                        pos++;
390

    
391
             // If empty line or begins with a comment character, skip this line.
392
             if ((line.length() - pos) == 0
393
                             || line.charAt(pos) == '#' || line.charAt(pos) == '!')
394
                     continue;
395

    
396

    
397
             // do something
398
             String[] entry = line.substring(pos).split(" *[=: ] *", 2);
399
             if (entry.length==2) {
400
                     put(entry[0], entry[1]);
401
             }
402
             else {
403
                     put(entry[0], "");
404
             }
405
        }
406
}
407

    
408

    
409
/**
410
* Writes the key/value pairs to the given output stream, in a format
411
* suitable for
412
* {@link #load(InputStream, String) load(InputStream is, String encoding)}.
413
* Note that this method does not use escape sequences to represent
414
* characters outside the encoding range, charset-dependent
415
* substitution sequence will
416
* be generated if such character is present in the stream.
417
* Moreover, because there is no escape sequences, the newline (\n)
418
* and carriage return (\r) characters must not be used (the 
419
* resulting property file will be incorrect). Because of the
420
* same reason, whitespaces, :, =, ! and # must not be used in the
421
* key<br>
422
*
423
* If header is not null, this method writes a comment containing
424
* the header as first line to the stream.  The next line (or first
425
* line if header is null) contains a comment with the current date.
426
* Afterwards the key/value pairs are written to the stream in the
427
* following format.<br>
428
*
429
* Each line has the form <code>key = value</code>.
430
*
431
* Following the listing, the output stream is flushed but left open.
432
*
433
* @param out the output stream
434
* @param header the header written in the first line, may be null
435
* @throws ClassCastException if this property contains any key or
436
*         value that isn't a string
437
* @throws IOException if writing to the stream fails
438
* @throws NullPointerException if out is null
439
* @since 1.2
440
*/
441
public void store(OutputStream out, String header, String encoding) throws IOException {
442
        PrintWriter writer
443
           = new PrintWriter(new OutputStreamWriter(out, encoding));
444
        if (header != null)
445
                writer.println("#" + header);
446
        writer.println ("#" + Calendar.getInstance ().getTime ());
447

    
448
        Iterator iter = entrySet ().iterator ();
449
        int i = size ();
450
        while (--i >= 0) {
451
                Entry entry = (Entry) iter.next ();
452
            writer.println (entry.getKey()+"="+entry.getValue());
453
        }
454

    
455
        writer.flush ();
456
}
457

    
458

    
459

    
460
/**
461
* Writes the key/value pairs to the given output stream, in a format
462
* suitable for {@link #load(InputStream) load(InputStream)}.<br>
463
*
464
* If header is not null, this method writes a comment containing
465
* the header as first line to the stream.  The next line (or first
466
* line if header is null) contains a comment with the current date.
467
* Afterwards the key/value pairs are written to the stream in the
468
* following format.<br>
469
*
470
* Each line has the form <code>key = value</code>.  Newlines,
471
* Returns and tabs are written as <code>\n,\t,\r</code> resp.
472
* The characters <code>\, !, #, =</code> and <code>:</code> are
473
* preceeded by a backslash.  Spaces are preceded with a backslash,
474
* if and only if they are at the beginning of the key.  Characters
475
* that are not in the ascii range 33 to 127 are written in the
476
* <code>\</code><code>u</code>xxxx Form.<br>
477
*
478
* Following the listing, the output stream is flushed but left open.
479
*
480
* @param out the output stream
481
* @param header the header written in the first line, may be null
482
* @throws ClassCastException if this property contains any key or
483
*         value that isn't a string
484
* @throws IOException if writing to the stream fails
485
* @throws NullPointerException if out is null
486
* @since 1.2
487
*/
488
public void store(OutputStream out, String header) throws IOException
489
{
490
 // The spec says that the file must be encoded using ISO-8859-1.
491
 PrintWriter writer
492
   = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1"));
493
 if (header != null)
494
   writer.println("#" + header);
495
 writer.println ("#" + Calendar.getInstance ().getTime ());
496
 
497
 Iterator iter = entrySet ().iterator ();
498
 int i = size ();
499
 StringBuffer s = new StringBuffer (); // Reuse the same buffer.
500
 while (--i >= 0)
501
   {
502
     Entry entry = (Entry) iter.next ();
503
     formatForOutput ((String) entry.getKey (), s, true);
504
     s.append ('=');
505
     formatForOutput ((String) entry.getValue (), s, false);
506
     writer.println (s);
507
   }
508

    
509
 writer.flush ();
510
}
511

    
512
/**
513
* Gets the property with the specified key in this property list.
514
* If the key is not found, the default property list is searched.
515
* If the property is not found in the default, null is returned.
516
*
517
* @param key The key for this property
518
* @return the value for the given key, or null if not found
519
* @throws ClassCastException if this property contains any key or
520
*         value that isn't a string
521
* @see #defaults
522
* @see #setProperty(String, String)
523
* @see #getProperty(String, String)
524
*/
525
public String getProperty(String key)
526
{
527
 OrderedProperties prop = this;
528
 // Eliminate tail recursion.
529
 do
530
   {
531
     String value = (String) prop.get(key);
532
     if (value != null)
533
       return value;
534
     prop = prop.defaults;
535
   }
536
 while (prop != null);
537
 return null;
538
}
539

    
540
/**
541
* Gets the property with the specified key in this property list.  If
542
* the key is not found, the default property list is searched.  If the
543
* property is not found in the default, the specified defaultValue is
544
* returned.
545
*
546
* @param key The key for this property
547
* @param defaultValue A default value
548
* @return The value for the given key
549
* @throws ClassCastException if this property contains any key or
550
*         value that isn't a string
551
* @see #defaults
552
* @see #setProperty(String, String)
553
*/
554
public String getProperty(String key, String defaultValue)
555
{
556
 String prop = getProperty(key);
557
 if (prop == null)
558
   prop = defaultValue;
559
 return prop;
560
}
561

    
562
/**
563
* Returns an enumeration of all keys in this property list, including
564
* the keys in the default property list.
565
*
566
* @return an Enumeration of all defined keys
567
*/
568
public Enumeration propertyNames()
569
{
570
 // We make a new Set that holds all the keys, then return an enumeration
571
 // for that. This prevents modifications from ruining the enumeration,
572
 // as well as ignoring duplicates.
573
 OrderedProperties prop = this;
574
 Set s = new HashSet();
575
 // Eliminate tail recursion.
576
 do
577
   {
578
     s.addAll(prop.keySet());
579
     prop = prop.defaults;
580
   }
581
 while (prop != null);
582
 return Collections.enumeration(s);
583
}
584

    
585
/**
586
* Prints the key/value pairs to the given print stream.  This is 
587
* mainly useful for debugging purposes.
588
*
589
* @param out the print stream, where the key/value pairs are written to
590
* @throws ClassCastException if this property contains a key or a
591
*         value that isn't a string
592
* @see #list(PrintWriter)
593
*/
594
public void list(PrintStream out)
595
{
596
 PrintWriter writer = new PrintWriter (out);
597
 list (writer);
598
}
599

    
600
/**
601
* Prints the key/value pairs to the given print writer.  This is
602
* mainly useful for debugging purposes.
603
*
604
* @param out the print writer where the key/value pairs are written to
605
* @throws ClassCastException if this property contains a key or a
606
*         value that isn't a string
607
* @see #list(PrintStream)
608
* @since 1.1
609
*/
610
public void list(PrintWriter out)
611
{
612
 out.println ("-- listing properties --");
613

    
614
 Iterator iter = entrySet ().iterator ();
615
 int i = size ();
616
 while (--i >= 0)
617
   {
618
     Entry entry = (Entry) iter.next ();
619
     out.print ((String) entry.getKey () + "=");
620

    
621
     // JDK 1.3/1.4 restrict the printed value, but not the key,
622
     // to 40 characters, including the truncating ellipsis.
623
     String s = (String ) entry.getValue ();
624
     if (s != null && s.length () > 40)
625
       out.println (s.substring (0, 37) + "...");
626
     else
627
       out.println (s);
628
   }
629
 out.flush ();
630
}
631

    
632
/**
633
* Formats a key or value for output in a properties file.
634
* See store for a description of the format.
635
*
636
* @param str the string to format
637
* @param buffer the buffer to add it to
638
* @param key true if all ' ' must be escaped for the key, false if only
639
*        leading spaces must be escaped for the value
640
* @see #store(OutputStream, String)
641
*/
642
private void formatForOutput(String str, StringBuffer buffer, boolean key)
643
{
644
 if (key)
645
   {
646
     buffer.setLength(0);
647
     buffer.ensureCapacity(str.length());
648
   }
649
 else
650
   buffer.ensureCapacity(buffer.length() + str.length());
651
 boolean head = true;
652
 int size = str.length();
653
 for (int i = 0; i < size; i++)
654
   {
655
     char c = str.charAt(i);
656
     switch (c)
657
       {
658
       case '\n':
659
         buffer.append("\\n");
660
         break;
661
       case '\r':
662
         buffer.append("\\r");
663
         break;
664
       case '\t':
665
         buffer.append("\\t");
666
         break;
667
       case ' ':
668
         buffer.append(head ? "\\ " : " ");
669
         break;
670
       case '\\':
671
       case '!':
672
       case '#':
673
       case '=':
674
       case ':':
675
         buffer.append('\\').append(c);
676
         break;
677
       default:
678
         if (c < ' ' || c > '~')
679
           {
680
             String hex = Integer.toHexString(c);
681
             buffer.append("\\u0000".substring(0, 6 - hex.length()));
682
             buffer.append(hex);
683
           }
684
         else
685
           buffer.append(c);
686
       }
687
     if (c != ' ')
688
       head = key;
689
   }
690
}
691

    
692
} // class OrderedProperties
693

    
694

    
695
class StringComparator implements Comparator {
696
         public int compare(Object o1, Object o2) {
697
                 String s1 = o1.toString();
698
                 String s2 = o2.toString();
699

    
700
                 if (s1.compareToIgnoreCase(s2)!=0) // we want case insensitive ordenation, but we still need to differenciate 'OK' from 'Ok'
701
                         return s1.compareToIgnoreCase(s2);
702
                 else
703
                         return -s1.compareTo(s2); // we want lower case before upper case
704
         }
705
}