This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git

commit 452533ab6df70e537be714e9745f446499395fef
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Thu Jul 13 08:49:52 2023 -0400

    Add org.apache.commons.collections4.properties.OrderedProperties and
    OrderedPropertiesFactory
---
 pom.xml                                            |   2 +-
 src/changes/changes.xml                            |   3 +
 .../collections4/properties/OrderedProperties.java | 133 +++++++++++
 .../properties/OrderedPropertiesFactory.java       |  50 ++++
 .../properties/AbstractPropertiesFactoryTest.java  |   8 +-
 .../properties/OrderedPropertiesFactoryTest.java   |  38 +++
 .../properties/OrderedPropertiesTest.java          | 256 +++++++++++++++++++++
 .../properties/test-reverse.properties             |  25 ++
 8 files changed, 510 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index 6a1b9397c..440102faa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -647,7 +647,7 @@
           
<suppressionsLocation>${basedir}/src/conf/checkstyle-suppressions.xml</suppressionsLocation>
           <enableRulesSummary>false</enableRulesSummary>
           <includeTestSourceDirectory>true</includeTestSourceDirectory>
-          
<resourceExcludes>NOTICE.txt,LICENSE.txt,**/pom.properties,**/test.properties,**/resolver-status.properties</resourceExcludes>
+          
<resourceExcludes>NOTICE.txt,LICENSE.txt,**/pom.properties,**/test*.properties,**/resolver-status.properties</resourceExcludes>
         </configuration>
       </plugin>
       <plugin>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7b9842359..65d7b60de 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -270,6 +270,9 @@
     <action issue="COLLECTIONS-746" dev="ggregory" type="add" due-to="Gary 
Gregory">
       Add PropertiesFactory.
     </action>
+    <action dev="ggregory" type="add" due-to="Gary Gregory">
+      Add org.apache.commons.collections4.properties.OrderedProperties and 
OrderedPropertiesFactory.
+    </action>
     <!-- UPDATE -->
     <action dev="ggregory" type="update" due-to="Gary Gregory, Dependabot">
       Bump org.easymock:easymock from 4.0.2 to 5.1.0 #352, #355, #375.
diff --git 
a/src/main/java/org/apache/commons/collections4/properties/OrderedProperties.java
 
b/src/main/java/org/apache/commons/collections4/properties/OrderedProperties.java
new file mode 100644
index 000000000..34e21ab91
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/collections4/properties/OrderedProperties.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections4.properties;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * A drop-in replacement for {@link Properties} for ordered keys.
+ * <p>
+ * Overrides methods to keep keys in insertion order. Allows other methods in 
the superclass to work with ordered keys.
+ * </p>
+ *
+ * @see OrderedPropertiesFactory#INSTANCE
+ * @since 4.5
+ */
+public class OrderedProperties extends Properties {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Preserves the insertion order.
+     */
+    private final LinkedHashSet<Object> orderedKeys = new LinkedHashSet<>();
+
+    @Override
+    public synchronized void clear() {
+        orderedKeys.clear();
+        super.clear();
+    }
+
+    @Override
+    public synchronized Object compute(Object key, BiFunction<? super Object, 
? super Object, ? extends Object> remappingFunction) {
+        Object compute = super.compute(key, remappingFunction);
+        if (compute != null) {
+            orderedKeys.add(key);
+        }
+        return compute;
+    }
+
+    @Override
+    public synchronized Object computeIfAbsent(Object key, Function<? super 
Object, ? extends Object> mappingFunction) {
+        Object computeIfAbsent = super.computeIfAbsent(key, mappingFunction);
+        if (computeIfAbsent != null) {
+            orderedKeys.add(key);
+        }
+        return computeIfAbsent;
+    }
+
+    @Override
+    public synchronized Enumeration<Object> keys() {
+        return Collections.enumeration(orderedKeys);
+    }
+
+    @Override
+    public Set<Object> keySet() {
+        return orderedKeys;
+    }
+
+    @Override
+    public synchronized Object merge(final Object key, final Object value,
+            final BiFunction<? super Object, ? super Object, ? extends Object> 
remappingFunction) {
+        orderedKeys.add(key);
+        return super.merge(key, value, remappingFunction);
+    }
+
+    @Override
+    public Enumeration<?> propertyNames() {
+        return Collections.enumeration(orderedKeys);
+    }
+
+    @Override
+    public synchronized Object put(final Object key, final Object value) {
+        Object put = super.put(key, value);
+        if (put == null) {
+            orderedKeys.add(key);
+        }
+        return put;
+    }
+
+    @Override
+    public synchronized void putAll(final Map<? extends Object, ? extends 
Object> t) {
+        orderedKeys.addAll(t.keySet());
+        super.putAll(t);
+    }
+
+    @Override
+    public synchronized Object putIfAbsent(final Object key, final Object 
value) {
+        Object putIfAbsent = super.putIfAbsent(key, value);
+        if (putIfAbsent == null) {
+            orderedKeys.add(key);
+        }
+        return putIfAbsent;
+    }
+
+    @Override
+    public synchronized Object remove(final Object key) {
+        Object remove = super.remove(key);
+        if (remove != null) {
+            orderedKeys.remove(key);
+        }
+        return remove;
+    }
+
+    @Override
+    public synchronized boolean remove(final Object key, final Object value) {
+        boolean remove = super.remove(key, value);
+        if (remove) {
+            orderedKeys.remove(key);
+        }
+        return remove;
+    }
+}
diff --git 
a/src/main/java/org/apache/commons/collections4/properties/OrderedPropertiesFactory.java
 
