Lines 41-69
Link Here
|
41 |
|
41 |
|
42 |
package org.netbeans.spi.project.support.ant; |
42 |
package org.netbeans.spi.project.support.ant; |
43 |
|
43 |
|
44 |
import java.io.BufferedReader; |
|
|
45 |
import java.io.BufferedWriter; |
46 |
import java.io.IOException; |
44 |
import java.io.IOException; |
47 |
import java.io.InputStream; |
45 |
import java.io.InputStream; |
48 |
import java.io.InputStreamReader; |
|
|
49 |
import java.io.OutputStream; |
46 |
import java.io.OutputStream; |
50 |
import java.io.OutputStreamWriter; |
|
|
51 |
import java.util.AbstractMap; |
47 |
import java.util.AbstractMap; |
52 |
import java.util.AbstractSet; |
|
|
53 |
import java.util.ArrayList; |
54 |
import java.util.Arrays; |
55 |
import java.util.HashMap; |
56 |
import java.util.Iterator; |
57 |
import java.util.LinkedList; |
58 |
import java.util.List; |
59 |
import java.util.ListIterator; |
60 |
import java.util.Map; |
48 |
import java.util.Map; |
61 |
import java.util.NoSuchElementException; |
|
|
62 |
import java.util.Set; |
49 |
import java.util.Set; |
63 |
|
50 |
|
64 |
// XXX: consider adding getInitialComment() and setInitialComment() methods |
|
|
65 |
// (useful e.g. for GeneratedFilesHelper) |
66 |
|
67 |
/** |
51 |
/** |
68 |
* Similar to {@link java.util.Properties} but designed to retain additional |
52 |
* Similar to {@link java.util.Properties} but designed to retain additional |
69 |
* information needed for safe hand-editing. |
53 |
* information needed for safe hand-editing. |
Lines 78-109
Link Here
|
78 |
* The file format (including encoding etc.) is compatible with the regular JRE implementation. |
62 |
* The file format (including encoding etc.) is compatible with the regular JRE implementation. |
79 |
* Only (non-null) String is supported for keys and values. |
63 |
* Only (non-null) String is supported for keys and values. |
80 |
* This class is not thread-safe; use only from a single thread, or use {@link java.util.Collections#synchronizedMap}. |
64 |
* This class is not thread-safe; use only from a single thread, or use {@link java.util.Collections#synchronizedMap}. |
81 |
* @author Jesse Glick, David Konecny |
65 |
* <p>This class exists here only for historical reasons. It delegates to {@link org.openide.util.EditableProperties}. |
82 |
*/ |
66 |
*/ |
83 |
public final class EditableProperties extends AbstractMap<String,String> implements Cloneable { |
67 |
public final class EditableProperties extends AbstractMap<String,String> implements Cloneable { |
84 |
|
|
|
85 |
/** List of Item instances as read from the properties file. Order is important. |
86 |
* Saving properties will save then in this order. */ |
87 |
private final LinkedList<Item> items; |
88 |
|
68 |
|
89 |
/** Map of [property key, Item instance] for faster access. */ |
69 |
private final org.openide.util.EditableProperties delegate; |
90 |
private final Map<String,Item> itemIndex; |
|
|
91 |
|
70 |
|
92 |
private final boolean alphabetize; |
71 |
private EditableProperties(org.openide.util.EditableProperties delegate) { |
93 |
|
72 |
this.delegate = delegate; |
94 |
private static final String keyValueSeparators = "=: \t\r\n\f"; |
73 |
} |
95 |
|
|
|
96 |
private static final String strictKeyValueSeparators = "=:"; |
97 |
|
98 |
private static final String whiteSpaceChars = " \t\r\n\f"; |
99 |
|
100 |
private static final String commentChars = "#!"; |
101 |
|
102 |
private static final String INDENT = " "; |
103 |
|
104 |
// parse states: |
105 |
private static final int WAITING_FOR_KEY_VALUE = 1; |
106 |
private static final int READING_KEY_VALUE = 2; |
107 |
|
74 |
|
108 |
/** |
75 |
/** |
109 |
* Creates empty instance whose items will not be alphabetized. |
76 |
* Creates empty instance whose items will not be alphabetized. |
Lines 119-127
Link Here
|
119 |
* @param alphabetize alphabetize new items according to key or not |
86 |
* @param alphabetize alphabetize new items according to key or not |
120 |
*/ |
87 |
*/ |
121 |
public EditableProperties(boolean alphabetize) { |
88 |
public EditableProperties(boolean alphabetize) { |
122 |
this.alphabetize = alphabetize; |
89 |
this(new org.openide.util.EditableProperties(alphabetize)); |
123 |
items = new LinkedList<Item>(); |
|
|
124 |
itemIndex = new HashMap<String,Item>(); |
125 |
} |
90 |
} |
126 |
|
91 |
|
127 |
/** |
92 |
/** |
Lines 136-164
Link Here
|
136 |
} |
101 |
} |
137 |
|
102 |
|
138 |
/** |
103 |
/** |
139 |
* Creates new instance from an existing one. |
|
|
140 |
* @param ep an instance of EditableProperties |
141 |
*/ |
142 |
private EditableProperties(EditableProperties ep) { |
143 |
// #64174: use a simple deep copy for speed |
144 |
alphabetize = ep.alphabetize; |
145 |
items = new LinkedList<Item>(); |
146 |
itemIndex = new HashMap<String,Item>(ep.items.size() * 4 / 3 + 1); |
147 |
for (Item _i : ep.items) { |
148 |
Item i = (Item) _i.clone(); |
149 |
items.add(i); |
150 |
itemIndex.put(i.getKey(), i); |
151 |
} |
152 |
} |
153 |
|
154 |
/** |
155 |
* Returns a set view of the mappings ordered according to their file |
104 |
* Returns a set view of the mappings ordered according to their file |
156 |
* position. Each element in this set is a Map.Entry. See |
105 |
* position. Each element in this set is a Map.Entry. See |
157 |
* {@link AbstractMap#entrySet} for more details. |
106 |
* {@link AbstractMap#entrySet} for more details. |
158 |
* @return set with Map.Entry instances. |
107 |
* @return set with Map.Entry instances. |
159 |
*/ |
108 |
*/ |
160 |
public Set<Map.Entry<String,String>> entrySet() { |
109 |
public Set<Map.Entry<String,String>> entrySet() { |
161 |
return new SetImpl(this); |
110 |
return delegate.entrySet(); |
162 |
} |
111 |
} |
163 |
|
112 |
|
164 |
/** |
113 |
/** |
Lines 167-211
Link Here
|
167 |
* @throws IOException if the contents are malformed or the stream could not be read |
116 |
* @throws IOException if the contents are malformed or the stream could not be read |
168 |
*/ |
117 |
*/ |
169 |
public void load(InputStream stream) throws IOException { |
118 |
public void load(InputStream stream) throws IOException { |
170 |
int state = WAITING_FOR_KEY_VALUE; |
119 |
delegate.load(stream); |
171 |
BufferedReader input = new BufferedReader(new InputStreamReader(stream, "ISO-8859-1")); |
|
|
172 |
List<String> tempList = new LinkedList<String>(); |
173 |
String line; |
174 |
int commentLinesCount = 0; |
175 |
// Read block of lines and create instance of Item for each. |
176 |
// Separator is: either empty line or valid end of proeprty declaration |
177 |
while (null != (line = input.readLine())) { |
178 |
tempList.add(line); |
179 |
boolean empty = isEmpty(line); |
180 |
boolean comment = isComment(line); |
181 |
if (state == WAITING_FOR_KEY_VALUE) { |
182 |
if (empty) { |
183 |
// empty line: create Item without any key |
184 |
createNonKeyItem(tempList); |
185 |
commentLinesCount = 0; |
186 |
} else { |
187 |
if (comment) { |
188 |
commentLinesCount++; |
189 |
} else { |
190 |
state = READING_KEY_VALUE; |
191 |
} |
192 |
} |
193 |
} |
194 |
if (state == READING_KEY_VALUE && !isContinue(line)) { |
195 |
// valid end of property declaration: create Item for it |
196 |
createKeyItem(tempList, commentLinesCount); |
197 |
state = WAITING_FOR_KEY_VALUE; |
198 |
commentLinesCount = 0; |
199 |
} |
200 |
} |
201 |
if (tempList.size() > 0) { |
202 |
if (state == READING_KEY_VALUE) { |
203 |
// value was not ended correctly? ignore. |
204 |
createKeyItem(tempList, commentLinesCount); |
205 |
} else { |
206 |
createNonKeyItem(tempList); |
207 |
} |
208 |
} |
209 |
} |
120 |
} |
210 |
|
121 |
|
211 |
/** |
122 |
/** |
Lines 214-254
Link Here
|
214 |
* @throws IOException if the stream could not be written to |
125 |
* @throws IOException if the stream could not be written to |
215 |
*/ |
126 |
*/ |
216 |
public void store(OutputStream stream) throws IOException { |
127 |
public void store(OutputStream stream) throws IOException { |
217 |
boolean previousLineWasEmpty = true; |
128 |
delegate.store(stream); |
218 |
BufferedWriter output = new BufferedWriter(new OutputStreamWriter(stream, "ISO-8859-1")); |
|
|
219 |
for (Item item : items) { |
220 |
if (item.isSeparate() && !previousLineWasEmpty) { |
221 |
output.newLine(); |
222 |
} |
223 |
String line = null; |
224 |
Iterator<String> it = item.getRawData().iterator(); |
225 |
while (it.hasNext()) { |
226 |
line = it.next(); |
227 |
output.write(line); |
228 |
output.newLine(); |
229 |
} |
230 |
if (line != null) { |
231 |
previousLineWasEmpty = isEmpty(line); |
232 |
} |
233 |
} |
234 |
output.flush(); |
235 |
} |
129 |
} |
236 |
|
130 |
|
237 |
@Override |
131 |
@Override |
238 |
public String put(String key, String value) { |
132 |
public String put(String key, String value) { |
239 |
if (key == null || value == null) { |
133 |
return delegate.put(key, value); |
240 |
throw new NullPointerException(); |
|
|
241 |
} |
242 |
Item item = itemIndex.get(key); |
243 |
String result = null; |
244 |
if (item != null) { |
245 |
result = item.getValue(); |
246 |
item.setValue(value); |
247 |
} else { |
248 |
item = new Item(key, value); |
249 |
addItem(item, alphabetize); |
250 |
} |
251 |
return result; |
252 |
} |
134 |
} |
253 |
|
135 |
|
254 |
/** |
136 |
/** |
Lines 258-264
Link Here
|
258 |
* @return the property value, or null if it was not defined |
140 |
* @return the property value, or null if it was not defined |
259 |
*/ |
141 |
*/ |
260 |
public String getProperty(String key) { |
142 |
public String getProperty(String key) { |
261 |
return get(key); |
143 |
return delegate.getProperty(key); |
262 |
} |
144 |
} |
263 |
|
145 |
|
264 |
/** |
146 |
/** |
Lines 269-275
Link Here
|
269 |
* @return previous value of the property or null if there was not any |
151 |
* @return previous value of the property or null if there was not any |
270 |
*/ |
152 |
*/ |
271 |
public String setProperty(String key, String value) { |
153 |
public String setProperty(String key, String value) { |
272 |
return put(key, value); |
154 |
return delegate.setProperty(key, value); |
273 |
} |
155 |
} |
274 |
|
156 |
|
275 |
/** |
157 |
/** |
Lines 284-301
Link Here
|
284 |
* @return previous value of the property or null if there was not any |
166 |
* @return previous value of the property or null if there was not any |
285 |
*/ |
167 |
*/ |
286 |
public String setProperty(String key, String[] value) { |
168 |
public String setProperty(String key, String[] value) { |
287 |
String result = get(key); |
169 |
return delegate.setProperty(key, value); |
288 |
if (key == null || value == null) { |
|
|
289 |
throw new NullPointerException(); |
290 |
} |
291 |
List<String> valueList = Arrays.asList(value); |
292 |
Item item = itemIndex.get(key); |
293 |
if (item != null) { |
294 |
item.setValue(valueList); |
295 |
} else { |
296 |
addItem(new Item(key, valueList), alphabetize); |
297 |
} |
298 |
return result; |
299 |
} |
170 |
} |
300 |
|
171 |
|
301 |
/** |
172 |
/** |
Lines 309-319
Link Here
|
309 |
* delimiter character is included |
180 |
* delimiter character is included |
310 |
*/ |
181 |
*/ |
311 |
public String[] getComment(String key) { |
182 |
public String[] getComment(String key) { |
312 |
Item item = itemIndex.get(key); |
183 |
return delegate.getComment(key); |
313 |
if (item == null) { |
|
|
314 |
return new String[0]; |
315 |
} |
316 |
return item.getComment(); |
317 |
} |
184 |
} |
318 |
|
185 |
|
319 |
/** |
186 |
/** |
Lines 329-340
Link Here
|
329 |
* item by empty line |
196 |
* item by empty line |
330 |
*/ |
197 |
*/ |
331 |
public void setComment(String key, String[] comment, boolean separate) { |
198 |
public void setComment(String key, String[] comment, boolean separate) { |
332 |
// XXX: check validity of comment parameter |
199 |
delegate.setComment(key, comment, separate); |
333 |
Item item = itemIndex.get(key); |
|
|
334 |
if (item == null) { |
335 |
throw new IllegalArgumentException("Cannot set comment for non-existing property "+key); |
336 |
} |
337 |
item.setComment(comment, separate); |
338 |
} |
200 |
} |
339 |
|
201 |
|
340 |
@Override |
202 |
@Override |
Lines 347-881
Link Here
|
347 |
* @return a clone of this object |
209 |
* @return a clone of this object |
348 |
*/ |
210 |
*/ |
349 |
public EditableProperties cloneProperties() { |
211 |
public EditableProperties cloneProperties() { |
350 |
return new EditableProperties(this); |
212 |
return new EditableProperties(delegate.cloneProperties()); |
351 |
} |
213 |
} |
352 |
|
214 |
|
353 |
// non-key item is block of empty lines/comment not associated with any property |
|
|
354 |
private void createNonKeyItem(List<String> lines) { |
355 |
// First check that previous item is not non-key item. |
356 |
if (!items.isEmpty()) { |
357 |
Item item = items.getLast(); |
358 |
if (item.getKey() == null) { |
359 |
// it is non-key item: merge them |
360 |
item.addCommentLines(lines); |
361 |
lines.clear(); |
362 |
return; |
363 |
} |
364 |
} |
365 |
// create new non-key item |
366 |
Item item = new Item(lines); |
367 |
addItem(item, false); |
368 |
lines.clear(); |
369 |
} |
370 |
|
371 |
// opposite to non-key item: item with valid property declaration and |
372 |
// perhaps some comment lines |
373 |
private void createKeyItem(List<String> lines, int commentLinesCount) { |
374 |
Item item = new Item(lines.subList(0, commentLinesCount), lines.subList(commentLinesCount, lines.size())); |
375 |
addItem(item, false); |
376 |
lines.clear(); |
377 |
} |
378 |
|
379 |
private void addItem(Item item, boolean sort) { |
380 |
String key = item.getKey(); |
381 |
if (sort) { |
382 |
assert key != null; |
383 |
ListIterator<Item> it = items.listIterator(); |
384 |
while (it.hasNext()) { |
385 |
String k = it.next().getKey(); |
386 |
if (k != null && k.compareToIgnoreCase(key) > 0) { |
387 |
it.previous(); |
388 |
it.add(item); |
389 |
itemIndex.put(key, item); |
390 |
return; |
391 |
} |
392 |
} |
393 |
} |
394 |
items.add(item); |
395 |
if (key != null) { |
396 |
itemIndex.put(key, item); |
397 |
} |
398 |
} |
399 |
|
400 |
// does property declaration continue on next line? |
401 |
private boolean isContinue(String line) { |
402 |
int index = line.length() - 1; |
403 |
int slashCount = 0; |
404 |
while (index >= 0 && line.charAt(index) == '\\') { |
405 |
slashCount++; |
406 |
index--; |
407 |
} |
408 |
// if line ends with odd number of backslash then property definition |
409 |
// continues on next line |
410 |
return (slashCount % 2 != 0); |
411 |
} |
412 |
|
413 |
// does line start with comment delimiter? (whitespaces are ignored) |
414 |
private static boolean isComment(String line) { |
415 |
line = trimLeft(line); |
416 |
if (line.length() != 0 && commentChars.indexOf(line.charAt(0)) != -1) { |
417 |
return true; |
418 |
} else { |
419 |
return false; |
420 |
} |
421 |
} |
422 |
|
423 |
// is line empty? (whitespaces are ignored) |
424 |
private static boolean isEmpty(String line) { |
425 |
return trimLeft(line).length() == 0; |
426 |
} |
427 |
|
428 |
// remove all whitespaces from left |
429 |
private static String trimLeft(String line) { |
430 |
int start = 0; |
431 |
while (start < line.length()) { |
432 |
if (whiteSpaceChars.indexOf(line.charAt(start)) == -1) { |
433 |
break; |
434 |
} |
435 |
start++; |
436 |
} |
437 |
return line.substring(start); |
438 |
} |
439 |
|
440 |
/** |
441 |
* Representation of one item read from properties file. It can be either |
442 |
* valid property declaration with associated comment or chunk of empty |
443 |
* lines or lines with comment which are not associated with any property. |
444 |
*/ |
445 |
private static class Item implements Cloneable { |
446 |
|
447 |
/** Lines of comment as read from properties file and as they will be |
448 |
* written back to properties file. */ |
449 |
private List<String> commentLines; |
450 |
|
451 |
/** Lines with property name and value declaration as read from |
452 |
* properties file and as they will be written back to properties file. */ |
453 |
private List<String> keyValueLines; |
454 |
|
455 |
/** Property key */ |
456 |
private String key; |
457 |
|
458 |
/** Property value */ |
459 |
private String value; |
460 |
|
461 |
/** Should this property be separated from previous one by at least |
462 |
* one empty line. */ |
463 |
private boolean separate; |
464 |
|
465 |
// constructor only for cloning |
466 |
private Item() { |
467 |
} |
468 |
|
469 |
/** |
470 |
* Create instance which does not have any key and value - just |
471 |
* some empty or comment lines. This item is READ-ONLY. |
472 |
*/ |
473 |
public Item(List<String> commentLines) { |
474 |
this.commentLines = new ArrayList<String>(commentLines); |
475 |
} |
476 |
|
477 |
/** |
478 |
* Create instance from the lines of comment and property declaration. |
479 |
* Property name and value will be split. |
480 |
*/ |
481 |
public Item(List<String> commentLines, List<String> keyValueLines) { |
482 |
this.commentLines = new ArrayList<String>(commentLines); |
483 |
this.keyValueLines = new ArrayList<String>(keyValueLines); |
484 |
parse(keyValueLines); |
485 |
} |
486 |
|
487 |
/** |
488 |
* Create new instance with key and value. |
489 |
*/ |
490 |
public Item(String key, String value) { |
491 |
this.key = key; |
492 |
this.value = value; |
493 |
} |
494 |
|
495 |
/** |
496 |
* Create new instance with key and value. |
497 |
*/ |
498 |
public Item(String key, List<String> value) { |
499 |
this.key = key; |
500 |
setValue(value); |
501 |
} |
502 |
|
503 |
// backdoor for merging non-key items |
504 |
void addCommentLines(List<String> lines) { |
505 |
assert key == null; |
506 |
commentLines.addAll(lines); |
507 |
} |
508 |
|
509 |
public String[] getComment() { |
510 |
String[] res = new String[commentLines.size()]; |
511 |
for (int i = 0; i < res.length; i++) { |
512 |
// #60249: the comment might have Unicode chars in escapes. |
513 |
res[i] = decodeUnicode(commentLines.get(i)); |
514 |
} |
515 |
return res; |
516 |
} |
517 |
|
518 |
public void setComment(String[] commentLines, boolean separate) { |
519 |
this.separate = separate; |
520 |
this.commentLines = new ArrayList<String>(commentLines.length); |
521 |
for (int i = 0; i < commentLines.length; i++) { |
522 |
// #60249 again - write only ISO-8859-1. |
523 |
this.commentLines.add(encodeUnicode(commentLines[i])); |
524 |
} |
525 |
} |
526 |
|
527 |
public String getKey() { |
528 |
return key; |
529 |
} |
530 |
|
531 |
public String getValue() { |
532 |
return value; |
533 |
} |
534 |
|
535 |
public void setValue(String value) { |
536 |
this.value = value; |
537 |
keyValueLines = null; |
538 |
} |
539 |
|
540 |
public void setValue(List<String> value) { |
541 |
StringBuffer val = new StringBuffer(); |
542 |
List<String> l = new ArrayList<String>(); |
543 |
if (!value.isEmpty()) { |
544 |
l.add(encode(key, true) + "=\\"); // NOI18N |
545 |
Iterator<String> it = value.iterator(); |
546 |
while (it.hasNext()) { |
547 |
String s = it.next(); |
548 |
val.append(s); |
549 |
s = encode(s, false); |
550 |
l.add(it.hasNext() ? INDENT + s + '\\' : INDENT + s); // NOI18N |
551 |
} |
552 |
} else { |
553 |
// #45061: for no vals, use just "prop=" |
554 |
l.add(encode(key, true) + '='); // NOI18N |
555 |
} |
556 |
this.value = val.toString(); |
557 |
keyValueLines = l; |
558 |
} |
559 |
|
560 |
public boolean isSeparate() { |
561 |
return separate; |
562 |
} |
563 |
|
564 |
/** |
565 |
* Returns persistent image of this property. |
566 |
*/ |
567 |
public List<String> getRawData() { |
568 |
List<String> l = new ArrayList<String>(); |
569 |
if (commentLines != null) { |
570 |
l.addAll(commentLines); |
571 |
} |
572 |
if (keyValueLines == null) { |
573 |
keyValueLines = new ArrayList<String>(); |
574 |
if (key != null && value != null) { |
575 |
keyValueLines.add(encode(key, true)+"="+encode(value, false)); |
576 |
} |
577 |
} |
578 |
l.addAll(keyValueLines); |
579 |
return l; |
580 |
} |
581 |
|
582 |
private void parse(List<String> keyValueLines) { |
583 |
// merge lines into one: |
584 |
String line = mergeLines(keyValueLines); |
585 |
// split key and value |
586 |
splitKeyValue(line); |
587 |
} |
588 |
|
589 |
private String mergeLines(List<String> lines) { |
590 |
String line = ""; // XXX use StringBuilder instead |
591 |
Iterator<String> it = lines.iterator(); |
592 |
while (it.hasNext()) { |
593 |
String l = trimLeft(it.next()); |
594 |
// if this is not the last line then remove last backslash |
595 |
if (it.hasNext()) { |
596 |
assert l.endsWith("\\") : lines; |
597 |
l = l.substring(0, l.length()-1); |
598 |
} |
599 |
line += l; |
600 |
} |
601 |
return line; |
602 |
} |
603 |
|
604 |
private void splitKeyValue(String line) { |
605 |
int separatorIndex = 0; |
606 |
while (separatorIndex < line.length()) { |
607 |
char ch = line.charAt(separatorIndex); |
608 |
if (ch == '\\') { |
609 |
// ignore next one character |
610 |
separatorIndex++; |
611 |
} else { |
612 |
if (keyValueSeparators.indexOf(ch) != -1) { |
613 |
break; |
614 |
} |
615 |
} |
616 |
separatorIndex++; |
617 |
} |
618 |
key = decode(line.substring(0, separatorIndex)); |
619 |
line = trimLeft(line.substring(separatorIndex)); |
620 |
if (line.length() == 0) { |
621 |
value = ""; |
622 |
return; |
623 |
} |
624 |
if (strictKeyValueSeparators.indexOf(line.charAt(0)) != -1) { |
625 |
line = trimLeft(line.substring(1)); |
626 |
} |
627 |
value = decode(line); |
628 |
} |
629 |
|
630 |
private static String decode(String input) { |
631 |
char ch; |
632 |
int len = input.length(); |
633 |
StringBuffer output = new StringBuffer(len); |
634 |
for (int x=0; x<len; x++) { |
635 |
ch = input.charAt(x); |
636 |
if (ch != '\\') { |
637 |
output.append(ch); |
638 |
continue; |
639 |
} |
640 |
x++; |
641 |
if (x==len) { |
642 |
// backslash at the end? syntax error: ignore it |
643 |
continue; |
644 |
} |
645 |
ch = input.charAt(x); |
646 |
if (ch == 'u') { |
647 |
if (x+5>len) { |
648 |
// unicode character not finished? syntax error: ignore |
649 |
output.append(input.substring(x-1)); |
650 |
x += 4; |
651 |
continue; |
652 |
} |
653 |
String val = input.substring(x+1, x+5); |
654 |
try { |
655 |
output.append((char)Integer.parseInt(val, 16)); |
656 |
} catch (NumberFormatException e) { |
657 |
// #46234: handle gracefully |
658 |
output.append(input.substring(x - 1, x + 5)); |
659 |
} |
660 |
x += 4; |
661 |
} else { |
662 |
if (ch == 't') ch = '\t'; |
663 |
else if (ch == 'r') ch = '\r'; |
664 |
else if (ch == 'n') ch = '\n'; |
665 |
else if (ch == 'f') ch = '\f'; |
666 |
output.append(ch); |
667 |
} |
668 |
} |
669 |
return output.toString(); |
670 |
} |
671 |
|
672 |
private static String encode(String input, boolean escapeSpace) { |
673 |
int len = input.length(); |
674 |
StringBuffer output = new StringBuffer(len*2); |
675 |
|
676 |
for(int x=0; x<len; x++) { |
677 |
char ch = input.charAt(x); |
678 |
switch(ch) { |
679 |
case ' ': |
680 |
if (x == 0 || escapeSpace) { |
681 |
output.append('\\'); |
682 |
} |
683 |
output.append(' '); |
684 |
break; |
685 |
case '\\': |
686 |
output.append("\\\\"); |
687 |
break; |
688 |
case '\t': |
689 |
output.append("\\t"); |
690 |
break; |
691 |
case '\n': |
692 |
output.append("\\n"); |
693 |
break; |
694 |
case '\r': |
695 |
output.append("\\r"); |
696 |
break; |
697 |
case '\f': |
698 |
output.append("\\f"); |
699 |
break; |
700 |
default: |
701 |
if ((ch < 0x0020) || (ch > 0x007e)) { |
702 |
output.append("\\u"); |
703 |
String hex = Integer.toHexString(ch); |
704 |
for (int i = 0; i < 4 - hex.length(); i++) { |
705 |
output.append('0'); |
706 |
} |
707 |
output.append(hex); |
708 |
} else { |
709 |
output.append(ch); |
710 |
} |
711 |
} |
712 |
} |
713 |
return output.toString(); |
714 |
} |
715 |
|
716 |
private static String decodeUnicode(String input) { |
717 |
char ch; |
718 |
int len = input.length(); |
719 |
StringBuffer output = new StringBuffer(len); |
720 |
for (int x = 0; x < len; x++) { |
721 |
ch = input.charAt(x); |
722 |
if (ch != '\\') { |
723 |
output.append(ch); |
724 |
continue; |
725 |
} |
726 |
x++; |
727 |
if (x==len) { |
728 |
// backslash at the end? syntax error: ignore it |
729 |
continue; |
730 |
} |
731 |
ch = input.charAt(x); |
732 |
if (ch == 'u') { |
733 |
if (x+5>len) { |
734 |
// unicode character not finished? syntax error: ignore |
735 |
output.append(input.substring(x-1)); |
736 |
x += 4; |
737 |
continue; |
738 |
} |
739 |
String val = input.substring(x+1, x+5); |
740 |
try { |
741 |
output.append((char)Integer.parseInt(val, 16)); |
742 |
} catch (NumberFormatException e) { |
743 |
// #46234: handle gracefully |
744 |
output.append(input.substring(x - 1, x + 5)); |
745 |
} |
746 |
x += 4; |
747 |
} else { |
748 |
output.append(ch); |
749 |
} |
750 |
} |
751 |
return output.toString(); |
752 |
} |
753 |
|
754 |
private static String encodeUnicode(String input) { |
755 |
int len = input.length(); |
756 |
StringBuffer output = new StringBuffer(len * 2); |
757 |
for (int x = 0; x < len; x++) { |
758 |
char ch = input.charAt(x); |
759 |
if ((ch < 0x0020) || (ch > 0x007e)) { |
760 |
output.append("\\u"); // NOI18N |
761 |
String hex = Integer.toHexString(ch); |
762 |
for (int i = 0; i < 4 - hex.length(); i++) { |
763 |
output.append('0'); |
764 |
} |
765 |
output.append(hex); |
766 |
} else { |
767 |
output.append(ch); |
768 |
} |
769 |
} |
770 |
return output.toString(); |
771 |
} |
772 |
|
773 |
@Override |
774 |
public Object clone() { |
775 |
Item item = new Item(); |
776 |
if (keyValueLines != null) { |
777 |
item.keyValueLines = new ArrayList<String>(keyValueLines); |
778 |
} |
779 |
if (commentLines != null) { |
780 |
item.commentLines = new ArrayList<String>(commentLines); |
781 |
} |
782 |
item.key = key; |
783 |
item.value = value; |
784 |
item.separate = separate; |
785 |
return item; |
786 |
} |
787 |
|
788 |
} |
789 |
|
790 |
private static class SetImpl extends AbstractSet<Map.Entry<String,String>> { |
791 |
|
792 |
private EditableProperties props; |
793 |
|
794 |
public SetImpl(EditableProperties props) { |
795 |
this.props = props; |
796 |
} |
797 |
|
798 |
public Iterator<Map.Entry<String,String>> iterator() { |
799 |
return new IteratorImpl(props); |
800 |
} |
801 |
|
802 |
public int size() { |
803 |
return props.items.size(); |
804 |
} |
805 |
|
806 |
} |
807 |
|
808 |
private static class IteratorImpl implements Iterator<Map.Entry<String,String>> { |
809 |
|
810 |
private final EditableProperties props; |
811 |
private ListIterator<Item> delegate; |
812 |
|
813 |
public IteratorImpl(EditableProperties props) { |
814 |
this.props = props; |
815 |
delegate = props.items.listIterator(); |
816 |
} |
817 |
|
818 |
public boolean hasNext() { |
819 |
return findNext() != null; |
820 |
} |
821 |
|
822 |
public Map.Entry<String,String> next() { |
823 |
Item item = findNext(); |
824 |
if (item == null) { |
825 |
throw new NoSuchElementException(); |
826 |
} |
827 |
delegate.next(); |
828 |
return new MapEntryImpl(item); |
829 |
} |
830 |
|
831 |
public void remove() { |
832 |
delegate.previous(); |
833 |
Item item = findNext(); |
834 |
if (item == null) { |
835 |
throw new IllegalStateException(); |
836 |
} |
837 |
int index = delegate.nextIndex(); |
838 |
props.items.remove(item); |
839 |
props.itemIndex.remove(item.getKey()); |
840 |
delegate = props.items.listIterator(index); |
841 |
} |
842 |
|
843 |
private Item findNext() { |
844 |
while (delegate.hasNext()) { |
845 |
Item item = delegate.next(); |
846 |
if (item.getKey() != null && item.getValue() != null) { |
847 |
// Found one. Back up! |
848 |
delegate.previous(); |
849 |
return item; |
850 |
} |
851 |
} |
852 |
return null; |
853 |
} |
854 |
|
855 |
} |
856 |
|
857 |
private static class MapEntryImpl implements Map.Entry<String,String> { |
858 |
|
859 |
private Item item; |
860 |
|
861 |
public MapEntryImpl(Item item) { |
862 |
this.item = item; |
863 |
} |
864 |
|
865 |
public String getKey() { |
866 |
return item.getKey(); |
867 |
} |
868 |
|
869 |
public String getValue() { |
870 |
return item.getValue(); |
871 |
} |
872 |
|
873 |
public String setValue(String value) { |
874 |
String result = item.getValue(); |
875 |
item.setValue(value); |
876 |
return result; |
877 |
} |
878 |
|
879 |
} |
880 |
|
881 |
} |
215 |
} |