Package cn.bran.japid.compiler

Source Code of cn.bran.japid.compiler.JapidAbstractCompiler

/**
* Copyright 2010 Bing Ran<bing_ran@hotmail.com>
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0.
*
* 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 cn.bran.japid.compiler;

import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import cn.bran.japid.classmeta.AbstractTemplateClassMetaData;
import cn.bran.japid.classmeta.InnerClassMeta;
import cn.bran.japid.classmeta.MimeTypeEnum;
import cn.bran.japid.compiler.JapidParser.Token;
import cn.bran.japid.compiler.Tag.TagIf;
import cn.bran.japid.template.ActionRunner;
import cn.bran.japid.template.JapidTemplate;
import cn.bran.japid.template.RenderResult;
import cn.bran.japid.util.DirUtil;
import cn.bran.japid.util.WebUtils;

/**
* based on the original code from the Play! Framework
*
* the parent class for all three type compilers: regular template compiler, the
* LayoutCompiler and the TagCompiler.
*
* @author original Play! authors
* @author Bing Ran<bing_ran@hotmail.com>
*
*/
public abstract class JapidAbstractCompiler {
  public static final String DO_LAYOUT = "doLayout";
  private static final String ELVIS = "?:";
  // pattern: } else if xxx {
  static final String ELSE_IF_PATTERN_STRING = "\\s*\\}\\s*else\\s*if\\s+([^\\(].*)\\s*";
  static final Pattern ELSE_IF_PATTERN = Pattern.compile(ELSE_IF_PATTERN_STRING);
  static final String OPEN_ELSE_IF_PATTERN_STRING = "\\s*else\\s*if\\s+([^\\(].*)\\s*";
  static final Pattern OPEN_ELSE_IF_PATTERN = Pattern.compile(OPEN_ELSE_IF_PATTERN_STRING);
  static final String OPEN_ELSE_STRING = "\\s*else\\s*";
  static final String SPACE_OR_NONE = "\\s*";
  static final String SPACE_AT_LEAST_ONE = "\\s+";

  // please trim the string before applying the pattern
  // the idea is to substitute the open for with "each" tag
  static final String OPEN_FOR_PATTERN_STRING = "for\\s+([^\\(].+)\\s*:\\s*(.+[^\\{])";
  static final Pattern OPEN_FOR_PATTERN = Pattern.compile(OPEN_FOR_PATTERN_STRING);

  // pattern: if xxx {
  static final String OPEN_IF_PATTERN1 = "if\\s+[^\\(].*";

  private static final String JAPID_RESULT = "cn.bran.play.JapidResult";

  private static final String ARGS = "args";

  protected static final String HTML = ".html";
  // private static final String DO_BODY = "doBody";
  protected static final String SPACE = " ";
  protected static final String NEW_LINE = "\n";
  protected JapidTemplate template;
  protected JapidParser parser;
  protected boolean doNextScan = true;

  // the tagsStack only tracks the regular tags, particularly not including
  // the open if
  private Stack<Tag> tagsStack = new Stack<Tag>();
  // the shadow is not used to keep the stacking of open if and regular tag
  private Stack<Tag> tagsStackShadow = new Stack<Tag>();

  protected int tagIndex;
  protected boolean skipLineBreak;
  protected boolean useWithPlay = true;

  public void compile(JapidTemplate t) {
    template = t;
    getTemplateClassMetaData().setOriginalTemplate(t.name);
    getTemplateClassMetaData().useWithPlay = this.useWithPlay;
    hop();
  }

  /**
   *
   */
  protected void parse() {
    // Parse
    loop: for (;;) {

      if (doNextScan) {
        stateBeforePreviousState = previousState;
        previousState = state;
        state = parser.nextToken();
      } else {
        doNextScan = true;
      }

      String token = parser.getToken();

      switch (state) {
      case EOF:
        break loop;
      case PLAIN:
        plain(token);
        break;
      case VERBATIM:
        plain(token);
        break;
      case SCRIPT:
        script(token);
        break;
      case CLOSING_BRACE:
        closingBrace(token);
        break;
      case SCRIPT_LINE:
        if (previousState == Token.PLAIN && stateBeforePreviousState == Token.SCRIPT_LINE) {
          String spacer = this.getTemplateClassMetaData().removeLastSingleEmptyLine();
          if (spacer != null) {
            Tag currentScope = this.tagsStack.peek();
            // currentScope.bodyBuffer.append(text);
            int lastIndex = currentScope.bodyTextList.size() - 1;
            currentScope.bodyTextList.remove(lastIndex);
            currentScope.bodyTextList.set(lastIndex - 1, spacer);
          }
        }
        scriptline(token);
        break;
      case EXPR:
        expr(token);
        break;
      case MESSAGE:
        message(token);
        break;
      case ACTION:
        action(token, false);
        break;
      case ABS_ACTION:
        action(token, true);
        break;
      case COMMENT:
        skipLineBreak = true;
        break;
      case START_TAG:
        startTag(buildTag(token));
        break;
      case END_TAG:
        String tagName = token.trim();
        if (tagsStack.isEmpty()) {
          throw new JapidCompilationException(template, currentLine, "#{/" + tagName + "} is not opened.");
        }
        Tag tag = popStack();
        endTag(tag);
        break;
      case TEMPLATE_ARGS:
        templateArgs(token);
        break;
      }
    }
  }

