mindas
mindas

Reputation: 26713

Using Apache Velocity with StringBuilders/CharSequences

We are using Apache Velocity for dynamic templates. At the moment Velocity has following methods for evaluation/replacing:

public static boolean evaluate(Context context, Writer writer, String logTag, Reader reader)

public static boolean evaluate(Context context, Writer out, String logTag, String instring)

We use these methods by providing StringWriter to write evaluation results. Our incoming data is coming in StringBuilder format so we use StringBuilder.toString and feed it as instring.

The problem is that our templates are fairly large (can be megabytes, tens of Ms on rare cases), replacements occur very frequently and each replacement operation triples the amount of required memory (incoming data + StringBuilder.toString() which creates a new copy + outgoing data).

I was wondering if there is a way to improve this. E.g. if I could find a way to provide a Reader and Writer on top of same StringBuilder instance that only uses extra memory for in/out differences, would that be a good approach? Has anybody done anything similar and could share any source for such a class? Or maybe there any better solutions to given problem?

Upvotes: 1

Views: 1414

Answers (3)

Dan Berindei
Dan Berindei

Reputation: 7194

You can save one copy of the string by reading the value field from the StringBuilder through reflection and creating a CharArrayReader on that:

    StringBuilder sb = new StringBuilder("bla");
    Field valueField = StringBuilder.class.getSuperclass().getDeclaredField("value");
    valueField.setAccessible(true);
    char[] value = (char[]) valueField.get(sb);
    Reader r = new CharArrayReader(value, 0, sb.length());

Upvotes: 1

WhiteFang34
WhiteFang34

Reputation: 72039

Velocity needs to parse the whole template before it can be evaluated. You won't be able to provide a Reader and Writer to gain anything in a single evaluation. You could however break up your templates into smaller parts to evaluate them individually. That's going to depend on what's in them and if the parts would depend on each other. And the overhead might not be worth it, depending on your situation.

If you're only dealing with variable substitution in your templates you could simply evaluate each line of your input. Ideally you can intercept that before it goes into the StringBuilder. Otherwise you're still going to have to incur the cost of that memory plus its toString() that you'd feed into a BufferedReader to make readLine() calls against.

If there are #set directives you'll need to keep passing the same context for evaluation. If there are any #if or #foreach blocks it's going to get tricky. I have actually done this before and read in enough lines to capture the block of input for Velocity to parse and evaluate. At that point however you're starting to do Velocity's job and it's probably not worth it.

Upvotes: 2

Nathan Bubna
Nathan Bubna

Reputation: 6933

Yikes. That's a pretty heavyweight use for evaluate(). I assume you have good reasons for not using the standard resource loader stuff, so i won't pontificate. :)

I haven't heard of any solution that would fit this, but since Reader is not a particularly complicated class, my instinct would be to just create your own StringBufferReader class and pass that in.

Upvotes: 0

Related Questions