views:

187

answers:

4

I have a class with an attribute and getter method:

public Class MyClass
{
  private String myValue = "foo";

  public String getMyValue();
}

I would like to be able to use the value of foo in a formatted string as such:

String someString = "Your value is {myValue}."
String result = Formatter.format(someString, new MyClass());
// result is now "Your value is foo."

That is, I would like to have some function like .format above which takes a format string specifying properties on some object, and an instance with those properties, and formats the string accordingly.

Is it possible to do accomplish this feat in Java?

A: 

It's in theory possible with a stackbased parser to determine the valueholders in the string, in combination with reflection (or better, a Javabean inspection API, such as Commons BeanUtils) to get the bean property values.

Unfortunately no ready-made nor 3rd party API comes to mind, if you were looking for that. It's an interesting question however.

BalusC
A: 

You could create one with struts2/xwork/OGNL, similar to the below (copied from an email from Vlad)

public static String translateOgnl(String message, Map<Object, Object> args) {
    OgnlValueStack stack = new OgnlValueStack();
    stack.push(args);
    return TextParseUtil.translateVariables(message, stack);
}

The javadocs for TextParseUtil.translateVariables() say

Converts all instances of ${...} in expression to the value returned by a call to ValueStack.findValue(java.lang.String). If an item cannot be found on the stack (null is returned), then the entire variable ${...} is not displayed, just as if the item was on the stack but returned an empty string.

Stephen Denne
+2  A: 

you could use JUEL for this. it's an implementation of the Java Expression Language. the code is rather compact and looks like this:

ExpressionFactory factory = new ExpressionFactoryImpl();

// create a context and add a Person object to the context, this variable will be used
// in the property replacement
// objects of type Person have two fields: firstName and lastName

SimpleContext context = new SimpleContext();
Person person = new Person("John", "Doe");
context.setVariable("person", factory.createValueExpression(person, Person.class));

// create the expression

String expr = "My name is ${person.firstName} ${person.lastName}";
ValueExpression e = factory.createValueExpression(context, expr, String.class);

// evaluate the expression
System.out.println(e.getValue(context));

which prints 'My name is John Doe'

note that it's also possible to use an expression like this: '${firstName}' instead of '${person.firstName}', but then you will have to write and provide a custom resolver (javax.el.ELResolver) for the variable and property resolution

Stefan De Boey
As luck would have it, we already have JUEL in our dependency tree, so JUEL it is. Thanks!
Jason R. Coombs
+2  A: 

(My other answer's probably only useful if you're already using struts.)

Similar to sdb's answer, there is apache JEXL.

The UnifiedJEXL class provides template-like functionality, so you can write (as shown in javadocs):

JexlEngine jexl = new JexlEngine();
UnifiedJEXL ujexl = new UnifiedJEXL(jexl);
UnifiedJEXL.Expression expr = ujexl.parse("Hello ${user}");
String hello = expr.evaluate(context, expr).toString();

(The expr not only looks strange being passed as a parameter to a method on itself, but is indeed not needed as a parameter)

The context setup is shown earlier in the same page:

// Create a context and add data
JexlContext jc = new MapContext();
jc.set("foo", new Foo() );

You'll also need either commons-logging, or you can configure JEXL to use your own logger.


So to get close to what you asked, you can create:

public class Formatter {
    public static String format(String format, Object ... inputs) {
        JexlContext context = new MapContext();
        for (int i=0;i<inputs.length;i++) {
            context.set("_" + (i+1), inputs[i] );
        }
        JexlEngine jexl = new JexlEngine();
        UnifiedJEXL ujexl = new UnifiedJEXL(jexl);
        UnifiedJEXL.Expression expr = ujexl.parse(format);
        return expr.evaluate(context).toString();
    }
}

and call it with

String someString = "Your value is ${_1.myValue}.";
String result = Formatter.format(someString, new MyClass());

At which point, result is "Your value is foo."

Stephen Denne