org.eclipse.titan.common.parsers.cfg.CfgParseTreePrinter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.titan.common.parsers.cfg.CfgParseTreePrinter.java

Source

/******************************************************************************
 * Copyright (c) 2000-2016 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/
package org.eclipse.titan.common.parsers.cfg;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNodeImpl;
import org.eclipse.core.runtime.Path;
import org.eclipse.titan.common.logging.ErrorReporter;
import org.eclipse.titan.common.parsers.AddedParseTree;
import org.eclipse.titan.common.path.PathConverter;

/**
 * Utility class to print a CFG parse tree with the following features:
 * <ul>
 *   <li> Print parse tree with or without resolving<br>
 *      Resolving means:
 *      <ul>
 *        <li> macros are changed to its actual values
 *        <li> include file name is changed to the content of the included config file (if it's not included yet)
 *      </ul>
 *   <li> Print parse tree with or without the hidden tokens before the parse tree.
 *        Hidden tokens inside the parse tree are always printed.
 * </ul>
 * @author Arpad Lovassy
 */
public class CfgParseTreePrinter {

    /** header for resolved included file contents in generated temp file */
    private static final String INCLUDED_BEGIN = "//This part was originally found in file: ";

    /** last line of resolved included file contents in generated temp file */
    private static final String INCLUDED_END = "//End of file: ";

    /** pattern for matching macro string, for example: \$a, \${a} */
    private static final Pattern PATTERN_MACRO = Pattern.compile("\\$\\s*\\{?\\s*([A-Za-z][A-Za-z0-9_]*)\\s*\\}?");

    /** pattern for matching typed macro string, for example: ${a, float} */
    private static final Pattern PATTERN_TYPED_MACRO = Pattern
            .compile("\\$\\s*\\{\\s*([A-Za-z][A-Za-z0-9_]*)\\s*,\\s*[A-Za-z][A-Za-z0-9_]*\\s*\\}");

    /**
     * Mode of resolving <br>
     * Resolving means:
     * <ul>
     *   <li> macros are changed to its actual values
     *   <li> include file name is changed to the content of the included config file (if it's not included yet)
     * </ul>
     * @author Arpad Lovassy
     */
    public enum ResolveMode {

        /** default */
        NO_RESOLVING,

        /** included file contents are copied after the main file after each other (used by [INCLUDE] section) */
        IN_ROW,

        /** included file content is copied in the place if the included file name (used by [ORDERED_INCLUDE] section) */
        NESTED
    }

    /**
     * StringBuilder, where the text is written
     */
    final StringBuilder mSb;

    /**
     * token types, which are not printed (also their children).
     * it can be null
     */
    final List<Integer> mDisallowedNodes;

    /**
     * original parse tree roots to resolve.
     * it can be null, if no resolving is done
     */
    final Map<Path, CfgParseResult> mCfgParseResults;

    /**
     * macro definitions, which are collected during parsing from [DEFINE] sections.
     * it can be null, if no resolving is done
     */
    final Map<String, CfgDefinitionInformation> mDefinitions;

    /**
     * environment variables.
     * it can be null, if no resolving is done
     */
    final Map<String, String> mEnvVariables;

    /**
     * list of files to resolve, files, which are already resolved are removed from this list.
     * it can be null, if no resolving is done
     */
    final List<Path> mFilesToResolve;

    /**
     * Constructor with all the functionalities
     * @param aSb (in/out) StringBuilder, where the text is written
     * @param aDisallowedNodes token types, which are not printed (also their children), it can be null
     * @param aCfgParseResults original parse tree roots to resolve
     *                        needed only if aResolveMode != NO_RESOLVING, otherwise it can be null
     * @param aDefinitions macro definitions, which are collected during parsing from [DEFINE] sections
     *                        needed only if aResolveMode != NO_RESOLVING, otherwise it can be null
     * @param aEnvVariables environment variables
     *                        needed only if aResolveMode != NO_RESOLVING, otherwise it can be null
     * @param aFilesToResolve list of files to resolve, files, which are already resolved are removed from this list
     *                        needed only if aResolveMode != NO_RESOLVING, otherwise it can be null
     */
    public CfgParseTreePrinter(final StringBuilder aSb, final List<Integer> aDisallowedNodes,
            final Map<Path, CfgParseResult> aCfgParseResults,
            final Map<String, CfgDefinitionInformation> aDefinitions, final Map<String, String> aEnvVariables,
            final List<Path> aFilesToResolve) {
        mSb = aSb;
        mDisallowedNodes = aDisallowedNodes;
        mCfgParseResults = aCfgParseResults;
        mDefinitions = aDefinitions;
        mEnvVariables = aEnvVariables;
        mFilesToResolve = aFilesToResolve;
    }