  /**
   * @return
   */
  private Tag popStack() {
    // tagsStackShadow.pop();
    return tagsStack.pop();
  }

  protected void closingBrace(String token) {
    // ok a } after some space in a line
    // treat it as `}
    print("}");
  }

  protected void plain(String token) {
    String text = token.replace("\\", "\\\\").replaceAll("\"", "\\\\\"");
    // text = text.replace("``", "`"); // escaped `, already done by parser
    if (skipLineBreak && text.startsWith(NEW_LINE)) {
      text = text.substring(1);
    }

    // add static content to classmetadata and print the ref to the
    // generated source code
    if (this.getTemplateClassMetaData().getTrimStaticContent()) {
      String r = text.trim();
      if (r.length() == 0)
        return;
      else {
        text = text.trim();
      }
    }
    String lines = composeValidMultiLines(text);
    String ref = this.getTemplateClassMetaData().addStaticText(lines, text);
    if (ref != null) {
      // print the static content via the varaible
      // print("p(" + ref + ");");
      // print the static content directly
      print("p(" + lines + ");");

      markLine(parser.getLineNumber());
      println();
    }
  }

  /**
   * break a line with newlines to multiple lines valid in java source code
   *
   * <pre>
   * "line1\n" +
   * "line2\n"  +
   * "line3";
   *
   * </pre>
   *
   * @param src
   * @return
   */
  public static String composeValidMultiLines(String text) {
    // multi-line
    String[] lines = text.split(NEW_LINE, 10000);
    String result = "";
    for (int i = 0; i < lines.length; i++) {
      String line = lines[i];
      if (line.length() > 0 && (int) line.charAt(line.length() - 1) == 13) {
        // remove the last newline
        line = line.substring(0, line.length() - 1);
      }

      result += "\"" + line;

      if (i == lines.length - 1 && !text.endsWith(NEW_LINE)) {
        // last line
        result += "\"";
      } else if (i == lines.length - 1 && line.equals("")) {
        result += "\"";
      } else {
        // regular line
        result += "\\n\" + \n";
      }

      // markLine(parser.getLine() + i);
    }
    String emptySuffix = " + \n\"\"";
    if (result.endsWith(emptySuffix)) {
      result = result.substring(0, result.length() - emptySuffix.length());
    }
    return result;
  }

  protected abstract void startTag(Tag tag);

  protected void println() {
    // print(NEW_LINE);
    Tag currentScope = this.tagsStack.peek();
    currentScope.bodyTextList.add("");
    currentLine++;
  }

  /**
   * always append to the last line
   *
   * @param text
   */
  protected void print(String text) {
    Tag currentScope = this.tagsStack.peek();
    // currentScope.bodyBuffer.append(text);
    int lastIndex = currentScope.bodyTextList.size() - 1;
    String lastLine = currentScope.bodyTextList.get(lastIndex);
    lastLine += text;
    currentScope.bodyTextList.set(lastIndex, lastLine);
    // else if (this.currentInnerClassName != null)
    // this.currentInnerClassRenderBody.append(text);
    // else
    // mainRenderBodySource.append(text);
  }

  protected void println(String text) {
    print(text);
    println();
    int i = 0;
    while (i++ < indentLevel) {
      print("\t");
    }
  }

  protected void markLine(int line) {
    if (!this.getTemplateClassMetaData().getTrimStaticContent()) {
      print("// line " + line);
    }

    template.linesMatrix.put(currentLine, line);
  }

  protected void scriptline(String token) {
    // String line = token.trim();
    // if ()
    script(token);
  }

