This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature/sort-dictionaries-prior-serializing in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-installer-factory-configuration.git
commit 5fb5ee648e817c16b37b627d668fd3aef3335331 Author: Konrad Windszus <[email protected]> AuthorDate: Fri Nov 15 18:07:19 2024 +0100 SLING-12488 Always emit properties in alphabetical order of their keys --- .../ConfigurationSerializerWebConsolePlugin.java | 4 +- .../configuration/impl/SortedDictionary.java | 113 +++++++++++++++++++++ .../configuration/impl/SortedDictionaryTest.java | 84 +++++++++++++++ 3 files changed, 200 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java index 46ce0b8..7eb8ecc 100644 --- a/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java +++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/ConfigurationSerializerWebConsolePlugin.java @@ -258,7 +258,9 @@ public class ConfigurationSerializerWebConsolePlugin extends GenericServlet { if (removeMergedDefaultProperties) { ConfigUtil.removeRedundantProperties(properties, mergedProperties); } - ConfigurationSerializerFactory.create(serializationFormat).serialize(properties, baos); + // always emit in alphabetical order of keys + ConfigurationSerializerFactory.create(serializationFormat) + .serialize(new SortedDictionary<>(properties), baos); pw.println("<textarea rows=\"20\" cols=\"120\" id=\"output\" readonly>"); pw.print(new String(baos.toByteArray(), StandardCharsets.UTF_8)); pw.println("</textarea>"); diff --git a/src/main/java/org/apache/sling/installer/factories/configuration/impl/SortedDictionary.java b/src/main/java/org/apache/sling/installer/factories/configuration/impl/SortedDictionary.java new file mode 100644 index 0000000..4878be1 --- /dev/null +++ b/src/main/java/org/apache/sling/installer/factories/configuration/impl/SortedDictionary.java @@ -0,0 +1,113 @@ +/* + * 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.sling.installer.factories.configuration.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * A sorted dictionary is a view on an existing {@link Dictionary} that is sorted by its keys + * in natural ordering. + * This is just a view on top of the delegate dictionary. All write operations modify + * the underlying delegate dictionary. + * @param <K> the type of the keys + * @param <V> the type of the values + */ +public class SortedDictionary<K, V> extends Dictionary<K, V> { + + private final Dictionary<K, V> delegate; + + public SortedDictionary(Dictionary<K, V> delegate) { + this.delegate = delegate; + } + + public int size() { + return delegate.size(); + } + + @Override + public int hashCode() { + return Objects.hash(delegate); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + public Enumeration<K> keys() { + return sortedEnumeration(delegate.keys()); + } + + public Enumeration<V> elements() { + // this needs to be sorted by keys + Enumeration<K> sortedKeys = keys(); + Collection<V> sortedValues = new ArrayList<V>(); + while (sortedKeys.hasMoreElements()) { + K key = sortedKeys.nextElement(); + sortedValues.add(delegate.get(key)); + } + return Collections.enumeration(sortedValues); + } + + public V get(Object key) { + return delegate.get(key); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + SortedDictionary other = (SortedDictionary) obj; + return Objects.equals(delegate, other.delegate); + } + + public V put(K key, V value) { + return delegate.put(key, value); + } + + public V remove(Object key) { + return delegate.remove(key); + } + + public String toString() { + return delegate.toString(); + } + + /** + * Returns an sorted enumeration of the given enumeration, sorted according to the natural ordering of the elements. + * For Strings, this is the lexicographic order. + * + * @param enumeration the enumeration to sort + * @return the sorted enumeration + */ + private static <T> Enumeration<T> sortedEnumeration(Enumeration<T> enumeration) { + SortedSet<T> sortedSet = new TreeSet<>(); + while (enumeration.hasMoreElements()) { + sortedSet.add(enumeration.nextElement()); + } + return Collections.enumeration(sortedSet); + } +} diff --git a/src/test/java/org/apache/sling/installer/factories/configuration/impl/SortedDictionaryTest.java b/src/test/java/org/apache/sling/installer/factories/configuration/impl/SortedDictionaryTest.java new file mode 100644 index 0000000..9c2cbd5 --- /dev/null +++ b/src/test/java/org/apache/sling/installer/factories/configuration/impl/SortedDictionaryTest.java @@ -0,0 +1,84 @@ +/* + * 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.sling.installer.factories.configuration.impl; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.Hashtable; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class SortedDictionaryTest { + + @Test + public void testKeysAndElements() { + Dictionary<String, Object> dictionary = new Hashtable<>(); + dictionary.put("Z", "z"); + dictionary.put("A", "a"); + dictionary.put("B", "b"); + SortedDictionary<String, Object> sortedDictionary = new SortedDictionary<>(dictionary); + assertArrayEquals( + new String[] {"A", "B", "Z"}, + Collections.list(sortedDictionary.keys()).toArray(new String[] {})); + assertArrayEquals( + new String[] {"a", "b", "z"}, + Collections.list(sortedDictionary.elements()).toArray(new String[] {})); + } + + @Test + public void testPutGetRemove() { + Dictionary<String, Object> dictionary = new Hashtable<>(); + Dictionary<String, Object> sortedDictionary = new SortedDictionary<>(dictionary); + sortedDictionary.put("foo", "bar"); + assertEquals("bar", sortedDictionary.get("foo")); + assertEquals("bar", dictionary.get("foo")); + sortedDictionary.remove("foo"); + assertNull(sortedDictionary.get("foo")); + assertNull(dictionary.get("foo")); + } + + @Test + public void testDelegatedGetters() { + Dictionary<String, Object> dictionary = new Hashtable<>(); + dictionary.put("foo", "bar"); + dictionary.put("bar", "baz"); + Dictionary<String, Object> sortedDictionary = new SortedDictionary<>(dictionary); + assertEquals(dictionary.size(), sortedDictionary.size()); + assertEquals(dictionary.isEmpty(), sortedDictionary.isEmpty()); + assertEquals(dictionary.toString(), sortedDictionary.toString()); + } + + @Test + public void testEqualsAndHashcode() { + Dictionary<String, Object> dictionary = new Hashtable<>(); + dictionary.put("foo", "bar"); + dictionary.put("bar", "baz"); + Dictionary<String, Object> sortedDictionary = new SortedDictionary<>(dictionary); + Dictionary<String, Object> dictionary2 = new Hashtable<>(); + dictionary2.put("foo", "bar"); + dictionary2.put("bar", "baz"); + Dictionary<String, Object> sortedDictionary2 = new SortedDictionary<>(dictionary2); + assertEquals(sortedDictionary, sortedDictionary2); + assertEquals(sortedDictionary.hashCode(), sortedDictionary2.hashCode()); + } +}
