should I use strategy pattern, If I have hundreds of actions - java

I have a class doing translate job. But it have hundreds of specific translate methods! The action code determine which method will be used! I want to use strategy pattern, but it will create hundreds of sub class! I want to name the methods end of action code and use reflection to do the translate, but I'm concern abort the execution performances. It will be called very frequently! What design pattern or patterns should I use to solve this problem!
code like this:
public class Test003_Translate {
private static final String PREFIX = "translate";
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Test003_Translate translate = new Test003_Translate();
Map<String, String> map = new HashMap<>();
map.put("key001", "001");
map.put("key002", "002");
map.put("key003", "003");
translate.doTranslate(map, "key001");
}
private void doTranslate(Map<String, String> map, String key) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String actionCode = map.get(key);
Method method = Test003_Translate.class.getMethod(PREFIX + actionCode, String.class);
String arg = "arg: ";
Object s = method.invoke(this, arg);
}
public String translate001(String input){
return input + "001";
}
public String translate002(String input){
return input + "002";
}
public String translate003(String input){
return input + "003";
}
}

You could use an EnumMap (smaller and faster then a HashMap), like this:
enum Key {
KEY_001,
....
}
EnumMap<Key, Runnable> enumMap = new EnumMap<>(Key.class);
enumMap.put(Key.KEY_001, YourClass::translate001);
....
And usage:
enumMap.get(someKey).run();

Related

Find direct and indirect subclasses by scanning filesystem

