-2

I need an expert advise on following scenario. I have a string template (lets assume email templates) as follows.

Dear {PERSON_NAME},
We would like to thank you on behalf of {CEO_NAME}, {COMPANY_NAME}. You have been selected
for the position of {POSITION_NAME} in {DEPARTMENT_NAME}
etc etc

Here is replacement code

String body = getTemplateBody(tempalteId);
sendMail( 
        body.replace("{PERSON_NAME}", personName )
            .replace("{CEO_NAME}", ceoName),
            .replace("{COMPANY_NAME}", companyName),
            .replace("{POSITION_NAME}", positionName),
            .replace("{DEPARTMENT_NAME}", deptName),
            .replace("{X}", someVar1),
            .replace("{Y}", someVar2),
            .replace("{Z}", someVar3),
            .replace("{ETC_ETC}", "etc")
        );

What we have:

  1. 20 variables enclosed with { }, like {PERSON_NAME}
  2. These variables are fixed not changing in single template string.
  3. These variables are unique, none of the variables is repeated again in same template
  4. 10,000 instances of each template is used in one hour. do daily would be (10,000 * 24)

Question: what is the efficient(not elegant) way to replace variables in a template string to get the actual resultant string Efficient in terms of memory first and then processing?

Would there be any memory leakage or any problem in above code?

Please note above code is just a sample to explain my requirements in simple words, and may not be checked for Variable or method names coding standards.

PHP Avenger
  • 1,744
  • 6
  • 37
  • 66
  • Related (but with no benchmarks or anything): http://stackoverflow.com/questions/1326682/java-replacing-multiple-different-substring-in-a-string-at-once-or-in-the-most – Alex Walker Dec 22 '14 at 11:14
  • That seems easy way of replacement but may not be efficient way. In my example I have 20 fixed variables to be replaced. a single template is replaced 10,000 times with new values in an hour. – PHP Avenger Dec 22 '14 at 11:20

4 Answers4

1

Wouldn't FreeMarker be a good option here? If you use FreeMarker you can provide your template in a separate template file and build the output by merging the template with the data model (a bean or a Map).

// Setup config
Configuration cfg = new Configuration(Configuration.VERSION_2_3_21);
cfg.setDirectoryForTemplateLoading(new File("/where/you/store/templates"));
cfg.setDefaultEncoding("UTF-8");

// Create the data model - e.g. a bean or a map
final Map<String, String> root = new HashMap<>();
root.put("PERSON_NAME", "Joe");
root.put("CEO_NAME", "Anne");

// Get the template
Template temp = cfg.getTemplate("myTemplate.ftl");

// Merge the template with the data model
Writer out = new OutputStreamWriter(System.out);
temp.process(root, out);
wassgren
  • 18,651
  • 6
  • 63
  • 77
0

The String::substring method is implemented as follows:

return Pattern.compile(target.toString(), Pattern.LITERAL)
    .matcher(this)
    .replaceAll(Matcher.quoteReplacement(replacement.toString()));

With the above code, you create 9 Patterns and 18 intermediate Strings. As every call inlines the replaced values, you are not suffering a memory leak, all references despite the final String are immediately freed.

However, you can implement this much more efficient by parsing the input for { - } enclosed values and composing the final String using a StringBuilder. This should be more of your concern.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
0

This solution has linear runtime, if you use a HashMap as replacement holder. There is nothing special about this solution, just plain old java ;)

public static String format(CharSequence text, final Map<String, String> data)
{
    if (text == null)
    {
        return "";
    }

    final int len = text.length();

    if (len < 1)
    {
        return "";
    }

    final StringBuilder textSB = new StringBuilder();
    final StringBuilder keySB = new StringBuilder();
    int pos = 0;
    boolean inKey = false;
    char ch;
    String value;

    while (pos < len)
    {
        ch = text.charAt(pos);

        if (ch == '{')
        {
            if (inKey)
            {
                throw new IllegalArgumentException("Invalid open key char at " + pos + 1);
            }
            inKey = true;
            keySB.setLength(0);
        }
        else if (ch == '}')
        {
            if (!inKey)
            {
                throw new IllegalArgumentException("Invalid close key char at " + pos + 1);
            }
            inKey = false;
            value = data.get(keySB.toString());
            if (value == null)
            {
                throw new IllegalArgumentException("No value found for key " + keySB);
            }
            textSB.append(value);
        }
        else
        {
            if (inKey)
            {
                keySB.append(ch);
            }
            else
            {
                textSB.append(ch);
            }
        }
        pos++;
    }

    if (inKey)
    {
        throw new IllegalArgumentException("None determined key detected.");
    }

    return textSB.toString();
}
Hannes
  • 2,018
  • 25
  • 32
-1

What about this?

Use a HashMap with key as the variable (i.e. "{PERSON_NAME}") and the value as the corresponding getter of such key (i.e. getPersonName()). Obviously, this getter has to be an Interface.

IMHO this would be a very good way.

What the more experienced people think?

kazbeel
  • 1,378
  • 19
  • 40