  protected void script(String token) {
    String[] lines = new String[] { token };
    if (token.indexOf(NEW_LINE) > -1) {
      lines = parser.getToken().split(NEW_LINE);
    }

    for (int i = 0; i < lines.length; i++) {
      String line = lines[i];// .trim();
      if (startsWithIgnoreSpace(line, "import")) {
        getTemplateClassMetaData().addImportLine(line);
      } else if (startsWithIgnoreSpace(line, "//")) {
        // ignore
      } else if (startsWithIgnoreSpace(line, "extends")) {
        String layout = line.trim().substring("extends".length()).trim();
        // remove quotes if they present

        boolean hasParam = false;
        int p = 0;
        for (; p < layout.length(); p++) {
          char c = layout.charAt(p);
          if (c == ' ' || c == '\t' || c == '(') {
            hasParam = true;
            break;
          }
        }

        if (!hasParam) {
          layout = layout.replace("'", "");
          layout = layout.replace("\"", "");
          layout = removeEndingString(layout, ";");
          layout = removeEndingString(layout, HTML);
          layout = removeEndingString(layout, "/");
          if (layout.startsWith(".")) {
            // new feature allow extends .sub.layout.html
            if (layout.startsWith("./")) {
              layout = getTemplateClassMetaData().packageName + layout.substring(1);
            } else {
              layout = getTemplateClassMetaData().packageName + layout;
            }
          }
          getTemplateClassMetaData().superClass = layout.replace('/', '.');
        } else {
          String layoutName = layout.substring(0, p);
          layoutName = layoutName.replace("'", "");
          layoutName = layoutName.replace("\"", "");
          layoutName = layoutName.replace('/', '.');
          layoutName = removeEndingString(layoutName, HTML);

          // due to similarity, let's borrow a tag parsing
          Tag tag = new TagInvocationLineParser().parse(layoutName + layout.substring(p));

          if (tag.tagName.startsWith(".")) {
            // partial path, use current package as the root and
            // append the path to it
            tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
          }
          getTemplateClassMetaData().superClass = tag.tagName;
          getTemplateClassMetaData().superClassRenderArgs = tag.args;
        }
      } else if (startsWithIgnoreSpace(line, "contentType")) {
        // TODO: should also take standard tag name: Content-Type
        String contentType = line.trim().substring("contentType".length()).trim().replace("'", "").replace("\"", "");
        if (contentType.endsWith(";"))
          contentType = contentType.substring(0, contentType.length());
        getTemplateClassMetaData().setContentType(contentType);
      } else if (startsWithIgnoreSpace(line, "setHeader")) {
        String headerkv = line.trim().substring("setHeader".length()).trim();
        String[] split = headerkv.split("[ |\t]");
        if (split.length < 2) {
          throw new RuntimeException("setHeaader must take a key and a value string");
        }
        String name = split[0];
        String value = headerkv.substring(name.length()).trim();
        getTemplateClassMetaData().setHeader(name, value);
      } else if (startsWithIgnoreSpace(line, ARGS)) {
        String args = line.trim().substring(ARGS.length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        Tag currentTag = this.tagsStack.peek();
        currentTag.callbackArgs = args;
      } else if (startsWithIgnoreSpace(line, "trim")) {
        String sw = line.trim().substring("trim".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        if ("on".equals(sw) || "true".equals(sw)) {
          getTemplateClassMetaData().trimStaticContent();
        }
      } else if (startsWithIgnoreSpace(line, "stopwatch")) {
        String sw = line.trim().substring("stopwatch".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        if ("on".equals(sw))
          getTemplateClassMetaData().turnOnStopwatch();
        // Tag currentTag = this.tagsStack.peek();
        // currentTag.bodyArgsString = contentType;
      } else if (startsWithIgnoreSpace(line, "log") || line.trim().equals("log")) {
        String args = line.trim().substring("log".length()).trim().replace(";", "");
        if (args.trim().length() == 0)
          args = "\"\"";
        String logLine = "System.out.println(\"" + this.template.name.replace('\\', '/') + "(line " + (parser.getLineNumber() + i)
            + "): \" + " + args + ");";
        println(logLine);
      } else if (startsWithIgnoreSpace(line, "invoke")) {
        String args = line.trim().substring("invoke".length()).trim().replace(";", "");
        doActionInvokeDirective(args);
      } else if (startsWith(line, "a")) { // `a == `invoke, the a must be
                        // the first char to avoid
                        // collision
        String args = line.substring(2).trim().replace(";", "");
        doActionInvokeDirective(args);
      } else if (startsWithIgnoreSpace(line, "suppressNull") || line.trim().equals("suppressNull")) {
        String npe = line.trim().substring("suppressNull".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
        if ("on".equals(npe) || "yes".equals(npe) || "".equals(npe))
          getTemplateClassMetaData().suppressNull();
      } else if (line.trim().equals("abstract")) {
        getTemplateClassMetaData().setAbstract(true);
      } else if (startsWithIgnoreSpace(line, "tag")) {
        String tagline = line.trim().substring(4);
        doTagDirective(tagline);
      } else if (startsWith(line, "t")) { // `t == `tag, the t must be the
                        // first char to avoid collision
        String tagline = line.substring(2);
        doTagDirective(tagline);
      } else if (startsWithIgnoreSpace(line, "each") || startsWithIgnoreSpace(line, "Each")) {
        // support one line type of tag invocation.
        Tag tag = buildTagDirective(line);
        tag.tagName = "Each";
        tag.hasBody = true;
        startTag(tag);
      } else if (startsWithIgnoreSpace(line, "set")) {
        Tag set = buildTagDirective(line);
        if (line.contains(":"))
          set.hasBody = false;
        else
          set.hasBody = true;
        startTag(set);

        if (!set.hasBody) { // one liner.
          set = popStack();
        }
      } else if (startsWithIgnoreSpace(line, "get")) {
        Tag get = buildTagDirective(line);
        get.hasBody = false;
        startTag(get);
        get = popStack();
      } else if (startsWithIgnoreSpace(line, "def")) {
        // a function definition block
        Tag get = buildTagDirective(line);
        get.hasBody = true;
        startTag(get);
      } else if (line.trim().startsWith("noplay")) {
        // template is play independent
        getTemplateClassMetaData().useWithPlay = false;
      } else if (line.trim().equalsIgnoreCase("xml")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.xml.header);
      } else if (line.trim().equalsIgnoreCase("json")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.json.header);
      } else if (line.trim().equalsIgnoreCase("css")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.css.header);
      } else if (line.trim().equalsIgnoreCase("txt") || line.trim().equalsIgnoreCase("text")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.txt.header);
      } else if (line.trim().equalsIgnoreCase("js") || line.trim().equalsIgnoreCase("javascript")) {
        // template is play independent
        getTemplateClassMetaData().setContentType(MimeTypeEnum.js.header);
      } else if (line.trim().startsWith("verbatim")) {
        parser.verbatim = true;
        Tag get = buildTagDirective(line);
        get.hasBody = true;
        startTag(get);
      } else if (startsWithIgnoreSpace(line, "if")) {
        // `if expr {, the last { is optional
        String expr = line.trim();
        if (expr.matches(OPEN_IF_PATTERN1)) {
          // get the expression
          String trim = expr.substring(2).trim();
          boolean negative = trim.startsWith("!");
          if (negative)
            trim = trim.substring(1).trim();
          if (trim.endsWith("{")) {
            // semi-open
            expr = removeEndingString(trim, "{").trim();
            expr = "if(" + (negative ? "!" : "") "asBoolean(" + expr + ")) {";
          } else {
            // true open. out a shadow tag, since we want reuse the ` as the end delimitor
            Tag.TagIf iftag = new Tag.TagIf(trim, parser.getLineNumber());
            pushToStack(iftag);
            expr = "if(" + (negative ? "!" : "") + "asBoolean(" + trim + ")) {";
          }
          print(expr);
          markLine(parser.getLineNumber() + i);
          println();
        } else {
          // plain Java if
          print(expr);
          markLine(parser.getLineNumber() + i);
          println();
        }
      } else if (line.matches(ELSE_IF_PATTERN_STRING)) {
        // semi open
        String expr = line.trim();
        Matcher matcher = ELSE_IF_PATTERN.matcher(line);
        if (matcher.matches()) {
          expr = matcher.group(1).trim();
          boolean negative = expr.startsWith("!");
          if (negative)
            expr = expr.substring(1).trim();
          expr = "} else if(" + (negative ? "!" : "") "asBoolean(" + removeEndingString(expr, "{") + ")) {";
          print(expr);
          markLine(parser.getLineNumber() + i);
          println();
        } else {
          print(expr);
          markLine(parser.getLineNumber() + i);
          println();
        }
      } else if (line.matches(OPEN_ELSE_IF_PATTERN_STRING)) {
        // open
        String expr = line.trim();
        Matcher matcher = OPEN_ELSE_IF_PATTERN.matcher(line);
        if (matcher.matches()) {
          expr = matcher.group(1).trim();
          boolean negative = expr.startsWith("!");
          if (negative)
            expr = expr.substring(1).trim();

          // end previous if shadow and star a new one
          Tag tagShadow = tagsStackShadow.peek();
          if (tagShadow instanceof TagIf) {
            tagsStackShadow.pop();
            // to close an open if
            // start a new if
            Tag.TagIf iftag = new Tag.TagIf(expr, parser.getLineNumber());
            pushToStack(iftag);
            expr = "} else if(" + (negative ? "!" : "") "asBoolean(" + expr + ")) {";
          } else {
            throw new RuntimeException("the open \"else if\" statement is not properly matched to a previous if");
          }
        }
        print(expr);
        markLine(parser.getLineNumber() + i);
        println();
      } else if (line.matches(OPEN_ELSE_STRING)) {
        Tag tagShadow = tagsStackShadow.peek();
        if (tagShadow instanceof TagIf) {
          tagsStackShadow.pop();
          // to close an open if
          // start a new if
          print("} else {");
          markLine(parser.getLineNumber() + i);
          println();
          Tag.TagIf iftag = new Tag.TagIf("", parser.getLineNumber());
          pushToStack(iftag);
        } else {
          throw new RuntimeException("the open \"else\" statement is not properly matched to a previous if");
        }
      } else if (line.trim().matches(OPEN_FOR_PATTERN_STRING)) {
        // simply replace it with a "each" tag call
        String expr = line.trim();
        Matcher matcher = OPEN_FOR_PATTERN.matcher(expr);
        if (matcher.matches()) {
          String instanceDecl = matcher.group(1);
          String collection = matcher.group(2);
          expr = "each " + collection + " | " + instanceDecl;
          Tag tag = buildTagDirective(expr);
          tag.tagName = "Each";
          tag.hasBody = true;
          startTag(tag);
        } else {
          // should not happen
          print(expr);
          markLine(parser.getLineNumber() + i);
          println();
        }
      } else if (line.trim().length() == 0) {
        // a single ` empty line, treated as the closing for `tag
        try {
          Tag tagShadow = tagsStackShadow.peek();
          if (tagShadow.isRoot()) {
//            System.out.println("");
          } else {
            tagShadow = tagsStackShadow.pop();

            if (tagShadow instanceof TagIf) {
              // to close an open if
              print("}");
              markLine(parser.getLineNumber() + i);
              println();
            } else {
              if (!tagsStack.empty()) {
                Tag tag = tagsStack.peek();
                if (!tag.isRoot()) {
                  tag = popStack();
                  endTag(tag);
                }
              }
            }
          }
        } catch (Exception e) {
          System.out.println(e);
        }
      } else {
        // OK plain Java code
        print(line);
        markLine(parser.getLineNumber() + i);
        println();
      }
    }
    skipLineBreak = true;
  }