    /**
     * Constructor if no resolving is needed
     * @param aSb (in/out) StringBuilder, where the text is written
     * @param aDisallowedNodes token types, which are not printed (also their children), it can be null
     */
    public CfgParseTreePrinter(final StringBuilder aSb, final List<Integer> aDisallowedNodes) {
        this(aSb, aDisallowedNodes, null, null, null, null);
    }

    /**
     * Constructor if no resolving is needed without disallowed nodes
     * @param aSb (in/out) StringBuilder, where the text is written
     * @param aDisallowedNodes token types, which are not printed (also their children), it can be null
     */
    public CfgParseTreePrinter(final StringBuilder aSb) {
        this(aSb, null);
    }

    /**
     * Builds parse tree text including hidden tokens inside the parse tree
     * @param aParseTreeRoot root of the parse tree to print
     * @param aPrintHiddenBefore true to print hidden tokens before the parse tree
     *                           (NOTE: hidden tokens in the parse tree will be printed)
     * @param aTokens token list from the lexer (all, hidden and not hidden also)
     * @return output parse tree text
     */
    public static String toStringWithHidden(final ParseTree aParseTreeRoot, final List<Token> aTokens,
            final boolean aPrintHiddenBefore) {
        final StringBuilder sb = new StringBuilder();
        CfgParseTreePrinter printer = new CfgParseTreePrinter(sb);
        printer.print(aParseTreeRoot, aTokens, aPrintHiddenBefore, ResolveMode.NO_RESOLVING, null);
        // there are no hidden tokens after the last token

        return sb.toString();
    }

    /**
     * Builds resolved parse tree text including hidden tokens (also before the rule)<br>
     * Resolving means:
     * <ul>
     *   <li> macros are changed to its actual values
     *   <li> include file name is changed to the content of the included config file (if it's not included yet)
     * </ul>
     * @param aCfgParseResults original parse tree roots to resolve
     * @param aSb (in/out) StringBuilder, where the text is written
     * @param aDisallowedNodes token types, which are not printed (also their children), it can be null
     * @param aResolveMode mode of resolving
     * @param aDefinitions macro definitions, which are collected during parsing from [DEFINE] sections
     *                        needed only if aResolveMode != NO_RESOLVING, otherwise it can be null
     * @param aEnvVariables environment variables
     *                        needed only if aResolveMode != NO_RESOLVING, otherwise it can be null
     * @return output parse tree text
     */
    public static void printResolved(final Map<Path, CfgParseResult> aCfgParseResults, final StringBuilder aSb,
            final List<Integer> aDisallowedNodes, final ResolveMode aResolveMode,
            final Map<String, CfgDefinitionInformation> aDefinitions, final Map<String, String> aEnvVariables) {
        switch (aResolveMode) {
        case IN_ROW: {
            // list of files to resolve, files, which are already resolved are removed from this list
            final List<Path> filesToResolve = new ArrayList<Path>(aCfgParseResults.keySet());
            CfgParseTreePrinter printer = new CfgParseTreePrinter(aSb, aDisallowedNodes, aCfgParseResults,
                    aDefinitions, aEnvVariables, filesToResolve);
            for (Entry<Path, CfgParseResult> entry : aCfgParseResults.entrySet()) {
                printer.printResolved(entry.getKey(), entry.getValue().getParseTreeRoot(),
                        entry.getValue().getTokens(), aResolveMode);
            }
            break;
        }
        case NESTED: {
            // list of files to resolve, files, which are already resolved are removed from this list
            final List<Path> filesToResolve = new ArrayList<Path>(aCfgParseResults.keySet());
            // run only the 1st, others will be nested in place
            Entry<Path, CfgParseResult> entry = aCfgParseResults.entrySet().iterator().next();
            CfgParseTreePrinter printer = new CfgParseTreePrinter(aSb, aDisallowedNodes, aCfgParseResults,
                    aDefinitions, aEnvVariables, filesToResolve);
            printer.printResolved(entry.getKey(), entry.getValue().getParseTreeRoot(), entry.getValue().getTokens(),
                    aResolveMode);
            break;
        }
        case NO_RESOLVING:
        default:
            // nothing to do
            break;
        }
    }

