/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.civilian-framework.org/license.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.civilian.tool.csp;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import org.civilian.Controller;
import org.civilian.internal.source.OutputFile;
import org.civilian.internal.source.OutputLocation;
import org.civilian.internal.source.PackageDetector;
import org.civilian.template.ComponentBuilder;
import org.civilian.template.Template;
import org.civilian.template.TemplateWriter;
import org.civilian.template.mixin.FormTableMixin;
import org.civilian.template.mixin.HtmlMixin;
import org.civilian.template.mixin.LangMixin;
import org.civilian.template.mixin.TableMixin;
import org.civilian.util.Arguments;
import org.civilian.util.ClassUtil;
import org.civilian.util.DateTime;
import org.civilian.util.FileType;
import org.civilian.util.IoUtil;
import org.civilian.util.Scanner;
import org.civilian.util.StringUtil;
/**
* CspCompiler is a TemplateCompiler which compiles
* Civilian Server Pages (CSP) into Java template classes.
*/
public class CspCompiler
{
private static final String START_TEMPLATE_SECTION = "{{";
private static final String END_TEMPLATE_SECTION = "}}";
/**
* The default extension of CSP files.
*/
public static final String EXTENSION = "csp";
/**
* The default encoding used for template files and generated files.
*/
public static final String DEFAULT_ENCODING = "UTF-8";
/**
* Runs the CspCompiler from the command-line
*/
public static void main(String[] args) throws CspException, IOException
{
compile(args);
}
/**
* Creates a new CspCompiler.
*/
public CspCompiler()
{
this(null);
}
/**
* Creates a new CspCompiler.
*/
public CspCompiler(Options options)
{
options_ = options != null ? options : new Options();
registerMixin(new MixinField(HtmlMixin.class, "html"));
registerMixin(new MixinField(LangMixin.class, "lang"));
registerMixin(new MixinField(TableMixin.class, "table"));
registerMixin(new MixinField(FormTableMixin.class, "formTable"));
}
/**
* Returns the options of the compiler.
*/
public Options options()
{
return options_;
}
/**
* Configures the compiler with the command line arguments and then runs the compiler.
* @param args command line arguments.
* @throws CspException if a compiler error occurs
* @throws IOException if an io error occurs
*/
public static synchronized void compile(String[] args) throws CspException, IOException
{
compile(new Arguments(args));
}
/**
* Configures the compiler with the command line arguments and then runs the compiler.
* @param args command line arguments, wrapped in a Arguments object
* @throws CspException if a compiler error occurs
* @throws IOException if an io error occurs
*/
public static void compile(Arguments args) throws CspException, IOException
{
if (!args.hasMore())
{
printHelp();
return;
}
Options options = new Options();
while(args.startsWith("-"))
{
if (args.consume("-force"))
options.force = true;
else if (args.consume("-r"))
options.recursive = args.nextBoolean("recursive mode");
else if (args.consume("-srcmap"))
options.srcMap = args.nextBoolean("srcmap mode");
else if (args.consume("-enc"))
options.setEncoding(args.next("encoding"));
else if (args.consume("-enc:in"))
options.encodingIn = args.next("input encoding");
else if (args.consume("-enc:out"))
options.encodingOut = args.next("output encoding");
else if (args.startsWith("-out:"))
options.outputLocation = OutputLocation.parse(args, true, true);
else if (args.consume("-ts"))
options.timestamp = args.nextBoolean("timestamp mode");
else if (args.consume("-v"))
options.verbose = args.nextInt("verbose level");
else
throw new IllegalArgumentException("unknown option " + args.next());
}
File input = args.nextFile("input file", FileType.EXISTENT);
CspCompiler compiler = new CspCompiler(options);
compiler.compile(input);
}
/**
* Prints a help screen.
*/
private static void printHelp()
{
System.out.println("usage:");
System.out.println("java " + CspCompiler.class.getName() + " [<parameter>]* <input>");
System.out.println();
System.out.println("input: either a single file to compile");
System.out.println(" or a directory whose files are compiled");
System.out.println("output: provide one of the -out parameters to");
System.out.println(" control the output. If no output parameter");
System.out.println(" is specified the output is placed into the");
System.out.println(" same directory than the template file");
System.out.println();
System.out.println("parameters: default:");
System.out.println("-enc <v> encoding of template and generated files " + DEFAULT_ENCODING);
System.out.println("-enc:in <v> encoding of template files " + DEFAULT_ENCODING);
System.out.println("-enc:out <v> encoding of generated files " + DEFAULT_ENCODING);
System.out.println("-ext <v> extension of template files " + EXTENSION);
System.out.println("-force force compilation (ignore timestamps)");
System.out.println("-srcmap <true|false> print source map comments true");
OutputLocation.printHelp(true);
System.out.println("-r <true|false> recurse subdirectories true");
System.out.println("-ts <true|false> print generation timestamp into file false");
System.out.println("-v 0|1|2 verbose 0");
}
public synchronized void compile(File input) throws CspException, IOException
{
FileType.EXISTENT.check(input);
options_.complete();
log(1, "run csp compiler");
if (input.isDirectory())
compileDirectory(input);
else if (acceptExtension(input))
compileFile(input);
else
System.err.println("not a template file: " + input);
}
/**
* Compiles a directory.
*/
private void compileDirectory(File dir) throws CspException, IOException
{
log(2, "scan", dir);
File files[] = dir.listFiles();
if (files != null)
{
for (int i=0; i<files.length; i++)
{
File file = files[i];
if (!file.isDirectory())
{
if (acceptExtension(file))
compileFile(file);
}
else if (options_.recursive)
compileDirectory(file);
}
}
}
private boolean acceptExtension(File templateFile)
{
String extension = IoUtil.getExtension(templateFile);
return EXTENSION.equals(extension);
}
/**
* Compiles a template file.
*/
private void compileFile(File templateFile) throws CspException, IOException
{
log(2, "check", templateFile);
String fileName = IoUtil.cutExtension(templateFile.getName());
String className = StringUtil.startUpperCase(fileName);
OutputFile outputFile = options_.outputLocation.getOutputFile(null /*don't know the package yet*/, className + ".java", templateFile);
if (outputFile == null)
log(2, "skip", templateFile);
else
{
if (options_.force || outputFile.generate(templateFile))
{
TemplateInput input = new TemplateInput(templateFile);
JavaOutput output = new JavaOutput();
output.file = outputFile.file;
output.className = className;
output.assumedPackage = outputFile.packageName;
log(1, "generate", outputFile.file.getAbsolutePath());
compileFile(input, output);
}
}
}
private synchronized void compileFile(TemplateInput input, JavaOutput output) throws CspException, IOException
{
try
{
compile(input, output);
// phase 2: write output, in order to not produce a null output if reading fails
BufferedWriter out = null;
try
{
File dir = output.file.getParentFile();
IoUtil.mkdirs(dir);
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(output.file), options_.encodingOut));
out.write(output_);
}
finally
{
IoUtil.close(out);
}
}
catch(CspException e)
{
throw e;
}
catch(IOException e)
{
throw e;
}
catch(Exception e)
{
throw new CspException("exception in compiler run", e, scanner_);
}
finally
{
scanner_ = null;
}
}
private void compile(TemplateInput input, JavaOutput output) throws CspException, IOException
{
scanner_ = new Scanner(input.readLines(options_.encodingIn));
scanner_.setSource(input.file.getName());
scanner_.setErrorHandler(new ErrorHandler());
if (scanner_.nextKeyword("encoding"))
{
String encoding = nextScannerToken("encoding");
if (!options_.encodingIn.equalsIgnoreCase(encoding))
{
scanner_.init(input.readLines(encoding));
scanner_.nextKeyword("encoding");
nextScannerToken("encoding");
}
}
compile(input.file, output);
}
private void compile(File templFile, JavaOutput output) throws CspException, IOException
{
StringWriter sw = new StringWriter();
SourceWriter out = new SourceWriter(sw);
if (scanner_.nextKeyword("java"))
{
// pure java mode: csp file is essentially a Java class
// with template snippets
scanner_.nextLine();
compileJavaLines(out, false);
}
else
{
// csp mode: parse commands which describe the generated
// java class
classData_ = new ClassData(output.className);
parsePackageCmd(templFile, output.assumedPackage);
parseImportCmds();
parsePrologCmds();
parseTemplateCmd();
// we compile the body and write it to another temporary writer
// since the template body can contain a super-call which
// we need in printClassData
StringWriter swBody = new StringWriter();
SourceWriter outBody = new SourceWriter(swBody);
outBody.increaseTab();
compileJavaLines(outBody, true);
printClass(out, templFile);
int tab = out.getTabCount();
out.setTabCount(0);
out.print(swBody.toString());
out.setTabCount(tab);
if ((classData_.args != null) || (classData_.mixins != null) || classData_.standalone)
printFields(out);
out.endBlock(); // class block, started in compileClassData
}
out.flush();
output_ = sw.toString();
}
private void parsePackageCmd(File templFile, String assumedPackage) throws CspException
{
if (scanner_.nextKeyword("package"))
{
classData_.packageName = StringUtil.cutRight(nextScannerToken("package"), ";");
if ((assumedPackage != null) && !assumedPackage.equals(classData_.packageName))
throw new CspException("package was set by compiler parameters to '" + assumedPackage + ", but the template specified '" + classData_.packageName + "'");
}
else if (assumedPackage != null)
classData_.packageName = assumedPackage;
else
{
PackageDetector detector = new PackageDetector();
String p = detector.detect(templFile);
if (p == null)
throw new CspException("cannot detect package for template '" + templFile.getName() + "', please provide a explicit package in the template", scanner_);
classData_.packageName = p;
}
}
private void parseImportCmds() throws CspException
{
while(scanner_.nextKeyword("import"))
{
String s = StringUtil.cutRight(nextScannerToken("import"), ";");
s = resolveRelativeImport(s);
classData_.imports.add(s);
}
}
private String resolveRelativeImport(String s) throws CspException
{
if (s.startsWith("./"))
return classData_.packageName + '.' + s.substring(2);
else if (s.startsWith("../"))
{
String t = s;
String prefix = classData_.packageName;
do
{
int p = prefix.lastIndexOf('.');
if (p < 0)
throw new CspException("relative import '" + s + "' can't be applied to package '" + classData_.packageName + "'", scanner_);
prefix = prefix.substring(0, p);
t = t.substring(3);
}
while(t.startsWith("../"));
return prefix + '.' + t;
}
else
return s;
}
private void parsePrologCmds() throws CspException
{
while(scanner_.nextKeyword("prolog"))
classData_.prolog.add(scanner_.consumeRest());
}
private void parseTemplateCmd() throws CspException, IOException
{
//-------------------------------------
// "template"
if (!scanner_.nextKeyword("template"))
throw new CspException("expected the template command, but reached end of file", scanner_);
parseTemplateArgs();
//-------------------------------------
// "package-access"
if (scanner_.nextKeyword("package-access"))
classData_.isPublic = false;
//-------------------------------------
// "extends"
if (scanner_.nextKeyword("extends"))
{
if (scanner_.next("-"))
{
classData_.standalone = true;
String writerClass = TemplateWriter.class.getSimpleName();
if (scanner_.nextKeyword("using"))
writerClass = nextScannerToken("using");
if ("TemplateWriter".equals(writerClass))
writerClass = TemplateWriter.class.getName();
classData_.writerClass = writerClass;
classData_.writerClassSimple = ClassUtil.cutPackageName(writerClass);
classData_.imports.add(writerClass);
}
else
classData_.extendsClass = nextScannerToken("extends");
}
if ((classData_.extendsClass == null) && !classData_.standalone)
{
classData_.imports.add(Template.class);
classData_.extendsClass = Template.class.getSimpleName();
}
//-------------------------------------
// "implements"
if (scanner_.nextKeyword("implements"))
classData_.implementsList = parseClassList();
//-------------------------------------
// "mixin"
if (scanner_.nextKeyword("mixin"))
classData_.mixins = parseMixins(scanner_, classData_.mixins);
if (classData_.mixins != null)
{
for (MixinField mixin : classData_.mixins)
classData_.imports.add(mixin.className);
}
//-------------------------------------
// "throws"
if (scanner_.nextKeyword("throws"))
{
classData_.exception = parseClassList();
if ("-".equals(classData_.exception))
classData_.exception = null;
}
if (scanner_.getPos() > 0)
throw new CspException("invalid input: '" + scanner_.getRest() + "'", scanner_);
}
private void parseTemplateArgs() throws CspException, IOException
{
if (!scanner_.next("("))
return;
if (scanner_.next(")"))
return;
StringBuilder argsString = new StringBuilder();
classData_.arguments = new ArrayList<>();
while(true)
{
Argument argument = new Argument(scanner_);
classData_.arguments.add(argument);
if (argsString.length() > 0)
argsString.append(", ");
argument.ctorArg(argsString);
if (scanner_.next(")"))
break;
if (!scanner_.next(","))
throw new CspException("expected closing bracket ')' of template argument list", scanner_);
}
classData_.args = argsString.toString();
}
private String parseClassList() throws CspException
{
StringBuffer list = new StringBuffer();
while(true)
{
String type = nextScannerToken("implements", ",");
if (list.length() > 0)
list.append(", ");
list.append(type);
if (!scanner_.next(","))
break;
}
return list.toString();
}
private ArrayList<MixinField> parseMixins(Scanner scanner, ArrayList<MixinField> list) throws CspException
{
do
{
String def = nextScannerToken("mixin def", ",");
String[] parts = def.split(":", 2);
if (parts.length > 0)
{
String className = parts[0];
String fieldName = parts.length > 1 ? parts[1] : null;
list = parseMixin(className, fieldName, list);
}
}
while(scanner.next(","));
return list;
}
private ArrayList<MixinField> parseMixin(String className, String fieldName, ArrayList<MixinField> list)
{
MixinField registeredMixin = registeredMixins_.get(className);
if (registeredMixin != null)
{
if (fieldName == null)
fieldName = registeredMixin.fieldName;
className = registeredMixin.className;
}
if (list == null)
list = new ArrayList<>();
list.add(new MixinField(className, fieldName));
return list;
}
private void registerMixin(MixinField mixin)
{
registeredMixins_.put(mixin.fieldName, mixin);
registeredMixins_.put(mixin.className, mixin);
}
private void printClass(SourceWriter out, File templFile) throws IOException
{
out.println("/**");
out.print(" * Generated from " + templFile.getName());
if (options_.timestamp)
out.print(" at " + new DateTime());
out.println();
out.println(" * Do not edit.");
out.println(" */");
out.print("package ");
out.print(classData_.packageName);
out.println(";");
out.println();
out.println();
if (classData_.imports.write(out, ClassUtil.getPackageName(Controller.class)))
{
out.println();
out.println();
}
for (String prolog : classData_.prolog)
out.println(prolog);
if (classData_.isPublic)
out.print("public ");
out.print("class ");
out.print(classData_.className);
if (classData_.extendsClass != null)
{
out.print(" extends ");
out.print(classData_.extendsClass);
}
if (classData_.implementsList != null)
{
out.print(" implements ");
out.print(classData_.implementsList);
}
out.println();
out.beginBlock();
if (classData_.needsCtor())
printClassCtor(out);
if (classData_.standalone)
printClassPublicPrintMethod(out);
if (classData_.mixins != null)
printClassInitMethod(out);
if (classData_.extendsClass != null)
out.print("@Override ");
out.print("protected void print()");
if (classData_.exception != null)
{
out.print(" throws ");
out.print(classData_.exception);
}
out.println();
}
private void printClassCtor(SourceWriter out) throws IOException
{
out.print("public ");
out.print(classData_.className);
out.print("(");
if (classData_.args != null)
out.print(classData_.args);
out.println(")");
out.beginBlock();
if (classData_.superCall != null)
{
out.print(classData_.superCall);
printSrcMapComment(out, classData_.superCall, classData_.superCallLine);
}
if (classData_.args != null)
{
for (Argument arg : classData_.arguments)
{
arg.fieldAssign(out);
out.println();
}
}
out.endBlock();
out.println();
out.println();
}
private void printClassPublicPrintMethod(SourceWriter out)
{
out.print("public synchronized void print(");
out.print(classData_.writerClassSimple);
out.print(" out)");
if (classData_.exception != null)
{
out.print(" throws ");
out.print(classData_.exception);
}
out.println();
out.beginBlock();
out.println("if (out == null)");
out.increaseTab();
out.println("throw new IllegalArgumentException(\"out is null\");");
out.decreaseTab();
out.println("this.out = out;");
if (classData_.mixins != null)
out.println("init();");
out.println("print();");
out.endBlock();
out.println();
out.println();
}
private void printClassInitMethod(SourceWriter out)
{
if (!classData_.standalone)
out.print("@Override ");
out.println("protected void init()");
out.beginBlock();
out.println("super.init();");
for (MixinField mixin : classData_.mixins)
{
out.print(mixin.fieldName);
out.print(" = new ");
out.print(mixin.simpleName);
out.println("(out);");
}
out.endBlock();
out.println();
out.println();
}
private void printFields(SourceWriter out) throws CspException, IOException
{
out.println();
out.println();
if (classData_.args != null)
{
for (Argument arg : classData_.arguments)
{
arg.fieldDecl(out);
out.println();
}
}
if (classData_.mixins != null)
{
for (MixinField mixin : classData_.mixins)
{
out.print("private ");
out.print(mixin.simpleName);
out.print(" ");
out.print(mixin.fieldName);
out.println(";");
}
}
if (classData_.standalone)
{
out.print("protected ");
out.print(classData_.writerClassSimple);
out.println(" out;");
}
}
private void compileJavaLines(SourceWriter out, boolean expectMainTemplate) throws CspException, IOException
{
do
{
String line = scanner_.getLine();
if (line.trim().equals(START_TEMPLATE_SECTION))
{
compileTemplateLines(out, expectMainTemplate);
expectMainTemplate = false;
}
else if (expectMainTemplate)
throw new CspException("expected main template body", scanner_);
else
out.println(line);
}
while(scanner_.nextLine());
}
private void compileTemplateLines(SourceWriter out, boolean isMainTemplate) throws CspException, IOException
{
TemplateLine tline = new TemplateLine();
// current line is the template start "{{"
parse(scanner_.getLine(), tline);
int tabBase1 = out.getTabCount();
while(out.getTabCount() < tline.indent)
out.increaseTab();
int tabBase2 = out.getTabCount();
out.beginBlock();
int componentLevel = -1;
int maxCompLevel = -1;
boolean canHaveSuperCall = isMainTemplate;
Block block = null;
while(true)
{
if (!scanner_.nextLine())
throw new CspException("template end '" + END_TEMPLATE_SECTION + "' expected", scanner_);
String line = scanner_.getLine();
parse(line, tline);
if (END_TEMPLATE_SECTION.equals(tline.content))
break;
if (tline.type == TemplateLine.Type.empty)
out.println("out.println();");
else
{
if (block == null)
block = new Block(null, tline.indent);
block = adjustTemplateIndent(block, tline, out);
block.isCodeBlock = false;
if (tline.type == TemplateLine.Type.code)
{
if (canHaveSuperCall && tline.content.startsWith("super("))
{
classData_.superCall = tline.content;
classData_.superCallLine = scanner_.getLineIndex();
}
else
{
block.isCodeBlock = true;
out.print(tline.content);
printSrcMapComment(out, tline.original);
}
}
else if (tline.type == TemplateLine.Type.literal)
{
printTemplateLiteralLine(out, tline.content, true);
}
else if (tline.type == TemplateLine.Type.componentStart)
{
boolean declare = false;
if (++componentLevel > maxCompLevel)
{
maxCompLevel = componentLevel;
declare = true;
}
int p = tline.content.indexOf(']');
if (p < 0)
{
printTemplateComponentStart(out, componentLevel, tline.content, true, declare);
}
else
{
String cbExpr = tline.content.substring(0, p).trim();
printTemplateComponentStart(out, componentLevel, cbExpr, false, declare);
printTemplateLiteralLine(out, tline.content.substring(p + 1).trim(), false);
printTemplateComponentEnd(out, componentLevel--, false, null);
}
}
else if (tline.type == TemplateLine.Type.componentEnd)
{
if (componentLevel < 0)
throw new CspException("unmatched component end", scanner_);
printTemplateComponentEnd(out, componentLevel--, true, tline.original);
}
else
throw new CspException("unexpected line type " + tline.type, scanner_);
canHaveSuperCall = false;
}
}
// close all open blocks
while(out.getTabCount() > tabBase2)
out.endBlock();
while(out.getTabCount() > tabBase1)
out.decreaseTab();
}
private void parse(String line, TemplateLine tline) throws CspException
{
if (!tline.parse(line))
throw new CspException(tline.error, scanner_);
}
private Block adjustTemplateIndent(Block block, TemplateLine tline, SourceWriter out) throws CspException
{
if (tline.indent == block.indent)
return block;
if (tline.indent > block.indent)
{
if (block.isCodeBlock)
out.beginBlock();
else
out.println("out.increaseTab();") ;
block = new Block(block, tline.indent);
}
else // (tline.indent < block.indent)
{
while(tline.indent < block.indent)
{
block = block.prev;
if (block == null)
throw new CspException("end of template marker '{{' expected", scanner_);
if (tline.indent > block.indent)
throw new CspException("inconsistent indent", scanner_);
if (block.isCodeBlock)
out.endBlock();
else
out.println("out.decreaseTab();") ;
}
}
return block;
}
private void printTemplateComponentStart(SourceWriter out, int level, String cbExpr, boolean multiLine, boolean declare)
{
String var = getCbVar(level);
if (declare)
{
out.print(ComponentBuilder.class.getName());
out.print(' ');
}
out.print(var);
out.print(" = ");
out.print(cbExpr);
out.println(";");
out.print(var);
out.print(".startComponent(");
out.print(multiLine);
out.print(");");
printSrcMapComment(out, cbExpr);
}
private void printTemplateComponentEnd(SourceWriter out, int level, boolean multiLine, String comment)
{
String var = getCbVar(level);
out.print(var);
out.print(".endComponent(");
out.print(multiLine);
out.print(");"); // ends the wrapper block
if (comment != null)
printSrcMapComment(out, comment);
else
out.println();
}
private void printTemplateLiteralLine(SourceWriter out, String line, boolean usePrintln) throws CspException
{
int length = line.length();
int start = 0;
int p = 0;
boolean lastPartWasCode = false;
while((start < length) && ((p = line.indexOf("<%", start)) != -1))
{
lastPartWasCode = false;
if (line.regionMatches(p, "<%%", 0, 3))
{
if ((p + 3 < length) && (line.charAt(p + 3) == '>'))
{
// <%%> detected
if (start < p)
printTemplateText(out, line, start, p, false);
start = p + 4;
}
else
{
// <%% detected: print literal
printTemplateText(out, line, start, p+2, false);
start = p + 3;
}
}
else
{
if (start < p)
printTemplateText(out, line, start, p, false);
int q = line.indexOf("%>", p);
// code end signal not found
if (q == -1)
throw new CspException("closing '%>' not found", scanner_);
// ignore empty code segments <%%>
if (q > p + 2)
{
// line end signal <%/%> found
if ((q == p + 3) && (line.charAt(p + 2) == '/'))
return;
String snippetRaw = line.substring(p, q + 2);
String snippetCode = line.substring(p +2, q).trim();
printTemplateSnippet(out, snippetRaw, snippetCode);
lastPartWasCode = true;
}
start = q + 2;
}
}
if (start < length)
printTemplateText(out, line, start, length, usePrintln);
else if (usePrintln)
{
if (lastPartWasCode)
out.println("out.printlnIfNotEmpty();");
else
out.println("out.println();");
}
}
private void printTemplateText(SourceWriter out, String content, int start, int end, boolean usePrintln)
{
out.print(usePrintln ? "out.println(\"" : "out.print(\"");
for (int i=start; i<end; i++)
{
char c = content.charAt(i);
switch(c)
{
case '\t':
out.print("\\t");
break;
case '\\':
out.print("\\\\");
break;
case '"':
out.print("\\\"");
break;
default:
out.print(c);
break;
}
}
out.print("\");");
printSrcMapComment(out, content.substring(start, end));
}
/**
* Prints a template code segment embedded in a literal line between "<%" and "%>".
* @param raw the snippet including the boundaries "<%" and "%>".
* @param code the code content, trimmed.
*/
private void printTemplateSnippet(SourceWriter out, String raw, String code) throws CspException
{
if (code.charAt(0) == '?')
{
if (code.length() != 1)
{
out.print("if (");
out.print(code.substring(1).trim());
out.print(")");
printSrcMapComment(out, raw);
out.beginBlock();
}
else
out.endBlock();
}
else if (code.endsWith(";"))
{
out.print(code);
printSrcMapComment(out, raw);
}
else
{
out.print("out.print(");
out.print(code);
out.print(");");
printSrcMapComment(out, raw);
}
}
private void printSrcMapComment(SourceWriter out, String s)
{
printSrcMapComment(out, s, scanner_.getLineIndex());
}
private void printSrcMapComment(SourceWriter out, String s, int lineIndex)
{
if (options_.srcMap)
{
int column = out.getColumn();
for (int i=column; i<=70; i++)
out.print(' ');
out.print(" // line ");
out.print(lineIndex + 1);
out.print(": ");
out.print(s.trim());
}
out.println();
}
private String nextScannerToken(String what) throws CspException
{
return nextScannerToken(what, "");
}
private String nextScannerToken(String what, String delims) throws CspException
{
String token = scanner_.consumeToken(delims);
if (token == null)
throw new CspException("missing " + what + "-value", scanner_);
return token;
}
private static String getCbVar(int level)
{
return "cspCb" + level;
}
/**
* Writes progress information to System.out.
*/
private void log(int level, String action, Object text)
{
if (options_.verbose >= level)
{
System.out.print(StringUtil.fillRight(action, 9));
System.out.println(text.toString());
}
}
private void log(int level, String text)
{
if (options_.verbose >= level)
System.out.println(text.toString());
}
/**
* Options for the CspCompiler.
*/
public static class Options
{
/**
* Flag that determines if the compiler always generates the template regardless
* of timestamps of template file and compiler target.
* The default is false.
*/
public boolean force;
/**
* If the compiler input is a directory, the recursive flag
* tells if the compiler should recurse in sub-directories
* to find template files. By default recursive processing
* is on.
*/
public boolean recursive = true;
/**
* Determines if a timestamp is printed into the generated file.
* The default is false.
*/
public boolean timestamp;
/**
* Determines if the compiler should print line comments
* in the generated class which show the source line.
* The default is true.
*/
public boolean srcMap = true;
/**
* The encoding of input template files.
* By default it is UTF-8.
*/
public String encodingIn;
/**
* The encoding of output template files.
* By default it is UTF-8.
*/
public String encodingOut;
/**
* The verbose level (0-2) determines the verbosity of compiler messages.
*/
public int verbose = 0;
/**
* The OutputLocation determines where
* to write the generated output. By default the output
* is placed in the same directory as the input file.
*/
public OutputLocation outputLocation;
/**
* Sets the encoding of of input and of generated files.
*/
public void setEncoding(String encoding)
{
encodingIn = encodingOut = encoding;
}
private void complete()
{
if (encodingIn == null)
encodingIn = DEFAULT_ENCODING;
if (encodingOut == null)
encodingOut = DEFAULT_ENCODING;
if (outputLocation == null)
outputLocation = OutputLocation.OUTPUT_TO_INPUT_DIR;
}
}
private Options options_;
private Scanner scanner_;
private ClassData classData_;
private String output_;
private HashMap<String,MixinField> registeredMixins_ = new HashMap<>();
}