How to replace a placeholder in a String with a SimpleDateFormat Pattern

44,681

Solution 1

Why don't you use MessageFormat instead?

String result = MessageFormat.format(".../uploads/{0}/{1,date,yyyyMMdd}/report.pdf", customer, date);

Or with String.format

String result = String.format(".../uploads/%1$s/%2$tY%2$tm%2$td/report.pdf", customer, date);

Solution 2

As NilsH suggested MessageFormat is really nice for this purpose. To have named variables you can hide MessageFormat behind your class:

public class FormattedStrSubstitutor {
    public static String formatReplace(Object source, Map<String, String> valueMap) {
        for (Map.Entry<String, String> entry : valueMap.entrySet()) {   
            String val = entry.getValue();
            if (isPlaceholder(val)) {
                val = getPlaceholderValue(val);
                String newValue = reformat(val);

                entry.setValue(newValue);
            }
        }

        return new StrSubstitutor(valueMap).replace(source);
    }

    private static boolean isPlaceholder(String isPlaceholder) {
        return isPlaceholder.startsWith("${");
    }

    private static String getPlaceholderValue(String val) {
        return val.substring(2, val.length()-1);
    }

    private static String reformat(String format) {
        String result = MessageFormat.format("{0,date," + format + "}", new Date());

        return result;
    }
}

And you have to adjust your testcase:

SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");

String template = ".../uploads/${customer}/${dateTime}/report.pdf";

@Test
public void shouldResolvePlaceholder() {
    final Map<String, String> model = new HashMap<String, String>();
    model.put("customer", "Mr. Foobar");
    model.put("dateTime", "${yyyyMMdd}");

    final String filledTemplate = FormattedStrSubstitutor.formatReplace(this.template,
        model);

    assertEquals(".../uploads/Mr. Foobar/" + this.formatter.format(new Date())
        + "/report.pdf", filledTemplate);
}

I have removed generics and replace those with String. Also isPlaceholder and getPlaceholderValue is hardcoded and expect ${value} syntax.

But this is just just the idea to solve your problem. To do this can use the methods from StrSubstitutor (just use is or make FormattedStrSubstitutor extends StrSubstitutor).

Also you can use for example $d{value} for date formatting and $foo{value} for foo formatting.

UPDATE

Could not sleep without full solution. You can add this method to FormattedStrSubstitutor class:

public static String replace(Object source,
        Map<String, String> valueMap) {

    String staticResolved = new StrSubstitutor(valueMap).replace(source);

    Pattern p = Pattern.compile("(\\$\\{date)(.*?)(\\})");
    Matcher m = p.matcher(staticResolved);

    String dynamicResolved = staticResolved;
    while (m.find()) {
        String result = MessageFormat.format("{0,date" + m.group(2) + "}",
                new Date());

        dynamicResolved = dynamicResolved.replace(m.group(), result);
    }

    return dynamicResolved;
}

Your testcase is like in your question (small changes in placeholder):

SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd");

String template = ".../uploads/${customer}/${date,yyyyMMdd}/report.pdf";

@Test
public void shouldResolvePlaceholder() {
    final Map<String, String> model = new HashMap<String, String>();
    model.put("customer", "Mr. Foobar");

    final String filledTemplate =  FormattedStrSubstitutor.replace(this.template,
            model);

    assertEquals(
            ".../uploads/Mr. Foobar/" + this.formatter.format(new Date())
                    + "/report.pdf", filledTemplate);
}

Same limitation as before; no generics and fix prefix and suffix for placeholder.

Share:
44,681
d0x
Author by

d0x

#SOreadytohelp :D

Updated on June 14, 2020

Comments

  • d0x
    d0x almost 4 years

    In a given String like this

    ".../uploads/${customer}/${dateTime('yyyyMMdd')}/report.pdf"
    

    I need to replace a customer and a yyyyMMdd timestamp.

    To replace the customer placeholder, I could use the StrSubstitutor from Apache Commons. But how to replace the SimpleDateFormat? We are running in a Spring enviourment, so maybe Spring EL is an option?

    The markup for the placeholders is not fixed, it is ok if another library needs syntactical changes.

    This small tests shows the problem:

    SimpleDateFormat            formatter   = new SimpleDateFormat("yyyyMMdd");
    
    String                      template    = ".../uploads/${customer}/${dateTime('yyyyMMdd')}/report.pdf";
    
    @Test
    public void shouldResolvePlaceholder()
    {
        final Map<String, String> model = new HashMap<String, String>();
        model.put("customer", "Mr. Foobar");
    
        final String filledTemplate = StrSubstitutor.replace(this.template, model);
    
        assertEquals(".../uploads/Mr. Foobar/" + this.formatter.format(new Date()) + "/report.pdf", filledTemplate);
    }
    
  • d0x
    d0x almost 11 years
    I like they way with named variables. Because the String comes from another module. They don't know in which I use. With the MessageFormat I need to communicate Index 0 = customer, 1 = current Date, 2 = .... hm....
  • NilsH
    NilsH almost 11 years
    Isn't the formatting an implementation detail? How it it "exposed" to your customers? Isn't it just a method call with typed parameters?