com.bstek.dorado.view.config.attachment.JavaScriptParser.java Source code

Java tutorial

Introduction

Here is the source code for com.bstek.dorado.view.config.attachment.JavaScriptParser.java

Source

/*
 * This file is part of Dorado 7.x (http://dorado7.bsdn.org).
 * 
 * Copyright (c) 2002-2012 BSTEK Corp. All rights reserved.
 * 
 * This file is dual-licensed under the AGPLv3 (http://www.gnu.org/licenses/agpl-3.0.html) 
 * and BSDN commercial (http://www.bsdn.org/licenses) licenses.
 * 
 * If you are unsure which license is appropriate for your use, please contact the sales department
 * at http://www.bstek.com/contact.
 */

package com.bstek.dorado.view.config.attachment;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import com.bstek.dorado.core.io.Resource;
import com.bstek.dorado.util.Assert;

/**
 * @author Benny Bao (mailto:benny.bao@bstek.com)
 * @since 2012-5-31
 */
public class JavaScriptParser {
    private final static char ESCAPE_CHAR = '\\';
    private final static char QUOTE1 = '\'';
    private final static char QUOTE2 = '"';

    private final static char BLOCK_START = '{';
    private final static char BLOCK_END = '}';
    private final static char BRACKET_START = '(';
    private final static char BRACKET_END = ')';

    private final static char BACKSLASH = '/';
    private final static char STAR = '*';
    private final static char SPACE = ' ';
    private final static char TAB = '\t';
    private final static char RETURN = '\n';

    private final static char ANONYMOUS_FUNCTION_PREFIX = '!';

    private final static String FUNCTION = "function";
    private final static int FUNCTION_LEN = FUNCTION.length();

    private final static String ANONYMOUS_FUNCTION = "!function";
    private final static int ANONYMOUS_FUNCTION_LEN = ANONYMOUS_FUNCTION.length();

    private final static String VALID_NAME_CHARS = "$_";

    private static final String CONTROLLER_ANNOTATION = "@Controller";
    private static final String BIND_ANNOTATION = "@Bind";
    private static final String GLOBAL_ANNOTATION = "@Global";
    private static final String VIEW_ANNOTATION = "@View";

    private static final String AUTO_FUNCTION_NAME_PREFIX = "_anonymous_";

    private List<String> extractComment(CodeReader reader, boolean isLineComment) throws IOException {
        List<String> comments = null;
        boolean inContent = false, inSpace = false;
        StringBuffer contentLine = new StringBuffer(8);
        StringBuffer contentBuf = new StringBuffer();
        while (true) {
            char c = reader.read();
            if (c == RETURN) {
                if (contentBuf.length() > 0) {
                    contentLine.append(contentBuf);
                    if (contentLine.length() > 0) {
                        if (comments == null) {
                            comments = new ArrayList<String>();
                        }
                        comments.add(contentLine.toString());
                        inContent = false;
                        contentBuf.setLength(0);
                        contentLine.setLength(0);
                    }
                }
                if (isLineComment) {
                    break;
                } else {
                    continue;
                }
            } else if (c == STAR && !isLineComment) {
                char lastChar = c;
                c = reader.read();
                if (c == BACKSLASH) {
                    break;
                } else {
                    c = lastChar;
                    reader.moveCursor(-1);
                }
            }

            if (!inContent) {
                if (c == SPACE || c == TAB || c == STAR) {
                    // do nothing
                } else {
                    inContent = true;
                    contentBuf.append(c);
                }
            } else {
                if (c == SPACE || c == TAB || c == STAR) {
                    inSpace = true;
                    int bufLen = contentBuf.length();
                    if (bufLen > 0) {
                        contentLine.append(contentBuf);
                        contentBuf.setLength(0);
                    }
                } else {
                    if (inSpace) {
                        contentBuf.append(SPACE);
                        inSpace = false;
                    }
                    contentBuf.append(c);
                }
            }
        }

        if (contentLine.length() > 0) {
            if (comments == null) {
                comments = new ArrayList<String>();
            }
            comments.add(contentLine.toString());
        }
        return comments;
    }

