KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > spi > project > support > ant > EditableProperties


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.spi.project.support.ant;
21
22 import java.io.BufferedReader JavaDoc;
23 import java.io.BufferedWriter JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.InputStream JavaDoc;
26 import java.io.InputStreamReader JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.io.OutputStreamWriter JavaDoc;
29 import java.util.AbstractMap JavaDoc;
30 import java.util.AbstractSet JavaDoc;
31 import java.util.ArrayList JavaDoc;
32 import java.util.Arrays JavaDoc;
33 import java.util.HashMap JavaDoc;
34 import java.util.Iterator JavaDoc;
35 import java.util.LinkedList JavaDoc;
36 import java.util.List JavaDoc;
37 import java.util.ListIterator JavaDoc;
38 import java.util.Map JavaDoc;
39 import java.util.NoSuchElementException JavaDoc;
40 import java.util.Set JavaDoc;
41
42 // XXX: consider adding getInitialComment() and setInitialComment() methods
43
// (useful e.g. for GeneratedFilesHelper)
44

45 /**
46  * Similar to {@link java.util.Properties} but designed to retain additional
47  * information needed for safe hand-editing.
48  * Useful for various <samp>*.properties</samp> in a project:
49  * <ol>
50  * <li>Can associate comments with particular entries.
51  * <li>Order of entries preserved during modifications whenever possible.
52  * <li>VCS-friendly: lines which are not semantically modified are not textually modified.
53  * <li>Can automatically insert line breaks in new or modified values at positions
54  * that are likely to be semantically meaningful, e.g. between path components
55  * </ol>
56  * The file format (including encoding etc.) is compatible with the regular JRE implementation.
57  * Only (non-null) String is supported for keys and values.
58  * This class is not thread-safe; use only from a single thread, or use {@link java.util.Collections#synchronizedMap}.
59  * @author Jesse Glick, David Konecny
60  */