  /**
   * @param string
   * @return
   */
  private String removeEndingString(String string, String ending) {
    if (string.endsWith(ending))
      return string.substring(0, string.lastIndexOf(ending));
    else
      return string;
  }

  /**
   * @param line
   * @param string
   * @return
   */
  protected static boolean startsWithIgnoreSpace(String line, String string) {
    line = line.trim();
    return line.startsWith(string + " ") || line.startsWith(string + "\t");
  }

  /**
   * @param line
   * @param string
   * @return
   */
  private boolean startsWith(String line, String string) {
    return line.startsWith(string + " ") || line.startsWith(string + "\t");
  }

  /**
   * @param args
   */
  private void doActionInvokeDirective(String args) {
    if (!getTemplateClassMetaData().useWithPlay) {
      throw new RuntimeException("action invocation is only supported in Play environment. ");
    } else {
      this.getTemplateClassMetaData().setHasActionInvocation();
      if (args.trim().length() == 0)
        args = "whatyouwantoinvoke()";
      printActionInvocation(args);
    }
  }

  /**
   * @param tagline
   */
  private void doTagDirective(String tagline) {
    Tag tag = buildTagDirective(tagline);
    startTag(tag);
    if (!tag.hasBody) { // one liner.
      tag = tagsStackShadow.pop();
      tag = popStack();
      endTag(tag);
    }
  }

