Pulling values from a Java Properties file in order?
Asked Answered
S

16

38

I have a properties file where the order of the values is important. I want to be able to iterate through the properties file and output the values based on the order of the original file.

However, since the Properties file is backed by, correct me if I'm wrong, a Map that does not maintain insertion order, the iterator returns the values in the wrong order.

Here is the code I'm using

Enumeration names = propfile.propertyNames();
while (names.hasMoreElements()) {
    String name = (String) names.nextElement();
    //do stuff
}

Is there anyway to get the Properties back in order short of writting my own custom file parser?

Silverware answered 21/8, 2009 at 14:43 Comment(0)
O
6

If you can alter the property names your could prefix them with a numeral or other sortable prefix and then sort the Properties KeySet.

Obvert answered 21/8, 2009 at 15:0 Comment(4)
Yes, that had occurred to me. That might be the simplest workaround.Silverware
Probably not going to give what you want though as string sortable doesn't match integer sortable for mixed strings. 11-SomePropName will sort before 2-OtherPropName if you're sorting strings by their natural sort value.Bank
This is actually what I ended up doing. I was only dealing with four values and commons configuration need too many dependencies which would have complicated my build.Silverware
However this method sorts the entries alphabetically and doesn't guarantee insertion order (the order of the original file)Abysm
I
67

Extend java.util.Properties, override both put() and keys():

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.HashMap;

public class LinkedProperties extends Properties {
    private final HashSet<Object> keys = new LinkedHashSet<Object>();

    public LinkedProperties() {
    }

    public Iterable<Object> orderedKeys() {
        return Collections.list(keys());
    }

    public Enumeration<Object> keys() {
        return Collections.<Object>enumeration(keys);
    }

    public Object put(Object key, Object value) {
        keys.add(key);
        return super.put(key, value);
    }
}
Irving answered 13/5, 2010 at 15:32 Comment(5)
It would be safer to create your class as a wrapper around Properties, rather than extending it. Only overriding those methods makes assumptions about how the underlying class works (the assumption that putAll() uses put()), and you could run into situations where your keys set is incorrect.Hindrance
You should really override remove() and clear() as well - otherwise you'll get NullPointerExceptions on save()! Also you should add synchronized as it is in the parent's methods unless the collection used for the keys is thread safe.Cornelison
the type can be inferred in return Collections.<Object>enumeration(keys);, so this is sufficient: return Collections.enumeration(keys);Continence
Explaining the code: The trick is to use Java's Properties class, override it's put method and put the key into a data structure that saves the order of insertion. This works because Properties puts the keys/values at the same order it reads them from the file - top->bottom.Criminality
-1 - You assume that the put method is called according to the order of properties in the source file. I think you can not assume that this is guaranteed. If the file was read from bottom to top you will get the properties in reversed order.Proprietary
O
11

Nope - maps are inherently "unordered".

You could possibly create your own subclass of Properties which overrode setProperty and possibly put, but it would probably get very implementation-specific... Properties is a prime example of bad encapsulation. When I last wrote an extended version (about 10 years ago!) it ended up being hideous and definitely sensitive to the implementation details of Properties.

Overfill answered 21/8, 2009 at 14:45 Comment(5)
I was afraid of that. I'm looking at the Properties code right now and I see exactly what you mean. The backing implementation should really should be a settable delegate. Can you recommend any alternatives? Like would the Apache Commons configuration help me out?Silverware
Just a quick correction, Java does have a implementation of Map, LinkedHashMap, that DOES maintain insertion order.Silverware
@nemo: Yes, but that's a map specifically designed for that. Maps in general aren't ordered. I believe Spring has its own properties file reader which you might find useful.Overfill
extend Properties, override put() and store the keys in an internal List. use said list to iterate the properties in order.Exemplar
[DataMember(Name = "attribute ID", Order = 0)] private int _attributeID; can't we have such thing in JavaPater
B
8

Working example :

Map<String,String> properties = getOrderedProperties(new FileInputStream(new File("./a.properties")));
properties.entrySet().forEach(System.out::println);

Code for it

