22

I have a Properties object and sometimes I need to add other Properties to it.

Properties myBasicProps = this.getClass.getResourceAsStream(MY_PROPS_PATH);
...
Properties otherProps = new Properties();
otherProps.load(new StringReader(tempPropsString)); //tempPropsString contains my temporary properties
myBasicProps.putAll(otherProps);

I want to sort myBasicProps after this. I don't want to get all keys and values, sort them with Collections.sort() and then put it all to a new object. Is there a better way?

flavio.donze
  • 7,432
  • 9
  • 58
  • 91
shift66
  • 11,760
  • 13
  • 50
  • 83

13 Answers13

23

No, java.util.Properties extends java.util.Hashtable which doesn't define a predictable sort order for keys or values.

You could try dumping all values into something like java.util.TreeMap, which will impose a natural ordering on your keys.

harto
  • 89,823
  • 9
  • 47
  • 61
21

all you have to do is create class which extends Properties. source: java2s.com

import java.io.FileOutputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;

public class Main{
  public static void main(String[] args) throws Exception {
    SortedProperties sp = new SortedProperties();
    sp.put("B", "value B");
    sp.put("C", "value C");
    sp.put("A", "value A");
    sp.put("D", "value D");
    FileOutputStream fos = new FileOutputStream("sp.props");
    sp.store(fos, "sorted props");
  }

}
class SortedProperties extends Properties {
  public Enumeration keys() {
     Enumeration keysEnum = super.keys();
     Vector<String> keyList = new Vector<String>();
     while(keysEnum.hasMoreElements()){
       keyList.add((String)keysEnum.nextElement());
     }
     Collections.sort(keyList);
     return keyList.elements();
  }

}

it works for me.

