Skip to content

Commit c39c421

Browse files
committed
Introduce support for sorted Properties
This commit introduces an internal SortedProperties class that is a specialization of java.util.Properties which sorts properties alphanumerically based on their keys. This can be useful when storing a java.util.Properties instance in a properties file, since it allows such files to be generated in a repeatable manner with consistent ordering of properties. Comments in generated properties files can also be optionally omitted. An instance of SortedProperties can be created via two new createSortedProperties() factory methods in org.springframework.core.CollectionFactory. Closes gh-23018
1 parent 98079a3 commit c39c421

File tree

3 files changed

+418
-0
lines changed

3 files changed

+418
-0
lines changed

spring-core/src/main/java/org/springframework/core/CollectionFactory.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,50 @@ public String getProperty(String key) {
346346
};
347347
}
348348

349+
/**
350+
* Create a variant of {@link java.util.Properties} that sorts properties
351+
* alphanumerically based on their keys.
352+
*
353+
* <p>This can be useful when storing the {@link Properties} instance in a
354+
* properties file, since it allows such files to be generated in a repeatable
355+
* manner with consistent ordering of properties. Comments in generated
356+
* properties files can also be optionally omitted.
357+
*
358+
* @param omitComments {@code true} if comments should be omitted when
359+
* storing properties in a file
360+
* @return a new {@code Properties} instance
361+
* @since 5.2
362+
* @see #createSortedProperties(Properties, boolean)
363+
*/
364+
public static Properties createSortedProperties(boolean omitComments) {
365+
return new SortedProperties(omitComments);
366+
}
367+
368+
/**
369+
* Create a variant of {@link java.util.Properties} that sorts properties
370+
* alphanumerically based on their keys.
371+
*
372+
* <p>This can be useful when storing the {@code Properties} instance in a
373+
* properties file, since it allows such files to be generated in a repeatable
374+
* manner with consistent ordering of properties. Comments in generated
375+
* properties files can also be optionally omitted.
376+
*
377+
* <p>The returned {@code Properties} instance will be populated with
378+
* properties from the supplied {@code properties} object, but default
379+
* properties from the supplied {@code properties} object will not be copied.
380+
*
381+
* @param properties the {@code Properties} object from which to copy the
382+
* initial properties
383+
* @param omitComments {@code true} if comments should be omitted when
384+
* storing properties in a file
385+
* @return a new {@code Properties} instance
386+
* @since 5.2
387+
* @see #createSortedProperties(boolean)
388+
*/
389+
public static Properties createSortedProperties(Properties properties, boolean omitComments) {
390+
return new SortedProperties(properties, omitComments);
391+
}
392+
349393
/**
350394
* Cast the given type to a subtype of {@link Enum}.
351395
* @param enumType the enum type, never {@code null}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
21+
import java.io.OutputStream;
22+
import java.io.StringWriter;
23+
import java.io.Writer;
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.Collections;
26+
import java.util.Comparator;
27+
import java.util.Enumeration;
28+
import java.util.Map.Entry;
29+
import java.util.Properties;
30+
import java.util.Set;
31+
import java.util.TreeSet;
32+
33+
import org.springframework.util.StringUtils;
34+
35+
/**
36+
* Specialization of {@link Properties} that sorts properties alphanumerically
37+
* based on their keys.
38+
*
39+
* <p>This can be useful when storing the {@link Properties} instance in a
40+
* properties file, since it allows such files to be generated in a repeatable
41+
* manner with consistent ordering of properties.
42+
*
43+
* <p>Comments in generated properties files can also be optionally omitted.
44+
*
45+
* @author Sam Brannen
46+
* @since 5.2
47+
* @see java.util.Properties
48+
*/
49+
@SuppressWarnings("serial")
50+
class SortedProperties extends Properties {
51+
52+
static final String EOL = System.getProperty("line.separator");
53+
54+
private static final Comparator<Object> keyComparator = //
55+
(key1, key2) -> String.valueOf(key1).compareTo(String.valueOf(key2));
56+
57+
private static final Comparator<Entry<Object, Object>> entryComparator = //
58+
Entry.comparingByKey(keyComparator);
59+
60+
private final boolean omitComments;
61+
62+
63+
/**
64+
* Construct a new {@code SortedProperties} instance that honors the supplied
65+
* {@code omitComments} flag.
66+
*
67+
* @param omitComments {@code true} if comments should be omitted when
68+
* storing properties in a file
69+
*/
70+
SortedProperties(boolean omitComments) {
71+
this.omitComments = omitComments;
72+
}
73+
74+
/**
75+
* Construct a new {@code SortedProperties} instance with properties populated
76+
* from the supplied {@link Properties} object and honoring the supplied
77+
* {@code omitComments} flag.
78+
*
79+
* <p>Default properties from the supplied {@code Properties} object will
80+
* not be copied.
81+
*
82+
* @param properties the {@code Properties} object from which to copy the
83+
* initial properties
84+
* @param omitComments {@code true} if comments should be omitted when
85+
* storing properties in a file
86+
*/
87+
SortedProperties(Properties properties, boolean omitComments) {
88+
this(omitComments);
89+
putAll(properties);
90+
}
91+
92+
@Override
93+
public void store(OutputStream out, String comments) throws IOException {
94+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
95+
super.store(baos, (this.omitComments ? null : comments));
96+
String contents = new String(baos.toByteArray(), StandardCharsets.ISO_8859_1);
97+
for (String line : StringUtils.tokenizeToStringArray(contents, EOL)) {
98+
if (!this.omitComments || !line.startsWith("#")) {
99+
out.write((line + EOL).getBytes(StandardCharsets.ISO_8859_1));
100+
}
101+
}
102+
}
103+
104+
@Override
105+
public void store(Writer writer, String comments) throws IOException {
106+
StringWriter stringWriter = new StringWriter();
107+
super.store(stringWriter, (this.omitComments ? null : comments));
108+
String contents = stringWriter.toString();
109+
for (String line : StringUtils.tokenizeToStringArray(contents, EOL)) {
110+
if (!this.omitComments || !line.startsWith("#")) {
111+
writer.write(line + EOL);
112+
}
113+
}
114+
}
115+
116+
@Override
117+
public void storeToXML(OutputStream out, String comments) throws IOException {
118+
super.storeToXML(out, (this.omitComments ? null : comments));
119+
}
120+
121+
@Override
122+
public void storeToXML(OutputStream out, String comments, String encoding) throws IOException {
123+
super.storeToXML(out, (this.omitComments ? null : comments), encoding);
124+
}
125+
126+
/**
127+
* Return a sorted enumeration of the keys in this {@link Properties} object.
128+
* @see #keySet()
129+
*/
130+
@Override
131+
public synchronized Enumeration<Object> keys() {
132+
return Collections.enumeration(keySet());
133+
}
134+
135+
/**
136+
* Return a sorted set of the keys in this {@link Properties} object.
137+
* <p>The keys will be converted to strings if necessary using
138+
* {@link String#valueOf(Object)} and sorted alphanumerically according to
139+
* the natural order of strings.
140+
*/
141+
@Override
142+
public Set<Object> keySet() {
143+
Set<Object> sortedKeys = new TreeSet<>(keyComparator);
144+
sortedKeys.addAll(super.keySet());
145+
return Collections.synchronizedSet(sortedKeys);
146+
}
147+
148+
/**
149+
* Return a sorted set of the entries in this {@link Properties} object.
150+
* <p>The entries will be sorted based on their keys, and the keys will be
151+
* converted to strings if necessary using {@link String#valueOf(Object)}
152+
* and compared alphanumerically according to the natural order of strings.
153+
*/
154+
@Override
155+
public Set<Entry<Object, Object>> entrySet() {
156+
Set<Entry<Object, Object>> sortedEntries = new TreeSet<>(entryComparator);
157+
sortedEntries.addAll(super.entrySet());
158+
return Collections.synchronizedSet(sortedEntries);
159+
}
160+
161+
}

0 commit comments

Comments
 (0)