    private void skipComment(CodeReader reader, boolean isLineComment) throws IOException {
        while (true) {
            char c = reader.read();
            if (isLineComment) {
                if (c == RETURN) {
                    break;
                }
            } else if (c == STAR) {
                c = reader.read();
                if (c == BACKSLASH) {
                    break;
                }
            }
        }
    }

    private boolean checkAnnotation(String commentLine, String annot) {
        return commentLine.equals(annot) || commentLine.startsWith(annot + ' ');
    }

    private void parseComment(CodeReader reader, boolean isLineComment, JavaScriptContext context)
            throws IOException {
        List<String> comments = extractComment(reader, isLineComment);
        if (comments != null) {
            for (String line : comments) {
                if (checkAnnotation(line, BIND_ANNOTATION)) {
                    String expression = line.substring(BIND_ANNOTATION.length() + 1);
                    if (StringUtils.isNotEmpty(expression)) {
                        BindingInfo bindingInfo = context.getCurrentBindingInfo();
                        if (bindingInfo == null) {
                            bindingInfo = new BindingInfo();
                            context.setCurrentBindingInfo(bindingInfo);
                        }
                        bindingInfo.addExpression(expression);
                    }
                } else if (checkAnnotation(line, CONTROLLER_ANNOTATION)) {
                    context.setIsController(true);
                } else if (checkAnnotation(line, GLOBAL_ANNOTATION)) {
                    context.setCurrentShouldRegisterToGlobal(true);
                } else if (checkAnnotation(line, VIEW_ANNOTATION)) {
                    context.setCurrentShouldRegisterToView(true);
                }
            }
        }
    }