  protected void expr(String token) {
    String expr = token;
    // let's support the  big or operator "|||"
    int i = token.indexOf(ELVIS);
    String substitute = null;
    if (i > 0) {
      expr = token.substring(0, i);
      substitute = token.substring(i + ELVIS.length()).trim();
      if (substitute.startsWith("\""))
        substitute = substitute.substring(1);
      if (substitute.endsWith("\""))
        substitute = substitute.substring(0, substitute.length() - 1);
    }
   
    if (substitute != null) {
      // trap any null or empty string and use the substitute
      printLine("try {" +
          " Object o = " + expr + "; " +
          "if (o.toString().length() ==0) { " +
          "p(\"" + substitute + "\"); } " +
          "else { p(o); } } " +
          "catch (NullPointerException npe) { " +
          "p(\"" + substitute + "\"); }");
//      printLine("try { Object o = expr; p(" + expr + "); } " +
//          "catch (NullPointerException npe) { " +
//          "p(\"" + substitute + "\"); }");
    }
    else {
      if (getTemplateClassMetaData().suppressNull)
        printLine("try { p(" + expr + "); } catch (NullPointerException npe) {}");
      else
        printLine("p(" + expr + ");");
    }
  }

  private void printLine(String string) {
    print(string);
    markLine(parser.getLineNumber());
    println();
  }