    /**
     * Builds resolved parse tree text including hidden tokens (but not before the rule) <br>
     * Resolving means:
     * <ul>
     *   <li> macros are changed to its actual values
     *   <li> include file name is changed to the content of the included config file (if it's not included yet)
     * </ul>
     * @param aFile the parse tree of this file to print
     * @param aParseTreeRoot root of the parse tree to print
     * @param aTokens token list from the lexer (all, hidden and not hidden also)
     * @param aResolveMode mode of resolving
     */
    private void printResolved(final Path aFile, final ParseTree aParseTreeRoot, final List<Token> aTokens,
            final ResolveMode aResolveMode) {
        if (mFilesToResolve.contains(aFile)) {
            mSb.append(INCLUDED_BEGIN).append(aFile.toOSString()).append('\n');
            print(aParseTreeRoot, aTokens, true, aResolveMode, aFile);
            mSb.append(INCLUDED_END).append(aFile.toOSString()).append('\n');
            mFilesToResolve.remove(aFile);
        }
    }

    /**
     * RECURSIVE
     * Builds parse tree text including hidden tokens
     * @param aParseTree parse tree
     * @param aTokens token list from the lexer (all, hidden and not hidden also)
     * @param aPrintHiddenBefore true to print hidden tokens before the parse tree
     *                           (NOTE: hidden tokens in the parse tree will be printed)
     * @param aResolveMode mode of resolving
     * @param aFile the parse tree of this file to print
     *                        needed only if aResolveMode != NO_RESOLVING, in case of [ORDERED_INCLUDE]
     */
    private void print(final ParseTree aParseTree, final List<Token> aTokens, final boolean aPrintHiddenBefore,
            final ResolveMode aResolveMode, final Path aFile) {
        if (aParseTree == null) {
            ErrorReporter.logWarning("ConfigTreeNodeUtilities.print(): aParseTree == null");
            return;
        }

        if (aParseTree instanceof ParserRuleContext) {
            final ParserRuleContext rule = (ParserRuleContext) aParseTree;
            if (mDisallowedNodes != null && mDisallowedNodes.contains(rule.start.getType())) {
                return;
            }
            if (aPrintHiddenBefore && rule.getChildCount() > 0 && rule.getChild(0) instanceof AddedParseTree) {
                // special case: if AddedParseTree is the 1st in the rule, it has no information
                // about the hidden tokens, as it has no position in the token list, but the rule may have
                printHiddenTokensBefore(rule, aTokens);
            }
        } else if (aParseTree instanceof TerminalNodeImpl) {
            final TerminalNodeImpl tn = (TerminalNodeImpl) aParseTree;
            final Token token = tn.getSymbol();
            if (mDisallowedNodes == null || !mDisallowedNodes.contains(token.getType())) {
                printToken(token, aTokens, aPrintHiddenBefore, aResolveMode, aFile);
            }
        } else if (aParseTree instanceof AddedParseTree) {
            final AddedParseTree t = (AddedParseTree) aParseTree;
            mSb.append(t.getText());
        } else {
            ErrorReporter.INTERNAL_ERROR("ConfigTreeNodeUtilities.print(): unexpected ParseTree type");
        }

        for (int i = 0; i < aParseTree.getChildCount(); i++) {
            ParseTree child = aParseTree.getChild(i);
            if (child == aParseTree) {
                ErrorReporter.INTERNAL_ERROR("ConfigTreeNodeUtilities.print(): child == aParseTree");
            } else {
                print(child, aTokens, aPrintHiddenBefore || i > 0, aResolveMode, aFile);
            }
        }
    }

