Pergunta

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.

Foi útil?

Solução

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!");
}
}

Outras dicas

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();
}}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top