61 public final class EditableProperties extends AbstractMap JavaDoc<String JavaDoc,String JavaDoc> implements Cloneable JavaDoc {
62     
63     /** List of Item instances as read from the properties file. Order is important.
64      * Saving properties will save then in this order. */

65     private final LinkedList JavaDoc<Item> items;
66
67     /** Map of [property key, Item instance] for faster access. */
68     private final Map JavaDoc<String JavaDoc,Item> itemIndex;
69
70     private final boolean alphabetize;
71     
72     private static final String JavaDoc keyValueSeparators = "=: \t\r\n\f";
73
74     private static final String JavaDoc strictKeyValueSeparators = "=:";
75
76     private static final String JavaDoc whiteSpaceChars = " \t\r\n\f";
77
78     private static final String JavaDoc commentChars = "#!";
79     
80     private static final String JavaDoc INDENT = " ";
81
82     // parse states:
83
private static final int WAITING_FOR_KEY_VALUE = 1;
84     private static final int READING_KEY_VALUE = 2;
85     
86     /**
87      * Creates empty instance whose items will not be alphabetized.
88      */

89     public EditableProperties() {
90         this(/* mentioned in #64174 - documented default */false);
91     }
92
93     /**
94      * Creates empty instance.
95      * @param alphabetize alphabetize new items according to key or not
96      */

97     public EditableProperties(boolean alphabetize) {
98         this.alphabetize = alphabetize;
99         items = new LinkedList JavaDoc<Item>();
100         itemIndex = new HashMap JavaDoc<String JavaDoc,Item>();
101     }
102     
103     /**
104      * Creates instance from an existing map. No comments will be defined.
105      * Any order from the existing map will be retained,
106      * and further additions will not be alphabetized.
107      * @param map a map from String to String
108      */

109     public EditableProperties(Map JavaDoc<String JavaDoc,String JavaDoc> map) {
110         this(false);
111         putAll(map);
112     }
113     
114     /**
115      * Creates new instance from an existing one.
116      * @param ep an instance of EditableProperties
117      */

118     private EditableProperties(EditableProperties ep) {
119         // #64174: use a simple deep copy for speed
120
alphabetize = ep.alphabetize;
121         items = new LinkedList JavaDoc<Item>();
122         itemIndex = new HashMap JavaDoc<String JavaDoc,Item>(ep.items.size() * 4 / 3 + 1);
123         for (Item _i : ep.items) {
124             Item i = (Item) _i.clone();
125             items.add(i);
126             itemIndex.put(i.getKey(), i);
127         }
128     }
129     
130     /**
131      * Returns a set view of the mappings ordered according to their file
132      * position. Each element in this set is a Map.Entry. See
133      * {@link AbstractMap#entrySet} for more details.
134      * @return set with Map.Entry instances.
135      */

136     public Set JavaDoc<Map.Entry JavaDoc<String JavaDoc,String JavaDoc>> entrySet() {
137         return new SetImpl(this);
138     }
139     
140     /**
141      * Load properties from a stream.
142      * @param stream an input stream
143      * @throws IOException if the contents are malformed or the stream could not be read
144      */

145     public void load(InputStream JavaDoc stream) throws IOException JavaDoc {
146         int state = WAITING_FOR_KEY_VALUE;
147         BufferedReader JavaDoc input = new BufferedReader JavaDoc(new InputStreamReader JavaDoc(stream, "ISO-8859-1"));
148         List JavaDoc<String JavaDoc> tempList = new LinkedList JavaDoc<String JavaDoc>();
149         String JavaDoc line;
150         int commentLinesCount = 0;
151         // Read block of lines and create instance of Item for each.
152
// Separator is: either empty line or valid end of proeprty declaration
153
while (null != (line = input.readLine())) {
154             tempList.add(line);
155             boolean empty = isEmpty(line);
156             boolean comment = isComment(line);
157             if (state == WAITING_FOR_KEY_VALUE) {
158                 if (empty) {
159                     // empty line: create Item without any key
160
createNonKeyItem(tempList);
161                     commentLinesCount = 0;
162                 } else {
163                     if (comment) {
164                         commentLinesCount++;
165                     } else {
166                         state = READING_KEY_VALUE;
167                     }
168                 }
169             }
170             if (state == READING_KEY_VALUE && !isContinue(line)) {
171                 // valid end of property declaration: create Item for it
172
createKeyItem(tempList, commentLinesCount);
173                 state = WAITING_FOR_KEY_VALUE;
174                 commentLinesCount = 0;
175             }
176         }
177         if (tempList.size() > 0) {
178             if (state == READING_KEY_VALUE) {
179                 // value was not ended correctly? ignore.
180
createKeyItem(tempList, commentLinesCount);
181             } else {
182                 createNonKeyItem(tempList);
183             }
184         }
185     }
186
187     /**
188      * Store properties to a stream.
189      * @param stream an output stream
190      * @throws IOException if the stream could not be written to
191      */

192     public void store(OutputStream JavaDoc stream) throws IOException JavaDoc {
193         boolean previousLineWasEmpty = true;
194         BufferedWriter JavaDoc output = new BufferedWriter JavaDoc(new OutputStreamWriter JavaDoc(stream, "ISO-8859-1"));
195         for (Item item : items) {
196             if (item.isSeparate() && !previousLineWasEmpty) {
197                 output.newLine();
198             }
199             String JavaDoc line = null;
200             Iterator JavaDoc<String JavaDoc> it = item.getRawData().iterator();
201             while (it.hasNext()) {
202                 line = it.next();
203                 output.write(line);
204                 output.newLine();
205             }
206             if (line != null) {
207                 previousLineWasEmpty = isEmpty(line);
208             }
209         }
210         output.flush();
211     }
212
213     @Override JavaDoc
214     public String JavaDoc put(String JavaDoc key, String JavaDoc value) {
215         if (key == null || value == null) {
216             throw new NullPointerException JavaDoc();
217         }
218         Item item = itemIndex.get(key);
219         String JavaDoc result = null;
220         if (item != null) {
221             result = item.getValue();
222             item.setValue(value);
223         } else {
224             item = new Item(key, value);
225             addItem(item, alphabetize);
226         }
227         return result;
228     }
229
230     /**
231      * Convenience method to get a property as a string.
232      * Same as {@link #get}; only here because of pre-generic code.
233      * @param key a property name; cannot be null nor empty
234      * @return the property value, or null if it was not defined
235      */

236     public String JavaDoc getProperty(String JavaDoc key) {
237         return get(key);
238     }
239     
240     /**
241      * Convenience method to set a property.
242      * Same as {@link #put}; only here because of pre-generic code.
243      * @param key a property name; cannot be null nor empty
244      * @param value the desired value; cannot be null
245      * @return previous value of the property or null if there was not any
246      */

247     public String JavaDoc setProperty(String JavaDoc key, String JavaDoc value) {
248         return put(key, value);
249     }
250
251     /**
252      * Sets a property to a value broken into segments for readability.
253      * Same behavior as {@link #setProperty(String,String)} with the difference that each item
254      * will be stored on its own line of text. {@link #getProperty} will simply concatenate
255      * all the items into one string, so generally separators
256      * (such as <samp>:</samp> for path-like properties) must be included in
257      * the items (for example, at the end of all but the last item).
258      * @param key a property name; cannot be null nor empty
259      * @param value the desired value; cannot be null; can be empty array
260      * @return previous value of the property or null if there was not any
261      */

262     public String JavaDoc setProperty(String JavaDoc key, String JavaDoc[] value) {
263         String JavaDoc result = get(key);
264         if (key == null || value == null) {
265             throw new NullPointerException JavaDoc();
266         }
267         List JavaDoc<String JavaDoc> valueList = Arrays.asList(value);
268         Item item = itemIndex.get(key);
269         if (item != null) {
270             item.setValue(valueList);
271         } else {
272             addItem(new Item(key, valueList), alphabetize);
273         }
274         return result;
275     }
276
277     /**
278      * Returns comment associated with the property. The comment lines are
279      * returned as defined in properties file, that is comment delimiter is
280      * included. Comment for property is defined as: continuous block of lines
281      * starting with comment delimiter which are followed by property
282      * declaration (no empty line separator allowed).
283      * @param key a property name; cannot be null nor empty
284      * @return array of String lines as specified in properties file; comment
285      * delimiter character is included
286      */

287     public String JavaDoc[] getComment(String JavaDoc key) {
288         Item item = itemIndex.get(key);
289         if (item == null) {
290             return new String JavaDoc[0];
291         }
292         return item.getComment();
293     }
294
295     /**
296      * Create comment for the property.
297      * <p>Note: if a comment includes non-ISO-8859-1 characters, they will be written
298      * to disk using Unicode escapes (and {@link #getComment} will interpret
299      * such escapes), but of course they will be unreadable for humans.
300      * @param key a property name; cannot be null nor empty
301      * @param comment lines of comment which will be written just above
302      * the property; no reformatting; comment lines must start with
303      * comment delimiter; cannot be null; cannot be emty array
304      * @param separate whether the comment should be separated from previous
305      * item by empty line
306      */

307     public void setComment(String JavaDoc key, String JavaDoc[] comment, boolean separate) {
308         // XXX: check validity of comment parameter
309
Item item = itemIndex.get(key);
310         if (item == null) {
311             throw new IllegalArgumentException JavaDoc("Cannot set comment for non-existing property "+key);
312         }
313         item.setComment(comment, separate);
314     }
315
316     @Override JavaDoc
317     public Object JavaDoc clone() {
318         return cloneProperties();
319     }
320     
321     /**
322      * Create an exact copy of this properties object.
323      * @return a clone of this object
324      */

325     public EditableProperties cloneProperties() {
326         return new EditableProperties(this);
327     }
328
329     // non-key item is block of empty lines/comment not associated with any property
330
private void createNonKeyItem(List JavaDoc<String JavaDoc> lines) {
331         // First check that previous item is not non-key item.
332
if (!items.isEmpty()) {
333             Item item = items.getLast();
334             if (item.getKey() == null) {
335                 // it is non-key item: merge them
336
item.addCommentLines(lines);
337                 lines.clear();
338                 return;
339             }
340         }
341         // create new non-key item
342
Item item = new Item(lines);
343         addItem(item, false);
344         lines.clear();
345     }
346
347     // opposite to non-key item: item with valid property declaration and
348
// perhaps some comment lines
349
private void createKeyItem(List JavaDoc<String JavaDoc> lines, int commentLinesCount) {
350         Item item = new Item(lines.subList(0, commentLinesCount), lines.subList(commentLinesCount, lines.size()));
351         addItem(item, false);
352         lines.clear();
353     }
354     
355     private void addItem(Item item, boolean sort) {
356         String JavaDoc key = item.getKey();
357         if (sort) {
358             assert key != null;
359             ListIterator JavaDoc<Item> it = items.listIterator();
360             while (it.hasNext()) {
361                 String JavaDoc k = it.next().getKey();
362                 if (k != null && k.compareToIgnoreCase(key) > 0) {
363                     it.previous();
364                     it.add(item);
365                     itemIndex.put(key, item);
366                     return;
367                 }
368             }
369         }
370         items.add(item);
371         if (key != null) {
372             itemIndex.put(key, item);
373         }
374     }
375     
376     private void removeItem(Item item) {
377         items.remove(item);
378         if (item.getKey() != null) {
379             itemIndex.remove(item.getKey());
380         }
381     }
382     
383     // does property declaration continue on next line?
384
private boolean isContinue(String JavaDoc line) {
385         int index = line.length() - 1;
386         int slashCount = 0;
387         while (index >= 0 && line.charAt(index) == '\\') {
388             slashCount++;
389             index--;
390         }
391         // if line ends with odd number of backslash then property definition
392
// continues on next line
393
return (slashCount % 2 != 0);
394     }
395     
396     // does line start with comment delimiter? (whitespaces are ignored)
397
private static boolean isComment(String JavaDoc line) {
398         line = trimLeft(line);
399         if (line.length() != 0 && commentChars.indexOf(line.charAt(0)) != -1) {
400             return true;
401         } else {
402             return false;
403         }
404     }
405
406     // is line empty? (whitespaces are ignored)
407
private static boolean isEmpty(String JavaDoc line) {
408         return trimLeft(line).length() == 0;
409     }
410
411     // remove all whitespaces from left
412
private static String JavaDoc trimLeft(String JavaDoc line) {
413         int start = 0;
414         while (start < line.length()) {
415             if (whiteSpaceChars.indexOf(line.charAt(start)) == -1) {
416                 break;
417             }
418             start++;
419         }
420         return line.substring(start);
421     }
422     
423     /**
424      * Representation of one item read from properties file. It can be either
425      * valid property declaration with associated comment or chunk of empty
426      * lines or lines with comment which are not associated with any property.
427      */

428     private static class Item implements Cloneable JavaDoc {
429
430         /** Lines of comment as read from properties file and as they will be
431          * written back to properties file. */

432         private List JavaDoc<String JavaDoc> commentLines;
433
434         /** Lines with property name and value declaration as read from
435          * properties file and as they will be written back to properties file. */

436         private List JavaDoc<String JavaDoc> keyValueLines;
437
438         /** Property key */
439         private String JavaDoc key;
440         
441         /** Property value */
442         private String JavaDoc value;
443
444         /** Should this property be separated from previous one by at least
445          * one empty line. */

446         private boolean separate;
447         
448         // constructor only for cloning
449
private Item() {
450         }
451         
452         /**
453          * Create instance which does not have any key and value - just
454          * some empty or comment lines. This item is READ-ONLY.
455          */

456         public Item(List JavaDoc<String JavaDoc> commentLines) {
457             this.commentLines = new ArrayList JavaDoc<String JavaDoc>(commentLines);
458         }
459
460         /**
461          * Create instance from the lines of comment and property declaration.
462          * Property name and value will be split.
463          */

464         public Item(List JavaDoc<String JavaDoc> commentLines, List JavaDoc<String JavaDoc> keyValueLines) {
465             this.commentLines = new ArrayList JavaDoc<String JavaDoc>(commentLines);
466             this.keyValueLines = new ArrayList JavaDoc<String JavaDoc>(keyValueLines);
467             parse(keyValueLines);
468         }
469
470         /**
471          * Create new instance with key and value.
472          */

473         public Item(String JavaDoc key, String JavaDoc value) {
474             this.key = key;
475             this.value = value;
476         }
477
478         /**
479          * Create new instance with key and value.
480          */

481         public Item(String JavaDoc key, List JavaDoc<String JavaDoc> value) {
482             this.key = key;
483             setValue(value);
484         }
485
486         // backdoor for merging non-key items
487
void addCommentLines(List JavaDoc<String JavaDoc> lines) {
488             assert key == null;
489             commentLines.addAll(lines);
490         }
491         
492         public String JavaDoc[] getComment() {
493             String JavaDoc[] res = new String JavaDoc[commentLines.size()];
494             for (int i = 0; i < res.length; i++) {
495                 // #60249: the comment might have Unicode chars in escapes.
496
res[i] = decodeUnicode(commentLines.get(i));
497             }
498             return res;
499         }
500         
501         public void setComment(String JavaDoc[] commentLines, boolean separate) {
502             this.separate = separate;
503             this.commentLines = new ArrayList JavaDoc<String JavaDoc>(commentLines.length);
504             for (int i = 0; i < commentLines.length; i++) {
505                 // #60249 again - write only ISO-8859-1.
506
this.commentLines.add(encodeUnicode(commentLines[i]));
507             }
508         }
509         
510         public String JavaDoc getKey() {
511             return key;
512         }
513         
514         public String JavaDoc getValue() {
515             return value;
516         }
517
518         public void setValue(String JavaDoc value) {
519             this.value = value;
520             keyValueLines = null;
521         }
522
523         public void setValue(List JavaDoc<String JavaDoc> value) {
524             StringBuffer JavaDoc val = new StringBuffer JavaDoc();
525             List JavaDoc<String JavaDoc> l = new ArrayList JavaDoc<String JavaDoc>();
526             if (!value.isEmpty()) {
527                 l.add(encode(key, true) + "=\\"); // NOI18N
528
Iterator JavaDoc<String JavaDoc> it = value.iterator();
529                 while (it.hasNext()) {
530                     String JavaDoc s = it.next();
531                     val.append(s);
532                     s = encode(s, false);
533                     l.add(it.hasNext() ? INDENT + s + '\\' : INDENT + s); // NOI18N
534
}
535             } else {
536                 // #45061: for no vals, use just "prop="
537
l.add(encode(key, true) + '='); // NOI18N
538
}
539             this.value = val.toString();
540             keyValueLines = l;
541         }
542
543         public boolean isSeparate() {
544             return separate;
545         }
546
547         /**
548          * Returns persistent image of this property.
549          */

550         public List JavaDoc<String JavaDoc> getRawData() {
551             List JavaDoc<String JavaDoc> l = new ArrayList JavaDoc<String JavaDoc>();
552             if (commentLines != null) {
553                 l.addAll(commentLines);
554             }
555             if (keyValueLines == null) {
556                 keyValueLines = new ArrayList JavaDoc<String JavaDoc>();
557                 if (key != null && value != null) {
558                     keyValueLines.add(encode(key, true)+"="+encode(value, false));
559                 }
560             }
561             l.addAll(keyValueLines);
562             return l;
563         }
564         
565         private void parse(List JavaDoc<String JavaDoc> keyValueLines) {
566             // merge lines into one:
567
String JavaDoc line = mergeLines(keyValueLines);
568             // split key and value
569
splitKeyValue(line);
570         }
571         
572         private String JavaDoc mergeLines(List JavaDoc<String JavaDoc> lines) {
573             String JavaDoc line = ""; // XXX use StringBuilder instead
574
Iterator JavaDoc<String JavaDoc> it = lines.iterator();
575             while (it.hasNext()) {
576                 String JavaDoc l = trimLeft(it.next());
577                 // if this is not the last line then remove last backslash
578
if (it.hasNext()) {
579                     assert l.endsWith("\\") : lines;
580                     l = l.substring(0, l.length()-1);
581                 }
582                 line += l;
583             }
584             return line;
585         }
586         
587         private void splitKeyValue(String JavaDoc line) {
588             int separatorIndex = 0;
589             while (separatorIndex < line.length()) {
590                 char ch = line.charAt(separatorIndex);
591                 if (ch == '\\') {
592                     // ignore next one character
593
separatorIndex++;
594                 } else {
595                     if (keyValueSeparators.indexOf(ch) != -1) {
596                         break;
597                     }
598                 }
599                 separatorIndex++;
600             }
601             key = decode(line.substring(0, separatorIndex));
602             line = trimLeft(line.substring(separatorIndex));
603             if (line.length() == 0) {
604                 value = "";
605                 return;
606             }
607             if (strictKeyValueSeparators.indexOf(line.charAt(0)) != -1) {
608                 line = trimLeft(line.substring(1));
609             }
610             value = decode(line);
611         }
612         
613         private static String JavaDoc decode(String JavaDoc input) {
614             char ch;
615             int len = input.length();
616             StringBuffer JavaDoc output = new StringBuffer JavaDoc(len);
617             for (int x=0; x<len; x++) {
618                 ch = input.charAt(x);
619                 if (ch != '\\') {
620                     output.append(ch);
621                     continue;
622                 }
623                 x++;
624                 if (x==len) {
625                     // backslash at the end? syntax error: ignore it
626
continue;
627                 }
628                 ch = input.charAt(x);
629                 if (ch == 'u') {
630                     if (x+5>len) {
631                         // unicode character not finished? syntax error: ignore
632
output.append(input.substring(x-1));
633                         x += 4;
634                         continue;
635                     }
636                     String JavaDoc val = input.substring(x+1, x+5);
637                     try {
638                         output.append((char)Integer.parseInt(val, 16));
639                     } catch (NumberFormatException JavaDoc e) {
640                         // #46234: handle gracefully
641
output.append(input.substring(x - 1, x + 5));
642                     }
643                     x += 4;
644                 } else {
645                     if (ch == 't') ch = '\t';
646                     else if (ch == 'r') ch = '\r';
647                     else if (ch == 'n') ch = '\n';
648                     else if (ch == 'f') ch = '\f';
649                     output.append(ch);
650                 }
651             }
652             return output.toString();
653         }
654
655         private static String JavaDoc encode(String JavaDoc input, boolean escapeSpace) {
656             int len = input.length();
657             StringBuffer JavaDoc output = new StringBuffer JavaDoc(len*2);
658
659             for(int x=0; x<len; x++) {
660                 char ch = input.charAt(x);
661                 switch(ch) {
662                     case ' ':
663                         if (x == 0 || escapeSpace) {
664                             output.append('\\');
665                         }
666                         output.append(' ');
667                         break;
668                     case '\\':
669                         output.append("\\\\");
670                         break;
671                     case '\t':
672                         output.append("\\t");
673                         break;
674                     case '\n':
675                         output.append("\\n");
676                         break;
677                     case '\r':
678                         output.append("\\r");
679                         break;
680                     case '\f':
681                         output.append("\\f");
682                         break;
683                     default:
684                         if ((ch < 0x0020) || (ch > 0x007e)) {
685                             output.append("\\u");
686                             String JavaDoc hex = Integer.toHexString(ch);
687                             for (int i = 0; i < 4 - hex.length(); i++) {
688                                 output.append('0');
689                             }
690                             output.append(hex);
691                         } else {
692                             output.append(ch);
693                         }
694                 }
695             }
696             return output.toString();
697         }
698         
699         private static String JavaDoc decodeUnicode(String JavaDoc input) {
700             char ch;
701             int len = input.length();
702             StringBuffer JavaDoc output = new StringBuffer JavaDoc(len);
703             for (int x = 0; x < len; x++) {
704                 ch = input.charAt(x);
705                 if (ch != '\\') {
706                     output.append(ch);
707                     continue;
708                 }
709                 x++;
710                 if (x==len) {
711                     // backslash at the end? syntax error: ignore it
712
continue;
713                 }
714                 ch = input.charAt(x);
715                 if (ch == 'u') {
716                     if (x+5>len) {
717                         // unicode character not finished? syntax error: ignore
718
output.append(input.substring(x-1));
719                         x += 4;
720                         continue;
721                     }
722                     String JavaDoc val = input.substring(x+1, x+5);
723                     try {
724                         output.append((char)Integer.parseInt(val, 16));
725                     } catch (NumberFormatException JavaDoc e) {
726                         // #46234: handle gracefully
727
output.append(input.substring(x - 1, x + 5));
728                     }
729                     x += 4;
730                 } else {
731                     output.append(ch);
732                 }
733             }
734             return output.toString();
735         }
736
737         private static String JavaDoc encodeUnicode(String JavaDoc input) {
738             int len = input.length();
739             StringBuffer JavaDoc output = new StringBuffer JavaDoc(len * 2);
740             for (int x = 0; x < len; x++) {
741                 char ch = input.charAt(x);
742                 if ((ch < 0x0020) || (ch > 0x007e)) {
743                     output.append("\\u"); // NOI18N
744
String JavaDoc hex = Integer.toHexString(ch);
745                     for (int i = 0; i < 4 - hex.length(); i++) {
746                         output.append('0');
747                     }
748                     output.append(hex);
749                 } else {
750                     output.append(ch);
751                 }
752             }
753             return output.toString();
754         }
755
756         @Override JavaDoc
757         public Object JavaDoc clone() {
758             Item item = new Item();
759             if (keyValueLines != null) {
760                 item.keyValueLines = new ArrayList JavaDoc<String JavaDoc>(keyValueLines);
761             }
762             if (commentLines != null) {
763                 item.commentLines = new ArrayList JavaDoc<String JavaDoc>(commentLines);
764             }
765             item.key = key;
766             item.value = value;
767             item.separate = separate;
768             return item;
769         }
770     
771     }
772     
773     private static class SetImpl extends AbstractSet JavaDoc<Map.Entry JavaDoc<String JavaDoc,String JavaDoc>> {
774
775         private EditableProperties props;
776         
777         public SetImpl(EditableProperties props) {
778             this.props = props;
779         }
780         
781         public Iterator JavaDoc<Map.Entry JavaDoc<String JavaDoc,String JavaDoc>> iterator() {
782             return new IteratorImpl(props);
783         }
784         
785         public int size() {
786             return props.items.size();
787         }
788         
789     }
790     
791     private static class IteratorImpl implements Iterator JavaDoc<Map.Entry JavaDoc<String JavaDoc,String JavaDoc>> {
792
793         private final EditableProperties props;
794         private ListIterator JavaDoc<Item> delegate;
795         
796         public IteratorImpl(EditableProperties props) {
797             this.props = props;
798             delegate = props.items.listIterator();
799         }
800         
801         public boolean hasNext() {
802             return findNext() != null;
803         }
804         
805         public Map.Entry JavaDoc<String JavaDoc,String JavaDoc> next() {
806             Item item = findNext();
807             if (item == null) {
808                 throw new NoSuchElementException JavaDoc();
809             }
810             delegate.next();
811             return new MapEntryImpl(item);
812         }
813         
814         public void remove() {
815             delegate.previous();
816             Item item = findNext();
817             if (item == null) {
818                 throw new IllegalStateException JavaDoc();
819             }
820             int index = delegate.nextIndex();
821             props.items.remove(item);
822             props.itemIndex.remove(item.getKey());
823             delegate = props.items.listIterator(index);
824         }
825         
826         private Item findNext() {
827             while (delegate.hasNext()) {
828                 Item item = delegate.next();
829                 if (item.getKey() != null && item.getValue() != null) {
830                     // Found one. Back up!
831
delegate.previous();
832                     return item;
833                 }
834             }
835             return null;
836         }
837         
838     }
839     
840     private static class MapEntryImpl implements Map.Entry JavaDoc<String JavaDoc,String JavaDoc> {
841         
842         private Item item;
843         
844         public MapEntryImpl(Item item) {
845             this.item = item;
846         }
847         
848         public String JavaDoc getKey() {
849             return item.getKey();
850         }
851         
852         public String JavaDoc getValue() {
853             return item.getValue();
854         }
855         
856         public String JavaDoc setValue(String JavaDoc value) {
857             String JavaDoc result = item.getValue();
858             item.setValue(value);
859             return result;
860         }
861         
862     }
863     
864 }
865
Popular Tags