    /**
     * Builds token text including hidden tokens before the token
     * @param aToken token to print
     * @param aTokens token list from the lexer (all, hidden and not hidden also)
     * @param aPrintHiddenBefore true to print hidden tokens before the token
     * @param aResolveMode mode of resolving
     * @param aFile the parse tree of this file to print
     *                        needed only if aResolveMode != NO_RESOLVING, in case of [ORDERED_INCLUDE]
     */
    private void printToken(final Token aToken, final List<Token> aTokens, final boolean aPrintHiddenBefore,
            final ResolveMode aResolveMode, final Path aFile) {
        final int tokenIndex = aToken.getTokenIndex();
        if (tokenIndex > -1 && aPrintHiddenBefore) {
            // Token has no index if tokenIndex == -1.
            // If a token is added to the parse tree after parse time, token start index in unknown (-1),
            // because token has no index in the token list.
            printHiddenTokensBefore(aToken, aTokens);
        }

        // the only non-hidden token
        if (aResolveMode != ResolveMode.NO_RESOLVING) {
            resolveToken(aToken, aResolveMode, aFile);
        } else {
            final String tokenText = aToken.getText();
            mSb.append(tokenText != null ? tokenText : "");
        }
    }

    /**
     * Builds hidden tokens before the token
     * @param aToken the token, this will NOT be printed
     * @param aTokens token list from the lexer (all, hidden and not hidden also)
     */
    private void printHiddenTokensBefore(final Token aToken, final List<Token> aTokens) {
        final int tokenIndex = aToken.getTokenIndex();
        if (tokenIndex == -1) {
            // Token has no index.
            // If a token is added to the parse tree after parse time, token start index in unknown (-1),
            // because token has no index in the token list.
            return;
        }
        int startHiddenIndex = tokenIndex;
        while (isHiddenToken(startHiddenIndex - 1, aTokens)) {
            startHiddenIndex--;
        }
        for (int i = startHiddenIndex; i < tokenIndex; i++) {
            final Token t = aTokens.get(i);
            final String tokenText = t.getText();
            mSb.append(tokenText != null ? tokenText : "");
        }
    }

    /**
     * Builds hidden tokens before the rule
     * @param aRule the rule, this will NOT be printed
     * @param aTokens token list from the lexer (all, hidden and not hidden also)
     */
    private void printHiddenTokensBefore(final ParserRuleContext aRule, final List<Token> aTokens) {
        Token startToken = aRule.start;
        if (startToken == null) {
            return;
        }
        printHiddenTokensBefore(startToken, aTokens);
    }

    /**
     * @param aIndex token index to check
     * @param aTokens token list from the lexer (all, hidden and not hidden also)
     * @return true, iff token index is valid AND token is hidden
     */
    public static boolean isHiddenToken(final int aIndex, final List<Token> aTokens) {
        if (aTokens == null) {
            ErrorReporter.INTERNAL_ERROR("ConfigTreeNodeUtilities.isHiddenToken(): aTokens == null");
            return false;
        }

        return aIndex >= 0 && aIndex < aTokens.size() && aTokens.get(aIndex).getChannel() > 0;
    }