danisupr4
  • 815
  • 1
  • 9
  • 22
  • 3
    Java 8 version: ArrayList result = Collections.list(super.keys()); Collections.sort(result, (a, b) -> a.toString().compareTo(b.toString())); return Collections.enumeration(result); – user2069723 Mar 31 '16 at 15:44
  • 2
    This does not work on `java >=9` . E.g. when using `BB`,`CC`.`AA`,`DD` for keys. See my [post](https://stackoverflow.com/a/55957344/1555615) below. – Marinos An May 02 '19 at 17:23
14

Overriding keys works well with Java 8, but since Java 9, the new implementation of method store don't call the method keys any more but the method entrySet.

So, you have to override entrySet too to make your Properties sorted with Java 8/9/10 when stored.

Here an example with inline overriding:

Properties properties = new Properties() {

    private static final long serialVersionUID = 1L;

    @Override
    public Set<Object> keySet() {
        return Collections.unmodifiableSet(new TreeSet<Object>(super.keySet()));
    }

    @Override
    public Set<Map.Entry<Object, Object>> entrySet() {

        Set<Map.Entry<Object, Object>> set1 = super.entrySet();
        Set<Map.Entry<Object, Object>> set2 = new LinkedHashSet<Map.Entry<Object, Object>>(set1.size());

        Iterator<Map.Entry<Object, Object>> iterator = set1.stream().sorted(new Comparator<Map.Entry<Object, Object>>() {

            @Override
            public int compare(java.util.Map.Entry<Object, Object> o1, java.util.Map.Entry<Object, Object> o2) {
                return o1.getKey().toString().compareTo(o2.getKey().toString());
            }
        }).iterator();

        while (iterator.hasNext())
            set2.add(iterator.next());

        return set2;
    }

    @Override
    public synchronized Enumeration<Object> keys() {
        return Collections.enumeration(new TreeSet<Object>(super.keySet()));
        }
    };
Stéphane Millien
  • 3,238
  • 22
  • 36
10

Solution working also for java > 8

Despite the fact that the question does not explicitly mention about store(OutputStream out, String comments) functionality, I find this (and load(InputStream inStream) respectively) to be the most interesting part of Properties.

If we were not interested in storing/loading properties we could just replace Properties with some Map or SortedMap implementation.

The difference in Properties compared to Map is the existence of store()/load() methods which offer some specific serialization rules. These rules have not (and will not) change across java versions. e.g.

  • Storing pairs like prop=value
  • Escaping rules for unicode and special characters.
  • Adding optional comment on top

Unfortunately the above 3 parts of the functionality are hidden inside private methods, and cannot be easily re-used inside extended or alternative implementations that wish to use different internal data-structures (e.g. for keeping properties sorted).

So what remains is to keep the same internal structures and override the two store() methods.

Note: For getting sorted properties, some time ago I followed danisupr4 implementation, but on java9 it broke.

TL;DR

The code below maintains the full functionality of Properties by overriding only store(OutputStream out, String comments) method, and is tested with all versions from java5 - java12.
I believe that filtering the output of this public method makes the implementation less fragile to future code changes in java.util.Properties class.


    class SortedStoreProperties extends Properties {
    
        @Override
        public void store(OutputStream out, String comments) throws IOException {
            Properties sortedProps = new Properties() {
                @Override
                public Set<Map.Entry<Object, Object>> entrySet() {
                    /*
                     * Using comparator to avoid the following exception on jdk >=9: 
                     * java.lang.ClassCastException: java.base/java.util.concurrent.ConcurrentHashMap$MapEntry cannot be cast to java.base/java.lang.Comparable
                     */
                    Set<Map.Entry<Object, Object>> sortedSet = new TreeSet<Map.Entry<Object, Object>>(new Comparator<Map.Entry<Object, Object>>() {
                        @Override
                        public int compare(Map.Entry<Object, Object> o1, Map.Entry<Object, Object> o2) {
                            return o1.getKey().toString().compareTo(o2.getKey().toString());
                        }
                    }
                    );
                    sortedSet.addAll(super.entrySet());
                    return sortedSet;
                }
    
                @Override
                public Set<Object> keySet() {
                    return new TreeSet<Object>(super.keySet());
                }
    
                @Override
                public synchronized Enumeration<Object> keys() {
                    return Collections.enumeration(new TreeSet<Object>(super.keySet()));
                }
    
            };
            sortedProps.putAll(this);
            sortedProps.store(out, comments);
        }
    }

Note: Depending on who calls the store(), it might be even better, to not override the existing method but create a new one: e.g. storeSorted().

NOTE:

For simplicity I override only one of the two store() methods but the same concept applies to both.

Marinos An
  • 9,481
  • 6
  • 63
  • 96
6

@danisupr4 has the best solution.

I would improve on it slightly so you won't receive any warnings in your IDE:

public static class MyProperties extends Properties {
    private static final long serialVersionUID = 1L;

    public Enumeration<Object> keys() {
        Enumeration<Object> keysEnum = super.keys();
        Vector<Object> keyList = new Vector<Object>();

        while (keysEnum.hasMoreElements()) {
            keyList.add(keysEnum.nextElement());
        }

        Collections.sort(keyList, new Comparator<Object>() {
            @Override
            public int compare(Object o1, Object o2) {
                return o1.toString().compareTo(o2.toString());
            }
        });

        return keyList.elements();
    }
}
Andy
  • 209
  • 2
  • 2
6

TreeMap should be the simplest way:

Properties myProps = this.getClass.getResourceAsStream(MY_PROPS_PATH);

try {
    myProps.load(new FileInputStream(extraPropertiesFilename));
        //you can load more properties from external file

    Map<String, String> sortedMap = new TreeMap(myProps);

    //output sorted properties (key=value)
    for (String key : sortedMap.keySet()) {
        System.out.println(key + "=" + sortedMap.get(key));
    }

} catch (Exception e) {
    e.printStackTrace();
}
chenlao
  • 61
  • 1
  • 2
4

it much simpler to just sort the keys:

List<String> keys = new ArrayList<String>()
for(String key : properties.stringPropertyNames()) {
  keys.add(key)
}

Collections.sort(keys);
Luke Samad
  • 37
  • 2
3

In Java 8 Properties.keys() was used for storing the properties , in Java 9 this changed and Properties.entrySet() was used:

Java 8

private void store0(BufferedWriter bw, String comments, boolean escUnicode)
    throws IOException
{
...
        for (Enumeration<?> e = keys(); e.hasMoreElements();) {

Java 9

private void store0(BufferedWriter bw, String comments, boolean escUnicode)
    throws IOException
{
...
        for (Map.Entry<Object, Object> e : entrySet()) {

I use this class to handle both java version:

public class SortedProperties extends Properties {

    /**
     * constructor
     *
     * @param unsortedProperties
     */
    public SortedProperties(Properties unsortedProperties) {
        putAll(unsortedProperties);
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public synchronized Enumeration keys() {
        Enumeration<Object> keysEnum = super.keys();
        Vector<String> keyList = new Vector<String>();
        while (keysEnum.hasMoreElements()) {
            keyList.add((String) keysEnum.nextElement());
        }
        Collections.sort(keyList);
        return keyList.elements();
    }

    @Override
    public Set<java.util.Map.Entry<Object, Object>> entrySet() {
        // use a TreeMap since in java 9 entrySet() instead of keys() is used in store()
        TreeMap<Object, Object> treeMap = new TreeMap<>();
        Set<Map.Entry<Object, Object>> entrySet = super.entrySet();
        for (Map.Entry<Object, Object> entry : entrySet) {
            treeMap.put(entry.getKey(), entry.getValue());
        }
        return Collections.synchronizedSet(treeMap.entrySet());
    }
}
flavio.donze
  • 7,432
  • 9
  • 58
  • 91
2

you can override keys() method for save sorted properties in txt file:

//this method downloaded from edu.umd.cs.findbugs.config package
@SuppressWarnings("unchecked")
@Override
public synchronized Enumeration<Object> keys() {
    Set<?> set = keySet();
    return (Enumeration<Object>) sortKeys((Set<String>) set);
}
static public Enumeration<?> sortKeys(Set<String> keySet) {
    List<String> sortedList = new ArrayList<String>();
    sortedList.addAll(keySet);
    Collections.sort(sortedList);
    return Collections.enumeration(sortedList);
}

and override stringPropertyNames() method for save sorted properties in xml file:

@Override
public Set<String> stringPropertyNames() {
Set<String> tmpSet = new TreeSet<String>();
for (Object key : keySet())
{
    tmpSet.add(key.toString());
}
    return tmpSet;
}
M-Razavi
  • 3,327
  • 2
  • 34
  • 46
2

I did by overriding method store, like this:

@Override
public void store(Writer writer, String comments) throws IOException {

    this.keySet().stream().map(k -> (String)k).sorted().forEach(k -> {
        try {
            writer.append(String.format("%s=%s\n", k, get(k)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

}

Previous answers hadn't fit well for me.

0

My solution, inspired of smillien62's one:

final Properties properties = new Properties()
{
  private boolean recurse;

  @Override
  public synchronized Set<Map.Entry<Object, Object>> entrySet()
  {
    if (this.recurse)
    {
      return super.entrySet();
    }
    else
    {
      this.recurse = true;
      final TreeMap<Object, Object> treeMap = new TreeMap<>(this);
      final Set<Map.Entry<Object, Object>> entries = treeMap.entrySet();
      this.recurse = false;
      return entries;
    }
  }
};
Olivier Faucheux
  • 2,520
  • 3
  • 29
  • 37
0

Try using a TreeMap (that sorts the key in natural order) and StringBuilder (we can use sysout also, but it is easy to log(slf4j) the return value)

public static String getPrettyPrintProps(Properties properties) {
Map<String, String> map = new TreeMap(properties);
StringBuilder sb = new StringBuilder();
map.forEach((k, v) -> sb.append("\n" + k + ":" + v ) );
return  sb.toString();
}
Anver Sadhat
  • 3,074
  • 1
  • 25
  • 26
0

How about:

    System.getProperties().entrySet().stream()
            .sorted((e1, e2) -> e1.getKey().toString().compareToIgnoreCase(e2.getKey().toString()))
Doctor Parameter
  • 1,202
  • 2
  • 15
  • 21