views:

566

answers:

4

Is there any libraries that would allow me to use the same known notation as we use in BeanUtils for extracting POJO parameters, but for easily replacing placeholders in a string?

I know it would be possible to roll my own, using BeanUtils itself or other libraries with similar features, but I didn't want to reinvent the wheel.

I would like to take a String as follows:

String s = "User ${user.name} just placed an order. Deliver is to be
made to ${user.address.street}, ${user.address.number} - ${user.address.city} / 
${user.address.state}";

And passing one instance of the User class below:

public class User {
   private String name;
   private Address address; 
   // (...)

   public String getName() { return name; } 
   public Address getAddress() {  return address; } 
}

public class Address {
   private String street;
   private int number;
   private String city;
   private String state;

   public String getStreet() { return street; }
   public int getNumber() {  return number; }
   // other getters...
}

To something like:

System.out.println(BeanUtilsReplacer.replaceString(s, user));

Would get each placeholder replaced with actual values.

Any ideas?

+1  A: 

See the answer to this similar question about using Expression Language in a standalone context.

insin
insin, I went down that path up until knowing the EL from some JSP implementation, the article you pointed seems to be *exacty* what I was looking for! Thanks!
kolrie
I voted your question but I will leave this topic open to see if there is any other simpler solutions. If not, I'll accept your answer.
kolrie
+1  A: 

Spring Framework should have a feature that does this (see Spring JDBC example below). If you can use groovy (just add the groovy.jar file) you can use Groovy's GString feature to do this quite nicely.

Groovy example

foxtype = 'quick'
foxcolor = ['b', 'r', 'o', 'w', 'n']
println "The $foxtype ${foxcolor.join()} fox"

Spring JDBC has a feature that I use to support named and nested named bind variables from beans like this:

public int countOfActors(Actor exampleActor) {

    // notice how the named parameters match the properties of the above 'Actor' class
    String sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName";

    SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);

    return this.namedParameterJdbcTemplate.queryForInt(sql, namedParameters);
}
Brian
+1  A: 

Rolling your own using BeanUtils wouldn't take too much wheel reinvention (assuming you want it to be as basic as asked for). This implementation takes a Map for replacement context, where the map key should correspond to the first portion of the variable lookup paths given for replacement.

import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.beanutils.BeanUtils;

public class BeanUtilsReplacer
{
    private static Pattern lookupPattern = Pattern.compile("\\$\\{([^\\}]+)\\}");

    public static String replaceString(String input, Map<String, Object> context)
        throws IllegalAccessException, InvocationTargetException, NoSuchMethodException
    {
        int position = 0;
        StringBuffer result = new StringBuffer();

        Matcher m = lookupPattern.matcher(input);
        while (m.find())
        {
            result.append(input.substring(position, m.start()));
            result.append(BeanUtils.getNestedProperty(context, m.group(1)));
            position = m.end();
        }

        if (position == 0)
        {
            return input;
        }
        else
        {
            result.append(input.substring(position));
            return result.toString();
        }
    }
}

Given the variables provided in your question:

Map<String, Object> context = new HashMap<String, Object>();
context.put("user", user);
System.out.println(BeanUtilsReplacer.replaceString(s, context));
insin
Just awesome, thank you!
kolrie
+1  A: 

Your string example is a valid template in at least a few templating engines, like Velocity or Freemarker. These libraries offer a way to merge a template with a context containing some objects (like 'user' in your example).

See http://velocity.apache.org/ or http://www.freemarker.org/

Some example code (from the Freemarker site):

 /* ------------------------------------------------------------------- */    
        /* You usually do it only once in the whole application life-cycle:    */    

        /* Create and adjust the configuration */
        Configuration cfg = new Configuration();
        cfg.setDirectoryForTemplateLoading(
                new File("/where/you/store/templates"));
        cfg.setObjectWrapper(new DefaultObjectWrapper());

        /* ------------------------------------------------------------------- */    
        /* You usually do these for many times in the application life-cycle:  */    

        /* Get or create a template */
        Template temp = cfg.getTemplate("test.ftl");

        /* Create a data-model */
        Map root = new HashMap();
        root.put("user", "Big Joe");
        Map latest = new HashMap();
        root.put("latestProduct", latest);
        latest.put("url", "products/greenmouse.html");
        latest.put("name", "green mouse");

        /* Merge data-model with template */
        Writer out = new OutputStreamWriter(System.out);
        temp.process(root, out);
        out.flush();
Tom