Package org.rythmengine.internal

Source Code of org.rythmengine.internal.CodeBuilder

/*
* Copyright (C) 2013 The Rythm Engine project
* Gelin Luo <greenlaw110(at)gmail.com>
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.rythmengine.internal;

import com.stevesoft.pat.Regex;
import org.rythmengine.Rythm;
import org.rythmengine.RythmEngine;
import org.rythmengine.Sandbox;
import org.rythmengine.conf.RythmConfiguration;
import org.rythmengine.conf.RythmConfigurationKey;
import org.rythmengine.exception.ParseException;
import org.rythmengine.extension.ICodeType;
import org.rythmengine.extension.ISourceCodeEnhancer;
import org.rythmengine.internal.compiler.ParamTypeInferencer;
import org.rythmengine.internal.compiler.TemplateClass;
import org.rythmengine.internal.dialect.BasicRythm;
import org.rythmengine.internal.dialect.SimpleRythm;
import org.rythmengine.internal.parser.BlockCodeToken;
import org.rythmengine.internal.parser.CodeToken;
import org.rythmengine.internal.parser.NotRythmTemplateException;
import org.rythmengine.internal.parser.build_in.BlockToken;
import org.rythmengine.internal.parser.build_in.InvokeTemplateParser;
import org.rythmengine.logger.ILogger;
import org.rythmengine.logger.Logger;
import org.rythmengine.resource.ITemplateResource;
import org.rythmengine.resource.StringTemplateResource;
import org.rythmengine.template.JavaTagBase;
import org.rythmengine.template.TagBase;
import org.rythmengine.template.TemplateBase;
import org.rythmengine.utils.S;
import org.rythmengine.utils.TextBuilder;

import java.util.*;


public class CodeBuilder extends TextBuilder {

    protected final static ILogger logger = Logger.get(CodeBuilder.class);

    private int renderArgCounter = 0;

    public static class RenderArgDeclaration implements Comparable<RenderArgDeclaration> {
        public int no;
        public String name;
        public String type;
        public String defVal;
        public int lineNo;
       
        private static final Map<String, String> byPrimitive; static {
            HashMap<String, String> m = new HashMap<String, String>();
            m.put("int", "Integer");
            m.put("long", "Long");
            m.put("short", "Short");
            m.put("byte", "Byte");
            m.put("float", "Float");
            m.put("double", "Double");
            m.put("char", "Character");
            m.put("boolean", "Boolean");
            byPrimitive = m;
        }
       
        private static char DEF_CHAR;

        public String objectType() {
            String s = byPrimitive.get(type);
            return null != s ? s : toNonGeneric(type);
        }
       
        private static final Map<String, String> nullVals; static {
            HashMap<String, String> m = new HashMap<String, String>();
            m.put("int", "0");
            m.put("long", "0L");
            m.put("short", "0");
            m.put("byte", "0");
            m.put("float", "0f");
            m.put("double", "0d");
            m.put("char", String.valueOf(DEF_CHAR));
            m.put("boolean", "false");
            nullVals = m;
        }
       
        public String nullVal() {
            String s = nullVals.get(type);
            return (null == s) ? "null" : s;
        }
       
        public RenderArgDeclaration(int lineNo, String type, String name) {
            this(-1, lineNo, type, name);
        }

        public RenderArgDeclaration(int lineNo, String type, String name, String defVal) {
            this(-1, lineNo, type, name, defVal);
        }

        public RenderArgDeclaration(int no, int lineNo, String type, String name) {
            this(no, lineNo, type, name, null);
        }

        public RenderArgDeclaration(int no, int lineNo, String type, String name, String defVal) {
            this.no = no;
            this.lineNo = lineNo;
            this.name = name;
            this.type = ParamTypeInferencer.typeTransform(type);
            defVal = defValTransform(type, defVal);
            this.defVal = null == defVal ? defVal(type) : defVal;
        }

        private static String defValTransform(String type, String defVal) {
            if (S.isEmpty(defVal)) return null;
            if ("String".equalsIgnoreCase(type) && "null".equalsIgnoreCase(defVal)) return "\"\"";
            if ("boolean".equalsIgnoreCase(type)) defVal = defVal.toLowerCase();
            if ("long".equalsIgnoreCase(type) && defVal.matches("[0-9]+")) return defVal + "L";
            if ("float".equalsIgnoreCase(type) && defVal.matches("[0-9]+")) return defVal + "f";
            if ("double".equalsIgnoreCase(type) && defVal.matches("[0-9]+")) return defVal + "d";
            if ("short".equalsIgnoreCase(type) && defVal.matches("[0-9]+]")) return "((short)" + defVal + ")";
            if ("byte".equalsIgnoreCase(type) && defVal.matches("[0-9]+]")) return "((byte)" + defVal + ")";
            return defVal;
        }

        private static String defVal(String type) {
            if (type.equalsIgnoreCase("String")) {
                return "\"\"";
            } else if (type.equalsIgnoreCase("boolean"))
                return "false";
            else if (type.equalsIgnoreCase("int"))
                return "0";
            else if (type.equalsIgnoreCase("long"))
                return "0L";
            else if (type.equals("char") || type.equals("Character"))
                return "(char)0";
            else if (type.equalsIgnoreCase("byte"))
                return "(byte)0";
            else if (type.equalsIgnoreCase("short"))
                return "(short)0";
            else if (type.equalsIgnoreCase("float"))
                return "0f";
            else if (type.equalsIgnoreCase("double"))
                return "0d";
            return "null";
        }

        @Override
        public int compareTo(RenderArgDeclaration o) {
            return no - o.no;
        }

    }

    private RythmEngine engine;

    public RythmEngine engine() {
        return engine;
    }

    private RythmConfiguration conf;

    private boolean isNotRythmTemplate = false;
    public ICodeType templateDefLang;

    public boolean isRythmTemplate() {
        return !isNotRythmTemplate;
    }

    protected String tmpl;
    private String cName;
    public String includingCName;
    private String pName;
    private String tagName;

    private String initCode = null;

    public void setInitCode(String code) {
        if (null != initCode)
            throw new ParseException(engine, templateClass, parser.currentLine(), "@init section already declared.");
        initCode = code;
    }

    private String extended; // the cName of the extended template

    protected String extended() {
        String defClass = TagBase.class.getName();
        return null == extended ? defClass : extended;
    }

    private String extendedResourceMark() {
        TemplateClass tc = extendedTemplateClass;
        return (null == tc) ? "" : String.format("//<extended_resource_key>%s</extended_resource_key>", tc.templateResource.getKey());
    }

    private TemplateClass extendedTemplateClass;

    public TemplateClass getExtendedTemplateClass() {
        return extendedTemplateClass;
    }

    private InvokeTemplateParser.ParameterDeclarationList extendArgs = null;
    public Set<String> imports = new HashSet<String>();
    private int extendDeclareLineNo = -1;
    // <argName, argClass>
    public Map<String, RenderArgDeclaration> renderArgs = new LinkedHashMap<String, RenderArgDeclaration>();
    private List<Token> builders = new ArrayList<Token>();
    private List<Token> builders() {
        if (macroStack.empty()) return builders;
        String macro = macroStack.peek();
        List<Token> bl = macros.get(macro);
        if (null == bl) {
            bl = new ArrayList<Token>();
            macros.put(macro, bl);
        }
        return bl;
    }
    public boolean isLastBuilderLiteral() {
        List<Token> bl = builders();
        for (int i = bl.size() - 1; i >= 0; --i) {
            Token tb = bl.get(i);
            if (tb instanceof Token.StringToken) {
                if (((Token.StringToken)tb).empty()) continue;
                return true;
            } else {
                return false;
            }
        }
        return false;
    }
    private TemplateParser parser;
    private TemplateClass templateClass;

    public TemplateClass getTemplateClass() {
        return templateClass;
    }

    private boolean simpleTemplate() {
        return parser.getDialect() instanceof SimpleRythm;
    }

    // public because event handler needs this method
    public boolean basicTemplate() {
        return parser.getDialect() instanceof BasicRythm;
    }

    transient public IDialect requiredDialect = null;

    public CodeBuilder(String template, String className, String tagName, TemplateClass templateClass, RythmEngine engine, IDialect requiredDialect) {
        tmpl = template;
        className = className.replace('/', '.');
        this.tagName = (null == tagName) ? className : tagName;
        cName = className;
        int i = className.lastIndexOf('.');
        if (-1 < i) {
            cName = className.substring(i + 1);
            pName = className.substring(0, i);
        }
        this.engine = null == engine ? Rythm.engine() : engine;
        this.conf = this.engine.conf();
        this.requiredDialect = requiredDialect;
        this.templateClass = templateClass;
        ICodeType type = templateClass.codeType;
        this.templateDefLang = type;
        String tmpl = RythmEvents.ON_PARSE.trigger(this.engine, this);
        this.tmpl = tmpl;
        this.parser = new TemplateParser(this);
    }

    /**
     * Reset to the state before construction
     */
    public void clear() {
        buffer().ensureCapacity(0);
        this.engine = null;
        this.tmpl = null;
        this.cName = null;
        this.pName = null;
        this.tagName = null;
        this.initCode = null;
        this.extended = null;
        this.extendedTemplateClass = null;
        if (null != this.extendArgs) this.extendArgs.pl.clear();
        this.imports.clear();
        this.extendDeclareLineNo = 0;
        this.renderArgs.clear();
        this.builders.clear();
        this.parser = null;
        this.templateClass = null;
        this.inlineTags.clear();
        this.inlineTagBodies.clear();
        this.importLineMap.clear();
        this.logTime = false;
        this.macros.clear();
        this.macroStack.clear();
        this.buildBody = null;
        this.templateDefLang = null;
    }

    /**
     * Rewind to the state when construction finished
     */
    public void rewind() {
        renderArgCounter = 0;
        this.initCode = null;
        this.extended = null;
        this.extendedTemplateClass = null;
        if (null != this.extendArgs) this.extendArgs.pl.clear();
        this.imports.clear();
        this.extendDeclareLineNo = 0;
        this.renderArgs.clear();
        this.builders.clear();
        this.inlineTags.clear();
        this.inlineTagBodies.clear();
        this.importLineMap.clear();
        this.logTime = false;
        this.macros.clear();
        this.macroStack.clear();
        this.buildBody = null;
    }

    public void merge(CodeBuilder codeBuilder) {
        if (null == codeBuilder) return;
        this.imports.addAll(codeBuilder.imports);
        for (InlineTag tag : codeBuilder.inlineTags) {
            inlineTags.add(tag.clone(this));
        }
        this.initCode = new StringBuilder(S.toString(this.initCode)).append(S.toString(codeBuilder.initCode)).toString();
        this.renderArgs.putAll(codeBuilder.renderArgs);
        this.importLineMap.putAll(codeBuilder.importLineMap);
        renderArgCounter += codeBuilder.renderArgCounter;
    }

    public CodeBuilder() {
        super();    //To change body of overridden methods use File | Settings | File Templates.
    }

    public String className() {
        return cName;
    }

    public String includingClassName() {
        return null == includingCName ? cName : includingCName;
    }

    private Map<String, Integer> importLineMap = new HashMap<String, Integer>();

    public void addImport(String imprt, int lineNo) {
        imports.add(imprt);
        if (imprt.endsWith(".*")) {
            imprt = imprt.substring(0, imprt.lastIndexOf(".*"));
            templateClass.importPaths.add(imprt);
        }
        importLineMap.put(imprt, lineNo);
    }

    public static class InlineTag {
        String tagName;
        String signature;
        String retType = "void";
        String body;
        boolean autoRet = false;
        List<Token> builders = new ArrayList<Token>();

        InlineTag(String name, String ret, String sig, String body) {
            tagName = name;
            signature = sig;
            retType = null == ret ? "void" : ret;
            this.body = body;
        }

        InlineTag clone(CodeBuilder newCaller) {
            InlineTag tag = new InlineTag(tagName, retType, signature, body);
            tag.builders.clear();
            for (Token tb : builders) {
                Token newTb = tb.clone(newCaller);
                tag.builders.add(newTb);
            }
            tag.autoRet = autoRet;
            return tag;
        }

        @Override
        public int hashCode() {
            return (37 + tagName.hashCode()) * 31 + signature.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == this) return true;
            if (obj instanceof InlineTag) {
                InlineTag that = (InlineTag) obj;
                return S.isEqual(that.signature, this.signature) && S.isEqual(that.tagName, this.tagName);
            }
            return false;
        }
    }

    private Set<InlineTag> inlineTags = new HashSet<InlineTag>();

    public boolean needsPrint(String tagName) {
        return templateClass.returnObject(tagName);
    }

    private Stack<List<Token>> inlineTagBodies = new Stack<List<Token>>();

    public InlineTag defTag(String tagName, String retType, String signature, String body) {
        tagName = tagName.trim();
        InlineTag tag = new InlineTag(tagName, retType, signature, body);
        if (inlineTags.contains(tag)) {
            throw new ParseException(engine, templateClass, parser.currentLine(), "inline tag already defined: %s", tagName);
        }
        inlineTags.add(tag);
        inlineTagBodies.push(builders);
        builders = tag.builders;
        if ("void".equals(tag.retType)) {
            tag.retType = "org.rythmengine.utils.RawData";
            tag.autoRet = true;
            String code = "StringBuilder __sb = this.getSelfOut();this.setSelfOut(new StringBuilder());";
            builders.add(new CodeToken(code, parser));
        }
        templateClass.setTagType(tagName, tag.retType);
        return tag;
    }

    public void endTag(InlineTag tag) {
        if (inlineTagBodies.empty())
            throw new ParseException(engine, templateClass, parser.currentLine(), "Unexpected tag definition close");
        if (tag.autoRet) {
            builders.add(new CodeToken("String __s = toString();this.setSelfOut(__sb);return s().raw(__s);", parser));
        }
        builders = inlineTagBodies.pop();
    }

    public String addIncludes(String includes, int lineNo) {
        StringBuilder sb = new StringBuilder();
        for (String s : includes.split("[\\s,;:]+")) {
            sb.append(addInclude(s, lineNo));
        }
        return sb.toString();
    }

    public String addInclude(String include, int lineNo) {
        String tmplName = engine.testTemplate(include, templateClass);
        if (null == tmplName) {
            throw new ParseException(engine, templateClass, lineNo, "include template not found: %s", include);
        }
        TemplateBase includeTmpl = (TemplateBase) engine.getRegisteredTemplate(tmplName);
        if (includeTmpl instanceof JavaTagBase) {
            throw new ParseException(engine, templateClass, lineNo, "cannot include Java tag: %s", include);
        }
        TemplateClass includeTc = includeTmpl.__getTemplateClass(false);
        includeTc.buildSourceCode(includingClassName());
        merge(includeTc.codeBuilder);
        templateClass.addIncludeTemplateClass(includeTc);
        return includeTc.codeBuilder.buildBody;
    }
   
    public String addInlineInclude(String inlineTemplate, int lineNo) {
        TemplateClass includeTc = new TemplateClass(new StringTemplateResource(inlineTemplate), engine, false);
        includeTc.buildSourceCode(includingClassName());
        merge(includeTc.codeBuilder);
        return includeTc.codeBuilder.buildBody;
    }

    public void setExtended(Class<? extends TemplateBase> c) {
        this.extended = c.getName();
    }

    public void setExtended(String extended, InvokeTemplateParser.ParameterDeclarationList args, int lineNo) {
        if (simpleTemplate()) {
            throw new ParseException(engine, templateClass, lineNo, "Simple template does not allow to extend layout template");
        }
        if (null != this.extended) {
            throw new ParseException(engine, templateClass, lineNo, "Extended template already declared");
        }
        String fullName = engine.testTemplate(extended, templateClass);
        if (null == fullName) {
            // try legacy style
            setExtended_deprecated(extended, args, lineNo);
            logger.warn("Template[%s]: Extended template declaration \"%s\" is deprecated, please switch to the new style \"%s\"", templateClass.getKey(), extended, extendedTemplateClass.getTagName());
        } else {
            TemplateBase tb = (TemplateBase) engine.getRegisteredTemplate(fullName);
            TemplateClass tc = tb.__getTemplateClass(false);
            this.extended = tc.name();
            this.extendedTemplateClass = tc;
            this.templateClass.extendedTemplateClass = tc;
            this.engine.addExtendRelationship(tc, this.templateClass);
            this.extendArgs = args;
            this.extendDeclareLineNo = lineNo;
        }
    }

    public void setExtended_deprecated(String extended, InvokeTemplateParser.ParameterDeclarationList args, int lineNo) {
        if (null != this.extended) {
            throw new IllegalStateException("Extended template already declared");
        }
        TemplateClass tc = null;
        String origin = extended;
        if (!extended.startsWith("/")) {
            // relative path ?
            String me = templateClass.getKey().toString();
            int pos = me.lastIndexOf("/");
            if (-1 != pos) extended = me.substring(0, pos) + "/" + extended;
            tc = engine.classes().getByTemplate(extended);
            if (null == tc) {
                ITemplateResource resource = engine.resourceManager().getResource(extended);
                if (resource.isValid()) {
                    tc = new TemplateClass(resource, engine);
                }
            }
        }
        if (null == tc && !extended.startsWith("/")) {
            // it's in class name style ?
            //if (!extended.endsWith(TemplateClass.CN_SUFFIX)) extended = extended + TemplateClass.CN_SUFFIX;
            tc = engine.classes().getByClassName(extended);
        }
        if (null == tc) {
            tc = engine.classes().getByTemplate(origin);
            if (null == tc) {
                ITemplateResource resource = engine.resourceManager().getResource(origin);
                if (resource.isValid()) tc = new TemplateClass(resource, engine);
            }
        }
        if (null == tc) {
            throw new ParseException(engine, templateClass, lineNo, "Cannot find extended template by name \"%s\"", origin);
        }
        this.extended = tc.name();
        this.extendedTemplateClass = tc;
        this.templateClass.extendedTemplateClass = tc;
        this.engine.addExtendRelationship(tc, this.templateClass);
        this.extendArgs = args;
        this.extendDeclareLineNo = lineNo;
    }

    protected boolean logTime = false;

    public void setLogTime() {
        logTime = true;
    }

    public String getRenderArgType(String name) {
        addInferencedRenderArgs();
        RenderArgDeclaration rad = renderArgs.get(name);
        if (null != rad) return rad.type;
        else return null;
    }

    public void addRenderArgs(RenderArgDeclaration declaration) {
        renderArgs.put(declaration.name, declaration);
    }

    public void addRenderArgs(int lineNo, String type, String name, String defVal) {
        renderArgs.put(name, new RenderArgDeclaration(renderArgCounter++, lineNo, type, name, defVal));
    }

    public void addRenderArgs(int lineNo, String type, String name) {
        renderArgs.put(name, new RenderArgDeclaration(renderArgCounter++, lineNo, type, name));
    }

    public void addRenderArgsIfNotDeclared(int lineNo, String type, String name) {
        if (!renderArgs.containsKey(name)) {
            renderArgs.put(name, new RenderArgDeclaration(renderArgCounter++, lineNo, type, name));
        }
    }

    private Map<String, List<Token>> macros = new HashMap<String, List<Token>>();
    private Stack<String> macroStack = new Stack<String>();

    public void pushMacro(String macro) {
        if (macros.containsKey(macro)) {
            throw new ParseException(engine, templateClass, parser.currentLine(), "Macro already defined: %s", macro);
        }
        macroStack.push(macro);
        macros.put(macro, new ArrayList<Token>());
    }

    public void popMacro() {
        if (macroStack.empty()) {
            throw new ParseException(engine, templateClass, parser.currentLine(), "no macro found in stack");
        }
        macroStack.pop();
    }

    public boolean hasMacro(String macro) {
        return macros.containsKey(macro);
    }

    public List<Token> getMacro(String macro) {
        List<Token> list = this.macros.get(macro);
        if (null == list) throw new NullPointerException();
        return list;
    }
   
    public boolean lastIsBlockToken() {
        List<Token> bs = builders();
        for (int i = bs.size() - 1; i >= 0; --i) {
            Token tb = bs.get(i);
            if (tb instanceof BlockCodeToken) return true;
            else if (tb instanceof Token.StringToken) {
                String s = tb.toString();
                if (S.empty(s)) continue;
                else return false;
            } else {
                return false;
            }
        }
        return false;
    }
   
    public boolean removeNextLF = false;
    public void addBuilder(Token builder) {
        if (builder == Token.EMPTY_TOKEN) {
            return;
        }
        Token token = builder;
        if (removeNextLF && token != Token.EMPTY_TOKEN2) {
            if (token.removeLeadingLineBreak()) {
                removeNextLF = false;
            }
        }
        if (token.removeNextLineBreak) {
            removeNextLF = true;
        }
        builders().add(builder);
    }
   
    /**
     * If from the current cursor to last linebreak are all space, then
     * remove all those spaces and the last line break
     */
    public void removeSpaceToLastLineBreak(IContext ctx) {
        boolean shouldRemoveSpace = true;
        List<Token> bl = builders();
        for (int i = bl.size() - 1; i >= 0; --i) {
            TextBuilder tb = bl.get(i);
            if (tb == Token.EMPTY_TOKEN || tb instanceof IDirective) {
                continue;
            }
            if (tb.getClass().equals(Token.StringToken.class)) {
                String s = tb.toString();
                if (s.matches("[ \\t\\x0B\\f]+")) {
                    continue;
                } else if (s.matches("(\\n\\r|\\r\\n|[\\r\\n])")) {
                } else {
                    shouldRemoveSpace = false;
                }
            }
            break;
        }
        if (shouldRemoveSpace) {
            for (int i = bl.size() - 1; i >= 0; --i) {
                TextBuilder tb = bl.get(i);
                if (tb == Token.EMPTY_TOKEN || tb instanceof IDirective) {
                    continue;
                }
                if (tb.getClass().equals(Token.StringToken.class)) {
                    String s = tb.toString();
                    if (s.matches("[ \\t\\x0B\\f]+")) {
                        bl.remove(i);
                        continue;
                    } else if (s.matches("(\\n\\r|\\r\\n|[\\r\\n])")) {
                        bl.remove(i);
                    }
                }
                break;
            }
        }
    }

    /**
     * If from the current cursor till last linebreak are all space, then
     * remove all those spaces and the last line break
     */
    public void removeSpaceTillLastLineBreak(IContext ctx) {
        boolean shouldRemoveSpace = true;
        List<Token> bl = builders();
        for (int i = bl.size() - 1; i >= 0; --i) {
            Token tb = bl.get(i);
            if (tb == Token.EMPTY_TOKEN || tb instanceof IDirective) {
                continue;
            }
            if (tb.getClass().equals(Token.StringToken.class)) {
                String s = tb.toString();
                if (s.matches("[ \\t\\x0B\\f]+")) {
                    continue;
                } else if (s.matches("(\\n\\r|\\r\\n|[\\r\\n])")) {
                } else {
                    shouldRemoveSpace = false;
                }
            }
            break;
        }
        if (shouldRemoveSpace) {
            for (int i = bl.size() - 1; i >= 0; --i) {
                TextBuilder tb = bl.get(i);
                if (tb == Token.EMPTY_TOKEN || tb instanceof IDirective) {
                    continue;
                }
                if (tb.getClass().equals(Token.StringToken.class)) {
                    String s = tb.toString();
                    if (s.matches("[ \\t\\x0B\\f]+")) {
                        bl.remove(i);
                        continue;
                    }
                }
                break;
            }
        }
    }

    String template() {
        return tmpl;
    }

    @Override
    public TextBuilder build() {
        long start = 0L;
        if (logger.isTraceEnabled()) {
            logger.trace("Begin to build %s", templateClass.getKey());
            start = System.currentTimeMillis();
        }
        try {
            RythmEngine engine = engine();
            parser.parse();
            String key = templateClass.getKey();
            if (!key.endsWith("/__global.rythm")) {
                boolean enableGlobalInclude = true;
                if (null != requiredDialect) {
                    if (requiredDialect instanceof BasicRythm || requiredDialect instanceof ToStringTemplateBase) {
                        enableGlobalInclude = false;
                    }
                }
                if (enableGlobalInclude && conf.hasGlobalInclude()) {
                    String code = addInclude("__global.rythm", -1);
                    CodeToken ck = new CodeToken(code, parser);
                    addBuilder(ck);
                }
            }
            invokeDirectives();
            //if (!basicTemplate()) addDefaultRenderArgs();
            RythmEvents.ON_BUILD_JAVA_SOURCE.trigger(engine, this);
            pPackage();
            pImports();
            pClassOpen();
            pTagImpl();
            pInitCode();
            pSetup();
            if (!simpleTemplate()) pExtendInitArgCode();
            pRenderArgs();
            pInlineTags();
            pBuild();
            RythmEvents.ON_CLOSING_JAVA_CLASS.trigger(engine, this);
            pClassClose();
            if (conf.debugJavaSourceEnabled()) {
                logger.info("java source code for %s: \n%s", templateClass, this);
            }
            return this;
        } catch (NotRythmTemplateException e) {
            isNotRythmTemplate = true;
            return this;
        } finally {
            parser.shutdown();
            if (logger.isTraceEnabled()) {
                logger.trace("%sms to build %s", System.currentTimeMillis() - start, templateClass.getKey());
            }
        }
    }

    private void invokeDirectives() {
        for (TextBuilder b : builders) {
            if (b instanceof IDirective) {
                ((IDirective) b).call();
            }
        }
    }

    protected void pPackage() {
        if (!S.isEmpty(pName)) p("package ").p(pName).pn(";");
    }

    private void pImport(String s, boolean sandbox) {
        if (S.isEmpty(s)) return;
        if (s.startsWith("import ")) {
            s = s.replaceFirst("import ", "");
        }
        if (sandbox) {
            String s0 = Sandbox.hasAccessToRestrictedClasses(engine, s);
            if (null != s0) return;
        }
        p("import ").p(s).p(';');
        Integer I = importLineMap.get(s);
        if (null != I) p(" //line: ").pn(I);
        else p("\n");
    }

    // print imports
    protected void pImports() {
        boolean sandbox = Rythm.insideSandbox();
        for (String s : imports) {
            pImport(s, sandbox);
        }
//        for (String s : globalImports) {
//            pImport(s, sandbox);
//        }
// moved to event handler
//        if (null != importProvider) {
//            for (String s : importProvider.imports()) {
//                pImport(s, sandbox);
//            }
//        }
// replaced by importProvider
//        IImplicitRenderArgProvider p = implicitRenderArgProvider;
//        if (null != p) {
//            for (String s : p.getImplicitImportStatements()) {
//                pImport(s, sandbox);
//            }
//        }
        // common imports
        pn("import java.util.*;");
        pn("import org.rythmengine.template.TemplateBase;");
        if (!sandbox) pn("import java.io.*;");
    }

    protected void pClassOpen() {
        np("public class ").p(cName).p(" extends ").p(extended()).p(" {").pn(extendedResourceMark());
    }

    protected void pClassClose() {
        np("}").pn();
    }

    private static String toNonGeneric(String type) {
        Regex regex = new Regex("(?@<>)", "");
        return regex.replaceAll(type);
    }
   
    private static boolean isGeneric(String type) {
        Regex regex = new Regex(".*(?@<>)");
        return regex.search(type);
    }
   
    private static boolean isArray(String type) {
        Regex regex = new Regex(".*(?@[])");
        return regex.search(type);
    }
   
    private void addInferencedRenderArgs() {
        if (renderArgs.isEmpty() && conf.typeInferenceEnabled()) {
            Map<String, String> tMap = ParamTypeInferencer.getTypeMap();
            List<String> ls = new ArrayList<String>(tMap.keySet());
            Collections.sort(ls);
            for (String name : ls) {
                String type = tMap.get(name);
                addRenderArgs(-1, type, name);
            }
        }
    }

    protected void pRenderArgs() {
        pn();
        addInferencedRenderArgs();
        // -- output private members
        for (String argName : renderArgs.keySet()) {
            RenderArgDeclaration arg = renderArgs.get(argName);
            pt("protected ").p(arg.type).p(" ").p(argName);
            if (null != arg.defVal) {
                p("=").p(arg.defVal).p(";");
            } else {
                p(";");
            }
            if (arg.lineNo > -1) p(" //line: ").pn(arg.lineNo);
            else pn();
        }
        List<RenderArgDeclaration> renderArgList = new ArrayList<RenderArgDeclaration>(renderArgs.values());
        Collections.sort(renderArgList);
       
        // -- output __renderArgName method
        pn();
        boolean first = true;
        ptn("protected java.lang.String __renderArgName(int __pos) {");
        p2tn("int __p = 0;");
        if (true) {
            first = true;
            for (RenderArgDeclaration arg : renderArgList) {
                if (first) {
                    first = false;
                    p2t("");
                } else {
                    p2t("else ");
                }
                p("if (__p++ == __pos) return \"").p(arg.name).p("\";").pn();
            }
            p2tn("throw new ArrayIndexOutOfBoundsException();");
        }
        ptn("}");

        // -- output __renderArgTypeMap method
        pn();
        ptn("protected java.util.Map<java.lang.String, java.lang.Class> __renderArgTypeMap() {");
        p2tn("java.util.Map<java.lang.String, java.lang.Class> __m = new java.util.HashMap<String, Class>();");
        for (String argName : renderArgs.keySet()) {
            RenderArgDeclaration arg = renderArgs.get(argName);
            String argType = arg.type;
            boolean isGeneric = isGeneric(argType);
            if (isGeneric) {
                p2t("__m.put(\"").p(argName).p("\", ").p(toNonGeneric(argType)).pn(".class);");
                Regex regex = new Regex(".*((?@<>))");
                regex.search(argType);
                String s = regex.stringMatched(1);
                s = S.strip(s, "<", ">");
                if (s.contains("<")) {
                    // not support embedded <> yet
                } else {
                    String[] sa = s.split(",");
                    for (int i = 0; i < sa.length; ++i) {
                        String type = sa[i];
                        if ("?".equals(type)) {
                            type = "Object";
                        }
                        p2t("__m.put(\"").p(argName).p("__").p(i).p("\", ").p(type).pn(".class);");
                    }
                }
            } else {
                String type = argType;
                if ("?".equals(type)) {
                    type = "Object";
                }
                p2t("__m.put(\"").p(argName).p("\", ").p(type).pn(".class);");
//                int lvl = 0;
//                if (isArray(type)) {
//                    int pos = type.lastIndexOf("[");
//                    type = type.substring(0, pos);
//                    p2t("__m.put(\"").p(argName).p("__").p(lvl).p("\", ").p(type).pn(".class);");
//                    //lvl++;
//                }
            }
        }
        p2tn("return __m;");
        ptn("}");

        // -- output __setRenderArgs method
        pn();
        ptn("@SuppressWarnings(\"unchecked\")\n\tpublic TemplateBase __setRenderArgs(java.util.Map<java.lang.String, java.lang.Object> __args) {");
        p2tn("if (null == __args) throw new NullPointerException();\n\t\tif (__args.isEmpty()) return this;");
        p2tn("super.__setRenderArgs(__args);");
        first = true;
        for (String argName : renderArgs.keySet()) {
            RenderArgDeclaration arg = renderArgs.get(argName);
            p2t("if (__args.containsKey(\"").p(argName).p("\")) this.").p(argName).p(" = __get(__args,\"").p(argName).p("\",").p(arg.objectType()).pn(".class);");
        }
        p2tn("return this;");
//        for (String argName : renderArgs.keySet()) {
//            p2t("System.err.println(\"").p(argName).p("=\" + this.").p(argName).pn(");");
//        }
        ptn("}");

        ISourceCodeEnhancer ce = engine.conf().get(RythmConfigurationKey.CODEGEN_SOURCE_CODE_ENHANCER);
        int userDefinedArgNumber = basicTemplate() ? renderArgs.size() : (renderArgs.size() - ((null == ce) ? 0 : ce.getRenderArgDescriptions().size()));
        if (0 < userDefinedArgNumber) {
            // -- output __setRenderArgs method with args passed in positioned order
            pn();
            ptn("@SuppressWarnings(\"unchecked\") public TemplateBase __setRenderArgs(java.lang.Object... __args) {");
            {
                p2tn("int __p = 0, __l = __args.length;");
                int i = userDefinedArgNumber;
                for (RenderArgDeclaration arg : renderArgList) {
                    p2t("if (__p < __l) { \n\t\t\tObject v = __args[__p++]; \n\t\t\t").p(arg.name).p(" = __safeCast(v, ").p(arg.objectType()).p(".class); \n\t\t\t__renderArgs.put(\"").p(arg.name).p("\",").p(arg.name).p(");\n\t\t}\n");
                    if (--i == 0) break;
                }
            }
            p2tn("return this;");
            ptn("}");

            // -- output __renderArgTypeArray method with args passed in positioned order
            pn();
            ptn("protected java.lang.Class[] __renderArgTypeArray() {");
            {
                p2t("return new java.lang.Class[]{");
                int i = userDefinedArgNumber;
                for (RenderArgDeclaration arg : renderArgList) {
                    p(toNonGeneric(arg.type)).p(".class").p(", ");
                    if (--i == 0) break;
                }
                pn("};");
            }
            ptn("}");
        }

        // -- output __setRenderArg by name
        pn();
        ptn("@SuppressWarnings(\"unchecked\") @Override public TemplateBase __setRenderArg(java.lang.String __name, java.lang.Object __arg) {");
        if (true) {
            first = true;
            for (RenderArgDeclaration arg : renderArgList) {
                if (first) {
                    first = false;
                    p2t("");
                } else {
                    p2t("else ");
                }
                String argName = arg.name;
                p("if (\"").p(argName).p("\".equals(__name)) this.").p(argName).p(" = __safeCast(__arg, ").p(arg.objectType()).pn(".class);");
            }
        }
        p2t("super.__setRenderArg(__name, __arg);\n\t\treturn this;\n\t}\n");

        // -- output __setRenderArg by position
        pn();
        ptn("@SuppressWarnings(\"unchecked\") public TemplateBase __setRenderArg(int __pos, java.lang.Object __arg) {");
        p2tn("int __p = 0;");
        if (true) {
            first = true;
            for (RenderArgDeclaration arg : renderArgList) {
                if (first) {
                    first = false;
                    p2t("");
                } else {
                    p2t("else ");
                }
                p2t("if (__p++ == __pos) { \n\t\t\tObject v = __arg; \n\t\t\t").p(arg.name).p(" = __safeCast(v, ").p(arg.objectType()).p(".class); \n\t\t\t__renderArgs.put(\"").p(arg.name).p("\", ").p(arg.name).p(");\n\t\t}\n");
            }
        }
        // the first argument has a default name "arg"
        p2tn("if(0 == __pos) __setRenderArg(\"arg\", __arg);");
        p2tn("return this;");
        ptn("}");
    }

    protected void pExtendInitArgCode() {
        if (null == extendArgs || extendArgs.pl.size() < 1) return;
        pn();
        ptn("@Override protected void __loadExtendingArgs() {");
        for (int i = 0; i < extendArgs.pl.size(); ++i) {
            InvokeTemplateParser.ParameterDeclaration pd = extendArgs.pl.get(i);
            if (S.isEmpty(pd.nameDef)) {
                p2t("__parent.__setRenderArg(").p(i).p(", ").p(pd.valDef).p(");");
            } else {
                p2t("__parent.__setRenderArg(\"").p(pd.nameDef).p("\", ").p(pd.valDef).p(");");
            }
            if (extendDeclareLineNo != -1) {
                p(" //line: ").pn(extendDeclareLineNo);
            } else {
                pn();
            }
        }
        ptn("}");
    }

    protected void pSetup() {
        if (!logTime && renderArgs.isEmpty()) return;
        pn();
        ptn("@Override protected void __setup() {");
        if (logTime) {
            p2tn("__logTime = true;");
        }
        for (String argName : renderArgs.keySet()) {
            RenderArgDeclaration arg = renderArgs.get(argName);
            p2t("if (__isDefVal(").p(argName).p(")) {");
            p(argName).p(" = __get(\"").p(argName).p("\",").p(arg.objectType()).p(".class) ;}\n");
        }
        ptn("}");
    }

    protected void pInitCode() {
        if (S.isEmpty(initCode)) return;
        pn();
        pt("@Override public void __init() {").p(initCode).p(";").pn("\n\t}");
    }

    protected void pTagImpl() {
        pn();
        pt("@Override public java.lang.String __getName() {\n\t\treturn \"").p(tagName).p("\";\n\t}\n");
    }

    public String buildBody = null;

    transient Map<Token.StringToken, String> consts = new HashMap<Token.StringToken, String>();

    private RythmEngine.OutputMode outputMode = RythmEngine.outputMode();

    private Token.StringToken addConst(Token.StringToken st) {
        if (!outputMode.writeOutput()) return st;
        if (consts.containsKey(st)) {
            st.constId = consts.get(st);
            return st;
        } else {
            String id = this.newVarName();
            st.constId = id;
            consts.put(st, id);
            return st;
        }
    }

    private List<Token> mergeStringTokens(List<Token> builders) {
        List<Token> merged = new ArrayList<Token>();
        Token.StringToken curTk = new Token.StringToken("", parser);
        for (int i = 0; i < builders.size(); ++i) {
            Token tb = builders.get(i);
            if (tb == Token.EMPTY_TOKEN) {
                continue;
            }
            if (tb instanceof Token.StringToken || tb instanceof BlockToken.LiteralBlock || tb instanceof IDirective) {
                if (tb instanceof Token.StringToken) {
                    Token.StringToken tk = (Token.StringToken) tb;
                    curTk = curTk.mergeWith(tk);
                } else if (tb instanceof IDirective) {
                    // do nothing
                } else {
                    BlockToken.LiteralBlock bk = (BlockToken.LiteralBlock) tb;
                    curTk = curTk.mergeWith(bk);
                }
            } else {
                if (null != curTk && curTk.s().length() > 0) {
                    curTk = addConst(curTk);
                    curTk.compact();
                    merged.add(curTk);
                }
                curTk = new Token.StringToken("", parser);
                merged.add(tb);
            }
        }
        if (null != curTk && curTk.s().length() > 0) {
            curTk = addConst(curTk);
            curTk.compact();
            merged.add(curTk);
        }
        return merged;
    }

    protected void pInlineTags() {
        pn();
        for (InlineTag tag : inlineTags) {
            p("\npublic ").p(tag.retType).p(" ").p(tag.tagName).p(tag.signature);
            p("{\norg.rythmengine.template.TemplateBase oldParent = this.__parent;\ntry{\nthis.__parent = this;\n");
            boolean isVoid = tag.autoRet;
            if (!isVoid) {
                p(tag.body);
            } else {
                List<Token> merged = mergeStringTokens(tag.builders);
                for (Token b : merged) {
                    b.build(parser);
                }
            }
            p("\n}catch(Exception __e){\nthrow new java.lang.RuntimeException(__e);\n} finally {this.__parent = oldParent;}\n}");
        }
    }

    protected void pBuild() {
        pn();
        pn();
        ptn("@Override public org.rythmengine.utils.TextBuilder build(){");
        p2t("buffer().ensureCapacity(").p(tmpl.length()).p(");").pn();
        StringBuilder sb = new StringBuilder();
        StringBuilder old = buffer();
        __setBuffer(sb);
        // try merge strings
        List<Token> merged = mergeStringTokens(this.builders);
        for (Token b : merged) {
            b.build();
        }
        buildBody = sb.toString();
        __setBuffer(old);
        p(buildBody);
        p("\n\t\treturn this;\n\t}\n");

        // print out consts
        for (Token.StringToken st : consts.keySet()) {
            pConst(st);
        }
    }

    private void pConst(Token.StringToken st) {
        String constId = st.constId;
        String s = st.s(), s0 = s;
        if (st.compactMode()) {
            s0 = s.replaceAll("(\\r?\\n)+", "\\\\n").replaceAll("\"", "\\\\\"");
        } else {
            s0 = s.replaceAll("(\\r?\\n)", "\\\\n").replaceAll("\"", "\\\\\"");
        }
        np("private static final org.rythmengine.utils.TextBuilder.StrBuf ").p(constId).p(" = new org.rythmengine.utils.TextBuilder.StrBuf(\"").p(s0);
        StrBuf sw = new StrBuf(s);
        if (outputMode == RythmEngine.OutputMode.os) {
            p("\", new byte[]{");
            byte[] ba = sw.toBinary();
            for (int i = 0; i < ba.length; ++i) {
                p(String.valueOf(ba[i]));
                if (i < ba.length - 1) p(",");
            }
            p("});");
        } else if (outputMode == RythmEngine.OutputMode.writer) {
            p("\", null);");
        } else {
            throw new AssertionError("should not go here");
        }
        p("// line:").pn(st.getLineNo());
    }

    private Set<String> varNames = new HashSet<String>();

    public String newVarName() {
        int i = 0;
        while (true) {
            String name = "__v" + i;
            if (!varNames.contains(name)) {
                varNames.add(name);
                return name;
            } else {
                i += new Random().nextInt(100000);
            }
        }
    }

    public static final String INTERRUPT_CODE = "\n{if (java.lang.Thread.interrupted()) throw new RuntimeException(\"interrupted\");}";

    private static final Regex R_FOR_0 = new Regex("([\\s;]for\\s*(?@())\\s*\\{(\\s*//\\s*line:\\s*[0-9]+)?)", "${1}" + INTERRUPT_CODE + "${2}" + "\n");
    private static final Regex R_FOR_1 = new Regex("([\\s;]for\\s*(?@()))\\s*([^\\{]+;)", "${1} \\{" + INTERRUPT_CODE + "${2} \n\\}");

    private static final Regex R_WHILE_0 = new Regex("([\\s;]while\\s*(?@())\\s*\\{)", "${1}" + INTERRUPT_CODE);
    private static final Regex R_WHILE_1 = new Regex("([\\s;]while\\s*(?@()))\\s*([^\\{]+;)", "${1} \\{" + INTERRUPT_CODE + "${2} \\}");

    private static final Regex R_DO_0 = new Regex("([\\s;]do\\s*\\{)", "${1}" + INTERRUPT_CODE);
    private static final Regex R_DO_1 = new Regex("([\\s;]do\\s*)([^\\{\\}]+[\\s;]while[\\s\\(])", "${1} \\{" + INTERRUPT_CODE + "${2}");

    public static String preventInfiniteLoop(String code) {
        code = R_FOR_0.replaceAll(code);
        code = R_FOR_1.replaceAll(code);
        code = R_WHILE_0.replaceAll(code);
        code = R_WHILE_1.replaceAll(code);
        code = R_DO_0.replaceAll(code);
        code = R_DO_1.replaceAll(code);
        return code;
    }

}
TOP

Related Classes of org.rythmengine.internal.CodeBuilder

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.