001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.tomcat5.util;
043:
044: import java.io.BufferedReader;
045: import java.io.BufferedWriter;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.InputStreamReader;
049: import java.io.OutputStream;
050: import java.io.OutputStreamWriter;
051: import java.util.AbstractMap;
052: import java.util.AbstractSet;
053: import java.util.ArrayList;
054: import java.util.Arrays;
055: import java.util.HashMap;
056: import java.util.Iterator;
057: import java.util.LinkedList;
058: import java.util.List;
059: import java.util.Map;
060: import java.util.NoSuchElementException;
061: import java.util.Set;
062:
063: // TODO: remove this class once the issue #65229 gets fixed.
064:
065: /**
066: * Similar to {@link Properties} but designed to retain additional
067: * information needed for safe hand-editing.
068: * Useful for various <samp>*.properties</samp> in a project:
069: * <ol>
070: * <li>Can associate comments with particular entries.
071: * <li>Order of entries preserved during modifications whenever possible.
072: * <li>VCS-friendly: lines which are not semantically modified are not textually modified.
073: * <li>Can automatically insert line breaks in new or modified values at positions
074: * that are likely to be semantically meaningful, e.g. between path components
075: * </ol>
076: * The file format (including encoding etc.) is compatible with the regular JRE implementation.
077: * Only (non-null) String is supported for keys and values.
078: * This class is not thread-safe; use only from a single thread, or use {@link Collections#synchronizedMap}.
079: * @author Jesse Glick, David Konecny
080: */
081: public final class EditableProperties extends AbstractMap
082: /*<String,String>*/implements Cloneable {
083:
084: /** List of Item instances as read from the properties file. Order is important.
085: * Saving properties will save then in this order. */
086: private List/*<Item>*/items;
087:
088: /** Map of [property key, Item instance] for faster access. */
089: private Map itemIndex;
090:
091: private boolean alphabetize = true;
092:
093: private static final String keyValueSeparators = "=: \t\r\n\f";
094:
095: private static final String strictKeyValueSeparators = "=:";
096:
097: private static final String whiteSpaceChars = " \t\r\n\f";
098:
099: private static final String commentChars = "#!";
100:
101: private static final String INDENT = " ";
102:
103: // parse states:
104: private static final int WAITING_FOR_KEY_VALUE = 1;
105: private static final int READING_KEY_VALUE = 2;
106:
107: /**
108: * Creates empty instance which items will not be sorted by default.
109: */
110: public EditableProperties() {
111: items = new ArrayList();
112: itemIndex = new HashMap();
113: }
114:
115: /**
116: * Creates empty instance.
117: * @param alphabetize alphabetize new items according to key or not
118: */
119: public EditableProperties(boolean alphabetize) {
120: this ();
121: this .alphabetize = alphabetize;
122: }
123:
124: /**
125: * Creates instance from an existing map. No comments will be defined.
126: * Any order from the existing map will be retained.
127: * @param map a map from String to String
128: */
129: public EditableProperties(Map map) {
130: this ();
131: putAll(map);
132: }
133:
134: /**
135: * Creates new instance from an existing one.
136: * @param ep an instance of EditableProperties
137: */
138: EditableProperties(EditableProperties ep) {
139: this ();
140: Iterator it = ep.items.iterator();
141: while (it.hasNext()) {
142: Item item = (Item) it.next();
143: addItem((Item) item.clone(), false);
144: }
145: }
146:
147: /**
148: * Returns a set view of the mappings ordered according to their file
149: * position. Each element in this set is a Map.Entry. See
150: * {@link AbstractMap#entrySet} for more dertails.
151: * @return set with Map.Entry instances.
152: */
153: public Set entrySet() {
154: return new SetImpl(this );
155: }
156:
157: /**
158: * Load properties from a stream.
159: * @param stream an input stream
160: * @throws IOException if the contents are malformed or the stream could not be read
161: */
162: public void load(InputStream stream) throws IOException {
163: int state = WAITING_FOR_KEY_VALUE;
164: BufferedReader input = new BufferedReader(
165: new InputStreamReader(stream, "ISO-8859-1"));
166: LinkedList tempList = new LinkedList();
167: String line;
168: int commentLinesCount = 0;
169: // Read block of lines and create instance of Item for each.
170: // Separator is: either empty line or valid end of proeprty declaration
171: while (null != (line = input.readLine())) {
172: tempList.add(line);
173: boolean empty = isEmpty(line);
174: boolean comment = isComment(line);
175: if (state == WAITING_FOR_KEY_VALUE) {
176: if (empty) {
177: // empty line: create Item without any key
178: createNonKeyItem(tempList);
179: commentLinesCount = 0;
180: } else {
181: if (comment) {
182: commentLinesCount++;
183: } else {
184: state = READING_KEY_VALUE;
185: }
186: }
187: }
188: if (state == READING_KEY_VALUE && !isContinue(line)) {
189: // valid end of property declaration: create Item for it
190: createKeyItem(tempList, commentLinesCount);
191: state = WAITING_FOR_KEY_VALUE;
192: commentLinesCount = 0;
193: }
194: }
195: if (tempList.size() > 0) {
196: if (state == READING_KEY_VALUE) {
197: // value was not ended correctly? ignore.
198: createKeyItem(tempList, commentLinesCount);
199: } else {
200: createNonKeyItem(tempList);
201: }
202: }
203: }
204:
205: /**
206: * Store properties to a stream.
207: * @param stream an output stream
208: * @throws IOException if the stream could not be written to
209: */
210: public void store(OutputStream stream) throws IOException {
211: boolean previousLineWasEmpty = true;
212: BufferedWriter output = new BufferedWriter(
213: new OutputStreamWriter(stream, "ISO-8859-1"));
214: Iterator it = items.iterator();
215: while (it.hasNext()) {
216: Item item = (Item) it.next();
217: if (item.isSeparate() && !previousLineWasEmpty) {
218: output.newLine();
219: }
220: Iterator it2 = item.getRawData().iterator();
221: String line = null;
222: while (it2.hasNext()) {
223: line = (String) it2.next();
224: output.write(line);
225: output.newLine();
226: }
227: if (line != null) {
228: previousLineWasEmpty = isEmpty(line);
229: }
230: }
231: output.flush();
232: }
233:
234: public Object put(Object key, Object value) {
235: if (key == null || value == null) {
236: throw new NullPointerException();
237: }
238: Item item = (Item) itemIndex.get((String) key);
239: String result = null;
240: if (item != null) {
241: result = item.getValue();
242: item.setValue((String) value);
243: } else {
244: item = new Item((String) key, (String) value);
245: addItem(item, alphabetize);
246: }
247: return result;
248: }
249:
250: /**
251: * Convenience method to get a property as a string.
252: * Same behavior as {@link #get} but has the correct return type.
253: * @param key a property name; cannot be null nor empty
254: * @return the property value, or null if it was not defined
255: */
256: public String getProperty(String key) {
257: return (String) get(key);
258: }
259:
260: /**
261: * Convenience method to set a property.
262: * Same behavior as {@link #put} but has the correct argument types.
263: * (Slightly slower however.)
264: * @param key a property name; cannot be null nor empty
265: * @param value the desired value; cannot be null
266: * @return previous value of the property or null if there was not any
267: */
268: public String setProperty(String key, String value) {
269: String result = getProperty(key);
270: put(key, value);
271: return result;
272: }
273:
274: /**
275: * Sets a property to a value broken into segments for readability.
276: * Same behavior as {@link #setProperty(String,String)} with the difference that each item
277: * will be stored on its own line of text. {@link #getProperty} will simply concatenate
278: * all the items into one string, so generally separators
279: * (such as <samp>:</samp> for path-like properties) must be included in
280: * the items (for example, at the end of all but the last item).
281: * @param key a property name; cannot be null nor empty
282: * @param value the desired value; cannot be null; can be empty array
283: * @return previous value of the property or null if there was not any
284: */
285: public String setProperty(String key, String[] value) {
286: String result = getProperty(key);
287: if (key == null || value == null) {
288: throw new NullPointerException();
289: }
290: List/*<String>*/valueList = Arrays.asList(value);
291: Item item = (Item) itemIndex.get(key);
292: if (item != null) {
293: item.setValue(valueList);
294: } else {
295: addItem(new Item(key, valueList), alphabetize);
296: }
297: return result;
298: }
299:
300: /**
301: * Returns comment associated with the property. The comment lines are
302: * returned as defined in properties file, that is comment delimiter is
303: * included. Comment for property is defined as: continuous block of lines
304: * starting with comment delimiter which are followed by property
305: * declaration (no empty line separator allowed).
306: * @param key a property name; cannot be null nor empty
307: * @return array of String lines as specified in properties file; comment
308: * delimiter character is included
309: */
310: public String[] getComment(String key) {
311: Item item = (Item) itemIndex.get(key);
312: if (item == null) {
313: return new String[0];
314: }
315: return item.getComment();
316: }
317:
318: /**
319: * Create comment for the property.
320: * <p>Note: if a comment includes non-ISO-8859-1 characters, they will be written
321: * to disk using Unicode escapes (and {@link #getComment} will interpret
322: * such escapes), but of course they will be unreadable for humans.
323: * @param key a property name; cannot be null nor empty
324: * @param comment lines of comment which will be written just above
325: * the property; no reformatting; comment lines must start with
326: * comment delimiter; cannot be null; cannot be emty array
327: * @param separate whether the comment should be separated from previous
328: * item by empty line
329: */
330: public void setComment(String key, String[] comment,
331: boolean separate) {
332: // XXX: check validity of comment parameter
333: Item item = (Item) itemIndex.get(key);
334: if (item == null) {
335: throw new IllegalArgumentException(
336: "Cannot set comment for non-existing property "
337: + key);
338: }
339: item.setComment(comment, separate);
340: }
341:
342: public Object clone() {
343: return cloneProperties();
344: }
345:
346: /**
347: * Create an exact copy of this properties object.
348: * @return a clone of this object
349: */
350: public EditableProperties cloneProperties() {
351: return new EditableProperties(this );
352: }
353:
354: // non-key item is block of empty lines/comment not associated with any property
355: private void createNonKeyItem(List/*<String>*/lines) {
356: // First check that previous item is not non-key item.
357: if (items.size() > 0) {
358: Item item = (Item) items.get(items.size() - 1);
359: if (item.getKey() == null) {
360: // it is non-key item: merge them
361: item.addCommentLines(lines);
362: lines.clear();
363: return;
364: }
365: }
366: // create new non-key item
367: Item item = new Item(lines);
368: addItem(item, false);
369: lines.clear();
370: }
371:
372: // opposite to non-key item: item with valid property declaration and
373: // perhaps some comment lines
374: private void createKeyItem(List/*<String>*/lines,
375: int commentLinesCount) {
376: Item item = new Item(lines.subList(0, commentLinesCount), lines
377: .subList(commentLinesCount, lines.size()));
378: addItem(item, false);
379: lines.clear();
380: }
381:
382: private void addItem(Item item, boolean sort) {
383: String key = item.getKey();
384: if (sort) {
385: assert key != null;
386: for (int i = 0; i < items.size(); i++) {
387: String k = ((Item) items.get(i)).getKey();
388: if (k != null && k.compareToIgnoreCase(key) > 0) {
389: items.add(i, item);
390: itemIndex.put(key, item);
391: return;
392: }
393: }
394: }
395: items.add(item);
396: if (key != null) {
397: itemIndex.put(key, item);
398: }
399: }
400:
401: private void removeItem(Item item) {
402: items.remove(item);
403: if (item.getKey() != null) {
404: itemIndex.remove(item.getKey());
405: }
406: }
407:
408: // does property declaration continue on next line?
409: private boolean isContinue(String line) {
410: int index = line.length() - 1;
411: int slashCount = 0;
412: while (index >= 0 && line.charAt(index) == '\\') {
413: slashCount++;
414: index--;
415: }
416: // if line ends with odd number of backslash then property definition
417: // continues on next line
418: return (slashCount % 2 != 0);
419: }
420:
421: // does line start with comment delimiter? (whitespaces are ignored)
422: private static boolean isComment(String line) {
423: line = trimLeft(line);
424: if (line.length() != 0
425: && commentChars.indexOf(line.charAt(0)) != -1) {
426: return true;
427: } else {
428: return false;
429: }
430: }
431:
432: // is line empty? (whitespaces are ignored)
433: private static boolean isEmpty(String line) {
434: return trimLeft(line).length() == 0;
435: }
436:
437: // remove all whitespaces from left
438: private static String trimLeft(String line) {
439: int start = 0;
440: while (start < line.length()) {
441: if (whiteSpaceChars.indexOf(line.charAt(start)) == -1) {
442: break;
443: }
444: start++;
445: }
446: return line.substring(start);
447: }
448:
449: /**
450: * Representation of one item read from properties file. It can be either
451: * valid property declaration with associated comment or chunk of empty
452: * lines or lines with comment which are not associated with any property.
453: */
454: private static class Item implements Cloneable {
455:
456: /** Lines of comment as read from properties file and as they will be
457: * written back to properties file. */
458: private List/*<String>*/commentLines;
459:
460: /** Lines with property name and value declaration as read from
461: * properties file and as they will be written back to properties file. */
462: private List/*<String>*/keyValueLines;
463:
464: /** Property key */
465: private String key;
466:
467: /** Property value */
468: private String value;
469:
470: /** Should this property be separated from previous one by at least
471: * one empty line. */
472: private boolean separate;
473:
474: // constructor only for cloning
475: private Item() {
476: }
477:
478: /**
479: * Create instance which does not have any key and value - just
480: * some empty or comment lines. This item is READ-ONLY.
481: */
482: public Item(List/*<String>*/commentLines) {
483: this .commentLines = new ArrayList(commentLines);
484: }
485:
486: /**
487: * Create instance from the lines of comment and property declaration.
488: * Property name and value will be split.
489: */
490: public Item(List/*<String>*/commentLines,
491: List/*<String>*/keyValueLines) {
492: this .commentLines = new ArrayList(commentLines);
493: this .keyValueLines = new ArrayList(keyValueLines);
494: parse(keyValueLines);
495: }
496:
497: /**
498: * Create new instance with key and value.
499: */
500: public Item(String key, String value) {
501: this .key = key;
502: this .value = value;
503: }
504:
505: /**
506: * Create new instance with key and value.
507: */
508: public Item(String key, List/*<String>*/value) {
509: this .key = key;
510: setValue(value);
511: }
512:
513: // backdoor for merging non-key items
514: void addCommentLines(List/*<String>*/lines) {
515: assert key == null;
516: commentLines.addAll(lines);
517: }
518:
519: public String[] getComment() {
520: String[] res = new String[commentLines.size()];
521: for (int i = 0; i < res.length; i++) {
522: // #60249: the comment might have Unicode chars in escapes.
523: res[i] = decodeUnicode((String) commentLines.get(i));
524: }
525: return res;
526: }
527:
528: public void setComment(String[] commentLines, boolean separate) {
529: this .separate = separate;
530: this .commentLines = new ArrayList(commentLines.length);
531: for (int i = 0; i < commentLines.length; i++) {
532: // #60249 again - write only ISO-8859-1.
533: this .commentLines.add(encodeUnicode(commentLines[i]));
534: }
535: }
536:
537: public String getKey() {
538: return key;
539: }
540:
541: public String getValue() {
542: return value;
543: }
544:
545: public void setValue(String value) {
546: this .value = value;
547: keyValueLines = null;
548: }
549:
550: public void setValue(List/*<String>*/value) {
551: StringBuffer val = new StringBuffer();
552: List/*<String>*/l = new ArrayList();
553: if (!value.isEmpty()) {
554: l.add(encode(key, true) + "=\\"); // NOI18N
555: Iterator it = value.iterator();
556: while (it.hasNext()) {
557: String s = (String) it.next();
558: val.append(s);
559: s = encode(s, false);
560: l
561: .add(it.hasNext() ? INDENT + s + '\\'
562: : INDENT + s); // NOI18N
563: }
564: } else {
565: // #45061: for no vals, use just "prop="
566: l.add(encode(key, true) + '='); // NOI18N
567: }
568: this .value = val.toString();
569: keyValueLines = l;
570: }
571:
572: public boolean isSeparate() {
573: return separate;
574: }
575:
576: /**
577: * Returns persistent image of this property.
578: */
579: public List/*<String>*/getRawData() {
580: ArrayList l = new ArrayList();
581: if (commentLines != null) {
582: l.addAll(commentLines);
583: }
584: if (keyValueLines != null) {
585: l.addAll(keyValueLines);
586: } else {
587: keyValueLines = new ArrayList();
588: if (key != null && value != null) {
589: keyValueLines.add(encode(key, true) + "="
590: + encode(value, false));
591: }
592: l.addAll(keyValueLines);
593: }
594: return l;
595: }
596:
597: private void parse(List/*<String>*/keyValueLines) {
598: // merge lines into one:
599: String line = mergeLines(keyValueLines);
600: // split key and value
601: splitKeyValue(line);
602: }
603:
604: private String mergeLines(List/*<String>*/lines) {
605: StringBuilder line = new StringBuilder();
606: Iterator it = lines.iterator();
607: while (it.hasNext()) {
608: String l = trimLeft((String) it.next());
609: // if this is not the last line then remove last backslash
610: if (it.hasNext()) {
611: assert l.endsWith("\\") : lines;
612: l = l.substring(0, l.length() - 1);
613: }
614: line.append(l);
615: }
616: return line.toString();
617: }
618:
619: private void splitKeyValue(String line) {
620: int separatorIndex = 0;
621: while (separatorIndex < line.length()) {
622: char ch = line.charAt(separatorIndex);
623: if (ch == '\\') {
624: // ignore next one character
625: separatorIndex++;
626: } else {
627: if (keyValueSeparators.indexOf(ch) != -1) {
628: break;
629: }
630: }
631: separatorIndex++;
632: }
633: key = decode(line.substring(0, separatorIndex));
634: line = trimLeft(line.substring(separatorIndex));
635: if (line.length() == 0) {
636: value = "";
637: return;
638: }
639: if (strictKeyValueSeparators.indexOf(line.charAt(0)) != -1) {
640: line = trimLeft(line.substring(1));
641: }
642: value = decode(line);
643: }
644:
645: private static String decode(String input) {
646: char ch;
647: int len = input.length();
648: StringBuffer output = new StringBuffer(len);
649: for (int x = 0; x < len; x++) {
650: ch = input.charAt(x);
651: if (ch != '\\') {
652: output.append(ch);
653: continue;
654: }
655: x++;
656: if (x == len) {
657: // backslash at the end? syntax error: ignore it
658: continue;
659: }
660: ch = input.charAt(x);
661: if (ch == 'u') {
662: if (x + 5 > len) {
663: // unicode character not finished? syntax error: ignore
664: output.append(input.substring(x - 1));
665: x += 4;
666: continue;
667: }
668: String val = input.substring(x + 1, x + 5);
669: try {
670: output.append((char) Integer.parseInt(val, 16));
671: } catch (NumberFormatException e) {
672: // #46234: handle gracefully
673: output.append(input.substring(x - 1, x + 5));
674: }
675: x += 4;
676: } else {
677: if (ch == 't') {
678: ch = '\t';
679: } else if (ch == 'r') {
680: ch = '\r';
681: } else if (ch == 'n') {
682: ch = '\n';
683: } else if (ch == 'f') {
684: ch = '\f';
685: }
686: output.append(ch);
687: }
688: }
689: return output.toString();
690: }
691:
692: private static String encode(String input, boolean escapeSpace) {
693: int len = input.length();
694: StringBuffer output = new StringBuffer(len * 2);
695:
696: for (int x = 0; x < len; x++) {
697: char ch = input.charAt(x);
698: switch (ch) {
699: case ' ':
700: if (x == 0 || escapeSpace) {
701: output.append('\\');
702: }
703: output.append(' ');
704: break;
705: case '\\':
706: output.append("\\\\");
707: break;
708: case '\t':
709: output.append("\\t");
710: break;
711: case '\n':
712: output.append("\\n");
713: break;
714: case '\r':
715: output.append("\\r");
716: break;
717: case '\f':
718: output.append("\\f");
719: break;
720: default:
721: if ((ch < 0x0020) || (ch > 0x007e)) {
722: output.append("\\u");
723: String hex = Integer.toHexString(ch);
724: for (int i = 0; i < 4 - hex.length(); i++) {
725: output.append('0');
726: }
727: output.append(hex);
728: } else {
729: output.append(ch);
730: }
731: }
732: }
733: return output.toString();
734: }
735:
736: private static String decodeUnicode(String input) {
737: char ch;
738: int len = input.length();
739: StringBuffer output = new StringBuffer(len);
740: for (int x = 0; x < len; x++) {
741: ch = input.charAt(x);
742: if (ch != '\\') {
743: output.append(ch);
744: continue;
745: }
746: x++;
747: if (x == len) {
748: // backslash at the end? syntax error: ignore it
749: continue;
750: }
751: ch = input.charAt(x);
752: if (ch == 'u') {
753: if (x + 5 > len) {
754: // unicode character not finished? syntax error: ignore
755: output.append(input.substring(x - 1));
756: x += 4;
757: continue;
758: }
759: String val = input.substring(x + 1, x + 5);
760: try {
761: output.append((char) Integer.parseInt(val, 16));
762: } catch (NumberFormatException e) {
763: // #46234: handle gracefully
764: output.append(input.substring(x - 1, x + 5));
765: }
766: x += 4;
767: } else {
768: output.append(ch);
769: }
770: }
771: return output.toString();
772: }
773:
774: private static String encodeUnicode(String input) {
775: int len = input.length();
776: StringBuffer output = new StringBuffer(len * 2);
777: for (int x = 0; x < len; x++) {
778: char ch = input.charAt(x);
779: if ((ch < 0x0020) || (ch > 0x007e)) {
780: output.append("\\u"); // NOI18N
781: String hex = Integer.toHexString(ch);
782: for (int i = 0; i < 4 - hex.length(); i++) {
783: output.append('0');
784: }
785: output.append(hex);
786: } else {
787: output.append(ch);
788: }
789: }
790: return output.toString();
791: }
792:
793: public Object clone() {
794: Item item = new Item();
795: if (keyValueLines != null) {
796: item.keyValueLines = new ArrayList(keyValueLines);
797: }
798: if (commentLines != null) {
799: item.commentLines = new ArrayList(commentLines);
800: }
801: item.key = key;
802: item.value = value;
803: return item;
804: }
805:
806: }
807:
808: private static class SetImpl extends AbstractSet {
809:
810: private EditableProperties props;
811:
812: public SetImpl(EditableProperties props) {
813: this .props = props;
814: }
815:
816: public Iterator iterator() {
817: return new IteratorImpl(props);
818: }
819:
820: public int size() {
821: return props.items.size();
822: }
823:
824: }
825:
826: private static class IteratorImpl implements Iterator {
827:
828: private EditableProperties props;
829: private int index = -1;
830: private Item item;
831:
832: public IteratorImpl(EditableProperties props) {
833: this .props = props;
834: }
835:
836: public boolean hasNext() {
837: return findNext() != -1;
838: }
839:
840: public Object next() {
841: index = findNext();
842: if (index == -1) {
843: throw new NoSuchElementException(
844: "There is no more items");
845: }
846: item = (Item) props.items.get(index);
847: return new MapEntryImpl(item);
848: }
849:
850: public void remove() {
851: if (item == null) {
852: throw new IllegalStateException();
853: }
854: props.removeItem(item);
855: index--;
856: item = null;
857: }
858:
859: private int findNext() {
860: int res = index + 1;
861: while (res < props.size()) {
862: Item i = (Item) props.items.get(res);
863: if (i.getKey() != null && i.getValue() != null) {
864: return res;
865: }
866: res++;
867: }
868: return -1;
869: }
870:
871: }
872:
873: private static class MapEntryImpl implements Map.Entry {
874:
875: private Item item;
876:
877: public MapEntryImpl(Item item) {
878: this .item = item;
879: }
880:
881: public Object getKey() {
882: return item.getKey();
883: }
884:
885: public Object getValue() {
886: return item.getValue();
887: }
888:
889: public Object setValue(Object value) {
890: String result = item.getValue();
891: item.setValue((String) value);
892: return result;
893: }
894:
895: }
896:
897: }
|