  protected void message(String token) {
    String expr = token.trim().replace('\'', '"');
    print(";p(getMessage(" + expr + "));");
    markLine(parser.getLineNumber());
    println();
  }

  /**
   * TODO: remove all the dependency on the Play classes
   *
   * @param token
   *
   * @param absolute
   */
  protected void action(String token, boolean absolute) {
    String action = token.trim();
    if (action.matches("^'.*'$") || action.matches("^\".*\"$")) {
      // static content like @{'my.css'}
      action = action.replace('\'', '"');
      // remove Play dependecy
      if (absolute) {
        // print("p(request.getBase() + play.mvc.Router.reverseWithCheck("
        // + action + ", play.Play.getVirtualFile(" + action + ")));");
        print("p(lookupStaticAbs(" + action + "));");
      } else {
        // print("p(play.mvc.Router.reverseWithCheck(" + action +
        // ", play.Play.getVirtualFile(" + action + ")));");
        print("p(lookupStatic(" + action + "));");
      }
    } else {
      if (!action.endsWith(")")) {
        action = action + "()";
      }
      // extract params if any
      int indexOfParam = action.indexOf("(");
      if (indexOfParam < 1) {
        throw new TemplateSyntaxException("action arguments must be enclosed in parenthesis.", template.name, action,
            this.currentLine);
      }

      String actionPart = action.substring(0, indexOfParam).trim();

      // extract the param list part
      String params = action.substring(indexOfParam + 1);
      params = params.substring(0, params.length() - 1).trim();
      if (params.length() == 0)
        params = "new Object[]{}";

      if (absolute) {
        print("p(lookupAbs(\"" + actionPart + "\", " + params + "));");
      } else {
        print("p(lookup(\"" + actionPart + "\", " + params + "));");
      }
    }
    markLine(parser.getLineNumber());
    println();
  }

  protected void hop() {

    String source = template.source;
    Tag rootTag = new Tag() {
      {
        tagName = ROOT_TAGNAME;
        startLine = 0;
        hasBody = true;
      }
    };

    this.tagsStack.push(rootTag);
    this.tagsStackShadow.push(rootTag);

    this.parser = new JapidParser(source);

    String tempName = template.name.replace("-", "_");// .replace('.', '_');
    String contentTypeHeader = MimeTypeEnum.getHeader(tempName.substring(tempName.lastIndexOf('.')));
    getTemplateClassMetaData().setContentType(contentTypeHeader);
    tempName = DirUtil.mapSrcToJava(tempName);
    tempName = tempName.substring(0, tempName.lastIndexOf(".java"));
    tempName = tempName.replace('\\', '/');
    // if (tempName.endsWith(HTML)) {
    // tempName = tempName.substring(0, tempName.indexOf(HTML));
    // }

    // extract path
    int lastSep = tempName.lastIndexOf('/');
    if (lastSep > 0) {
      String path = tempName.substring(0, lastSep);
      path = path.replace('/', '.');
      path = path.replace('\\', '.');
      getTemplateClassMetaData().packageName = path;
      getTemplateClassMetaData().setClassName(tempName.substring(lastSep + 1));
    } else {
      getTemplateClassMetaData().setClassName(tempName);
    }

    parse();
    // for (String n : BranTemplateCompiler.extensionsClassnames) {
    // println(" } ");
    // }
    // println("} }");
    // println("}");

    Tag tag = popStack();
    if (!tagsStack.empty())
      throw new RuntimeException("There is(are) " + tagsStack.size() + " unclosed tag(s) in the template: " + this.template.name);
    // remove print nothing statement to save a few CPU cycles
    this.getTemplateClassMetaData().body = tag.getBodyText().replace("p(\"\")", "").replace("pln(\"\")", "pln()");
    postParsing(tag);
    template.javaSource = this.getTemplateClassMetaData().generateCode();

  }

  /**
   * add anything before the java source generation
   */
  protected void postParsing(Tag tag) {
    this.getTemplateClassMetaData().renderArgs = tag.callbackArgs;
  }

