Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write java.util.Properties to XML with sorted keys?

Tags:

java

xml

I'd like to store a properties file as XML. Is there a way to sort the keys when doing this so that the generated XML file will be in alphabetical order?

String propFile = "/path/to/file";
Properties props = new Properties();
/*set some properties here*/
try {
    FileOutputStream xmlStream = new FileOutputStream(propFile);
    /*this comes out unsorted*/
    props.storeToXML(xmlStream,"");
} catch (IOException e) {
    e.printStackTrace();
}
like image 871
user5084 Avatar asked Sep 10 '08 15:09

user5084


7 Answers

In my testing, the other answers to this question don't work properly on AIX. My particular test machine is running this version:

IBM J9 VM (build 2.4, JRE 1.6.0 IBM J9 2.4 AIX ppc64-64 jvmap6460sr9-20110624_85526

After looking through the implementation of the store method, I found that it relies upon entrySet. This method works well for me.

public static void saveSorted(Properties props, FileWriter fw, String comment) throws IOException {
    Properties tmp = new Properties() {
        @Override
        public Set<Object> keySet() {
            return Collections.unmodifiableSet(new TreeSet<Object>(super.keySet()));
        }

        @Override
        public Set<java.util.Map.Entry<Object,Object>> entrySet() {
            TreeSet<java.util.Map.Entry<Object,Object>> tmp = new TreeSet<java.util.Map.Entry<Object,Object>>(new Comparator<java.util.Map.Entry<Object,Object>>() {
                @Override
                public int compare(java.util.Map.Entry<Object, Object> entry1, java.util.Map.Entry<Object, Object> entry2) {
                    String key1 = entry1.getKey().toString();
                    String key2 = entry2.getKey().toString();
                    return key1.compareTo(key2);
                }
            });

            tmp.addAll(super.entrySet());

            return Collections.unmodifiableSet(tmp);
        }

        @Override
        public synchronized Enumeration<Object> keys() {
            return Collections.enumeration(new TreeSet<Object>(super.keySet()));
        }

        @Override
        public Set<String> stringPropertyNames() {
            TreeSet<String> set = new TreeSet<String>();
            for(Object o : keySet()) {
                set.add((String)o);
            }
            return set;
        }
    };

    tmp.putAll(props);
    tmp.store(fw, comment);
}
like image 157
trevorsky Avatar answered Oct 02 '22 15:10

trevorsky


Here's a quick and dirty way to do it:

String propFile = "/path/to/file";
Properties props = new Properties();

/* Set some properties here */

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

tmp.putAll(props);

try {
    FileOutputStream xmlStream = new FileOutputStream(propFile);
    /* This comes out SORTED! */
    tmp.storeToXML(xmlStream,"");
} catch (IOException e) {
    e.printStackTrace();
}

Here are the caveats:

  • The tmp Properties (an anonymous subclass) doesn't fulfill the contract of Properties.

For example, if you got its keySet and tried to remove an element from it, an exception would be raised. So, don't allow instances of this subclass to escape! In the snippet above, you are never passing it to another object or returning it to a caller who has a legitimate expectation that it fulfills the contract of Properties, so it is safe.

  • The implementation of Properties.storeToXML could change, causing it to ignore the keySet method.

For example, a future release, or OpenJDK, could use the keys() method of Hashtable instead of keySet. This is one of the reasons why classes should always document their "self-use" (Effective Java Item 15). However, in this case, the worst that would happen is that your output would revert to unsorted.

  • Remember that the Properties storage methods ignore any "default" entries.
like image 21
erickson Avatar answered Oct 02 '22 14:10

erickson


Here's a way to produce sorted output for both store Properties.store(OutputStream out, String comments) and Properties.storeToXML(OutputStream os, String comment):

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

    @Override
    public synchronized Enumeration<Object> keys() {
        return Collections.enumeration(new TreeSet<Object>(super.keySet()));
    }
};
props.put("B", "Should come second");
props.put("A", "Should come first");
props.storeToXML(new FileOutputStream(new File("sortedProps.xml")), null);
props.store(new FileOutputStream(new File("sortedProps.properties")), null);
like image 43
Espen Avatar answered Oct 02 '22 15:10

Espen


The simplest hack would be to override keySet. A bit of a hack, and not guaranteed to work in future implementations:

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

(Disclaimer: I have not even tested that it compiles.)

Alternatively, you could use something like XSLT to reformat the produced XML.

like image 38
Tom Hawtin - tackline Avatar answered Oct 02 '22 13:10

Tom Hawtin - tackline


You could sort the keys first, then loop through the items in the properties file and write them to the xml file.

public static void main(String[] args){
        String propFile = "/tmp/test2.xml";
        Properties props = new Properties();
        props.setProperty("key", "value");
        props.setProperty("key1", "value1");
        props.setProperty("key2", "value2");
        props.setProperty("key3", "value3");
        props.setProperty("key4", "value4");

        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(propFile));
            List<String> list = new ArrayList<String>();
            for(Object o : props.keySet()){
                list.add((String)o);
            }
            Collections.sort(list);
            out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
            out.write("<!DOCTYPE properties SYSTEM \"http://java.sun.com/dtd/properties.dtd\">\n");
            out.write("<properties>\n");
            out.write("<comment/>\n");
            for(String s : list){
                out.write("<entry key=\"" + s + "\">" + props.getProperty(s) + "</entry>\n");
            }
            out.write("</properties>\n");
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
like image 31
ScArcher2 Avatar answered Oct 02 '22 14:10

ScArcher2


java.util.Properties is based on Hashtable, which does not store its values in alphabetical order, but in order of the hash of each item, that is why you are seeing the behaviour you are.

like image 26
samjudson Avatar answered Oct 02 '22 14:10

samjudson


java.util.Properties is a subclass of java.util.Hashtable. ('Hash', being the key here.)You'd have to come up with your own customer implementation based on something that keeps/defines order...like a TreeMap.

like image 30
Stu Thompson Avatar answered Oct 02 '22 14:10

Stu Thompson