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

com.yahoo.config.codegen.DefParser Maven / Gradle / Ivy

// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.config.codegen;

import java.io.*;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class generates a tree of CNodes from a .def file.
 *
 * @author gjoranv
 * @author hmusum
 */
public class DefParser {
    public static final String DEFAULT_PACKAGE_PREFIX = "com.yahoo.";

    static final Pattern commentPattern = Pattern.compile("^\\s*#+\\s*(.*?)\\s*$");
    public static final Pattern versionPattern = Pattern.compile("^(version\\s*=\\s*)([0-9][0-9-]*)$");
    // Namespace/package must start with a letter, since Java (Java language Spec, section  3.8) and C++ identifiers cannot start with a digit
    public static final Pattern namespacePattern = getNamespacePattern("namespace");
    public static final Pattern packagePattern = getNamespacePattern("package");

    private static Pattern getNamespacePattern(String directive) {
        return Pattern.compile("^(" + directive + "\\s*=\\s*)(([a-z][a-z0-9_]*)+([.][a-z][a-z0-9_]*)*)$");
    }

    private final BufferedReader reader;
    private final String name;
    private InnerCNode root = null;
    private NormalizedDefinition normalizedDefinition = null;


	private String comment = "";

    /**
     * Creates a new parser for a .def file with the given name and that can be accessed by the given reader.
     *
     * @param name  The name of the .def file (not including version number and the '.def' suffix).
     * @param defReader  A reader to the .def file.
     */
    public DefParser(String name, Reader defReader) {
        this.name = createName(name);
        if (defReader == null) {
            throw new CodegenRuntimeException("Must have a non-null reader for a .def file.");
        }
        if (defReader instanceof BufferedReader) {
            reader = (BufferedReader)defReader;
        } else {
            reader = new BufferedReader(defReader);
        }
    }

    // If name contains namespace, return just name
    private String createName(String name) {
        if (name.contains(".")) {
            return name.substring(name.lastIndexOf(".") + 1);
        } else {
            return name;
        }
    }

    /**
     * Parses the .def file upon the initial call. Subsequent calls returns the result from the initial call.
     *
     * @return  A tree of CNodes representing this instance's .def file.
     * @throws CodegenRuntimeException upon errors.
     */
    public InnerCNode getTree() throws CodegenRuntimeException {
        try {
            if (root == null) parse();
        } catch (DefParserException | IOException e) {
            throw new CodegenRuntimeException("Error parsing or reading config definition." + e.getMessage(), e);
        }
        return root;
    }

    /**
     * Parses the input from the reader and builds a tree of CNodes representing the .def file.
     *
     * @throws IOException upon reader errors.
     * @throws DefParserException upon parsing errors.
     */
    void parse() throws IOException, DefParserException {
        root = new InnerCNode(name);
        normalizedDefinition = new NormalizedDefinition();

        String s;
        List originalInput = new ArrayList<>();
        while ((s = reader.readLine()) != null) {
            originalInput.add(s);
        }
        reader.close();


        // Parse and build tree of the original input
        parseLines(root, originalInput, normalizedDefinition);
        root.setMd5(normalizedDefinition.generateMd5Sum());
    }

    /**
     * Parses one line from def-file and adds it to the tree.
     * TODO: Method too long!
     *
     * @param root     The root CNode in the tree.
     * @param line     A line from the def-file.
     * @param nd       A NormalizedDefinition object
     * @throws IllegalArgumentException upon error in line.
     */
    private void parseLine(CNode root, String line, NormalizedDefinition nd) throws IllegalArgumentException {
        line = NormalizedDefinition.normalize(line);
        line = line.trim();
        if (line.length() == 0) {
            // If having empty lines in between comments, that is logically a break in the comment too
            if (!comment.isEmpty()) {
                comment += "\n";
            }
            return;
        }
        Matcher commentMatch = commentPattern.matcher(line);
        if (commentMatch.matches()) {
            parseCommentLine(commentMatch);
            return;
        }
        Matcher versionMatch = versionPattern.matcher(line);
        if (versionMatch.matches()) {
            parseVersionLine(versionMatch);
            return;
        }
        Matcher namespaceMatcher = namespacePattern.matcher(line);
        if (namespaceMatcher.matches()) {
            parseNamespaceLine(namespaceMatcher.group(2));
            nd.addNormalizedLine(line);
            return;
        }
        Matcher packageMatcher = packagePattern.matcher(line);
        if (packageMatcher.matches()) {
            parsePackageLine(packageMatcher.group(2));
            nd.addNormalizedLine(line);
            return;
        }
        // Only add lines that are not version, namespace or comment lines
        nd.addNormalizedLine(line);
        DefLine defLine = new DefLine(line);
        root.setLeaf(root.getName() + "." + defLine.getName(), defLine, comment);
        comment = "";
    }

    private void parseCommentLine(Matcher commentMatch) {
        if (!comment.isEmpty()) comment += "\n";
        String addition = commentMatch.group(1);
        if (addition.isEmpty()) addition = " ";
        comment += addition;
    }

    private void parseVersionLine(Matcher matcher) {
        root.setVersion(matcher.group(2));
        root.setComment(comment);
        comment = "";
    }

    private void parseNamespaceLine(String namespace) {
        if (namespace.startsWith(DEFAULT_PACKAGE_PREFIX))
            throw new IllegalArgumentException("Please use 'package' instead of 'namespace'.");
        root.setNamespace(namespace);
        root.setComment(comment);
        comment = "";
    }

    private void parsePackageLine(String defPackage) {
        root.setPackage(defPackage);
        root.setComment(comment);
        comment = "";
    }

    void parseLines(CNode root, List defLines, NormalizedDefinition nd) throws DefParserException {
        DefParserException failure = null;
        int lineNumber = 1;
        for (String line : defLines) {
            try {
                parseLine(root, line, nd);
                lineNumber++;
            } catch (IllegalArgumentException e) {
                String msg = "Error when parsing line " + lineNumber + ": " + line + "\n" + e.getMessage();
                failure = new DefParserException(msg, e);
                break;
            }
        }

        if (failure != null) {
            throw (failure);
        }
    }

    public NormalizedDefinition getNormalizedDefinition() {
 		return normalizedDefinition;
 	}

    /**
     * For debugging - dump the tree from the given root to System.out.
     */
    public static void dumpTree(CNode root, String indent) {
        StringBuilder sb = new StringBuilder(indent + root.getName());
        if (root instanceof LeafCNode) {
            LeafCNode leaf = ((LeafCNode)root);
            if (leaf.getDefaultValue() != null) {
                sb.append(" = ").append(((LeafCNode)root).getDefaultValue().getValue());
            }
        }
        System.out.println(sb.toString());
        if (!root.getComment().isEmpty()) {
            String comment = root.getComment();
            if (comment.contains("\n")) {
                comment = comment.substring(0, comment.indexOf("\n")) + "...";
            }
            if (comment.length() > 60) {
                comment = comment.substring(0, 57) + "...";
            }
            System.out.println(indent + "    comment: " + comment);
        }
        CNode[] children = root.getChildren();
        for (CNode c : children) {
            dumpTree(c, indent + "  ");
        }

    }

    class DefParserException extends Exception {
        DefParserException(String s, Throwable cause) {
            super(s, cause);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy