Trying to speed up cloneProperties using copy-on-write semantics.
authorJesse Glick <jglick@netbeans.org>
Mon, 10 Aug 2009 15:30:45 -0400
changeset 817fb3f7efc8c45
parent 816 a608b762794e
child 818 156aad00435a
Trying to speed up cloneProperties using copy-on-write semantics.
openide.util/src/org/openide/util/EditableProperties.java
openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java
     1.1 --- a/openide.util/src/org/openide/util/EditableProperties.java	Mon Aug 10 14:36:37 2009 -0400
     1.2 +++ b/openide.util/src/org/openide/util/EditableProperties.java	Mon Aug 10 15:30:45 2009 -0400
     1.3 @@ -82,14 +82,33 @@
     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 INDENT = "    ";
    1.44 @@ -104,8 +123,7 @@
    1.45       */
    1.46      public EditableProperties(boolean alphabetize) {
    1.47          this.alphabetize = alphabetize;
    1.48 -        items = new LinkedList<Item>();
    1.49 -        itemIndex = new HashMap<String,Item>();
    1.50 +        state = new State();
    1.51      }
    1.52      
    1.53      /**
    1.54 @@ -115,12 +133,13 @@
    1.55      private EditableProperties(EditableProperties ep) {
    1.56          // #64174: use a simple deep copy for speed
    1.57          alphabetize = ep.alphabetize;
    1.58 -        items = new LinkedList<Item>();
    1.59 -        itemIndex = new HashMap<String,Item>(ep.items.size() * 4 / 3 + 1);
    1.60 -        for (Item _i : ep.items) {
    1.61 -            Item i = (Item) _i.clone();
    1.62 -            items.add(i);
    1.63 -            itemIndex.put(i.getKey(), i);
    1.64 +        state = ep.state;
    1.65 +        state.shared = true;
    1.66 +    }
    1.67 +
    1.68 +    private void writeOperation() {
    1.69 +        if (state.shared) {
    1.70 +            state = new State(state);
    1.71          }
    1.72      }
    1.73      
    1.74 @@ -131,7 +150,7 @@
    1.75       * @return set with Map.Entry instances.
    1.76       */
    1.77      public Set<Map.Entry<String,String>> entrySet() {
    1.78 -        return new SetImpl(this);
    1.79 +        return new SetImpl();
    1.80      }
    1.81      
    1.82      /**
    1.83 @@ -140,7 +159,7 @@
    1.84       * @throws IOException if the contents are malformed or the stream could not be read
    1.85       */
    1.86      public void load(InputStream stream) throws IOException {
    1.87 -        int state = WAITING_FOR_KEY_VALUE;
    1.88 +        int parseState = WAITING_FOR_KEY_VALUE;
    1.89          BufferedReader input = new BufferedReader(new InputStreamReader(stream, "ISO-8859-1"));
    1.90          List<String> tempList = new LinkedList<String>();
    1.91          String line;
    1.92 @@ -151,7 +170,7 @@
    1.93              tempList.add(line);
    1.94              boolean empty = isEmpty(line);
    1.95              boolean comment = isComment(line);
    1.96 -            if (state == WAITING_FOR_KEY_VALUE) {
    1.97 +            if (parseState == WAITING_FOR_KEY_VALUE) {
    1.98                  if (empty) {
    1.99                      // empty line: create Item without any key
   1.100                      createNonKeyItem(tempList);
   1.101 @@ -160,19 +179,19 @@
   1.102                      if (comment) {
   1.103                          commentLinesCount++;
   1.104                      } else {
   1.105 -                        state = READING_KEY_VALUE;
   1.106 +                        parseState = READING_KEY_VALUE;
   1.107                      }
   1.108                  }
   1.109              }
   1.110 -            if (state == READING_KEY_VALUE && !isContinue(line)) {
   1.111 +            if (parseState == READING_KEY_VALUE && !isContinue(line)) {
   1.112                  // valid end of property declaration: create Item for it
   1.113                  createKeyItem(tempList, commentLinesCount);
   1.114 -                state = WAITING_FOR_KEY_VALUE;
   1.115 +                parseState = WAITING_FOR_KEY_VALUE;
   1.116                  commentLinesCount = 0;
   1.117              }
   1.118          }
   1.119          if (tempList.size() > 0) {
   1.120 -            if (state == READING_KEY_VALUE) {
   1.121 +            if (parseState == READING_KEY_VALUE) {
   1.122                  // value was not ended correctly? ignore.
   1.123                  createKeyItem(tempList, commentLinesCount);
   1.124              } else {
   1.125 @@ -189,7 +208,7 @@
   1.126      public void store(OutputStream stream) throws IOException {
   1.127          boolean previousLineWasEmpty = true;
   1.128          BufferedWriter output = new BufferedWriter(new OutputStreamWriter(stream, "ISO-8859-1"));
   1.129 -        for (Item item : items) {
   1.130 +        for (Item item : state.items) {
   1.131              if (item.isSeparate() && !previousLineWasEmpty) {
   1.132                  output.newLine();
   1.133              }
   1.134 @@ -212,7 +231,7 @@
   1.135          if (!(key instanceof String)) {
   1.136              return null;
   1.137          }
   1.138 -        Item item = itemIndex.get((String) key);
   1.139 +        Item item = state.itemIndex.get((String) key);
   1.140          return item != null ? item.getValue() : null;
   1.141      }
   1.142  
   1.143 @@ -220,7 +239,8 @@
   1.144      public String put(String key, String value) {
   1.145          Parameters.notNull("key", key);
   1.146          Parameters.notNull(key, value);
   1.147 -        Item item = itemIndex.get(key);
   1.148 +        writeOperation();
   1.149 +        Item item = state.itemIndex.get(key);
   1.150          String result = null;
   1.151          if (item != null) {
   1.152              result = item.getValue();
   1.153 @@ -270,7 +290,8 @@
   1.154              throw new NullPointerException();
   1.155          }
   1.156          List<String> valueList = Arrays.asList(value);
   1.157 -        Item item = itemIndex.get(key);
   1.158 +        writeOperation();
   1.159 +        Item item = state.itemIndex.get(key);
   1.160          if (item != null) {
   1.161              item.setValue(valueList);
   1.162          } else {
   1.163 @@ -290,7 +311,7 @@
   1.164       *    delimiter character is included
   1.165       */
   1.166      public String[] getComment(String key) {
   1.167 -        Item item = itemIndex.get(key);
   1.168 +        Item item = state.itemIndex.get(key);
   1.169          if (item == null) {
   1.170              return new String[0];
   1.171          }
   1.172 @@ -311,7 +332,8 @@
   1.173       */
   1.174      public void setComment(String key, String[] comment, boolean separate) {
   1.175          // XXX: check validity of comment parameter
   1.176 -        Item item = itemIndex.get(key);
   1.177 +        writeOperation();
   1.178 +        Item item = state.itemIndex.get(key);
   1.179          if (item == null) {
   1.180              throw new IllegalArgumentException("Cannot set comment for non-existing property "+key);
   1.181          }
   1.182 @@ -333,9 +355,10 @@
   1.183  
   1.184      // non-key item is block of empty lines/comment not associated with any property
   1.185      private void createNonKeyItem(List<String> lines) {
   1.186 +        writeOperation();
   1.187          // First check that previous item is not non-key item.
   1.188 -        if (!items.isEmpty()) {
   1.189 -            Item item = items.getLast();
   1.190 +        if (!state.items.isEmpty()) {
   1.191 +            Item item = state.items.getLast();
   1.192              if (item.getKey() == null) {
   1.193                  // it is non-key item:  merge them
   1.194                  item.addCommentLines(lines);
   1.195 @@ -358,23 +381,24 @@
   1.196      }
   1.197      
   1.198      private void addItem(Item item, boolean sort) {
   1.199 +        writeOperation();
   1.200          String key = item.getKey();
   1.201          if (sort) {
   1.202              assert key != null;
   1.203 -            ListIterator<Item> it = items.listIterator();
   1.204 +            ListIterator<Item> it = state.items.listIterator();
   1.205              while (it.hasNext()) {
   1.206                  String k = it.next().getKey();
   1.207                  if (k != null && k.compareToIgnoreCase(key) > 0) {
   1.208                      it.previous();
   1.209                      it.add(item);
   1.210 -                    itemIndex.put(key, item);
   1.211 +                    state.itemIndex.put(key, item);
   1.212                      return;
   1.213                  }
   1.214              }
   1.215          }
   1.216 -        items.add(item);
   1.217 +        state.items.add(item);
   1.218          if (key != null) {
   1.219 -            itemIndex.put(key, item);
   1.220 +            state.itemIndex.put(key, item);
   1.221          }
   1.222      }
   1.223      
   1.224 @@ -578,7 +602,7 @@
   1.225              splitKeyValue(line);
   1.226          }
   1.227          
   1.228 -        private String mergeLines(List<String> lines) {
   1.229 +        private static String mergeLines(List<String> lines) {
   1.230              if (lines.size() == 1) {
   1.231                  return trimLeft(lines.get(0));
   1.232              }
   1.233 @@ -800,32 +824,26 @@
   1.234      
   1.235      }
   1.236      
   1.237 -    private static class SetImpl extends AbstractSet<Map.Entry<String,String>> {
   1.238 +    private class SetImpl extends AbstractSet<Map.Entry<String,String>> {
   1.239  
   1.240 -        private EditableProperties props;
   1.241 -        
   1.242 -        public SetImpl(EditableProperties props) {
   1.243 -            this.props = props;
   1.244 -        }
   1.245 +        public SetImpl() {}
   1.246          
   1.247          public Iterator<Map.Entry<String,String>> iterator() {
   1.248 -            return new IteratorImpl(props);
   1.249 +            return new IteratorImpl();
   1.250          }
   1.251          
   1.252          public int size() {
   1.253 -            return props.items.size();
   1.254 +            return state.items.size();
   1.255          }
   1.256          
   1.257      }
   1.258      
   1.259 -    private static class IteratorImpl implements Iterator<Map.Entry<String,String>> {
   1.260 +    private class IteratorImpl implements Iterator<Map.Entry<String,String>> {
   1.261  
   1.262 -        private final EditableProperties props;
   1.263          private ListIterator<Item> delegate;
   1.264          
   1.265 -        public IteratorImpl(EditableProperties props) {
   1.266 -            this.props = props;
   1.267 -            delegate = props.items.listIterator();
   1.268 +        public IteratorImpl() {
   1.269 +            delegate = state.items.listIterator();
   1.270          }
   1.271          
   1.272          public boolean hasNext() {
   1.273 @@ -848,9 +866,11 @@
   1.274                  throw new IllegalStateException();
   1.275              }
   1.276              int index = delegate.nextIndex();
   1.277 -            props.items.remove(item);
   1.278 -            props.itemIndex.remove(item.getKey());
   1.279 -            delegate = props.items.listIterator(index);
   1.280 +            writeOperation();
   1.281 +            Item removed = state.items.remove(index);
   1.282 +            assert removed.getKey().equals(item.getKey());
   1.283 +            state.itemIndex.remove(item.getKey());
   1.284 +            delegate = state.items.listIterator(index);
   1.285          }
   1.286          
   1.287          private Item findNext() {
   1.288 @@ -867,7 +887,7 @@
   1.289          
   1.290      }
   1.291      
   1.292 -    private static class MapEntryImpl implements Map.Entry<String,String> {
   1.293 +    private class MapEntryImpl implements Map.Entry<String,String> {
   1.294          
   1.295          private Item item;
   1.296          
   1.297 @@ -884,6 +904,8 @@
   1.298          }
   1.299          
   1.300          public String setValue(String value) {
   1.301 +            writeOperation();
   1.302 +            item = state.itemIndex.get(item.getKey());
   1.303              String result = item.getValue();
   1.304              item.setValue(value);
   1.305              return result;
     2.1 --- a/openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java	Mon Aug 10 14:36:37 2009 -0400
     2.2 +++ b/openide.util/test/unit/src/org/openide/util/EditablePropertiesTest.java	Mon Aug 10 15:30:45 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);