    /**
     * Resolves token if needed<br>
     * Resolving means:
     * <ul>
     *   <li> macros are changed to its actual values
     *   <li> include file name is changed to the content of the included config file (if it's not included yet)
     * </ul>
     * @param aToken token to resolve or print
     * @param aResolveMode mode of resolving
     * @param aFile the parse tree of this file to print
     *                        needed only if aResolveMode != NO_RESOLVING, in case of [ORDERED_INCLUDE]
     */
    private void resolveToken(final Token aToken, final ResolveMode aResolveMode, final Path aFile) {
        final int tokenType = aToken.getType();
        if (isMacro(tokenType)) {
            final String macroValue = getMacroValue(aToken);
            mSb.append(macroValue);
        } else if (isTypedMacro(tokenType)) {
            final String macroValue = getTypedMacroValue(aToken);
            mSb.append(macroValue);
        } else if (tokenType == CfgLexer.STRING2) {
            // Quoted string in [INCLUDE] section
            // this is the only non-hidden token in [INCLUDE] section
            if (aResolveMode == ResolveMode.NESTED) {
                resolveTokenNestedInclude(aToken, aResolveMode, aFile);
            }
            // otherwise nothing to do, included files are already collected in mParseTreeRoots
        } else if (tokenType == CfgLexer.STRING4) {
            // Quoted string in [ORDERED_INCLUDE] section
            // this is the only non-hidden token in [ORDERED_INCLUDE] section
            resolveTokenNestedInclude(aToken, aResolveMode, aFile);
        } else {
            final String tokenText = aToken.getText();
            mSb.append(tokenText != null ? tokenText : "");
        }
    }

    /**
     * Include file is changed to its content
     * @param aToken token to resolve or print
     * @param aResolveMode mode of resolving
     * @param aFile the parse tree of this file to print
     *                        needed only if aResolveMode != NO_RESOLVING, in case of [ORDERED_INCLUDE]
     */
    private void resolveTokenNestedInclude(final Token aToken, final ResolveMode aResolveMode, final Path aFile) {
        // token text is the file name with quotes, which will be included in place
        final String tokenText = aToken.getText();
        // remove quotes
        final String filename = tokenText.replaceAll("^\"|\"$", "");
        // filename is a relative file name to aFile, we need the absolute file name,
        // because absolute file names are used as keys in aParseTreeRoots
        final Path absolutePath = getAbsolutePath(aFile, filename);
        final CfgParseResult cfgParseResult = mCfgParseResults.get(absolutePath);
        if (cfgParseResult != null) {
            printResolved(absolutePath, cfgParseResult.getParseTreeRoot(), cfgParseResult.getTokens(),
                    aResolveMode);
        } else {
            // include file is missing from mCfgParseResults, so the included cfg file is not parsed
            // in ConfigFileHandler.readFromFile()
            ErrorReporter.INTERNAL_ERROR("ParseTreePrinter.resolveTokenNestedInclude(): cfgParseResult == null");
        }
    }

    /**
     * Converts relative filename to absolute path
     * @param aBaseFile the file to be used as the base of the full path
     * @param aFilename filename to convert
     * @return the converted absolute path of the given relative filename
     */
    private static Path getAbsolutePath(final Path aBaseFile, final String aFilename) {
        final String absoluteFilename = PathConverter.getAbsolutePath(aBaseFile.toOSString(), aFilename);
        if (absoluteFilename == null) {
            return null;
        }
        return new Path(absoluteFilename);
    }

    /**
     * @param aToken token to check
     * @return true, if and only if token type is a macro, for example: \$a, \${a}
     */
    private static boolean isMacro(final int aTokenType) {
        return aTokenType == CfgLexer.MACRO1 || aTokenType == CfgLexer.MACRO5 || aTokenType == CfgLexer.MACRO6
                || aTokenType == CfgLexer.MACRO7 || aTokenType == CfgLexer.MACRO9 || aTokenType == CfgLexer.MACRO10
                || aTokenType == CfgLexer.MACRO11 || aTokenType == CfgLexer.MACRO12;
    }