public Map<String, String> getOrderedProperties(InputStream in) throws IOException{
    Map<String, String> mp = new LinkedHashMap<>();
    (new Properties(){
        public synchronized Object put(Object key, Object value) {
            return mp.put((String) key, (String) value);
        }
    }).load(in);
    return mp;
}
Bucci answered 15/12, 2017 at 12:18 Comment(1)
That's a nice an minimalist example which fits my need: I don't need the old fashioned Properties structure but only a LinkedHashMap<String,String>. The Properties is only useful for parsing.Lachrymator
O
6

If you can alter the property names your could prefix them with a numeral or other sortable prefix and then sort the Properties KeySet.

Obvert answered 21/8, 2009 at 15:0 Comment(4)
Yes, that had occurred to me. That might be the simplest workaround.Silverware
Probably not going to give what you want though as string sortable doesn't match integer sortable for mixed strings. 11-SomePropName will sort before 2-OtherPropName if you're sorting strings by their natural sort value.Bank
This is actually what I ended up doing. I was only dealing with four values and commons configuration need too many dependencies which would have complicated my build.Silverware
However this method sorts the entries alphabetically and doesn't guarantee insertion order (the order of the original file)Abysm
C
5

Apache Commons Configuration might do the trick for you. I haven't tested this myself, but I checked their sources and looks like property keys are backed by LinkedList in AbstractFileConfiguration class:

public Iterator getKeys()
{
    reload();
    List keyList = new LinkedList();
    enterNoReload();
    try
    {
        for (Iterator it = super.getKeys(); it.hasNext();)
        {
            keyList.add(it.next());
        }

        return keyList.iterator();
    }
    finally
    {
        exitNoReload();
    }
}
Crooks answered 21/8, 2009 at 15:30 Comment(2)
This looks interesting, but the using one piece of the commons requires loading several other pieces. I ended up going with a quick and dirty solution.Silverware
Most of the dependencies are optional. For a simple PropertiesConfiguration you only need Commons Lang and Commons Collections.Offhand
C
5

Dominique Laurent's solution above works great for me. I also added the following method override:

public Set<String> stringPropertyNames() {
    Set<String> set = new LinkedHashSet<String>();

    for (Object key : this.keys) {
        set.add((String)key);
    }

    return set;
}

Probably not the most efficient, but it's only executed once in my servlet lifecycle.

Thanks Dominique!

Commutual answered 16/2, 2011 at 19:23 Comment(1)
It's not very practical to refer to someone else solution, with an added method.Lachrymator
W
4

I'll add one more famous YAEOOJP (Yet Another Example Of Ordered Java Properties) to this thread because it seems nobody could ever care less about default properties which you can feed to your properties.

@see http://docs.oracle.com/javase/tutorial/essential/environment/properties.html

That's my class: surely not 1016% compliant with any possible situation, but that is fine for my limited dumb purposes right now. Any further comment for correction is appreciated so the Greater Good can benefit.

import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * Remember javadocs  >:o
 */
public class LinkedProperties extends Properties {

    protected LinkedProperties linkedDefaults;
    protected Set<Object> linkedKeys = new LinkedHashSet<>();

    public LinkedProperties() { super(); }

    public LinkedProperties(LinkedProperties defaultProps) {
        super(defaultProps); // super.defaults = defaultProps;
        this.linkedDefaults = defaultProps;
    }

    @Override
    public synchronized Enumeration<?> propertyNames() {
        return keys();
    }

    @Override
    public Enumeration<Object> keys() {
        Set<Object> allKeys = new LinkedHashSet<>();
        if (null != defaults) {
            allKeys.addAll(linkedDefaults.linkedKeys);
        }
        allKeys.addAll(this.linkedKeys);
        return Collections.enumeration(allKeys);
    }

    @Override
    public synchronized Object put(Object key, Object value) {
        linkedKeys.add(key);
        return super.put(key, value);
    }

    @Override
    public synchronized Object remove(Object key) {
        linkedKeys.remove(key);
        return super.remove(key);
    }

    @Override
    public synchronized void putAll(Map<?, ?> values) {
        for (Object key : values.keySet()) {
            linkedKeys.add(key);
        }
        super.putAll(values);
    }

    @Override
    public synchronized void clear() {
        super.clear();
        linkedKeys.clear();
    }

    private static final long serialVersionUID = 0xC00L;
}
Wallpaper answered 17/4, 2015 at 8:16 Comment(1)
Thanks!!!! It resolve my problem to save in same order as it present in the file.Skylar
S
3

