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.0-M1
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      /**
52       * Constructs a new instance.
53       */
54      public OrderedProperties() {
55          // empty
56      }
57  
58      @Override
59      public synchronized void clear() {
60          orderedKeys.clear();
61          super.clear();
62      }
63  
64      @Override
65      public synchronized Object compute(final Object key, final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
66          final Object compute = super.compute(key, remappingFunction);
67          if (compute != null) {
68              orderedKeys.add(key);
69          }
70          return compute;
71      }
72  
73      @Override
74      public synchronized Object computeIfAbsent(final Object key, final Function<? super Object, ? extends Object> mappingFunction) {
75          final Object computeIfAbsent = super.computeIfAbsent(key, mappingFunction);
76          if (computeIfAbsent != null) {
77              orderedKeys.add(key);
78          }
79          return computeIfAbsent;
80      }
81  
82      @Override
83      public Set<Map.Entry<Object, Object>> entrySet() {
84          return orderedKeys.stream().map(k -> new SimpleEntry<>(k, get(k))).collect(Collectors.toCollection(LinkedHashSet::new));
85      }
86  
87      @Override
88      public synchronized void forEach(final BiConsumer<? super Object, ? super Object> action) {
89          Objects.requireNonNull(action);
90          orderedKeys.forEach(k -> action.accept(k, get(k)));
91      }
92  
93      @Override
94      public synchronized Enumeration<Object> keys() {
95          return Collections.enumeration(orderedKeys);
96      }
97  
98      @Override
99      public Set<Object> keySet() {
100         return orderedKeys;
101     }
102 
103     @Override
104     public synchronized Object merge(final Object key, final Object value,
105             final BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) {
106         orderedKeys.add(key);
107         return super.merge(key, value, remappingFunction);
108     }
109 
110     @Override
111     public Enumeration<?> propertyNames() {
112         return Collections.enumeration(orderedKeys);
113     }
114 
115     @Override
116     public synchronized Object put(final Object key, final Object value) {
117         final Object put = super.put(key, value);
118         if (put == null) {
119             orderedKeys.add(key);
120         }
121         return put;
122     }
123 
124     @Override
125     public synchronized void putAll(final Map<? extends Object, ? extends Object> t) {
126         orderedKeys.addAll(t.keySet());
127         super.putAll(t);
128     }
129 
130     @Override
131     public synchronized Object putIfAbsent(final Object key, final Object value) {
132         final Object putIfAbsent = super.putIfAbsent(key, value);
133         if (putIfAbsent == null) {
134             orderedKeys.add(key);
135         }
136         return putIfAbsent;
137     }
138 
139     @Override
140     public synchronized Object remove(final Object key) {
141         final Object remove = super.remove(key);
142         if (remove != null) {
143             orderedKeys.remove(key);
144         }
145         return remove;
146     }
147 
148     @Override
149     public synchronized boolean remove(final Object key, final Object value) {
150         final boolean remove = super.remove(key, value);
151         if (remove) {
152             orderedKeys.remove(key);
153         }
154         return remove;
155     }
156 
157     @Override
158     public synchronized String toString() {
159         // Must override for Java 17 to maintain order since the implementation is based on a map
160         final int max = size() - 1;
161         if (max == -1) {
162             return "{}";
163         }
164         final StringBuilder sb = new StringBuilder();
165         final Iterator<Map.Entry<Object, Object>> it = entrySet().iterator();
166         sb.append('{');
167         for (int i = 0;; i++) {
168             final Map.Entry<Object, Object> e = it.next();
169             final Object key = e.getKey();
170             final Object value = e.getValue();
171             sb.append(key == this ? "(this Map)" : key.toString());
172             sb.append('=');
173             sb.append(value == this ? "(this Map)" : value.toString());
174             if (i == max) {
175                 return sb.append('}').toString();
176             }
177             sb.append(", ");
178         }
179     }
180 }