    /**
     * @param aToken token to check
     * @return true, if and only if token type is a typed macro, for example: ${a, float}
     */
    private static boolean isTypedMacro(final int aTokenType) {
        return aTokenType == CfgLexer.MACRO_HOSTNAME1 || aTokenType == CfgLexer.MACRO_INT1
                || aTokenType == CfgLexer.MACRO_EXP_CSTR1 || aTokenType == CfgLexer.MACRO_FLOAT1
                || aTokenType == CfgLexer.MACRO_ID5 || aTokenType == CfgLexer.MACRO_INT5
                || aTokenType == CfgLexer.MACRO_BOOL5 || aTokenType == CfgLexer.MACRO_FLOAT5
                || aTokenType == CfgLexer.MACRO_EXP_CSTR5 || aTokenType == CfgLexer.MACRO_BSTR5
                || aTokenType == CfgLexer.MACRO_HSTR5 || aTokenType == CfgLexer.MACRO_OSTR5
                || aTokenType == CfgLexer.MACRO_BINARY5 || aTokenType == CfgLexer.MACRO_HOSTNAME5
                || aTokenType == CfgLexer.MACRO_EXP_CSTR6 || aTokenType == CfgLexer.MACRO_INT7
                || aTokenType == CfgLexer.MACRO_ID7 || aTokenType == CfgLexer.MACRO_EXP_CSTR7
                || aTokenType == CfgLexer.MACRO_ID8 || aTokenType == CfgLexer.MACRO_ID9
                || aTokenType == CfgLexer.MACRO_INT9 || aTokenType == CfgLexer.MACRO_BOOL9
                || aTokenType == CfgLexer.MACRO_FLOAT9 || aTokenType == CfgLexer.MACRO_EXP_CSTR9
                || aTokenType == CfgLexer.MACRO_BSTR9 || aTokenType == CfgLexer.MACRO_HSTR9
                || aTokenType == CfgLexer.MACRO_OSTR9 || aTokenType == CfgLexer.MACRO_BINARY9
                || aTokenType == CfgLexer.MACRO_ID10 || aTokenType == CfgLexer.MACRO_HOSTNAME10
                || aTokenType == CfgLexer.MACRO_BOOL11 || aTokenType == CfgLexer.MACRO_ID11
                || aTokenType == CfgLexer.MACRO_INT11 || aTokenType == CfgLexer.MACRO_EXP_CSTR11;
    }

    /**
     * Gets the value of a macro or an environment variable
     * @param aDefinition macro or environment variable
     * @return macro or environment variable value, or null if there is no such definition
     */
    private String getDefinitionValue(final String aDefinition) {
        if (mDefinitions != null && mDefinitions.containsKey(aDefinition)) {
            return mDefinitions.get(aDefinition).getValue();
        } else if (mEnvVariables != null && mEnvVariables.containsKey(aDefinition)) {
            return mEnvVariables.get(aDefinition);
        } else {
            return null;
        }
    }

    /**
     * Extracts macro name from macro string
     * @param aMacroString macro string, for example: \$a, \${a}
     * @return extracted macro name without extra characters, for example: a
     */
    private static String getMacroName(final String aMacroString) {
        final Matcher m = PATTERN_MACRO.matcher(aMacroString);
        if (m.find()) {
            return m.group(1);
        } else {
            return null;
        }
    }

    /**
     * Extracts macro name from typed macro string
     * @param aMacroString macro string, for example: \${a, float}
     * @return extracted macro name without extra characters, for example: a
     */
    private static String getTypedMacroName(final String aMacroString) {
        final Matcher m = PATTERN_TYPED_MACRO.matcher(aMacroString);
        if (m.find()) {
            return m.group(1);
        } else {
            return null;
        }
    }

    /**
     * Gets the macro value string of a macro (without type)
     * @param aMacroToken the macro token
     * @return the macro value string
     *         or "" if macro is invalid. In this case an error marker is also created
     */
    private String getMacroValue(final Token aMacroToken) {
        final String definition = getMacroName(aMacroToken.getText());
        final String value = getDefinitionValue(definition);
        if (value == null) {
            return "";
        }
        return value;
    }

    /**
     * Gets the macro value string of a macro (with type)
     * @param aMacroToken the macro token
     * @return the macro value string
     *         or "" if macro is invalid. In this case an error marker is also created
     */
    private String getTypedMacroValue(Token aMacroToken) {
        final String definition = getTypedMacroName(aMacroToken.getText());
        final String value = getDefinitionValue(definition);
        if (value == null) {
            return "";
        }
        return value;
    }

}