In the interest of completeness ...

public class LinkedProperties extends Properties {

    private final LinkedHashSet<Object> keys = new LinkedHashSet<Object>();

    @Override
    public Enumeration<?> propertyNames() {
        return Collections.enumeration(keys);
    }

    @Override
    public synchronized Enumeration<Object> elements() {
        return Collections.enumeration(keys);
    }

    public Enumeration<Object> keys() {
        return Collections.enumeration(keys);
    }

    public Object put(Object key, Object value) {
        keys.add(key);
        return super.put(key, value);
    }

    @Override
    public synchronized Object remove(Object key) {
        keys.remove(key);
        return super.remove(key);
    }

    @Override
    public synchronized void clear() {
        keys.clear();
        super.clear();
    }
}

I dont think the methods returning set should be overridden as a set by definition does not maintain insertion order

Sheeree answered 14/2, 2013 at 12:36 Comment(0)
S
3
Map<String, String> mapFile = new LinkedHashMap<String, String>();
ResourceBundle bundle = ResourceBundle.getBundle(fileName);
TreeSet<String> keySet = new TreeSet<String>(bundle.keySet());
for(String key : keySet){
    System.out.println(key+" "+bundle.getString(key));
    mapFile.put(key, bundle.getString(key));
}

This persist the order of property file

Sacker answered 18/4, 2016 at 10:29 Comment(0)
V
2

See https://github.com/etiennestuder/java-ordered-properties for a complete implementation that allows to read/write properties files in a well-defined order.

OrderedProperties properties = new OrderedProperties();
properties.load(new FileInputStream(new File("~/some.properties")));
Variegation answered 12/12, 2014 at 16:37 Comment(0)
N
2

For those who read this topic recently: just use class PropertiesConfiguration from org.apache.commons:commons-configuration2. I've tested that it keeps properties ordering (because it uses LinkedHashMap internally). Doing:

`
    PropertiesConfiguration properties = new PropertiesConfiguration();
    properties.read(new FileReader("/some/path));
    properties.write(new FileWriter("/some/other/path"));
`

only removes trailing whitespace and unnecessary escapes.

Nananne answered 26/7, 2019 at 10:11 Comment(0)
B
1

You must override also keySet() if you want to export Properties as XML:

public Set<Object> keySet() { return keys; }

Boltrope answered 18/8, 2010 at 20:34 Comment(1)
This does not answer to the OP.Lachrymator
C
1

As I see it, Properties is to much bound to Hashtable. I suggest reading it in order to a LinkedHashMap. For that you'll only need to override a single method, Object put(Object key, Object value), disregarding the Properties as a key/value container:

public class InOrderPropertiesLoader<T extends Map<String, String>> {

    private final T map;

    private final Properties properties = new Properties() {
        public Object put(Object key, Object value) {
            map.put((String) key, (String) value);
            return null;
        }

    };

    public InOrderPropertiesLoader(T map) {
        this.map = map;
    }

    public synchronized T load(InputStream inStream) throws IOException {
        properties.load(inStream);

        return map;
    }
}

Usage:

LinkedHashMap<String, String> props = new LinkedHashMap<>();
try (InputStream inputStream = new FileInputStream(file)) {
    new InOrderPropertiesLoader<>(props).load(inputStream);
}
Criminality answered 11/8, 2016 at 15:26 Comment(2)
I like the idea of using the Properties only for parsing, but the implementation does not guarantee the file lines order (it depends on the Map implementation passed in the constructor).Lachrymator
Yep, the properties are loaded and passed in order. The passed map can decide how to store them.Criminality
P
1

In some answers it is assumed that properties read from file are put to instance of Properties (by calls to put) in order they appear they in file. While this is in general how it behaves I don't see any guarantee for such order.

IMHO: it is better to read the file line by line (so that the order is guaranteed), than use the Properties class just as a parser of single property line and finally store it in some ordered Collection like LinkedHashMap.

This can be achieved like this:

