package ch.vorburger.mtemplating;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
/**
* Generator for Template Generators.
*
* @author Michael Vorburger
*/
// TODO self-bootstrap me, and generate this via a previous version of this generator!
public class GeneratorGenerator implements Generator<Template> {
// Don't need this? static enum LexerState { PASSING_THROUGH, IN_EXPRESSION, IN_CODE }
static public void generateIt(Writer w, Template t) throws IOException {
w.write("package ");
w.write(t.dottedPackageName);
w.write(";\n\n");
// for (Class<?> typeToImport : t.typesToImport) {
// w.write("import ");
// w.write(typeToImport.getCanonicalName());
// w.write(";\n");
// }
// TODO Insert some kind of @Generated by GeneratorGenerator.class.getName() from template ... on ... Date
w.write("public class ");
w.write(t.className);
w.write(" {\n\n");
w.write("\tstatic public void generate(java.io.Writer w, ");
w.write(t.parameterClass.getCanonicalName());
w.write(' ');
w.write(t.parameterVariableName);
w.write(") throws java.io.IOException {\n");
boolean streamingThroughNonEscapedCharacters = false;
while(hasMore(t.reader)) {
// TODO Should check that there are no embedded escapes !!
// NOTE: MUST check the 'longer' escape tag before the shorter one!
if(check(t.reader, t.expressionEscapeStartTag)) {
if (streamingThroughNonEscapedCharacters)
w.write("\");\n");
// NOTE: The '"" + expression' trick is used to allow non-Object (e.g. int) in expressions
w.write("\t\tw.write(\"\" + ");
streamingThroughNonEscapedCharacters = true;
} else if (check(t.reader, t.expressionEscapeEndTag)) {
if (streamingThroughNonEscapedCharacters)
w.write(");\n");
streamingThroughNonEscapedCharacters = false;
} else if (check(t.reader, t.codeEscapeStartTag)) {
if (streamingThroughNonEscapedCharacters)
w.write("\");\n");
streamingThroughNonEscapedCharacters = true;
} else if (check(t.reader, t.codeEscapeEndTag)) {
// if (streamingThroughNonEscapedCharacters)
// w.write("\");\n");
w.write('\n');
streamingThroughNonEscapedCharacters = false;
} else {
// OK so it's just plain text to pass through
if (!streamingThroughNonEscapedCharacters) {
w.write("\t\tw.write(\"");
streamingThroughNonEscapedCharacters = true;
}
char c = (char) t.reader.read();
if (c == '\n')
w.write("\\n");
else
w.write(c);
}
}
if (streamingThroughNonEscapedCharacters)
w.write("\");\n");
w.write("\t\tw.flush();\n\t}\n}\n");
}
/**
* Check if the Reader has more to read, without consuming it.
*/
// TODO move this out into a ReaderUtil ? Or, better, create a BetterReader wrapper?
// TODO Am I dumb or is this the best & only way of doing this?!
static boolean hasMore(Reader reader) throws IOException {
reader.mark(1);
int next = reader.read();
reader.reset();
return !(next == -1);
}
/**
* Check if what's coming up to read next in a Reader matches some kind of 'tag'.
* If it doesn't match, then the Reader is reset() and nothing is read.
* If it matches, that tag is consumed (read).
*/
// TODO move this out into a ReaderUtil ? Or, better, create a BetterReader wrapper?
static boolean check(Reader reader, CharSequence tag) throws IOException {
reader.mark(tag.length());
char[] buffer = new char[tag.length()];
int n = reader.read(buffer);
if (n == tag.length() && equals(tag, buffer)) {
return true;
} else {
reader.reset();
return false;
}
}
/**
* Highly efficient comparison of CharSequence and char[].
* Avoids the creation of an intermediary object.
*/
// TODO move this out into a ReaderUtil ? Or, better, create a BetterReader wrapper?
// TODO You'd think something like this is available in the JDK (or Apache Commons), but I couldn't find one.
static boolean equals(CharSequence charSequence, char[] charArray) {
// Following code is inspired by java.util.Arrays.equals()
if (charSequence == null && charArray == null) {
return true;
}
if (charSequence == null || charArray == null) {
return false;
}
int length = charArray.length;
if (charSequence.length() != length) {
return false;
}
for (int i=0; i<length; i++) {
if (charSequence.charAt(i) != charArray[i]) {
return false;
}
}
return true;
}
@Override
public void generate(Writer w, Template i) throws IOException {
generateIt(w, i);
}
}