    private String extractFunctionName(CodeReader reader) throws IOException {
        StringBuffer content = new StringBuffer();
        while (true) {
            char c = reader.read();
            if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
                    || VALID_NAME_CHARS.indexOf(c) >= 0) {
                content.append(c);
            } else if ((c == SPACE || c == TAB || c == RETURN) && content.length() == 0) {
                // do nothing
            } else {
                break;
            }
        }
        return content.toString();
    }

    private void skipString(CodeReader reader, char endToken) throws IOException {
        while (true) {
            char c = reader.read();
            if (c == ESCAPE_CHAR) {
                reader.moveCursor(1);
            } else if (c == endToken) {
                break;
            }
        }
    }

    private void skipBlock(CodeReader reader, char endToken) throws IOException {
        while (true) {
            char c = reader.read();
            if (c == BACKSLASH) {
                c = reader.read();
                if (c == BACKSLASH || c == STAR) {
                    skipComment(reader, (c == BACKSLASH));
                    continue;
                }
            }

            if (c == QUOTE1 || c == QUOTE2) {
                skipString(reader, c);
            } else if (c == BLOCK_START) {
                skipBlock(reader, BLOCK_END);
            } else if (c == BRACKET_START) {
                skipBlock(reader, BRACKET_END);
            } else if (c == endToken) {
                break;
            }
        }
    }

    private JavaScriptContent parse(StringBuffer source, JavaScriptContext context) throws IOException {
        StringBuffer reserveWord = new StringBuffer(8), modifiedSource = null;
        int autoNameSeed = 0, copyStart = 0;
        Boolean isController = context.getIsController();
        CodeReader reader = new CodeReader(source);
        try {
            while (true) {
                char c = reader.read();
                if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9'
                        || VALID_NAME_CHARS.indexOf(c) >= 0) {
                    reserveWord.append(c);
                } else if (c == RETURN) {
                    // do nothing
                } else if (c == ANONYMOUS_FUNCTION_PREFIX) {
                    reserveWord.setLength(0);
                    reserveWord.append(c);
                } else {
                    int len = reserveWord.length();
                    if (len > 0) {
                        if (len == FUNCTION_LEN && reserveWord.toString().equals(FUNCTION)) {
                            reader.moveCursor(-1);
                            String functionName = extractFunctionName(reader);
                            if (StringUtils.isNotEmpty(functionName)) {
                                if (context.isHasAnnotation()) {
                                    FunctionInfo functionInfo = new FunctionInfo(functionName);
                                    functionInfo.setBindingInfo(context.getCurrentBindingInfo());
                                    functionInfo
                                            .setShouldRegisterToGlobal(context.getCurrentShouldRegisterToGlobal());
                                    functionInfo.setShouldRegisterToView(context.getCurrentShouldRegisterToView());
                                    context.addFunctionInfo(functionInfo);
                                }
                            }
                        } else if (len == ANONYMOUS_FUNCTION_LEN
                                && reserveWord.toString().equals(ANONYMOUS_FUNCTION)) {
                            reader.moveCursor(-1);
                            int copyEnd = reader.getCurrentPos() - ANONYMOUS_FUNCTION_LEN;
                            String functionName = extractFunctionName(reader);
                            if (StringUtils.isEmpty(functionName) && context.isHasAnnotation()) {
                                BindingInfo currentBindingInfo = context.getCurrentBindingInfo();
                                if (currentBindingInfo != null) {
                                    functionName = AUTO_FUNCTION_NAME_PREFIX + (++autoNameSeed);

                                    if (modifiedSource == null) {
                                        modifiedSource = new StringBuffer(source.length());
                                    }

                                    modifiedSource.append(source.substring(copyStart, copyEnd)).append(FUNCTION)
                                            .append(SPACE).append(functionName);
                                    copyStart = reader.getCurrentPos() - 1;

                                    FunctionInfo functionInfo = new FunctionInfo(functionName);
                                    functionInfo.setBindingInfo(context.getCurrentBindingInfo());
                                    context.addFunctionInfo(functionInfo);
                                }
                            }
                        }

                        if (context.isHasAnnotation()) {
                            context.reset();
                        }
                        reserveWord.setLength(0);
                    } else {
                        if (c == BACKSLASH) {
                            c = reader.read();
                            if (c == BACKSLASH || c == STAR) {
                                parseComment(reader, (c == BACKSLASH), context);
                                isController = context.getIsController();
                                if (isController != null && !isController) {
                                    break;
                                }
                                continue;
                            }
                        }

                        if (isController == null && c != SPACE && c != TAB && c != RETURN) {
                            context.setIsController(false);
                            break;
                        }

                        if (c == QUOTE1 || c == QUOTE2) {
                            skipString(reader, c);
                        } else if (c == BLOCK_START) {
                            skipBlock(reader, BLOCK_END);
                        } else if (c == BRACKET_START) {
                            skipBlock(reader, BRACKET_END);
                        }
                    }
                }
            }
        } catch (EOFException e) {
            // do nothing
        }

        if (modifiedSource != null) {
            modifiedSource.append(source.substring(copyStart));
            source = modifiedSource;
        }

        JavaScriptContent javaScriptContent = new JavaScriptContent();
        javaScriptContent.setContent(source.toString());
        javaScriptContent.setIsController(context.getIsController());
        javaScriptContent.setFunctionInfos(context.getFunctionInfos());
        return javaScriptContent;
    }

    public JavaScriptContent parse(Resource resource, String charset, boolean asControllerInDefault)
            throws IOException {
        InputStream in = resource.getInputStream();
        try {
            InputStreamReader reader;
            if (StringUtils.isNotEmpty(charset)) {
                reader = new InputStreamReader(in, charset);
            } else {
                reader = new InputStreamReader(in);
            }
            BufferedReader br = new BufferedReader(reader);

            StringBuffer source = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
                source.append(line).append('\n');
            }
            br.close();
            reader.close();

            JavaScriptContext context = new JavaScriptContext();
            if (asControllerInDefault) {
                context.setIsController(true);
            }
            return parse(source, context);
        } finally {
            in.close();
        }
    }

}

class CodeReader {
    private StringBuffer buf;
    private int cursor;

    public CodeReader(StringBuffer buf) {
        this.buf = buf;
    }

    public char read() throws EOFException {
        if (cursor > buf.length() - 1) {
            throw new EOFException();
        } else {
            return buf.charAt(cursor++);
        }
    }