private LinkedHashMap<String, String> readPropertiesInOrderFrom(InputStream propertiesFileInputStream)
                                                           throws IOException {
    if (propertiesFileInputStream == null) {
      return new LinkedHashMap(0);
    }

    LinkedHashMap<String, String> orderedProperties = new LinkedHashMap<String, String>();

    final Properties properties = new Properties(); // use only as a parser
    final BufferedReader reader = new BufferedReader(new InputStreamReader(propertiesFileInputStream));

    String rawLine = reader.readLine();

    while (rawLine != null) {
      final ByteArrayInputStream lineStream = new ByteArrayInputStream(rawLine.getBytes("ISO-8859-1"));
      properties.load(lineStream); // load only one line, so there is no problem with mixing the order in which "put" method is called


      final Enumeration<?> propertyNames = properties.<String>propertyNames();

      if (propertyNames.hasMoreElements()) { // need to check because there can be empty or not parsable line for example

        final String parsedKey = (String) propertyNames.nextElement();
        final String parsedValue = properties.getProperty(parsedKey);

        orderedProperties.put(parsedKey, parsedValue);
        properties.clear(); // make sure next iteration of while loop does not access current property
      }

      rawLine = reader.readLine();
    }

    return orderedProperties;

  }

Just note that the method posted above takes an InputStream which should be closed afterwards (of course there is no problem to rewrite it to take just a file as an argument).

Proprietary answered 9/12, 2016 at 10:56 Comment(2)
While I like the idea of using the Properties only for parsing, it looks a bit overkill.Lachrymator
@JulienKronegg: please note that this is to guarantee the order and you do this only once (on app startup). Looking at internal Java apis (collectors for example) would you say they are overkill as well? :)Proprietary
W
0

For Kotlin users, here's a basic example that's functional for write operations. The order is simply determined by the order of your calls to setProperty(k,v).

Per Kotlin's documentation for MutableMap and MutableSet, they both:

preserve the entry iteration order.

Not all use cases are covered.

class OrderedProperties: Properties() {

    private val orderedMap = mutableMapOf<Any, Any>()

    override val entries: MutableSet<MutableMap.MutableEntry<Any, Any>>
        get() = Collections.synchronizedSet(orderedMap.entries)


    @Synchronized
    override fun put(key: Any?, value: Any?): Any? {
        key ?: return null
        value ?: return null
        orderedMap[key] = value
        return orderedMap
    }

    override fun setProperty(key: String?, value: String?): Any? {
        return this.put(key, value)
    }

}
Warranty answered 16/8, 2022 at 23:20 Comment(0)
I
-2

An alternative is just to write your own properties file using LinkedHashMap, here is what I use :

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;

public class OrderedProperties {

    private static Map<String, String> properties = new LinkedHashMap<String, String>();

    private static OrderedProperties instance = null;

    private OrderedProperties() {

    }

    //The propertyFileName is read from the classpath and should be of format : key=value
    public static synchronized OrderedProperties getInstance(String propertyFileName) {
        if (instance == null) {
            instance = new OrderedProperties();
            readPropertiesFile(propertyFileName);
        }
        return instance;
    }

    private static void readPropertiesFile(String propertyFileName){
        LineIterator lineIterator = null;
        try {

            //read file from classpath
            URL url = instance.getClass().getResource(propertyFileName);

            lineIterator = FileUtils.lineIterator(new File(url.getFile()), "UTF-8");
            while (lineIterator.hasNext()) {
                String line = lineIterator.nextLine();

                //Continue to parse if there are blank lines (prevents IndesOutOfBoundsException)
                if (!line.trim().isEmpty()) {
                    List<String> keyValuesPairs = Arrays.asList(line.split("="));
                    properties.put(keyValuesPairs.get(0) , keyValuesPairs.get(1));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            lineIterator.close();
        }
    }

    public Map<String, String> getProperties() {
        return OrderedProperties.properties;
    }

    public String getProperty(String key) {
        return OrderedProperties.properties.get(key);
    }

}

To use :

    OrderedProperties o = OrderedProperties.getInstance("/project.properties");
    System.out.println(o.getProperty("test"));

Sample properties file (in this case project.properties) :

test=test2
Invertebrate answered 18/12, 2013 at 14:58 Comment(1)
The problem with this approach is, the original Properties class supports more than just the simple example "test=test2" when loading. For example, data can have "=", you can use escapes for special characters etc. Writing your own class means you have to implement a whole lot more.Seventy

© 2022 - 2024 — McMap. All rights reserved.