I'm having a problem in writing an algorithm to help me scan a file system and find all subclasses of a certain class.
Details:
I've an app that scans an external application using nio Files.walk() while retrieving I check for "extends SuperClass" while reading the file if the word exits, I add the class name in my list as follows:
List<String> subclasses = new ArrayList<>();
Files.walk(appPath)
.filter(p->Files.isRegularFile(p) && p.toString()
.endsWith(".java")).forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
Pattern pattern = Pattern.compile("\\bextends SuperClass\\b");
Matcher matcher = pattern
.matcher(lines.stream()
.collect(Collectors.joining(" ")));
boolean isChild = matcher.find();
if(isChild) subclasses.add(path.getFileName().toString());
}catch (IOException e){
//handle IOE
}
The problem with the above is that it only gets direct subclasses of SuperClass but I need to retrieve all direct and indirect subclasses.
I thought about recursion since I've no Idea how many subclasses of SuperClass there is but I couldn't implement any reasonable implementation.
NOTES:
Scanning more than 600 thousands file
I have no Idea how many direct/indirect subclasses of SuperClass there is
The application that I'm scanning is external and I can't modify its code so I'm only allowed to access it by reading files and see where extends exists
If there is a non-recursive solution to the problem that would be great but if there's no other way, I'll be more than happy to accept a recursive one since I care about the solution more than performance.
Edit:
I use the following regex to compare both name and import to make sure even in case of same name different packages the output is correct:
Pattern pattern = Pattern.compile("("+superClasss.getPackage()+")[\\s\\S]*(\\bextends "+superClass.getName()+"\\b)[\\s\\S]");
I also tried:
Pattern pattern = Pattern.compile("\\bextends "+superClass.getName()+"\\b");
But there is also some missing subclasses, I believe that the code bellow skips some checks, and doesn't fully work:
public static List<SuperClass> getAllSubClasses(Path path, SuperClass parentClass) throws IOException{
classesToDo.add(baseClass);
while(classesToDo.size() > 0) {
SuperClass superClass = classesToDo.remove(0);
List<SuperClass> subclasses = getDirectSubClasses(parentPath,parentClass);
if(subclasses.size() > 0)
classes.addAll(subclasses);
classesToDo.addAll(subclasses);
}
return classes;
}
Any help is truly appreciated!
Edit 2
I also noticed another problem, is that when I detect a subclass I get the file name currentPath.getFileName() which might or might not be the subclass name as the subclass may be a nested or non-public class in the same file.
I strongly recommend parsing compiled class files instead of source code. Since these class files are already optimized for being processed by machines, a lot of the complexity and corner cases of the source code file processing has been eliminated.
So a solution to build a complete class hierarchy tree using the ASM library would look like this:
public static Map<String, Set<String>> getClassHierarchy(Path root) throws IOException {
return Files.walk(root)
.filter(p->Files.isRegularFile(p) && isClass(p.getFileName().toString()))
.map(p -> getClassAndSuper(p))
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
}
private static boolean isClass(String fName) {
// skip package-info and module-info
return fName.endsWith(".class") && !fName.endsWith("-info.class");
}
private static Map.Entry<String,String> getClassAndSuper(Path p) {
final class CV extends ClassVisitor {
Map.Entry<String,String> result;
public CV() {
super(Opcodes.ASM5);
}
#Override
public void visit(int version, int access,
String name, String signature, String superName, String[] interfaces) {
result = new AbstractMap.SimpleImmutableEntry<>(
Type.getObjectType(name).getClassName(),
superName!=null? Type.getObjectType(superName).getClassName(): "");
}
}
try {
final CV visitor = new CV();
new ClassReader(Files.readAllBytes(p)).accept(visitor, ClassReader.SKIP_CODE);
return visitor.result;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
As a bonus, resp. to create some test cases, the following method adds the ability to build the hierarchy for a runtime class’ source:
public static Map<String, Set<String>> getClassHierarchy(Class<?> context)
throws IOException, URISyntaxException {
Path p;
URI clURI = context.getResource(context.getSimpleName()+".class").toURI();
if(clURI.getScheme().equals("jrt")) p = Paths.get(URI.create("jrt:/modules"));
else {
if(!clURI.getScheme().equals("file")) try {
FileSystems.getFileSystem(clURI);
} catch(FileSystemNotFoundException ex) {
FileSystems.newFileSystem(clURI, Collections.emptyMap());
}
String qn = context.getName();
p = Paths.get(clURI).getParent();
for(int ix = qn.indexOf('.'); ix>0; ix = qn.indexOf('.', ix+1)) p = p.getParent();
}
return getClassHierarchy(p);
}
Then, you can do
Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("Direct subclasses of "+Number.class);
hierarchy.getOrDefault("java.lang.Number", Collections.emptySet())
.forEach(System.out::println);
and get
Direct subclasses of class java.lang.Number
java.lang.Float
java.math.BigDecimal
java.util.concurrent.atomic.AtomicLong
java.lang.Double
java.lang.Long
java.util.concurrent.atomic.AtomicInteger
java.lang.Short
java.math.BigInteger
java.lang.Byte
java.util.concurrent.atomic.Striped64
java.lang.Integer
or
Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("All subclasses of "+Number.class);
printAllClasses(hierarchy, "java.lang.Number", " ");
private static void printAllClasses(
Map<String, Set<String>> hierarchy, String parent, String i) {
hierarchy.getOrDefault(parent, Collections.emptySet())
.forEach(x -> {
System.out.println(i+x);
printAllClasses(hierarchy, x, i+" ");
});
}
to get
All subclasses of class java.lang.Number
java.lang.Float
java.math.BigDecimal
java.util.concurrent.atomic.AtomicLong
java.lang.Double
java.lang.Long
java.util.concurrent.atomic.AtomicInteger
java.lang.Short
java.math.BigInteger
java.lang.Byte
java.util.concurrent.atomic.Striped64
java.util.concurrent.atomic.LongAdder
java.util.concurrent.atomic.LongAccumulator
java.util.concurrent.atomic.DoubleAdder
java.util.concurrent.atomic.DoubleAccumulator
java.lang.Integer
DISCLAIMER: This solution might not work if you have several classes with the same name as it does not take packages names into account.
I think you can do it with keeping track of the classes to lookup in a List and use a while loop until all the values on the list have been explored.
Here is a bit of code which creates a Map<String, List<String>>, key is the class name, value is the list of child classes.
public class Test {
private static Path appPath = //your path
private static Map<String, List<String>> classes = new HashMap<>();
private static List<String> classesToDo = new ArrayList<>();
public static void main(String[] args) throws IOException {
classesToDo.add("AnswerValueValidatorBase");
while(classesToDo.size() > 0) {
String className = classesToDo.remove(0);
List<String> subclasses = getDirectSubclasses(className);
if(subclasses.size() > 0)
classes.put(className, subclasses);
classesToDo.addAll(subclasses);
}
System.out.println(classes);
}
private static List<String> getDirectSubclasses(String className) throws IOException {
List<String> subclasses = new ArrayList<>();
Files.walk(appPath)
.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java"))
.forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
Pattern pattern = Pattern.compile("\\bextends "+className+"\\b");
Matcher matcher = pattern.matcher(lines.stream().collect(Collectors.joining(" ")));
boolean isChild = matcher.find();
if(isChild) {
String fileName = path.getFileName().toString();
String clazzName = fileName.substring(0, fileName.lastIndexOf("."));
subclasses.add(clazzName);
}
} catch(IOException e) {
//handle IOE
}
});
return subclasses;
}
}
Running it on my project returns something that looks correct
{
AnswerValueValidatorBase=[SingleNumericValidator, DefaultValidator, RatingValidator, ArrayValidatorBase, DocumentValidator],
ArrayValidatorBase=[MultiNumericValidator, StringArrayValidator, IntegerArrayValidator, MultiCheckboxValidator],
DefaultValidator=[IntegerValidator, DateValidator, StringValidator, CountryValidator, PercentageValidator],
IntegerArrayValidator=[MultiPercentageValidator, RankValidator, MultiDropValidator, MultiRadioValidator, CheckboxValidator],
SingleNumericValidator=[SliderValidator],
MultiNumericValidator=[MultiSliderValidator],
StringArrayValidator=[MultiTextValidator, ChecklistValidator]
}
EDIT
A recursive way of doing it would be
public class Test {
private static Path appPath = // your path
public static void main(String[] args) throws IOException {
List<String> classesToDo = new ArrayList<>();
classesToDo.add("AnswerValueValidatorBase");
Map<String, List<String>> classesMap = getSubclasses(new HashMap<>(), classesToDo);
System.out.println(classesMap);
}
private static Map<String, List<String>> getSubclasses(Map<String, List<String>> classesMap, List<String> classesToDo) throws IOException {
if(classesToDo.size() == 0) {
return classesMap;
} else {
String className = classesToDo.remove(0);
List<String> subclasses = getDirectSubclasses(className);
if(subclasses.size() > 0)
classesMap.put(className, subclasses);
classesToDo.addAll(subclasses);
return getSubclasses(classesMap, classesToDo);
}
}
private static List<String> getDirectSubclasses(String className) throws IOException {
// same as above
}
}

