View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4.properties;
18  
19  import java.util.AbstractMap.SimpleEntry;
20  import java.util.Collections;
21  import java.util.Enumeration;
22  import java.util.Iterator;
23  import java.util.LinkedHashSet;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.Properties;
27  import java.util.Set;
28  import java.util.function.BiConsumer;
29  import java.util.function.BiFunction;
30  import java.util.function.Function;
31  import java.util.stream.Collectors;
32  
33  /**
34   * A drop-in replacement for {@link Properties} for ordered keys.
35   * <p>
36   * Overrides methods to keep keys in insertion order. Allows other methods in the superclass to work with ordered keys.
37   * </p>
38   *
39   * @see OrderedPropertiesFactory#INSTANCE
40   * @since 4.5
41   */
42  public class OrderedProperties extends Properties {
43  
44      private static final long serialVersionUID = 1L;
45  
46      /**
47       * Preserves the insertion order.
48       */
49      private final LinkedHashSet<Object> orderedKeys = new LinkedHashSet<>();
50  
51      @Override
52      public synchronized void clear() {
53          orderedKeys.clear();
54          super.clear();
55      }
56  
57      @Override
58      public synchronized Object compute(final Object key, final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
59          final Object compute = super.compute(key, remappingFunction);
60          if (compute != null) {
61              orderedKeys.add(key);
62          }
63          return compute;
64      }
65  
66      @Override
67      public synchronized Object computeIfAbsent(final Object key, final Function<? super Object, ? extends Object> mappingFunction) {
68          final Object computeIfAbsent = super.computeIfAbsent(key, mappingFunction);
69          if (computeIfAbsent != null) {
70              orderedKeys.add(key);
71          }
72          return computeIfAbsent;
73      }
74  
75      @Override
76      public Set<Map.Entry<Object, Object>> entrySet() {
77          return orderedKeys.stream().map(k -> new SimpleEntry<>(k, get(k))).collect(Collectors.toCollection(LinkedHashSet::new));
78      }
79  
80      @Override
81      public synchronized void forEach(final BiConsumer<? super Object, ? super Object> action) {
82          Objects.requireNonNull(action);
83          orderedKeys.forEach(k -> action.accept(k, get(k)));
84      }
85  
86      @Override
87      public synchronized Enumeration<Object> keys() {
88          return Collections.enumeration(orderedKeys);
89      }
90  
91      @Override
92      public Set<Object> keySet() {
93          return orderedKeys;
94      }
95  
96      @Override
97      public synchronized Object merge(final Object key, final Object value,
98              final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
99          orderedKeys.add(key);
100         return super.merge(key, value, remappingFunction);
101     }
102 
103     @Override
104     public Enumeration<?> propertyNames() {
105         return Collections.enumeration(orderedKeys);
106     }
107 
108     @Override
109     public synchronized Object put(final Object key, final Object value) {
110         final Object put = super.put(key, value);
111         if (put == null) {
112             orderedKeys.add(key);
113         }
114         return put;
115     }
116 
117     @Override
118     public synchronized void putAll(final Map<? extends Object, ? extends Object> t) {
119         orderedKeys.addAll(t.keySet());
120         super.putAll(t);
121     }
122 
123     @Override
124     public synchronized Object putIfAbsent(final Object key, final Object value) {
125         final Object putIfAbsent = super.putIfAbsent(key, value);
126         if (putIfAbsent == null) {
127             orderedKeys.add(key);
128         }
129         return putIfAbsent;
130     }
131 
132     @Override
133     public synchronized Object remove(final Object key) {
134         final Object remove = super.remove(key);
135         if (remove != null) {
136             orderedKeys.remove(key);
137         }
138         return remove;
139     }
140 
141     @Override
142     public synchronized boolean remove(final Object key, final Object value) {
143         final boolean remove = super.remove(key, value);
144         if (remove) {
145             orderedKeys.remove(key);
146         }
147         return remove;
148     }
149 
150     @Override
151     public synchronized String toString() {
152         // Must override for Java 17 to maintain order since the implementation is based on a map
153         final int max = size() - 1;
154         if (max == -1) {
155             return "{}";
156         }
157         final StringBuilder sb = new StringBuilder();
158         final Iterator<Map.Entry<Object, Object>> it = entrySet().iterator();
159         sb.append('{');
160         for (int i = 0;; i++) {
161             final Map.Entry<Object, Object> e = it.next();
162             final Object key = e.getKey();
163             final Object value = e.getValue();
164             sb.append(key == this ? "(this Map)" : key.toString());
165             sb.append('=');
166             sb.append(value == this ? "(this Map)" : value.toString());
167             if (i == max) {
168                 return sb.append('}').toString();
169             }
170             sb.append(", ");
171         }
172     }
173 }