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); } }