Java indeterminate number of arguments of indeterminate type

My company has an application server that receives sets of instructions in their own bespoke XTML syntax. As this is limited, there's a special "drop to Java" command that sends arguments to a JVM (1.6.0_39). Arguments are passed as "in" only, or "in/out", where the special "in/out" variables are a library of mutables for use with this platform.
Previously the only way to receive external configuration was to use a different special command to read from an XTML file. For reasons not worth delving into, this method of configuration is difficult to scale, so I'm working on a way to do this with Java.
The syntax for this configuration was two-tuples of (String,T) where String was the property name in the XTML file, and T was the in/out mutable that the application server would assign the property value to.
I'm attempting to make this transition as seamless as possible, and not have to do annoying string parsing in the application server.
I already have a function
public String[] get(String ... keys)
That retrieves the values from the application servers' keys, but What I really need is a function
public static void get(T ... args)
that accepts the two-tuples. However, note it needs to be static in order to be called from the application server, and my understanding is that T can't be used in a static context.
I'm at a loss for how to approach this problem in a way that doesn't require (at least) two steps, and there is no way to loop over the arguments in the application server.
I know I'm working within a tight set of constraints here, so if the answer is "you have to some messed up stuff", that's fine - I'd just like any insight into another way.
-- edit --
Editing a more specific example.
The configuration is a set of key-value pairs, and can be in a database or a file. The get function is:
public JSONObject get(String ... keys) throws ClassNotFoundException, SQLException, KeyNotFoundException, FileNotFoundException, IOException {
JSONObject response = new JSONObject();
if(this.isDatabase) {
for(int i=0;i<keys.length;i++){
PreparedStatement statement = this.prepare("SELECT value FROM "+this.databaseSchema+"."+this.settingsTableName+" WHERE key = ? LIMIT 1");
statement.setString(1, keys[i]);
ResultSet results = statement.executeQuery();
boolean found = false;
while(results.next()){
String value = results.getString("value");
value = value.replace("\"","");
response.put(keys[i], value);
found = true;
}
if(!found){
throw new KeyNotFoundException(keys[i]);
}
}
} else if (this.isFile) {
boolean[] found = new boolean[keys.length];
BufferedReader br = new BufferedReader(new FileReader(this.settingsFile));
String line;
while((line = br.readLine()) != null ){
String key;
String value;
for(int i=0;i<line.length();i++){
if(line.charAt(i) == '='){
key = line.substring(0,i);
value = line.substring(i+1,line.length());
if(indexOfString(keys,key) != -1){
value = value.replace("\"","");
found[indexOfString(keys,key)] = true;
response.put(key,value);
if(allFound(found)==-1){
return response;
}
}
break;
}
}
}
if(allFound(found)!=-1){
throw new KeyNotFoundException(keys[allFound(found)]);
}
}
return response;
If I had my way, it would look like ...
// ConfigurationReader.java
public class ConfigurationReader{
public ConfigurationReader( ... ){}
public static JSONObject get(String key){
// Get the key
}
}
// ConfigurationInterface.java
public static void get(T ... args){
ConfigurationReader cfgReader = new ConfigurationReader( ... );
for(var i=0;i<args.length;i+=2){
in = args[i];
out = args[i+1];
out = cfgReader.get(in);
}
}
You can use generic types in a static context. Your question is somewhat vague/unclear about how you intend to do this, but consider the example below:
public class Example {
public static void main(String[] args) {
Type t1 = new Type("foo");
Type t2 = new Type("bar");
Type t3 = new Type("baz");
Printer.<Type> printNames(t1, t2, t3);
}
public static class Printer {
#SafeVarargs
public static <T extends Type> void printNames(T... objs) {
for (T obj : objs) {
System.out.println(obj);
}
}
}
public static class Type {
private final String name;
public Type(String name) {
this.name = name;
}
#Override
public final String toString() {
return name;
}
}
}
Printer.<Type> printNames(t1, t2, t3) makes a static reference to the printNames method, parameterized with the Type generic type.
Note that this is type-safe. Attempting to pass an object of a different type into that parameterized method will fail at compile-time (assuming the type is known to be different at that point):
Example.java:8: error: method printNames in class Printer cannot be applied to given types;
Printer.<Type> printNames(t1, t2, t3, "test");
^
required: T[]
found: Type,Type,Type,String
reason: varargs mismatch; String cannot be converted to Type
where T is a type-variable:
T extends Type declared in method <T>printNames(T...)
Edit
Based on your comment, the issue isn't that you're trying use a generic type for your method argument (in the Java-sense of the word generic, anyway); you're simply looking for any non-specific, parent class that both String and your custom type inherit from. There's only one such class: Object.
I'd strongly recommend reconsidering your design if you have any flexibility, since this will make for poor API design. However you can have your method accept an arbitrary number of arbitrarily-typed objects using Object... objs.
For example:
public class Example {
public static void main(String[] args) {
Printer.printNames("a", "b", new Type("foo"), new Type("bar"));
}
public static class Printer {
public static void printNames(Object... objs) {
for (Object obj : objs) {
if (obj instanceof String) {
System.out.println(((String) obj).toUpperCase());
}
else if (obj instanceof Type) {
System.out.println(obj);
}
}
}
}
public static class Type {
private final String name;
public Type(String name) { this.name = name; }
public final String toString() { return name; }
}
}
Based on #nbrooks work, I found a solution. I made a temporary MutableString (to be replaced by the classes provided by the library).
public static class MutableString {
public String value;
public MutableString(){}
}
// One for every mutable type
public static void Pair(String key, MutableString mutable, ApplicationConfiguration appConfig) throws Exception{
mutable.value = appConfig.get(key).toString();
}
public static void Retrieve(Object ... args) throws Exception {
ApplicationConfiguration appConfig = new ApplicationConfiguration( ##args## );
for(int i=0;i<args.length;i+=2){
if(args[i+1].getClass().equals(new MutableString().getClass())){
ApplicationConfiguration.Pair( (String) args[i], (MutableString) args[i+1], appConfig);
} // One for every mutable type
}
}

How to use the value of a String as a variable name

My question is suppose there is a String variable like String abc="Shini";.
So is it possible to use "Shini" as new variable name by some automatic means not by explicit typing.
String abc = "Shini";
String Shini = "somevale";
Variables must be declared at compile time, so no it is not possibile at runtime
Best thing it comes to my mind is to use it as a map key
String abc = "Shini";
Map<String, String> myMap = new Hashmap<>();
myMap.put(abc, "something");
Then myMap.get("Shini") will give "something".
Yes. You can use.
class Foo {
private int lorem;
private int ipsum;
public Foo(){}
public setAttribute(String attr, int val) throws NoSuchFieldException, IllegalAccessException {
Field field = getClass().getDeclaredField(attr);
field.setInt(this, val);
}
public static void main(String [] args) {
Foo f = new Foo();
f.setAttribute("lorem", 1);
f.setAttribute("ipsum", 2);
}
}
I did not compile this code. Please check if this can have minor mistakes.

How to check if variable name contains string and then output string variable content

So I have these 4 variables
private final String PROG_DEPT = "PROGRAMMING/ENGINEERING";
private final String DES_DEPT = "DESIGN/WRITING";
private final String ART_DEPT = "VISUAL ARTS";
private final String SOUND_DEPT = "AUDIO";
What I want to be able to do is to get a string and compare it to the variable and then out put what the variable contains if it equals it.
For example if my string equals "ART_DEPT" then it check if there is a variable called ART_DEPT and then output "VISUAL ARTS"
I was thinking of putting it in a 2D String array or a list but I'm not really sure as to how to do what I want to do
The data type you're looking for is Map<String, String>.
Map<String, String> departmentNames = new HashMap<String, String>();
departmentNames.put("PROG_DEPT", "PROGRAMMING/ENGINEERING");
departmentNames.put("DES_DEPT", "DESIGN/WRITING");
//...etc...
//...
String dept = "PROG_DEPT";
String deptName = departmentNames.get(dept);
System.out.println(deptName); //outputs "PROGRAMMING/ENGINEERING"
A Map binds a unique key to a value. In this case both have the type String. You add bindings using put(key, value) and get the binding for a key using get(key).
I would go with an enum:
package com.stackoverflow.so18327373;
public class App {
public static void main(final String[] args) {
final String in = "DES_DEPT";
try {
final Departement departement = Departement.valueOf(in);
System.out.println(departement.getLabel());
} catch (final IllegalArgumentException ex) {
// in was not a known departement
System.err.println("Bad value: " + in);
}
}
public static enum Departement {
PROG_DEPT("PROGRAMMING/ENGINEERING"),
DES_DEPT("DESIGN/WRITING"),
ART_DEPT("VISUAL ARTS"),
SOUND_DEPT("AUDIO");
private final String label;
private Departement(final String label) {
this.label = label;
}
public String getLabel() {
return this.label;
}
}
}
then use valueOf()
You probably want to use some kind of Map, such as a HashMap<String,String>. I suggest you read the Javadocs for the Map interface and the HashMap class.
What you need to use is a Map.
private final Map<String,String> myMap= new HashMap<String,String>() ;
{
myMap.put("PROG_DEPT","PROGRAMMING/ENGINEERING");
myMap.put("DES_DEPT","DESIGN/WRITING");
myMap.put("ART_DEPT","VISUAL ARTS");
myMap.put("SOUND_DEPT","AUDIO");
}
Then use it in the following way:
String input= "ART_DEPT" ;
System.out.println( myMap.get(input) );
Try this
List<String> list=new ArrayList<>();
list.add("private final String PROG_DEPT = \"PROGRAMMING/ENGINEERING\";");
list.add("private final String DES_DEPT = \"DESIGN/WRITING\";");
list.add("private final String ART_DEPT = \"VISUAL ARTS\";");
list.add("private final String SOUND_DEPT = \"AUDIO\";");
String search="ART_DEPT";
for (String i:list){
if(i.contains(search)){
System.out.println(i.split("=")[1].replaceAll(";",""));
}
}
Live Demo here. You can do this using Map but to do that you have to create a map from these Strings.
Sounds like you are looking for reflection (or if you want to use a different data type instead of looking up a variable in a class then a Map<String, String>). Looks like the Map approach is well covered, so only because this is interesting to me, here is the reflection approach (not that this is not the best way to solve this problem, but since you asked for checking if a variable exists and then getting it's value)
import java.lang.reflect.Field;
public class SOQuestion {
private final String PROG_DEPT = "PROGRAMMING/ENGINEERING";
private final String DES_DEPT = "DESIGN/WRITING";
private final String ART_DEPT = "VISUAL ARTS";
private final String SOUND_DEPT = "AUDIO";
public static void main(String ... args) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
System.out.println(reflectValue("ART_DEPT", SOQuestion.class));
System.out.println(reflectValue("COMP_DEPT", SOQuestion.class));
}
public static String reflectValue(String varible, Class thing) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
Field[] fs = thing.getDeclaredFields();
for(int i = 0; i < fs.length; i++) {
if(fs[i].getName().equals(varible)) {
fs[i].setAccessible(true);
return (String) fs[i].get(thing.newInstance());
}
}
return null;
}
}
The first request to print "ATR_DEPT" will print VISUAL ARTS and the second request to the nonexistent "COMP_DEPT" will return null;
private String getStaticFieldValue(String fieldName){
String value = null;
try {
Field field = getClass().getDeclaredField(fieldName);
if (Modifier.isStatic(field.getModifiers())){
value = field.get(null).toString();
}
}
catch (Exception e) {
return null;
}
return value;
}
you have few options as mentioned above :
using a Map , the disadvantage of using a map for this case is that you will have to maintain it, it means that every time you will need to add/remove/edit one of your final static fields, you will have to edit the map as well.
using reflection as mentioned in this post, which is my favorite solution (the above code snippet)
Use the concept of Map
import java.util.HashMap;
import java.util.Map;
public class MajorMap {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Map<String, String> deptMap = new HashMap<String, String>();
deptMap.put("PROG_DEPT", "PROGRAMMING/ENGINEERING");
deptMap.put("DES_DEPT","DESIGN/WRITING");
deptMap.put("ART_DEPT","VISUAL ARTS");
deptMap.put("SOUND_DEPT","AUDIO");
System.out.println("ART_DEPT----->>"+deptMap.get("ART_DEPT"));
}
}