  abstract protected AbstractTemplateClassMetaData getTemplateClassMetaData();

  protected void templateArgs(String token) {
    Tag currentTag = this.tagsStack.peek();
    String args = token;
    currentTag.callbackArgs = args;
  }

  /**
   * @return
   */
  protected Tag buildTag(String token) {
    String tagText = token.trim().replaceAll(NEW_LINE, SPACE);

    boolean hasBody = !parser.checkNext().endsWith("/");

    Tag tag = new TagInvocationLineParser().parse(tagText);
    if (tag.tagName == null || tag.tagName.length() == 0)
      throw new RuntimeException("tag name was empty: " + tagText);

    if (tag.tagName.startsWith(".")) {
      // partial path, use current package as the root and append the path
      // to it
      tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
    }
    tag.startLine = parser.getLineNumber();
    tag.hasBody = hasBody;
    tag.tagIndex = tagIndex++;
    return tag;
  }

  /**
   * e.g.:
   *
   * <pre>
   * `tag myTag a, c |String c
   * </pre>
   *
   * @return
   */
  protected Tag buildTagDirective(String token) {
    String tagText = token.trim();

    Tag tag = new TagInvocationLineParser().parse(tagText);
    if (tag.tagName == null || tag.tagName.length() == 0)
      throw new RuntimeException("tag name was empty: " + tagText);

    if (tag.tagName.startsWith(".")) {
      // partial path, use current package as the root and append the path
      // to it
      tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
    }
    tag.startLine = parser.getLineNumber();
    tag.tagIndex = tagIndex++;
    return tag;
  }

  /**
   * @param actionInvocationWithCache
   */
  protected static String createActionRunner(String actionInvocationWithCache) {
    List<String> params = new TagArgsParser(actionInvocationWithCache).split();
    String action = params.get(0);
    // remove the argument part to extract action string as key base
    int left = action.indexOf('(');
    if (left < 1) {
      throw new RuntimeException("invoke: action arguments must be enclosed in parenthesis.");
    }
    int right = action.lastIndexOf(')');
    String actionPath = "\"" + action.substring(0, left) + "\"";
    String args = action.substring(left + 1, right).trim();
    String ttl = "\"\"";

    if (params.size() >= 2) {
      ttl = params.get(1);
      if (params.size() > 2) {
        for (int i = 2; i < params.size(); i++) {
          args += "," + params.get(i);
        }

        if (args.startsWith(","))
          args = args.substring(1);

        if (args.endsWith(","))
          args = args.substring(0, args.length() - 1);
      }
    }
    return (createActionRunner(action, ttl, actionPath, args));
  }

  protected void printActionInvocation(String action) {
    println(createActionRunner(action));
  }

  /**
   * @param tag
   */
  protected void regularTagInvoke(Tag tag) {
    if ("extends".equals(tag.tagName)) {
      String layoutName = tag.args;
      layoutName = layoutName.replace("'", "");
      layoutName = layoutName.replace("\"", "");
      layoutName = removeEndingString(layoutName, HTML);
      layoutName = removeEndingString(layoutName, "/");

      getTemplateClassMetaData().superClass = layoutName.replace('/', '.');
    } else if (tag.tagName.equals("invoke")) {
      invokeAction(tag);
    } else {
      String tagVar = "_" + tag.getTagVarName() + tag.tagIndex;
      if (!tag.hasBody) {
        String tagline = tagVar + ".setOut(getOut()); ";
        tagline += tagVar + ".render(" + tag.args + ");";
        println(tagline);
      }
    }
  }

  /**
   * @param tag
   */
  protected void invokeAction(Tag tag) {
    if (tag.hasBody) {
      throw new JapidCompilationException(template, currentLine, "invoke tag cannot have a body. Must be ended with /}");
    }

    this.getTemplateClassMetaData().setHasActionInvocation();
    String action = tag.args;
    printActionInvocation(action);
  }

  /**
   * @param tag
   */
  protected void endRegularTag(Tag tag) {
    if (tag.hasBody) {
      InnerClassMeta inner = this.getTemplateClassMetaData().addCallTagBodyInnerClass(tag.tagName, tag.tagIndex, tag.callbackArgs,
          tag.getBodyText());
      if (inner == null)
        System.out.println(tag.tagName + " not allowed to have instance of this tag");
      String tagVar = "_" + tag.getTagVarName() + tag.tagIndex;
      String tagLine = tagVar + ".setOut(getOut()); "; // make sure to use the current string builder
      tagLine += tagVar + ".render(" + (WebUtils.asBoolean(tag.args) ? tag.args + ", " : "") + inner.getAnonymous() + ");";
      println(tagLine);
    } else {
      // for simple tag call without call back:
      this.getTemplateClassMetaData().addCallTagBodyInnerClass(tag.tagName, tag.tagIndex, null, null);
    }
  }