    public int moveCursor(int distence) {
        cursor += distence;
        if (cursor < 0) {
            return distence + (0 - cursor);
        } else if (cursor > buf.length() - 1) {
            return distence - (cursor - buf.length() - 1);
        } else {
            return distence;
        }
    }

    public int getCurrentPos() {
        return cursor;
    }
}

class JavaScriptContext {
    private boolean hasAnnotation;
    private Boolean isController;
    private BindingInfo currentBindingInfo;
    private boolean currentShouldRegisterToGlobal;
    private boolean currentShouldRegisterToView;
    private List<FunctionInfo> functionInfos;
    private Set<String> functionNames;

    public Boolean getIsController() {
        return isController;
    }

    public void setIsController(Boolean isController) {
        this.isController = isController;
    }

    public BindingInfo getCurrentBindingInfo() {
        return currentBindingInfo;
    }

    public void setCurrentBindingInfo(BindingInfo currentBindingInfo) {
        this.currentBindingInfo = currentBindingInfo;
        hasAnnotation = true;
    }

    public boolean getCurrentShouldRegisterToGlobal() {
        return currentShouldRegisterToGlobal;
    }

    public void setCurrentShouldRegisterToGlobal(boolean currentShouldRegisterToGlobal) {
        this.currentShouldRegisterToGlobal = currentShouldRegisterToGlobal;
        hasAnnotation = true;
    }

    public boolean getCurrentShouldRegisterToView() {
        return currentShouldRegisterToView;
    }

    public void setCurrentShouldRegisterToView(boolean currentShouldRegisterToView) {
        this.currentShouldRegisterToView = currentShouldRegisterToView;
        hasAnnotation = true;
    }

    public List<FunctionInfo> getFunctionInfos() {
        return functionInfos;
    }

    public void addFunctionInfo(FunctionInfo functionInfo) {
        if (functionNames != null && functionNames.contains(functionInfo.getFunctionName())) {

        }

        if (functionInfos == null) {
            functionInfos = new ArrayList<FunctionInfo>();
            functionNames = new HashSet<String>();
        }
        functionInfos.add(functionInfo);
        reset();
    }

    public boolean isHasAnnotation() {
        return hasAnnotation;
    }

    public void reset() {
        currentBindingInfo = null;
        currentShouldRegisterToGlobal = false;
        currentShouldRegisterToView = false;
        hasAnnotation = false;
    }

};

class FunctionInfo {
    private String functionName;
    private BindingInfo bindingInfo;
    private boolean shouldRegisterToGlobal;
    private boolean shouldRegisterToView;

    public FunctionInfo(String functionName) {
        this.functionName = functionName;
    }

    public String getFunctionName() {
        return functionName;
    }

    public BindingInfo getBindingInfo() {
        return bindingInfo;
    }

    public void setBindingInfo(BindingInfo bindingInfo) {
        this.bindingInfo = bindingInfo;
    }

    public boolean getShouldRegisterToGlobal() {
        return shouldRegisterToGlobal;
    }

    public void setShouldRegisterToGlobal(boolean shouldRegisterToGlobal) {
        this.shouldRegisterToGlobal = shouldRegisterToGlobal;
    }

    public boolean getShouldRegisterToView() {
        return shouldRegisterToView;
    }

    public void setShouldRegisterToView(boolean shouldRegisterToView) {
        this.shouldRegisterToView = shouldRegisterToView;
    }
};

class BindingInfo {
    private List<String> expressions = new ArrayList<String>();
    private boolean loose;

    public void addExpression(String expression) {
        Assert.notEmpty(expression);
        int i = expression.indexOf('.');
        if (i > 0) {
            String objects = expression.substring(0, i);
            String eventName = expression.substring(i + 1);
            Assert.notEmpty(objects);
            Assert.notEmpty(eventName);
        } else {
            throw new IllegalArgumentException("Invalid Bind expression \"" + expression + "\".");
        }
        expressions.add(expression);
    }

    public List<String> getExpressions() {
        return expressions;
    }

    public boolean isLoose() {
        return loose;
    }

    public void setLoose(boolean loose) {
        this.loose = loose;
    }

}