Java string templatizer / formatter with named arguments

Is there a standard or at least widespread implementation of something like String.format, but with named arguments?
I'd like to format a templatized string in a way like that:
Map<String, Object> args = new HashMap<String, Object>();
args.put("PATH", "/usr/bin");
args.put("file", "foo");
String s = someHypotheticalMethod("#{PATH}/ls #{file}");
// "/usr/bin/ls foo"
Technically, it's almost the same as:
String[] args = new String[] { "/usr/bin", "foo" };
String s = String.format("%1$s/ls %2$s", args);
// "/usr/bin/ls foo"
but with named arguments.
I'm aware of:
String.format
Formatter
MessageFormat
but all of them use ordered or at least numbered arguments, not named ones. I know it's trivial to implement one, but is there a mechanism I'm looking for in standard Java libraries or at least in Apache Commons / Guava / something similar, without bringing in high-profile template engines?
NOTE: I'm not really interested in full-blown template engines, with features like some imperative / functional logic, flow control, modifiers, sub-templates / inclusions, iterators, etc. Generally the following method is a working 4-line implementation - that's all I need:
public static String interpolate(String format, Map<String, ? extends Object> args) {
String out = format;
for (String arg : args.keySet()) {
out = Pattern.compile(Pattern.quote("#{" + arg + "}")).
matcher(out).
replaceAll(args.get(arg).toString());
}
return out;
}
You might also try org.apache.commons.lang3.text.StrSubstitutor if Java 7 is not an option. It does exactly what you want it to do. Whether it’s light-weight might depend on whether you use something else of commons-lang as well.
Matcher#appendReplacement() would help
I recently discovered JUEL which fits the description nicely. It is the expression language taken out of JSP. It claims to be very fast, too.
I'm about to try it out in one of my own projects.
But for a lighter-weight, which is a variant of yours, try this (wrapped in a unit test):
public class TestInterpolation {
public static class NamedFormatter {
public final static Pattern pattern = Pattern.compile("#\\{(?<key>.*)}");
public static String format(final String format, Map<String, ? extends Object> kvs) {
final StringBuffer buffer = new StringBuffer();
final Matcher match = pattern.matcher(format);
while (match.find()) {
final String key = match.group("key");
final Object value = kvs.get(key);
if (value != null)
match.appendReplacement(buffer, value.toString());
else if (kvs.containsKey(key))
match.appendReplacement(buffer, "null");
else
match.appendReplacement(buffer, "");
}
match.appendTail(buffer);
return buffer.toString();
}
}
#Test
public void test() {
assertEquals("hello world", NamedFormatter.format("hello #{name}", map("name", "world")));
assertEquals("hello null", NamedFormatter.format("hello #{name}", map("name", null)));
assertEquals("hello ", NamedFormatter.format("hello #{name}", new HashMap<String, Object>()));
}
private Map<String, Object> map(final String key, final Object value) {
final Map<String, Object> kvs = new HashMap<>();
kvs.put(key, value);
return kvs;
}
}
I'd extend it to add convenience methods to for quick key-value pairs
format(format, key1, value1)
format(format, key1, value1, key2, value2)
format(format, key1, value1, key2, value2, key3, value3)
...
And it shouldn't be too hard to convert from java 7+ to java 6-
StringTemplate may be as light-weight an interpolation engine as you're likely to get, although I don't know how it stacks up resource-wise against things like FreeMarker, Mustache, or Velocity.
Another option might be an EL engine like MVEL, which has a templating engine.
Here is my solution:
public class Template
{
private Pattern pattern;
protected Map<CharSequence, String> tokens;
private String template;
public Template(String template)
{
pattern = Pattern.compile("\\$\\{\\w+\\}");
tokens = new HashMap<CharSequence, String>();
this.template = template;
}
public void clearAllTokens()
{
tokens.clear();
}
public void setToken(String token, String replacement)
{
if(token == null)
{
throw new NullPointerException("Token can't be null");
}
if(replacement == null)
{
throw new NullPointerException("Replacement string can't be null");
}
tokens.put(token, Matcher.quoteReplacement(replacement));
}
public String getText()
{
final Matcher matcher = pattern.matcher(template);
final StringBuffer sb = new StringBuffer();
while(matcher.find())
{
final String entry = matcher.group();
final CharSequence key = entry.subSequence(2, entry.length() - 1);
if(tokens.containsKey(key))
{
matcher.appendReplacement(sb, tokens.get(key));
}
}
matcher.appendTail(sb);
return sb.toString();
}
public static void main(String[] args) {
Template template = new Template("Hello, ${name}.");
template.setToken("name", "Eldar");
System.out.println(template.getText());
}
}
I know my answer comes a little late, but if you still need this functionality, without the need to download a full-fledged template engine you can take a look at aleph-formatter (I am one of the authors):
Student student = new Student("Andrei", 30, "Male");
String studStr = template("#{id}\tName: #{st.getName}, Age: #{st.getAge}, Gender: #{st.getGender}")
.arg("id", 10)
.arg("st", student)
.format();
System.out.println(studStr);
Or you can chain the arguments:
String result = template("#{x} + #{y} = #{z}")
.args("x", 5, "y", 10, "z", 15)
.format();
System.out.println(result);
// Output: "5 + 10 = 15"
Internally it works using a StringBuilder creating the result by "parsing" the expression, no string concatenation, regex/replace is performed.
I also made one in my str utils (not tested) string.MapFormat("abcd {var}",map).
//util
public static String mapFormat(String template, HashMap<String, String> mapSet) {
String res = template;
for (String key : mapSet.keySet()) {
res = template.replace(String.format("{%s}", key), mapSet.get(key));
}
return res;
}
//use
public static void main(String[] args) {
boolean isOn=false;
HashMap<String, String> kvMap=new HashMap<String, String>();
kvMap.put("isOn", isOn+"");
String exp=StringUtils.mapFormat("http://localhost/api/go?isOn={isOn}", kvMap);
System.out.println(exp);
}

Categories