b/src/main/java/org/apache/commons/collections4/properties/OrderedPropertiesFactory.java
new file mode 100644
index 000000000..e2039fcad
--- /dev/null
+++ 
b/src/main/java/org/apache/commons/collections4/properties/OrderedPropertiesFactory.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.collections4.properties;
+
+/**
+ * Creates and loads {@link OrderedProperties}.
+ *
+ * @see OrderedProperties
+ * @since 4.5
+ */
+public class OrderedPropertiesFactory extends 
AbstractPropertiesFactory<OrderedProperties> {
+
+    /**
+     * The singleton instance.
+     */
+    public static final OrderedPropertiesFactory INSTANCE = new 
OrderedPropertiesFactory();
+
+    /**
+     * Constructs an instance.
+     */
+    private OrderedPropertiesFactory() {
+        // There is only one instance.
+    }
+
+    /**
+     * Subclasses override to provide customized properties instances.
+     *
+     * @return a new Properties instance.
+     */
+    @Override
+    protected OrderedProperties createProperties() {
+        return new OrderedProperties();
+    }
+
+}
diff --git 
a/src/test/java/org/apache/commons/collections4/properties/AbstractPropertiesFactoryTest.java
 
b/src/test/java/org/apache/commons/collections4/properties/AbstractPropertiesFactoryTest.java
index f4daf39ba..ac7d1f3cf 100644
--- 
a/src/test/java/org/apache/commons/collections4/properties/AbstractPropertiesFactoryTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/properties/AbstractPropertiesFactoryTest.java
@@ -64,14 +64,14 @@ public abstract class AbstractPropertiesFactoryTest<T 
extends Properties> {
         assertEquals("value11", properties.getProperty("key11"));
     }
 
-    private boolean isXmlTest(final String fileExtension) {
-        return ".xml".equals(fileExtension);
-    }
-
     private String getPathString(final String fileExtension) {
         return BulkTest.TEST_PROPERTIES_PATH + "test" + fileExtension;
     }
 
+    private boolean isXmlTest(final String fileExtension) {
+        return ".xml".equals(fileExtension);
+    }
+
     @Test
     public void testInstance() {
         assertNotNull(PropertiesFactory.INSTANCE);
diff --git 
a/src/test/java/org/apache/commons/collections4/properties/OrderedPropertiesFactoryTest.java
 
b/src/test/java/org/apache/commons/collections4/properties/OrderedPropertiesFactoryTest.java
new file mode 100644
index 000000000..7384a6f5e
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/collections4/properties/OrderedPropertiesFactoryTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections4.properties;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link OrderedPropertiesFactory}.
+ */
+public class OrderedPropertiesFactoryTest extends 
AbstractPropertiesFactoryTest<OrderedProperties> {
+
+    public OrderedPropertiesFactoryTest() {
+        super(OrderedPropertiesFactory.INSTANCE);
+    }
+
+    @Test
+    @Override
+    public void testInstance() {
+        assertNotNull(OrderedPropertiesFactory.INSTANCE);
+    }
+
+}
diff --git 
a/src/test/java/org/apache/commons/collections4/properties/OrderedPropertiesTest.java
 
