Automated merge with main-silver
authorffjre@netbeans.org
Wed, 12 Aug 2009 02:55:48 +0400
changeset 934052f7ff1a828
parent 933 90b133c9fb62
parent 818 156aad00435a
child 935 90a6c54a9bf6
Automated merge with main-silver
     1.1 --- a/openide.util/src/org/openide/util/EditableProperties.java	Sun Aug 09 02:49:58 2009 +0400
     1.2 +++ b/openide.util/src/org/openide/util/EditableProperties.java	Wed Aug 12 02:55:48 2009 +0400
     1.3 @@ -82,24 +82,35 @@
     1.4   * @since org.openide.util 7.26
     1.5   */
     1.6  public final class EditableProperties extends AbstractMap<String,String> implements Cloneable {
     1.7 +
     1.8 +    private static class State {
     1.9 +        /** whether multiple EP instances are currently linking to this */
    1.10 +        boolean shared;
    1.11 +        /** List of Item instances as read from the properties file. Order is important.
    1.12 +         * Saving properties will save then in this order. */
    1.13 +        final LinkedList<Item> items;
    1.14 +        /** Map of [property key, Item instance] for faster access. */
    1.15 +        final Map<String, Item> itemIndex;
    1.16 +        /** create fresh state */
    1.17 +        State() {
    1.18 +            items = new LinkedList<Item>();
    1.19 +            itemIndex = new HashMap<String,Item>();
    1.20 +        }
    1.21 +        /** duplicate state */
    1.22 +        State(State original) {
    1.23 +            items = new LinkedList<Item>();
    1.24 +            itemIndex = new HashMap<String,Item>(original.items.size() * 4 / 3 + 1);
    1.25 +            for (Item _i : original.items) {
    1.26 +                Item i = (Item) _i.clone();
    1.27 +                items.add(i);
    1.28 +                itemIndex.put(i.getKey(), i);
    1.29 +            }
    1.30 +        }
    1.31 +    }
    1.32      
    1.33 -    /** List of Item instances as read from the properties file. Order is important.
    1.34 -     * Saving properties will save then in this order. */
    1.35 -    private final LinkedList<Item> items;
    1.36 -
    1.37 -    /** Map of [property key, Item instance] for faster access. */
    1.38 -    private final Map<String,Item> itemIndex;
    1.39 -
    1.40 +    private State state;
    1.41      private final boolean alphabetize;
    1.42      
    1.43 -    private static final String keyValueSeparators = "=: \t\r\n\f";
    1.44 -
    1.45 -    private static final String strictKeyValueSeparators = "=:";
    1.46 -
    1.47 -    private static final String whiteSpaceChars = " \t\r\n\f";
    1.48 -
    1.49 -    private static final String commentChars = "#!";
    1.50 -    
    1.51      private static final String INDENT = "    ";
    1.52  
    1.53      // parse states:
    1.54 @@ -112,8 +123,7 @@
    1.55       */
    1.56      public EditableProperties(boolean alphabetize) {
    1.57          this.alphabetize = alphabetize;
    1.58 -        items = new LinkedList<Item>();
    1.59 -        itemIndex = new HashMap<String,Item>();
    1.60 +        state = new State();
    1.61      }
    1.62      
    1.63      /**
    1.64 @@ -123,12 +133,13 @@
    1.65      private EditableProperties(EditableProperties ep) {
    1.66          // #64174: use a simple deep copy for speed
    1.67          alphabetize = ep.alphabetize;
    1.68 -        items = new LinkedList<Item>();
    1.69 -        itemIndex = new HashMap<String,Item>(ep.items.size() * 4 / 3 + 1);
    1.70 -        for (Item _i : ep.items) {
    1.71 -            Item i = (Item) _i.clone();
    1.72 -            items.add(i);
    1.73 -            itemIndex.put(i.getKey(), i);
    1.74 +        state = ep.state;
    1.75 +        state.shared = true;
    1.76 +    }
    1.77 +
    1.78 +    private void writeOperation() {
    1.79 +        if (state.shared) {
    1.80 +            state = new State(state);
    1.81          }
    1.82      }
    1.83      
    1.84 @@ -139,7 +150,7 @@
    1.85       * @return set with Map.Entry instances.
    1.86       */
    1.87      public Set<Map.Entry<String,String>> entrySet() {
    1.88 -        return new SetImpl(this);
    1.89 +        return new SetImpl();
    1.90      }
    1.91      
    1.92      /**
    1.93 @@ -148,7 +159,7 @@
    1.94       * @throws IOException if the contents are malformed or the stream could not be read
    1.95       */
    1.96      public void load(InputStream stream) throws IOException {
    1.97 -        int state = WAITING_FOR_KEY_VALUE;
    1.98 +        int parseState = WAITING_FOR_KEY_VALUE;
    1.99          BufferedReader input = new BufferedReader(new InputStreamReader(stream, "ISO-8859-1"));
   1.100          List<String> tempList = new LinkedList<String>();
   1.101          String line;
   1.102 @@ -159,7 +170,7 @@
   1.103              tempList.add(line);
   1.104              boolean empty = isEmpty(line);
   1.105              boolean comment = isComment(line);
   1.106 -            if (state == WAITING_FOR_KEY_VALUE) {
   1.107 +            if (parseState == WAITING_FOR_KEY_VALUE) {
   1.108                  if (empty) {
   1.109                      // empty line: create Item without any key
   1.110                      createNonKeyItem(tempList);
   1.111 @@ -168,19 +179,19 @@
   1.112                      if (comment) {
   1.113                          commentLinesCount++;
   1.114                      } else {
   1.115 -                        state = READING_KEY_VALUE;
   1.116 +                        parseState = READING_KEY_VALUE;
   1.117                      }
   1.118                  }
   1.119              }
   1.120 -            if (state == READING_KEY_VALUE && !isContinue(line)) {
   1.121 +            if (parseState == READING_KEY_VALUE && !isContinue(line)) {
   1.122                  // valid end of property declaration: create Item for it
   1.123                  createKeyItem(tempList, commentLinesCount);
   1.124 -                state = WAITING_FOR_KEY_VALUE;
   1.125 +                parseState = WAITING_FOR_KEY_VALUE;
   1.126                  commentLinesCount = 0;
   1.127              }
   1.128          }
   1.129          if (tempList.size() > 0) {
   1.130 -            if (state == READING_KEY_VALUE) {
   1.131 +            if (parseState == READING_KEY_VALUE) {
   1.132                  // value was not ended correctly? ignore.
   1.133                  createKeyItem(tempList, commentLinesCount);
   1.134              } else {
   1.135 @@ -197,7 +208,7 @@
   1.136      public void store(OutputStream stream) throws IOException {
   1.137          boolean previousLineWasEmpty = true;
   1.138          BufferedWriter output = new BufferedWriter(new OutputStreamWriter(stream, "ISO-8859-1"));
   1.139 -        for (Item item : items) {
   1.140 +        for (Item item : state.items) {
   1.141              if (item.isSeparate() && !previousLineWasEmpty) {
   1.142                  output.newLine();
   1.143              }
   1.144 @@ -220,7 +231,7 @@
   1.145          if (!(key instanceof String)) {
   1.146              return null;
   1.147          }
   1.148 -        Item item = itemIndex.get((String) key);
   1.149 +        Item item = state.itemIndex.get((String) key);
   1.150          return item != null ? item.getValue() : null;
   1.151      }
   1.152  
   1.153 @@ -228,7 +239,8 @@
   1.154      public String put(String key, String value) {
   1.155          Parameters.notNull("key", key);
   1.156          Parameters.notNull(key, value);
   1.157 -        Item item = itemIndex.get(key);
   1.158 +        writeOperation();
   1.159 +        Item item = state.itemIndex.get(key);
   1.160          String result = null;
   1.161          if (item != null) {
   1.162              result = item.getValue();
   1.163 @@ -278,7 +290,8 @@
   1.164              throw new NullPointerException();
   1.165          }
   1.166          List<String> valueList = Arrays.asList(value);
   1.167 -        Item item = itemIndex.get(key);
   1.168 +        writeOperation();
   1.169 +        Item item = state.itemIndex.get(key);
   1.170          if (item != null) {
   1.171              item.setValue(valueList);
   1.172          } else {
   1.173 @@ -298,7 +311,7 @@
   1.174       *    delimiter character is included
   1.175       */
   1.176      public String[] getComment(String key) {
   1.177 -        Item item = itemIndex.get(key);
   1.178 +        Item item = state.itemIndex.get(key);
   1.179          if (item == null) {
   1.180              return new String[0];
   1.181          }
   1.182 @@ -319,7 +332,8 @@
   1.183       */
   1.184      public void setComment(String key, String[] comment, boolean separate) {
   1.185          // XXX: check validity of comment parameter
   1.186 -        Item item = itemIndex.get(key);
   1.187 +        writeOperation();
   1.188 +        Item item = state.itemIndex.get(key);
   1.189          if (item == null) {
   1.190              throw new IllegalArgumentException("Cannot set comment for non-existing property "+key);
   1.191          }
   1.192 @@ -341,9 +355,10 @@
   1.193  
   1.194      // non-key item is block of empty lines/comment not associated with any property
   1.195      private void createNonKeyItem(List<String> lines) {
   1.196 +        writeOperation();
   1.197          // First check that previous item is not non-key item.
   1.198 -        if (!items.isEmpty()) {
   1.199 -            Item item = items.getLast();
   1.200 +        if (!state.items.isEmpty()) {
   1.201 +            Item item = state.items.getLast();
   1.202              if (item.getKey() == null) {
   1.203                  // it is non-key item:  merge them
   1.204                  item.addCommentLines(lines);
   1.205 @@ -366,23 +381,24 @@
   1.206      }
   1.207      
   1.208      private void addItem(Item item, boolean sort) {
   1.209 +        writeOperation();
   1.210          String key = item.getKey();
   1.211          if (sort) {
   1.212              assert key != null;
   1.213 -            ListIterator<Item> it = items.listIterator();
   1.214 +            ListIterator<Item> it = state.items.listIterator();
   1.215              while (it.hasNext()) {
   1.216                  String k = it.next().getKey();
   1.217                  if (k != null && k.compareToIgnoreCase(key) > 0) {
   1.218                      it.previous();
   1.219                      it.add(item);
   1.220 -                    itemIndex.put(key, item);
   1.221 +                    state.itemIndex.put(key, item);
   1.222                      return;
   1.223                  }
   1.224              }
   1.225          }
   1.226 -        items.add(item);
   1.227 +        state.items.add(item);
   1.228          if (key != null) {
   1.229 -            itemIndex.put(key, item);
   1.230 +            state.itemIndex.put(key, item);
   1.231          }
   1.232      }
   1.233      
   1.234 @@ -402,11 +418,14 @@
   1.235      // does line start with comment delimiter? (whitespaces are ignored)
   1.236      private static boolean isComment(String line) {
   1.237          line = trimLeft(line);
   1.238 -        if (line.length() != 0 && commentChars.indexOf(line.charAt(0)) != -1) {
   1.239 -            return true;
   1.240 -        } else {
   1.241 -            return false;
   1.242 +        if (line.length() > 0) {
   1.243 +            switch (line.charAt(0)) {
   1.244 +            case '#':
   1.245 +            case '!':
   1.246 +                return true;
   1.247 +            }
   1.248          }
   1.249 +        return false;
   1.250      }
   1.251  
   1.252      // is line empty? (whitespaces are ignored)
   1.253 @@ -417,11 +436,19 @@
   1.254      // remove all whitespaces from left
   1.255      private static String trimLeft(String line) {
   1.256          int start = 0;
   1.257 -        while (start < line.length()) {
   1.258 -            if (whiteSpaceChars.indexOf(line.charAt(start)) == -1) {
   1.259 +        int len = line.length();
   1.260 +        NONWS: while (start < len) {
   1.261 +            switch (line.charAt(start)) {
   1.262 +            case ' ':
   1.263 +            case '\t':
   1.264 +            case '\r':
   1.265 +            case '\n':
   1.266 +            case '\f':
   1.267 +                start++;
   1.268                  break;
   1.269 +            default:
   1.270 +                break NONWS;
   1.271              }
   1.272 -            start++;
   1.273          }
   1.274          return line.substring(start);
   1.275      }
   1.276 @@ -575,7 +602,10 @@
   1.277              splitKeyValue(line);
   1.278          }
   1.279          
   1.280 -        private String mergeLines(List<String> lines) {
   1.281 +        private static String mergeLines(List<String> lines) {
   1.282 +            if (lines.size() == 1) {
   1.283 +                return trimLeft(lines.get(0));
   1.284 +            }
   1.285              StringBuilder line = new StringBuilder();
   1.286              Iterator<String> it = lines.iterator();
   1.287              while (it.hasNext()) {
   1.288 @@ -592,14 +622,22 @@
   1.289          
   1.290          private void splitKeyValue(String line) {
   1.291              int separatorIndex = 0;
   1.292 -            while (separatorIndex < line.length()) {
   1.293 +            int len = line.length();
   1.294 +            POS: while (separatorIndex < len) {
   1.295                  char ch = line.charAt(separatorIndex);
   1.296                  if (ch == '\\') {
   1.297                      // ignore next one character
   1.298                      separatorIndex++;
   1.299                  } else {
   1.300 -                    if (keyValueSeparators.indexOf(ch) != -1) {
   1.301 -                        break;
   1.302 +                    switch (ch) {
   1.303 +                    case '=':
   1.304 +                    case ':':
   1.305 +                    case ' ':
   1.306 +                    case '\t':
   1.307 +                    case '\r':
   1.308 +                    case '\n':
   1.309 +                    case '\f':
   1.310 +                        break POS;
   1.311                      }
   1.312                  }
   1.313                  separatorIndex++;
   1.314 @@ -610,13 +648,18 @@
   1.315                  value = "";
   1.316                  return;
   1.317              }
   1.318 -            if (strictKeyValueSeparators.indexOf(line.charAt(0)) != -1) {
   1.319 +            switch (line.charAt(0)) {
   1.320 +            case '=':
   1.321 +            case ':':
   1.322                  line = trimLeft(line.substring(1));
   1.323              }
   1.324              value = decode(line);
   1.325          }
   1.326          
   1.327          private static String decode(String input) {
   1.328 +            if (input.indexOf('\\') == -1) {
   1.329 +                return input; // shortcut
   1.330 +            }
   1.331              char ch;
   1.332              int len = input.length();
   1.333              StringBuilder output = new StringBuilder(len);
   1.334 @@ -784,32 +827,26 @@
   1.335      
   1.336      }
   1.337      
   1.338 -    private static class SetImpl extends AbstractSet<Map.Entry<String,String>> {
   1.339 +    private class SetImpl extends AbstractSet<Map.Entry<String,String>> {
   1.340  
   1.341 -        private EditableProperties props;
   1.342 -        
   1.343 -        public SetImpl(EditableProperties props) {
   1.344 -            this.props = props;
   1.345 -        }
   1.346 +        public SetImpl() {}
   1.347          
   1.348          public Iterator<Map.Entry<String,String>> iterator() {
   1.349 -            return new IteratorImpl(props);
   1.350 +            return new IteratorImpl();
   1.351          }
   1.352          
   1.353          public int size() {
   1.354 -            return props.items.size();
   1.355 +            return state.items.size();
   1.356          }
   1.357          
   1.358      }
   1.359      
   1.360 -    private static class IteratorImpl implements Iterator<Map.Entry<String,String>> {
   1.361 +    private class IteratorImpl implements Iterator<Map.Entry<String,String>> {
   1.362  
   1.363 -        private final EditableProperties props;
   1.364          private ListIterator<Item> delegate;
   1.365          
   1.366 -        public IteratorImpl(EditableProperties props) {
   1.367 -            this.props = props;
   1.368 -            delegate = props.items.listIterator();
   1.369 +        public IteratorImpl() {
   1.370 +            delegate = state.items.listIterator();
   1.371          }
   1.372          
   1.373          public boolean hasNext() {
   1.374 @@ -832,9 +869,11 @@
   1.375                  throw new IllegalStateException();
   1.376              }
   1.377              int index = delegate.nextIndex();
   1.378 -            props.items.remove(item);
   1.379 -            props.itemIndex.remove(item.getKey());
   1.380 -            delegate = props.items.listIterator(index);
   1.381 +            writeOperation();
   1.382 +            Item removed = state.items.remove(index);
   1.383 +            assert removed.getKey().equals(item.getKey());
   1.384 +            state.itemIndex.remove(item.getKey());
   1.385 +            delegate = state.items.listIterator(index);
   1.386          }
   1.387          
   1.388          private Item findNext() {
   1.389 @@ -851,7 +890,7 @@
   1.390          
   1.391      }
   1.392      
   1.393 -    private static class MapEntryImpl implements Map.Entry<String,String> {
   1.394 +    private class MapEntryImpl implements Map.Entry<String,String> {
   1.395          
   1.396          private Item item;
   1.397          
   1.398 @@ -868,6 +907,8 @@
   1.399          }
   1.400          
   1.401          public String setValue(String value) {
   1.402 +            writeOperation();
   1.403 +            item = state.itemIndex.get(item.getKey());
   1.404              String result = item.getValue();
   1.405              item.setValue(value);
   1.406              return result;
     2.1 --- a/openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java	Sun Aug 09 02:49:58 2009 +0400
     2.2 +++ b/openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java	Wed Aug 12 02:55:48 2009 +0400
     2.3 @@ -135,6 +135,30 @@
     2.4          assertFile("Saved cloned properties must be the same as original one", filenameOfTestProperties(), dest, (String)null);
     2.5      }
     2.6  
     2.7 +    public void testCopyOnWriteClonability() throws Exception {
     2.8 +        EditableProperties ep1 = new EditableProperties(true);
     2.9 +        ep1.setProperty("k1", "v1");
    2.10 +        EditableProperties ep2 = ep1.cloneProperties();
    2.11 +        ep2.setProperty("k2", "v2");
    2.12 +        EditableProperties ep3 = ep2.cloneProperties();
    2.13 +        ep1.setProperty("k4", "v4");
    2.14 +        ep2.setProperty("k2", "v2a");
    2.15 +        Iterator<Map.Entry<String,String>> it = ep3.entrySet().iterator();
    2.16 +        it.next().setValue("v1b");
    2.17 +        it.next();
    2.18 +        it.remove();
    2.19 +        ep3.setProperty("k3", "v3");
    2.20 +        assertEquals("{k1=v1, k4=v4}", ep1.toString());
    2.21 +        assertEquals("{k1=v1, k2=v2a}", ep2.toString());
    2.22 +        assertEquals("{k1=v1b, k3=v3}", ep3.toString());
    2.23 +        ep1 = new EditableProperties(true);
    2.24 +        ep1.setProperty("k", "v1");
    2.25 +        ep2 = ep1.cloneProperties();
    2.26 +        ep2.entrySet().iterator().next().setValue("v2");
    2.27 +        assertEquals("{k=v1}", ep1.toString());
    2.28 +        assertEquals("{k=v2}", ep2.toString());
    2.29 +    }
    2.30 +
    2.31      // test that array values are stored correctly
    2.32      public void testArrayValues() throws Exception {
    2.33          EditableProperties ep = new EditableProperties(false);