package com.google.sitebricks.compiler; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import com.google.common.io.CharStreams; /** * @author Dhanji R. Prasanna (dhanji@gmail.com) */ public final class TemplateCompileException extends RuntimeException { private final List<CompileError> errors; private final List<String> templateLines; private final String template; private final Class<?> page; private final List<CompileError> warnings; public TemplateCompileException(Class<?> page, String template, List<CompileError> errors, List<CompileError> warnings) { this.page = page; this.warnings = warnings; try { //noinspection unchecked this.templateLines = CharStreams.readLines(new StringReader(template)); } catch (IOException e) { throw new IllegalStateException("Fatal error, could not read template after compile", e); } this.template = template; this.errors = errors; } @Override public String getMessage() { if (null == errors) return super.getMessage(); StringBuilder builder = new StringBuilder("Compilation errors in template for "); builder.append(page.getName()); builder.append("\n\n"); AtomicInteger i = new AtomicInteger(0); if (!errors.isEmpty()) { toString(builder, i, errors); builder.append("\nTotal errors: "); builder.append(errors.size()); builder.append("\n\n"); } if (!warnings.isEmpty()) { toString(builder, i, warnings); builder.append("\nTotal warnings: "); builder.append(warnings.size()); builder.append("\n\n"); } return builder.toString(); } private void toString(StringBuilder builder, AtomicInteger i, List<CompileError> errors) { for (CompileError error : errors) { EvaluatorCompiler.CompileErrorDetail cause = error.getCause(); builder.append(i.incrementAndGet()); builder.append(") "); if (cause == null) { builder.append("Unknown cause"); builder.append("\n\n"); } else { builder.append(cause.getError().getMessage()); builder.append("\n\n"); } // Context (source code) of the error. int lineNumber = error.getLine(); if (lineNumber - 1 >= 0) { builder.append(lineNumber); builder.append(": "); builder.append(templateLines.get(lineNumber - 1)); builder.append('\n'); } builder.append(lineNumber + 1); builder.append(": "); String fragment = templateLines.get(lineNumber); builder.append(fragment); builder.append('\n'); int contextLineNumber = lineNumber; // Compute offset (line number width + expression offset). int columnPad = Integer.toString(contextLineNumber).length() + 4; int offset; if (cause != null) offset = fragment.indexOf(cause.getExpression()) + columnPad; else offset = 0; // Code pointer (caret). // TODO fix this. It should appear directly beneath the line in question. char[] spaces = new char[offset]; Arrays.fill(spaces, ' '); builder.append(spaces); builder.append("^"); builder.append('\n'); } } public List<CompileError> getErrors() { return errors; } public List<CompileError> getWarnings() { return warnings; } }