b/src/test/java/org/apache/commons/collections4/properties/OrderedPropertiesTest.java
new file mode 100644
index 000000000..0974a617d
--- /dev/null
+++ 
b/src/test/java/org/apache/commons/collections4/properties/OrderedPropertiesTest.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.collections4.properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link OrderedProperties}.
+ */
+public class OrderedPropertiesTest {
+
+    private void assertAscendingOrder(final OrderedProperties 
orderedProperties) {
+        final int first = 1;
+        final int last = 11;
+        final Enumeration<Object> enumObjects = orderedProperties.keys();
+        for (int i = first; i <= last; i++) {
+            assertEquals("key" + i, enumObjects.nextElement());
+        }
+        final Iterator<Object> iterSet = orderedProperties.keySet().iterator();
+        for (int i = first; i <= last; i++) {
+            assertEquals("key" + i, iterSet.next());
+        }
+        final Enumeration<?> propertyNames = orderedProperties.propertyNames();
+        for (int i = first; i <= last; i++) {
+            assertEquals("key" + i, propertyNames.nextElement());
+        }
+    }
+
+    private OrderedProperties assertDescendingOrder(final OrderedProperties 
orderedProperties) {
+        final int first = 11;
+        final int last = 1;
+        final Enumeration<Object> enumObjects = orderedProperties.keys();
+        for (int i = first; i <= last; i--) {
+            assertEquals("key" + i, enumObjects.nextElement());
+        }
+        final Iterator<Object> iterSet = orderedProperties.keySet().iterator();
+        for (int i = first; i <= last; i--) {
+            assertEquals("key" + i, iterSet.next());
+        }
+        final Enumeration<?> propertyNames = orderedProperties.propertyNames();
+        for (int i = first; i <= last; i--) {
+            assertEquals("key" + i, propertyNames.nextElement());
+        }
+        return orderedProperties;
+    }
+
+    private OrderedProperties loadOrderedKeysReverse() throws 
FileNotFoundException, IOException {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        try (FileReader reader = new 
FileReader("src/test/resources/org/apache/commons/collections4/properties/test-reverse.properties"))
 {
+            orderedProperties.load(reader);
+        }
+        return assertDescendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testCompute() {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        int first = 1;
+        int last = 11;
+        for (int i = first; i <= last; i++) {
+            final AtomicInteger aInt = new AtomicInteger(i);
+            orderedProperties.compute("key" + i, (k, v) -> "value" + 
aInt.get());
+        }
+        assertAscendingOrder(orderedProperties);
+        orderedProperties.clear();
+        first = 11;
+        last = 1;
+        for (int i = first; i >= last; i--) {
+            final AtomicInteger aInt = new AtomicInteger(i);
+            orderedProperties.compute("key" + i, (k, v) -> "value" + 
aInt.get());
+        }
+        assertDescendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testComputeIfAbsent() {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        int first = 1;
+        int last = 11;
+        for (int i = first; i <= last; i++) {
+            final AtomicInteger aInt = new AtomicInteger(i);
+            orderedProperties.computeIfAbsent("key" + i, k -> "value" + 
aInt.get());
+        }
+        assertAscendingOrder(orderedProperties);
+        orderedProperties.clear();
+        first = 11;
+        last = 1;
+        for (int i = first; i >= last; i--) {
+            final AtomicInteger aInt = new AtomicInteger(i);
+            orderedProperties.computeIfAbsent("key" + i, k -> "value" + 
aInt.get());
+        }
+        assertDescendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testEntrySet() {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        for (char ch = 'Z'; ch >= 'A'; ch--) {
+            orderedProperties.put(String.valueOf(ch), "Value" + ch);
+        }
+        final Iterator<Map.Entry<Object, Object>> entries = 
orderedProperties.entrySet().iterator();
+        for (char ch = 'Z'; ch <= 'A'; ch++) {
+            final Map.Entry<Object, Object> entry = entries.next();
+            assertEquals(String.valueOf(ch), entry.getKey());
+            assertEquals("Value" + ch, entry.getValue());
+        }
+    }
+
+    @Test
+    public void testKeys() {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        for (char ch = 'Z'; ch >= 'A'; ch--) {
+            orderedProperties.put(String.valueOf(ch), "Value" + ch);
+        }
+        final Enumeration<Object> keys = orderedProperties.keys();
+        for (char ch = 'Z'; ch <= 'A'; ch++) {
+            assertEquals(String.valueOf(ch), keys.nextElement());
+        }
+    }
+
+    @Test
+    public void testLoadOrderedKeys() throws FileNotFoundException, 
IOException {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        try (FileReader reader = new 
FileReader("src/test/resources/org/apache/commons/collections4/properties/test.properties"))
 {
+            orderedProperties.load(reader);
+        }
+        assertAscendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testLoadOrderedKeysReverse() throws FileNotFoundException, 
IOException {
+        loadOrderedKeysReverse();
+    }
+
+    @Test
+    public void testMerge() {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        int first = 1;
+        int last = 11;
+        for (int i = first; i <= last; i++) {
+            orderedProperties.merge("key" + i, "value" + i, (k, v) -> v);
+        }
+        assertAscendingOrder(orderedProperties);
+        orderedProperties.clear();
+        first = 11;
+        last = 1;
+        for (int i = first; i >= last; i--) {
+            orderedProperties.merge("key" + i, "value" + i, (k, v) -> v);
+        }
+        assertDescendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testPut() {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        int first = 1;
+        int last = 11;
+        for (int i = first; i <= last; i++) {
+            orderedProperties.put("key" + i, "value" + i);
+        }
+        assertAscendingOrder(orderedProperties);
+        orderedProperties.clear();
+        first = 11;
+        last = 1;
+        for (int i = first; i >= last; i--) {
+            orderedProperties.put("key" + i, "value" + i);
+        }
+        assertDescendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testPutAll() {
+        final OrderedProperties sourceProperties = new OrderedProperties();
+        int first = 1;
+        int last = 11;
+        for (int i = first; i <= last; i++) {
+            sourceProperties.put("key" + i, "value" + i);
+        }
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        orderedProperties.putAll(sourceProperties);
+        assertAscendingOrder(orderedProperties);
+        orderedProperties.clear();
+        first = 11;
+        last = 1;
+        for (int i = first; i >= last; i--) {
+            orderedProperties.put("key" + i, "value" + i);
+        }
+        assertDescendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testPutIfAbsent() {
+        final OrderedProperties orderedProperties = new OrderedProperties();
+        int first = 1;
+        int last = 11;
+        for (int i = first; i <= last; i++) {
+            orderedProperties.putIfAbsent("key" + i, "value" + i);
+        }
+        assertAscendingOrder(orderedProperties);
+        orderedProperties.clear();
+        first = 11;
+        last = 1;
+        for (int i = first; i >= last; i--) {
+            orderedProperties.putIfAbsent("key" + i, "value" + i);
+        }
+        assertDescendingOrder(orderedProperties);
+    }
+
+    @Test
+    public void testRemoveKey() throws FileNotFoundException, IOException {
+        final OrderedProperties props = loadOrderedKeysReverse();
+        final String k = "key1";
+        props.remove(k);
+        assertFalse(props.contains(k));
+        assertFalse(props.containsKey(k));
+        assertFalse(Collections.list(props.keys()).contains(k));
+        assertFalse(Collections.list(props.propertyNames()).contains(k));
+    }
+
+    @Test
+    public void testRemoveKeyValue() throws FileNotFoundException, IOException 
{
+        final OrderedProperties props = loadOrderedKeysReverse();
+        final String k = "key1";
+        props.remove(k, "value1");
+        assertFalse(props.contains(k));
+        assertFalse(props.containsKey(k));
+        assertFalse(Collections.list(props.keys()).contains(k));
+        assertFalse(Collections.list(props.propertyNames()).contains(k));
+    }
+}
diff --git 
a/src/test/resources/org/apache/commons/collections4/properties/test-reverse.properties
 
b/src/test/resources/org/apache/commons/collections4/properties/test-reverse.properties
new file mode 100644
index 000000000..9734ebb06
--- /dev/null
+++ 
b/src/test/resources/org/apache/commons/collections4/properties/test-reverse.properties
@@ -0,0 +1,25 @@
+#   Licensed to the Apache Software Foundation (ASF) under one or more
+#   contributor license agreements.  See the NOTICE file distributed with
+#   this work for additional information regarding copyright ownership.
+#   The ASF licenses this file to You under the Apache License, Version 2.0
+#   (the "License"); you may not use this file except in compliance with
+#   the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+key11 = value11
+key10 = value10
+key9 = value9
+key8 = value8
+key7 = value7
+key6 = value6
+key5 = value5
+key4 = value4
+key3 = value3
+key2 = value2
+key1 = value1

Reply via email to