  /**
   * define a string returning method from a block
   *
   * @param tag
   */
  protected void def(Tag tag) {
  }

  protected void endDef(Tag tag) {
    if (tag.hasBody) { // must always
      this.getTemplateClassMetaData().addDefTag(tag);
    }
  }

  protected void endTag(Tag tag) {
    String lastInStack = tag.tagName;
    String tagName = lastInStack;
    // if (!lastInStack.equals(tagName)) {
    // throw new JapidCompilationException(template, tag.startLine, "#{" +
    // tag.tagName + "} is not closed.");
    // }
    if (tagName.equals("def")) {
      endDef(tag);
    } else if (tagName.equals("doBody")) {
    } else if (tagName.equals("extends")) {
    } else if (tagName.equals("get")) {
      // } else if (tagName.equals("set")) { // the set is handled in the
      // JapidTemplateCompiler endTagSpecial()
    } else if (tagName.equals("invoke")) {
    } else if (tagName.equals(DO_LAYOUT)) {
    } else if (endTagSpecial(tag)) {
    } else {
      endRegularTag(tag);
    }
    markLine(tag.startLine);
    println();
    // tagIndex--;
    skipLineBreak = true;
  }

  /**
   * sub class can detect special tag and return true to indicate the tag has
   * been processed.
   *
   * @param tag
   * @return
   */
  protected boolean endTagSpecial(Tag tag) {
    return false;
  }

  static String createActionRunner(String action, String ttl, String base, String keys) {
    String actionEscaped = action.replace("\"", "\\\"");
    String controllerActionPart = action.substring(0, action.indexOf('('));
    int lastDot = controllerActionPart.lastIndexOf('.');
    String controllerName = controllerActionPart.substring(0, lastDot);
    String actionName = controllerActionPart.substring(lastDot + 1);

    if (ttl == null) {
      String template =
          "    %s.put(getOut().length(), new %s() {\n" +
              "      @Override\n" +
              "      public %s run() {\n" +
              "        try {\n" +
              "          play.classloading.enhancers.ControllersEnhancer.ControllerInstrumentation.initActionCall();\n" +
              "          %s;\n" +
              "        } catch (%s jr) {\n" +
              "          return jr.getRenderResult();\n" +
              "        }\n" +
              "        throw new RuntimeException(\"No render result from running: %s\");\n" +
              "      }\n" +
              "    });";
      return String.format(template,
          AbstractTemplateClassMetaData.ACTION_RUNNERS,
          ActionRunner.class.getName(),
          RenderResult.class.getName(),
          action,
          JAPID_RESULT,
          actionEscaped);
    } else {
      // hardcode the cache action runner name to avoid dependency on the
      // Play jar

      String template =
          "    %s.put(getOut().length(), new %s(%s, %s, %s, %s) {\r\n" +
              "      @Override\r\n" +
              "      public void runPlayAction() throws %s {\r\n" +
              // "        super.checkActionCacheFor(%s.class, \"%s\");\n"
              // +
              "        %s; //\r\n" +
              "      }\r\n" +
              "    });\r\n";
      return String.format(template,
          AbstractTemplateClassMetaData.ACTION_RUNNERS,
          "cn.bran.play.CacheablePlayActionRunner",
          ttl,
          controllerName + ".class",
          "\"" + actionName + "\"",
          "".equals(keys) ? "\"\"" : keys,
          JAPID_RESULT,
          action
          );
      // return String.format(template,
      // AbstractTemplateClassMetaData.ACTION_RUNNERS,
      // "cn.bran.play.CacheablePlayActionRunner",
      // ttl,
      // base,
      // "".equals(keys) ? "\"\"" : keys,
      // JAPID_RESULT,
      // controllerName,
      // actionName,
      // action
      // );
    }

  }

  protected int currentLine = 1;

  protected int indentLevel = 0;
  JapidParser.Token state;
  JapidParser.Token previousState;
  JapidParser.Token stateBeforePreviousState;

  public JapidAbstractCompiler() {
    super();
  }

  public void setUseWithPlay(boolean play) {
    this.useWithPlay = play;
  }

  /**
   * @param tag
   */
  protected void pushToStack(Tag tag) {
    tagsStackShadow.push(tag);
    if (!(tag instanceof TagIf))
      tagsStack.push(tag);

  }
}
TOP

Related Classes of cn.bran.japid.compiler.JapidAbstractCompiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.