All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gridkit.jvmtool.stacktrace.util.IndentParser Maven / Gradle / Ivy

There is a newer version: 0.23
Show newest version
package org.gridkit.jvmtool.stacktrace.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public abstract class IndentParser {
    
    public static String POPUP = "POPUP";
    
    List resultStack = new ArrayList();
    List indentStack = new ArrayList();
    
    List parseStateStack = new ArrayList();
    
    int lineNumber;
    int lineIndent;
    
    protected IndentParser() {
        reset();
    }
    
    public void push(String line) throws ParseException {
        lineNumber++;
        if (isEmpty(line)) {
            return;
        }
        int n = lineIndent = indentOf(line);
        String token = line.substring(n, line.length());
        if (isIndentPending()) {
            if (n > lastIndent()) {
                updateIndent(n);
                pushToken(token);
            }
            else if (n == lastIndent()) {
                indentPopup();
                pushToken(token);
            }
            else {
                popupTo(n);
                pushToken(token);
            }
        }
        else {
            if (n == lastIndent()) {
                pushToken(token);
            }
            else {
                popupTo(n);
                pushToken(token);
            }            
        }
        if (parseState().expectedPatterns.isEmpty()) {
            throw new ParseException("Internal error: Parsing dead end", lineNumber, line.length() - 1);
        }
    }
    
    public void finish() throws ParseException {
        try {
            for(int i = 0; i != indentStack.size(); ++i) {
                sendPopup();
            }
            indentStack.clear();
        }
        catch(ParseException e) {
            throw e;
        }
        catch(Exception e) {
            throw new ParseException("Parse error: " + e.toString(), lineNumber + 1, 0);
        }
    }    

    private boolean isIndentPending() {        
        return indentStack.get(indentStack.size() - 1) == -1;
    }
    
    private void updateIndent(int n) {
        if (isIndentPending()) {
            indentStack.set(indentStack.size() - 1, n);
        }
        else {
            throw new IllegalArgumentException();
        }
    }
    
    private int lastIndent() {
        if (isIndentPending()) {
            return indentStack.size() == 1 ? -1 : indentStack.get(indentStack.size() - 2);
        }
        else {
            return indentStack.get(indentStack.size() - 1);
        }        
    }

    private void popupTo(int n) throws ParseException {
        while(true) {
            int m = indentStack.remove(indentStack.size() - 1);
            if (m == -1) {
                sendPopup();
                m = indentStack.remove(indentStack.size() - 1);
            }
            if (m == n) {
                indentStack.add(m); // return back
                return;
            }
            if (m < n) {
                throw new ParseException("Invalid indenation alignment, expected is " + m, lineNumber, n);
            }
            else if (indentStack.size() == 0) {
                throw new ParseException("Invalid indenation alignment, expected is " + m, lineNumber, n);                
            }
            sendPopup();
        }
    }
    
    private void pushToken(String token) throws ParseException {
        for(String tk: parseState().expectedPatterns.keySet()) {
            String ptr = parseState().expectedPatterns.get(tk);
            if (ptr.length() > 0 && token.matches(ptr)) {
                onToken(tk, token);
                return;
            }
        }
        throw new ParseException("Line is not expected. Expected " + parseState().getExpectedTokensHint() + ", line: " + token, lineNumber, lineIndent);
    }
    
    private void indentPopup() throws ParseException {
        indentStack.remove(indentStack.size() - 1);
        sendPopup();
    }
    
    private void sendPopup() throws ParseException {
        if (parseState().expectedPatterns.containsKey(POPUP)) {
            onPopup();
        }
        else {
            throw new ParseException("Unexpected unindent, expected " + parseState().getExpectedTokensHint(), lineNumber, lineIndent);
        }        
    }

    protected int indentOf(String line) throws ParseException {
        for(int i = 0; i != line.length(); ++i) {
            char ch = line.charAt(i);
            if (ch == ' ') {
                continue;
            }
            if (!Character.isWhitespace(ch)) {
                return i;
            }
            throw new ParseException("Unallowed char 0x" + Integer.toHexString(ch), lineNumber, i);
        }
        throw new IllegalArgumentException("Line is empty");
    }
    
    protected boolean isEmpty(String line) {
        line = line.trim();
        if (line.startsWith("#") || line.length() == 0) {
            return true;
        }
        else {
            return false;
        }
    }
    
    public void reset() {
        resultStack.clear();
        indentStack.clear();
        indentStack.add(0);
        parseStateStack.clear();
        parseStateStack.add(new ParseState());
        
        initialState();
    }
    
    protected abstract void initialState();
    
    protected abstract void onToken(String tokenType, String token) throws ParseException;
    
    protected abstract void onPopup() throws ParseException;
    
    public int getLineNumber() {
        return lineNumber; 
    }
    
    public int getIndent() {
        return lastIndent();
    }
    
    public Map getExpectedTokens() {
        return parseState().expectedTokens();
    }
    
    private ParseState parseState() {
        return parseStateStack.get(parseStateStack.size() - 1);
    }
    
    protected void error(String message) throws ParseException {
        throw new ParseException(message, lineNumber, lineIndent);
    }
    
    protected void pushParseState() {
        indentStack.add(-1);
        parseStateStack.add(new ParseState());
    }
    
    protected void popParseState() {
        parseStateStack.remove(parseStateStack.size() - 1);
    }
    
    protected Object value() {
        return resultStack.get(resultStack.size() - 1);
    }
    
    protected void pushValue(Object value) {
        resultStack.add(value);
    }
    
    protected Object popValue() {
        return resultStack.remove(resultStack.size() - 1);
    }
    
    protected void expectToken(String id, String pattern) {
        parseState().expectToken(id, pattern);
    }

    protected void unexpectToken(String id) {
        parseState().unexpectToken(id);
    }
    
    protected void expectPopup() {
        parseState().expectToken(POPUP, "");
    }

    protected void unexpectPopup() {
        parseState().unexpectToken(POPUP);
    }
    
    protected void unexpectAll() {
        parseState().expectedPatterns.clear();
    }
    
    protected static class ParseState {
        
        Map expectedPatterns = new LinkedHashMap();

        public Map expectedTokens() {
            return new LinkedHashMap(expectedPatterns);
        }

        public String getExpectedTokensHint() {
            List tokens = new ArrayList(expectedPatterns.keySet());
            Collections.sort(tokens);
            return tokens.toString();
        }

        public void expectToken(String name, String pattern) {
            expectedPatterns.put(name, pattern);
        }
        
        public void unexpectToken(String name) {
            expectedPatterns.remove(name);
        }
    }    
    
    @SuppressWarnings("serial")
    public static class ParseException extends Exception {
        
        private int line;
        private int position;
        
        public ParseException(String message, int line, int position) {
            super(message);
            this.line = line;
            this.position = position;
        }

        public ParseException(String message, int line, int position, Throwable cause) {
            super(message, cause);
            this.line = line;
            this.position = position;
        }        
        
        public int getLine() {
            return line;
        }
        
        public int getPosition() {
            return position;
        }
    }
}