regex replace adding unwanted additional bracket - java

Okay, so here's my code:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class View {
private String item;
private static final Pattern p = Pattern.compile("\\{(.*?)\\}");
Matcher m;
Map map;
public View(String str){
this.item = str;
m = p.matcher(item);
map = new HashMap();
while(m.find()){
String key = m.group(1);
map.put(key, null);
}
}
public void add(String str, Object obj){
for(Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator(); it.hasNext();){
Map.Entry<String, Object> e = it.next();
String key = e.getKey();
Object value = e.getValue();
if(str.equals(key)){
e.setValue(obj);
}
}
}
public Object render(){
for(Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator(); it.hasNext();){
Map.Entry<String, Object> e = it.next();
String key = e.getKey();
Object value = e.getValue();
System.out.println(key + "" + value);
item = item.replaceAll("\\{" + key + "\\}", value.toString());
}
return item;
}
}
and I need it to pass these two tests:
#Test
public void testList() {
View view = new View("<table>{rows}</table>");
view.add("rows", Arrays.asList("<tr><td>Louis</td><td>Armstrong</td></tr>", "<tr><td>Benny</td><td>Goodman</td></tr>"));
Assert.assertEquals("<table><tr><td>Louis</td><td>Armstrong</td></tr><tr><td>Benny</td><td>Goodman</td></tr></table>", view.render());
}
#Test
public void testView() {
View view = new View("<table>{rows}</table>");
View row1 = new View("<tr><td>{firstName}</td><td>{lastName}</td></tr>");
row1.add("firstName", "Louis");
row1.add("lastName", "Armstrong");
View row2 = new View("<tr><td>{firstName}</td><td>{lastName}</td></tr>");
row2.add("firstName", "Benny");
row2.add("lastName", "Goodman");
view.add("rows", Arrays.asList(row1, row2));
Assert.assertEquals("<table><tr><td>Louis</td><td>Armstrong</td></tr><tr><td>Benny</td><td>Goodman</td></tr></table>", view.render());
}
However, I keep getting errors when I run the tests and I'm not sure how to go about fixing the issue.
I've noticed that what is going on is that instead of simply putting one bracket [ around the item to replace the string with, it is putting two.
Here's the error I'm getting:
org.junit.ComparisonFailure: expected:<<table>[<tr><td>Louis</td><td>Armstrong</td></tr><tr><td>Benny</td><td>Goodman</td></tr>]</table>> but was:<<table>[[<tr><td>Louis</td><td>Armstrong</td></tr>, <tr><td>Benny</td><td>Goodman</td></tr>]]</table>>
at org.junit.Assert.assertEquals(Assert.java:115)
If you look, it says it expects [<tr><td>Louis... but is instead getting [[<tr><td>Louis...
Im not sure how to fix this issue, so if someone would please point me in the right direction.

Your problem is that the String representation of the List you pass in the tests is not what you expect it to be.
If you run this code
List list = Arrays.asList("<p>foo</p>", "<p>bar</p>");
System.out.println(list.toString());
it will output to the console
[<p>foo</p>, <p>bar</p>]
In your test, you expect it to output HTML without any further markup, like so
<p>foo</p><p>bar</p>
To solve this issue, you'll have to iterate over the object you pass if applicable. The easiest method is to change the signature of the add method so that you can always iterate over the object
void add(String str, Iterable obj);
If that is not an option because you will also pass non-iterables (e.g. plain String objects), you can overload your method. Alternatively, you can do some custom type checking at runtime but that can get ugly fast.
EDIT:
you can just add (not replace) a method with the signature I provided before. For the second test, you need to implement the toString() method. Code like so:
public void add(String str, Iterable obj){
for(Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator(); it.hasNext();){
Map.Entry<String, Object> e = it.next();
String key = e.getKey();
if(str.equals(key)){
String val = "";
for (Object o : obj)
{
val += o.toString();
}
e.setValue(val);
}
}
}
#Override
public String toString() {
return render().toString();
}

Related

How to find a String that startsWith other String in Map<String, String>, by the most efficient way

I collect a lot of String that is package name, and put these strings into the map as key, the value is alias of the key. (i chose TreeMap<String, String>)
like:
Map<String, String> packages = new TreeMap<>();
packages.put("org.springframework.boot.actuate", "SpringBoot#Actuate");
packages.put("org.springframework.boot.aop", "SpringBoot#AOP");
packages.put("org.apache.ibatis", "Mybatis#Core");
packages.put("org.mybatis.spring", "Mybatis#Spring");
packages.put("org.slf4j", "SLF4j#Core");
packages.put("ch.qos.logback.core", "Logback#Core");
then, the method parameter is specified value of fully-qualified Class name, i need return the value which alias(value) corresponds to the package(key)
like:
// className: ch.qos.logback.core.layout.EchoLayout
#Nullable
String find(String className) {
// method implementation
return ""; // return Logback#Core
}
i want to step-by-step lookup from the first level of package names using TreeMap.subMap(), and do it recursively
like:
// !!! pseudocode !!!
#Nullable
String find(String className, int position) {
int idx = className.indexOf(".", position);
String pkg = className.substring(0, idx);
Map<String, String> sub = packages.subMap(pkg, pkg + " ");
if (sub.size() == 1) {
return ""; // found it
} else {
return ""; // other situations
}
return ""; // not found any alias
}
I'm not sure that's the most efficient way to do it, this method will be called frequently. can anyone offer a better solution?
I'd argue it would be simpler to start with the FQCN from the argument and remove one level from the end and check for the existence in the map at each level. FQCN can get pretty long but realistically you're looking at maybe 10-20 existence checks in the worst case. Also, if you choose this approach I would use HashMap instead of TreeMap so lookups are O(1).
Example Code:
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.HashMap;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class Main {
static Map<String, String> packages = new HashMap<>();
public static void main(String[] args) {
packages.put("org.springframework.boot.actuate", "SpringBoot#Actuate");
packages.put("org.springframework.boot.aop", "SpringBoot#AOP");
packages.put("org.apache.ibatis", "Mybatis#Core");
packages.put("org.mybatis.spring", "Mybatis#Spring");
packages.put("org.slf4j", "SLF4j#Core");
packages.put("ch.qos.logback.core", "Logback#Core");
System.out.println(find("ch.qos.logback.core.layout.EchoLayout"));
}
// className: ch.qos.logback.core.layout.EchoLayout
static String find(String className) {
// method implementation
List<String> classNameParts = Arrays.asList(className.split("\\."));
// Checks for entries in the map in the following order
// ch.qos.logback.core.layout.EchoLayout
// ch.qos.logback.core.layout
// ch.qos.logback.core
// ch.qos.logback
// ch.qos
// ch
for(int i = classNameParts.size(); i >= 0; i--){
String packagePart = classNameParts.stream().limit(i).collect(Collectors.joining("."));
if(packages.containsKey(packagePart)){
return packages.get(packagePart);
}
}
// No entry found
return null;
}
}
Try it on Repl.it

Case-insensitive String Substitutor

I am using org.apache.commons.lang3.text.StrSubstitutor to parse a String. I have it setup similar to this:
StrSubstitutor sub = new StrSubstitutor(messageValues, "&(", ")");
String format = sub.replace("Information: &(killer) killed &(target)!");
This no longer works if I write the keys in different cases:
"Information: &(KILLER) killed &(TARGET)!"
Is there a way of making the keys for the String Substitutor case-insensitive?
I cannot use toLowerCase() because I only want the keys to be case-insensitive.
StrSubstitutor has a constructor that takes an instance of StrLookup. You can create an implementation of StrLookup that lowercases the keys its looking for before actually looking for them.
Here's how it looks like:
public class CaseInsensitiveStrLookup<V> extends StrLookup<V> {
private final Map<String, V> map;
CaseInsensitiveStrLookup(final Map<String, V> map) {
this.map = map;
}
#Override
public String lookup(final String key) {
String lowercaseKey = key.toLowerCase(); //lowercase the key you're looking for
if (map == null) {
return null;
}
final Object obj = map.get(lowercaseKey);
if (obj == null) {
return null;
}
return obj.toString();
}
}
Using this StrLookup implementation you don't need to worry about what kind of Map you're passing to the constructor.
The following test case returns succesfully, using the above implementation:
import org.apache.commons.lang3.text.StrSubstitutor;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.HashMap;
import java.util.Map;
#Test
public class TestClass {
#Test
public void test() {
Map<String, String> messageValues = new HashMap<String, String>();
messageValues.put("killer", "Johnson");
messageValues.put("target", "Quagmire");
StrSubstitutor sub = new StrSubstitutor(new CaseInsensitiveStrLookup<String>(messageValues), "&(", ")", '\\');
String format2 = sub.replace("Information: &(killer) killed &(target)!");
String format = sub.replace("Information: &(KILLER) killed &(TARGET)!");
Assert.assertEquals(format, "Information: Johnson killed Quagmire!");
Assert.assertEquals(format2, "Information: Johnson killed Quagmire!");
}
}
You don't need to write a custom class. Assuming you can live with the log(n) access times, just use a case-insensitive TreeMap.
public static void main(String[] args) {
Map<String, String> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
m.put("foo", "bar");
StrSubstitutor sub = new StrSubstitutor(m);
String s = sub.replace("${FOO}");
System.out.println(s);
} // prints "bar"
I think this case-insensitive map would work:
import java.util.HashMap;
import java.util.Map;
public class CaseMap<V> extends HashMap<String, V> {
public CaseMap() {
}
public CaseMap(int capacity) {
super(capacity);
}
public CaseMap(int capacity, float loadFactor) {
super(capacity, loadFactor);
}
public CaseMap(Map<String, ? extends V> map) {
putAll(map);
}
public V put(String key, V value) {
return super.put(key.toUpperCase(), value);
}
public V get(Object key) {
if (!(key instanceof String)) return null;
return super.get(((String)key).toUpperCase());
}
}
If you don't control the creation of the messageValues map, you could build a CaseMap from it like this:
CaseMap<String> caseFreeMessageValues = new CaseMap<String>(messageValues);
And then build your StrSubstitutor like this:
StrSubstitutor sub = new StrSubstitutor(messageValues, "&(", ")");
String format = sub.replace("Information: &(KILLER) killed &(TARGET)!");
You might want to think about other methods of Map that should be overridden as well, such as containsKey.
In case you need flexibility with both the Map and the Tokens being case insensitive AND you are not in control of the map being built you can use something like this.
String replaceTokens(String str, Map<String, String> messageValues) {
if(tokenToValue == null || tokenToValue.size() < 1) return str;
StrSubstitutor caseInsensitiveTokenReplacer = new StrSubstitutor(new CaseInsensitiveStrLookup<>(messageValues),
"&(", ")", '\\');
return caseInsensitiveTokenReplacer.replace(str);
}
StrLookup Implementation
public class CaseInsensitiveStrLookup<V> extends StrLookup<V> {
private final Map<String, V> map = new TreeMap<String, V>(String.CASE_INSENSITIVE_ORDER);
public CaseInsensitiveStrLookup(final Map<String, V> map) throws NullValueKeyNotSupported {
if(map.containsKey(null)) throw new Exception(); // Dont want to support null
this.map.putAll(map);
}
#Override
public String lookup(final String key) {
V value = map.get(key);
if(value == null) return null;
return value.toString();
}}

Java Object Scope

I am trying to create an object based on a condition, therefore the object creation is within the conditional's scope, however I need to see the object outside of that scope. I thought adding it to a Map would work, but it doesn't. Consider the following example:
TestModel.java
public class TestModel {
private String text;
public void setText(String text){
this.text = text;}
public String getText(){
return this.text;}
}
ScopeTest.java
import java.util.*;
class ScopeTest {
public static void main(String[] args) {
TestModel testModel;
Map<String, Object> myModel = new HashMap<String, Object>();
for (int i=1; i<2; i++){ // if a certain condition is met, create an object as below
testModel = new TestModel();
testModel.setText("test text");
myModel.put("test", testModel);
}
for (Map.Entry<String, Object> entry : myModel.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
System.out.println("key=" + key); // I can see the key value
System.out.println("value.getText()=" + value.getText()); // but can't see testModel object. I am not sure how to resolve.
}
}
}
cheers,
Geofrey Rainey.
You have to cast the Object value with your Class. Like this.
System.out.println("value.getText()=" + ((TestModel) value).getText());
If you dont want to cast the object then you can use like this.
class ScopeTest {
public static void main(String[] args) {
TestModel testModel;
Map<String, TestModel> myModel = new HashMap<String, TestModel>();//Use TestModel
instead of object
for (int i=1; i<2; i++){
testModel = new TestModel();
testModel.setText("test text");
myModel.put("test", testModel);
}
for (Entry<String, TestModel> entry : myModel.entrySet()) {
String key = entry.getKey();
TestModel value = entry.getValue();
System.out.println("key=" + key);
System.out.println("value.getText()=" + value.getText());
}
}
}
You should cast the Object into your model.
TestModel value = (TestModel) entry.getValue();
I think you might have to cast the object returned by the HashMap to be a TestModel before you can use a method of that class.
Why not just use TestModel as generic type of value in your Map like below?
Map<String, TestModel> myModel = new HashMap<>();
// ^^^^^^^^^ - instead of Object
This way you can iterate over your map with
for (Map.Entry<String, TestModel> entry : myModel.entrySet()) {
...
}
and you will be able to store value in
TestModel value = entry.getValue();
without needing to cast it. Now since type of value reference will be TestModel compiler will let you use its methods like getText() without problems.
System.out.println("value.getText()=" + value.getText());
Also I am not sure why you are using loop if you want to iterate over it once. Simple if would be better. Another thing is using Map to hold one element seems unnecessary. You can just use one reference like
boolean someCondition = true;
TestModel testModel = null;
if (someCondition) { // if a certain condition is met, create
testModel = new TestModel();
testModel.setText("test text");
}
if (testModel!=null){
System.out.println(testModel.getText());
}

Java Regex doesn't match although debug tools do

I have written a regular expression to parse strings of the format
OBJECT_NAME KEY1=value KEY2=value
(actually done by 2 regexps)
This is my utils class:
package de.hs.settlers.util;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ParseUtils {
public static final Pattern OBJECT_NAME_PATTERN =
Pattern.compile("^([A-Z0-9 ]+) ([A-Z]+=.+)$");
public static final Pattern KEY_VALUE_PATTERN =
Pattern.compile("^([A-Z0-9]+)=([^=]+)( [A-Z]+=.+)?$");
public static ParseResult parseKeyValueLine(String line) {
Matcher object = OBJECT_NAME_PATTERN.matcher(line.trim());
String objectName = object.group(1);
HashMap<String, String> data = new HashMap<String, String>();
String vals = object.group(2);
do {
Matcher matcher = KEY_VALUE_PATTERN.matcher(vals);
if (!matcher.matches()) {
break;
}
String key = matcher.group(1);
String value = matcher.group(2);
data.put(key, value);
vals = matcher.group(3);
if (vals != null) {
vals = vals.trim();
}
} while (vals != null);
return new ParseResult(objectName, data);
}
public static class ParseResult {
private String objectName;
private HashMap<String, String> data;
public ParseResult(String objectName, HashMap<String, String> data) {
super();
this.objectName = objectName;
this.data = data;
}
public String getObjectName() {
return objectName;
}
public HashMap<String, String> getData() {
return data;
}
public String get(String key) {
return getData().get(key);
}
}
}
I've written a test that tests the method parseKeyValueLine with "USER TEAM=Team A USER=tuxitux OTHER=bla" as the line argument, but the execution fails because the first expression (the one in OBJECT_NAME_PATTERN) apparenly didn't match.
The problem I have is that when I paste the expression and the string to test it with into regex debuggers, they all tell me it matches and give me the correct groups. (tested with http://gskinner.com/RegExr/ and http://www.debuggex.com/).
Is there anything wrong with how java does regular expressions?
The problem is here:
Matcher object = OBJECT_NAME_PATTERN.matcher(line.trim());
String objectName = object.group(1);
You created the matcher, but you didn't tell it to actually do its work on the string. As a result, even if there was a match you'd have no groups available.
You need to call one of the matching methods (.find(), .lookingAt() or .matches(), but all three will be equivalent for you since your regexes are anchored both at the beginning and end of input), and then collect the groups.
Example (.find()):
Matcher object = OBJECT_NAME_PATTERN.matcher(line.trim());
object.find(); // or you could have an if statement here
String objectName = object.group(1);
Make sure you do not have any watch expressions. For me it was the IDE watch expressions which caused the issue.

Pulling values from a Java Properties file in order?

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?
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);
}
}
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.
If you can alter the property names your could prefix them with a numeral or other sortable prefix and then sort the Properties KeySet.
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;
}
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!
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();
}
}
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;
}
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
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
You must override also keySet() if you want to export Properties as XML:
public Set<Object> keySet() {
return keys;
}
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")));
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).
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);
}
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.
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)
}
}
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

Categories