/* * FreeMarker: a tool that allows Java programs to generate HTML * output using templates. * Copyright (C) 1998-2004 Benjamin Geer * Email: beroul@users.sourceforge.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package freemarker.template.compiler; import java.util.ArrayList; import java.util.List; import freemarker.template.FunctionTemplateProcessor; import freemarker.template.TemplateProcessor; import freemarker.template.instruction.BreakInstruction; import freemarker.template.instruction.DefaultCaseInstruction; import freemarker.template.instruction.ElseInstruction; import freemarker.template.instruction.EmptyInstruction; import freemarker.template.instruction.ExitInstruction; import freemarker.template.instruction.FunctionInstruction; import freemarker.template.instruction.FunctionModel; import freemarker.template.instruction.GenericStartInstruction; import freemarker.template.instruction.IfElseInstruction; import freemarker.template.instruction.Instruction; import freemarker.template.instruction.ListInstruction; import freemarker.template.instruction.NOOPInstruction; import freemarker.template.instruction.SwitchInstruction; import freemarker.template.instruction.UnparsedInstruction; /** * <p> * Builds a template as a tree structure in which child nodes are stored in * {@link TemplateArrayList}s. Each instance can be used to compile one * template. * </p> * * <p> * This class is now misnamed, since it originally built a * <code>TemplateLinkedList</code>. * </p> * * @version $Id: LinkedListTemplateBuilder.java 1081 2005-08-28 10:51:16Z * run2000 $ */ public final class LinkedListTemplateBuilder implements TemplateBuilder { private final FunctionTemplateProcessor template; private final TemplateParser parser; private short listInstructions; private short switchInstructions; private short functionInstructions; /** * Constructs a new <code>LinkedListTemplateBuilder</code> with a * {@link freemarker.template.FunctionTemplateProcessor} and a * {@link TemplateParser}. * * @param template * the template to be built * @param parser * the parser to parse the input stream */ public LinkedListTemplateBuilder(FunctionTemplateProcessor template, TemplateParser parser) { this.template = template; this.parser = parser; } /** * Builds the template. * * @return the head of the built template. * @throws ParseException * the template could not be built */ public TemplateProcessor build() throws ParseException { List accumulator = new ArrayList(); listInstructions = 0; switchInstructions = 0; functionInstructions = 0; Instruction endInstruction = buildInstructions(accumulator); TemplateArrayList list = new TemplateArrayList(accumulator); if (endInstruction != null) { throw new ParseException("Unexpected instruction" + parser.atChar()); } return list; } /** * Builds a list of {@link freemarker.template.instruction.Instruction}s. * * @param list * the current <code>List</code> to which links are to be added. * @return an end instruction, if the current branch ends with one, * otherwise <code>null</code>. */ private Instruction buildInstructions(List list) throws ParseException { Instruction instruction; while (true) { // Search for an instruction. instruction = parser.getNextInstruction(); // If there aren't any more instructions, put the rest of the // file in a TextBlockInstruction, use that as the statement // for the current link, and return. if (instruction == null) { return null; } // If we've been given an end instruction, return it. if (instruction.isEndInstruction()) { return instruction; } // Have the instruction call us back to be built and inserted // into the link. list.add(instruction.callBuilder(this)); // If there's any text after the instruction, continue building // links. if (!parser.isMoreInstructions()) { return null; } } } /** * When this {@link TemplateBuilder} implementation calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it is an {@link freemarker.template.instruction.EmptyInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(EmptyInstruction instruction) throws ParseException { return instruction; } /** * When this {@link TemplateBuilder} implementation calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it should be built as a * {@link freemarker.template.instruction.GenericStartInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(GenericStartInstruction instruction) throws ParseException { buildGenericBody(instruction); return instruction; } /** * When this {@link TemplateBuilder} implementation calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it should be built as a * {@link freemarker.template.instruction.ListInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(ListInstruction instruction) throws ParseException { try { listInstructions++; buildGenericBody(instruction); } finally { listInstructions--; } return instruction; } /** * When this {@link TemplateBuilder} implementation calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it is a {@link freemarker.template.instruction.FunctionInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(FunctionInstruction instruction) throws ParseException { TemplateProcessor tp; try { functionInstructions++; tp = NOOPInstruction.getInstance(); buildGenericBody(instruction); template.addFunction(instruction.getName(), new FunctionModel(instruction)); } finally { functionInstructions--; } return tp; } /** * Builds a GenericStartInstruction's body. * * @param instruction * the instruction to be built */ private void buildGenericBody(GenericStartInstruction instruction) throws ParseException { List accumulator = new ArrayList(); Instruction endInstruction = buildInstructions(accumulator); TemplateArrayList body = new TemplateArrayList(accumulator); instruction.setBody(body); // Make sure buildLinks() returned the right end instruction. if (endInstruction == null || !instruction.testEndInstruction(endInstruction)) { throw new ParseException("Expected end instruction for " + instruction.toString() + parser.atChar()); } } /** * When this {@link TemplateBuilder} implementation calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it is an {@link freemarker.template.instruction.IfInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(IfElseInstruction instruction) throws ParseException { // Recursively build the if block. List accumulator = new ArrayList(); boolean elseFound = false; Instruction nextInstruction = buildInstructions(accumulator); TemplateArrayList body = new TemplateArrayList(accumulator); // Add the first "if" block. The "if" test is already inserted. instruction.setIfBlock(body); // Recursively build the elseif and else blocks, if there are any. while (true) { if (nextInstruction == null) { throw new ParseException("Expected end of if statement" + parser.atChar()); } int endType = nextInstruction.getEndType(); if (elseFound && (endType != Instruction.IF_END)) { throw new ParseException("Expected end of if statement" + parser.atChar()); } switch (endType) { case Instruction.IF_END: return instruction; case Instruction.ELSE: elseFound = true; // Intentional drop-through here... case Instruction.ELSEIF: ElseInstruction ifInstruction = (ElseInstruction) nextInstruction; accumulator.clear(); nextInstruction = buildInstructions(accumulator); body = new TemplateArrayList(accumulator); ifInstruction.setBody(body); instruction.addTest(ifInstruction); break; default: throw new ParseException("Expected end of if statement" + parser.atChar()); } } } /** * When this {@link TemplateBuilder} implementation calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it is a {@link freemarker.template.instruction.SwitchInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(SwitchInstruction instruction) throws ParseException { DefaultCaseInstruction caseInstruction = null; int lastType = Instruction.NONE; boolean defaultFound = false; switchInstructions++; // Get blocks followed by end instructions, and check for end // instructions that are meaningful to us. while (true) { List accumulator = new ArrayList(); Instruction endInstruction = buildInstructions(accumulator); TemplateArrayList body = new TemplateArrayList(accumulator); if (endInstruction == null) { switchInstructions--; throw new ParseException("Expected end of switch structure" + parser.atChar()); } if ((lastType == Instruction.CASE) || (lastType == Instruction.DEFAULT)) { caseInstruction.setBody(body); instruction.addCase(caseInstruction); } int endType = endInstruction.getEndType(); // Make sure we don't have more case or default instructions after // we find the first default instruction. if (defaultFound && (endType != Instruction.SWITCH_END)) { switchInstructions--; throw new ParseException("Expected end switch instruction following default" + parser.atChar()); } switch (endType) { case Instruction.DEFAULT: defaultFound = true; // Intentionally drop-through here... case Instruction.CASE: caseInstruction = (DefaultCaseInstruction) endInstruction; break; case Instruction.SWITCH_END: switchInstructions--; return instruction; default: switchInstructions--; throw new ParseException("Unexpected instruction" + parser.atChar()); } lastType = endType; } } /** * When this {@link TemplateBuilder} implementation calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it is an {@link freemarker.template.instruction.UnparsedInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(UnparsedInstruction instruction) throws ParseException { String text = parser.skipToEndInstruction(instruction); if (text != null) { instruction.setText(text); return instruction; } throw new ParseException("Expected end instruction for " + instruction.toString() + parser.atChar()); } /** * When an implementation of <code>TemplateBuilder</code> calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it is a {@link freemarker.template.instruction.BreakInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(BreakInstruction instruction) throws ParseException { if ((switchInstructions == 0) && (listInstructions == 0)) { throw new ParseException("Unexpected break instruction" + parser.atChar()); } return instruction; } /** * When an implementation of <code>TemplateBuilder</code> calls * {@link freemarker.template.instruction.Instruction#callBuilder}, the * {@link freemarker.template.instruction.Instruction} will call this method * if it is an {@link freemarker.template.instruction.ExitInstruction}. * * @param instruction * the <code>Instruction</code> on which * <code>callBuilder()</code> was called. */ public TemplateProcessor buildStatement(ExitInstruction instruction) throws ParseException { if (functionInstructions == 0) { throw new ParseException("Unexpected exit instruction" + parser.atChar()); } return instruction; } }