org.ganges.expressionengine.grammar.DefaultXMLGrammar.java Source code

Java tutorial

Introduction

Here is the source code for org.ganges.expressionengine.grammar.DefaultXMLGrammar.java

Source

/**
 * Created on Jan 27, 2006
 *
 * Copyright (c) 2005 Ganges Organisation all rights reserved.
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU Lesser General Public License as
 * published by the Free Software Foundation. See the GNU Lesser General Public License for more details.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.ganges.expressionengine.grammar;

import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ganges.expressionengine.grammar.rules.IProductionRule;
import org.ganges.expressionengine.grammar.rules.ProductionRule;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * This is the default XML based grammar implementation. It read the production 
 * rules from a XML file, and populate its rule sets. Use of XML makes it highly 
 * customizable without making any change to code.
 * 
 * TODO
 *    Can we remove operators array and use precedence map keys?
 *    DefaultXMLGrammar can be loosely coupled with Compiler, i.e. can be picked from 
 *  configuration.
 * 
 * @author Parmod Kamboj
 * @author Mohit Gupta
 * @version 1.0
 */
public class DefaultXMLGrammar implements Grammar {

    private static Log LOGGER = LogFactory.getLog(DefaultXMLGrammar.class);

    /**
     * Static instance of the grammar.
     */
    private static final DefaultXMLGrammar instance = new DefaultXMLGrammar();

    /**
     * File path for grammar configuration.
     */
    public static final String FILE_PATH = "grammar.xml";

    /**
     * This is the constant name for production rules tag in XML
     */
    public static final String PRODUCTION_RULES = "productionRules";

    /**
     * This is the constant name for binary operators tag in XML
     */
    private static final String BINARY_OPERATORS = "binaryOperators";

    /**
     * This is the constant name for unary operators tag in XML
     */
    public static final String UNARY_OPERATORS = "unaryOperators";

    /**
     * This is the constant name for functions tag in XML
     */
    public static final String FUNCTIONS = "functions";

    /**
     * This is the constant name for delimiters tag in XML
     */
    public static final String DELIMITERS = "delimiters";

    /**
     * This is the constant name for brackets tag in XML
     */
    public static final String BRACKETS = "brackets";

    /**
     * This is the constant name for ignore blank tag in XML
     */
    public static final String IGNORE_BLANK = "ignoreBlank";

    /**
     * This is the constant name for true tag in XML
     */
    public static final String TRUE = "true";

    /**
     * This is the constant name for name tag in XML
     */
    public static final String NAME = "name";

    /**
     * This is the constant name for bracket tag in XML
     */
    public static final String BRACKET = "bracket";

    /**
     * This is the constant name for left tag in XML
     */
    public static final String LEFT = "left";

    /**
     * This is the constant name for right tag in XML
     */
    public static final String RIGHT = "right";

    /**
     * This is the constant name for operator tag in XML
     */
    public static final String OPERATOR = "operator";

    /**
     * This is the constant name for precedence tag in XML
     */
    public static final String PRECEDENCE = "precedence";

    /**
     * This is the constant name for delimiter tag in XML
     */
    public static final String DELIMITER = "delimiter";

    /**
     * This is the constant name for production rule tag in XML
     */
    public static final String PRODUCTION_RULE = "productionRule";

    /**
     * This is the constant name for approachable pattern tag in XML
     */
    public static final String APPROACHABLE_PATTERN = "approchablePattern";

    /**
     * This is the constant name for allowed pattern tag in XML
     */
    public static final String ALLOWED_PATTERN = "allowedPattern";

    /**
     * Array container for production rules of this grammar.
     */
    private IProductionRule[] productionRules;

    /**
     * Array container of unary operators.
     */
    private String[] unaryOperators;

    /**
     * Array container of binary operators.
     */
    private String[] binaryOperators;

    /**
     * Array container of functions.
     */
    private String[] functions;

    /**
     * Array container of delimiters.
     */
    private String[] delimiters;

    /**
     * Array container of bracket pair
     */
    private String[][] brackets;

    /**
     * Map container for precedence of binary operators, binary operator as key and 
     * precedence as value.
     */
    private Map binaryPrecedences;

    /**
     * Map container for precedence of unary operators, unary operator as key and 
     * precedence as value.
     */
    private Map unaryPrecedences;

    /**
     * This is the indicator whether to ignore the blank or not during parsing.
     */
    private boolean ignoreBlank;

    /**
     * Constructs the grammar
     */
    private DefaultXMLGrammar() {
        configure();
    }

    /**
     * Gets the shared instance of the grammar.
     * 
     * @return the singleton instance of the grammar.
     */
    public static final DefaultXMLGrammar getInstance() {
        return instance;
    }

    /**
     * Checks whether the token is a delimiter or not.
     * 
     * @param token the token
     * @return <code>true</code> if the token is delimiter <code>false</code>
     *         otherwise.
     */
    public boolean isDelimiter(ExpressionToken token) {
        return isDelimiter(token.getValue());
    }

    /**
     * Checks whether the token is a delimiter or not.
     * 
     * @param token the token
     * @return <code>true</code> if the token is delimiter <code>false</code>
     *         otherwise.
     */
    public boolean isDelimiter(String token) {
        return search(delimiters, token) != -1;
    }

    /**
     * Checks whether the given token is approachable using any of the pattern
     * or not. 
     * 
     * Given token can be partially or fully constructed token during 
     * parsing process. Parser generally calls this method to check whether the 
     * current token can be combined with next character of expression to form
     * some meaningful token or not. If not, then it utilize the existing 
     * collected characters as one token, otherwise it keep collecting 
     * characters.
     *  
     * @param token the token, partially or full constructed, to check whether 
     *         it can approach to any expression token pattern or not.
     * @return <code>true</code> if the token pattern is approachable
     *         <code>false</code> otherwise.
     */
    public boolean isApproachable(String token) {
        int length = productionRules == null ? 0 : productionRules.length;
        boolean result = false;

        for (int i = 0; i < length; i++) {
            result = productionRules[i].isApproaching(token);

            if (result) {
                break;
            }
        }

        return result;
    }

    /**
     * Checks whether the token is allowed or not. 
     * 
     * A token is fully constructed token. Parser generally calls this method to 
     * check whether the current token is a valid token as per the production 
     * rules or not.
     * 
     * @param token the token which is to be checked for its validity
     * @return <code>true</code> if the token is allowed <code>false</code>
     *         otherwise.
     */
    public boolean isAllowed(String token) {
        int length = productionRules == null ? 0 : productionRules.length;
        boolean result = false;

        for (int i = 0; i < length; i++) {
            result = productionRules[i].isAllowed(token);

            if (result) {
                break;
            }
        }

        return result;
    }

    /**
     * Checks whether to ignore the blanks in expression or not. It tells the 
     * parser whether to exclude the extra blanks while parsing or not.
     * 
     * @return <code>true</code> if parser wants to exclude the blanks 
     *          <code>false</code> otherwise.
     */
    public boolean isIgnoreBlank() {
        return ignoreBlank;
    }

    /**
     * Checks whether the given token is an operator or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is an operator
     *         <code>false</code> otherwise
     */
    public boolean isOperator(ExpressionToken token) {
        return isOperator(token.getValue());
    }

    /**
     * Checks whether the given token is an operator or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is an operator
     *         <code>false</code> otherwise
     */
    public boolean isOperator(String token) {
        return search(binaryOperators, token) != -1 || search(unaryOperators, token) != -1 || isFunction(token);
    }

    /**
     * Checks whether the given token is a binary operator or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is a binary operator
     *         <code>false</code> otherwise
     */
    public boolean isBinaryOperator(ExpressionToken token) {
        return isBinaryOperator(token.getValue());
    }

    /**
     * Checks whether the given token is a binary operator or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is a binary operator
     *         <code>false</code> otherwise
     */
    public boolean isBinaryOperator(String token) {
        return search(binaryOperators, token) != -1;
    }

    /**
     * Checks whether the token is an function or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is an function
     *         <code>false</code> otherwise
     */
    public boolean isFunction(ExpressionToken token) {
        return isFunction(token.getValue());
    }

    /**
     * Checks whether the token is an function or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is an function
     *         <code>false</code> otherwise
     */
    public boolean isFunction(String token) {
        return search(functions, token) != -1;
    }

    /**
     * Checks whether the given operator is a unary operator or not.
     * 
     * @param operator the operator to check
     * @return <code>true</code> if the operator is used as unary operator
     *         <code>false</code> otherwise.
     */
    public boolean isUnary(ExpressionToken operator) {
        return isUnary(operator.getValue());
    }

    /**
     * Checks whether the given operator is a unary operator or not.
     * 
     * @param operator the operator to check
     * @return <code>true</code> if the operator is used as unary operator
     *         <code>false</code> otherwise.
     */
    public boolean isUnary(String operator) {
        return search(unaryOperators, operator) != -1 || isFunction(operator);
    }

    /**
     * Checks whether the token is a left bracket or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token can be used as left bracket
     *         <code>false</code> otherwise.
     */
    public boolean isLeftBracket(ExpressionToken token) {
        return isLeftBracket(token.getValue());
    }

    /**
     * Checks whether the token is a left bracket or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token can be used as left bracket
     *         <code>false</code> otherwise.
     */
    public boolean isLeftBracket(String token) {
        boolean result = false;
        int length = brackets == null ? 0 : brackets.length;

        for (int i = 0; i < length; i++) {
            result = token.equals(brackets[i][0]);

            if (result) {
                break;
            }
        }

        return result;
    }

    /**
     * Checks whether the token is a right bracket or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token can be used as right bracket
     *         <code>false</code> otherwise.
     */
    public boolean isRightBracket(ExpressionToken token) {
        return isRightBracket(token.getValue());
    }

    /**
     * Checks whether the token is a right bracket or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token can be used as right bracket
     *         <code>false</code> otherwise.
     */
    public boolean isRightBracket(String token) {
        boolean result = false;
        int length = brackets == null ? 0 : brackets.length;

        for (int i = 0; i < length; i++) {
            result = token.equals(brackets[i][1]);

            if (result) {
                break;
            }
        }

        return result;
    }

    /**
     * Check whether the given token is a bracket or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is a bracket
     *         <code>false</code> otherwise
     */
    public boolean isBracket(ExpressionToken token) {
        return isBracket(token.getValue());
    }

    /**
     * Check whether the given token is a bracket or not.
     * 
     * @param token the token to check
     * @return <code>true</code> if the token is a bracket
     *         <code>false</code> otherwise
     */
    public boolean isBracket(String token) {
        return isRightBracket(token) || isLeftBracket(token);
    }

    /**
     * Returns the opposite bracket for the given bracket.
     * 
     * @param bracket the bracket for which we need to find the opposite bracket
     * @return the opposite part of bracket w.r.t given bracket
     */
    public String getOppositeBracket(String bracket) {
        String result = null;
        int length = brackets == null ? 0 : brackets.length;

        for (int i = 0; i < length; i++) {
            int index = search(brackets[i], bracket);

            if (index != -1) {
                result = index == 1 ? brackets[i][0] : brackets[i][1];

                break;
            }
        }

        return result;
    }

    /**
     * Gets the precedence order of the given operator
     * 
     * @param operator the operator to check for precedence
     * @param isUnary true if the operator is unary, as an operator can behave 
     *         either as unary or as binary
     * @return the precedence order of the operator
     */
    public int getPrecedenceOrder(ExpressionToken operator, boolean isUnary) {
        return getPrecedenceOrder(operator.getValue(), isUnary);
    }

    /**
     * Gets the precedence order of the given operator
     * 
     * @param operator the operator to check for precedence
     * @param isUnary true if the operator is unary, as an operator can behave 
     *         either as unary or as binary
     * @return the precedence order of the operator
     */
    public int getPrecedenceOrder(String operator, boolean isUnary) {
        return isUnary ? ((Integer) unaryPrecedences.get(operator)).intValue()
                : ((Integer) binaryPrecedences.get(operator)).intValue();
    }

    /**
     * Configures the grammar object with specified XML file
     */
    private void configure() {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setValidating(true);

            DocumentBuilder builder = factory.newDocumentBuilder();

            LOGGER.debug("resourcePath[" + getClass().getClassLoader().getResource(""));
            Document document = builder.parse(getClass().getClassLoader().getResourceAsStream(FILE_PATH));
            Element root = document.getDocumentElement();

            // Extracting production rules
            NodeList nodeList = root.getChildNodes();
            int length = nodeList.getLength();

            for (int i = 0; i < length; i++) {
                Node childNode = nodeList.item(i);
                String nodeName = childNode.getNodeName();

                if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                    if (PRODUCTION_RULES.equals(nodeName)) {
                        buildProductionRules((Element) childNode);
                    } else if (BINARY_OPERATORS.equals(nodeName)) {
                        buildBinaryOperators((Element) childNode);
                        loadBinaryPrecedence((Element) childNode);
                    } else if (UNARY_OPERATORS.equals(nodeName)) {
                        buildUnaryOperators((Element) childNode);
                        loadUnaryPrecedence((Element) childNode);
                    } else if (FUNCTIONS.equals(nodeName)) {
                        buildFunctions((Element) childNode);
                        loadFunctionPrecedence((Element) childNode);
                    } else if (DELIMITERS.equals(nodeName)) {
                        buildDelimiters((Element) childNode);
                    } else if (BRACKETS.equals(nodeName)) {
                        loadBrackets((Element) childNode);
                    } else if (IGNORE_BLANK.equals(nodeName)) {
                        ignoreBlank = TRUE.equals(((Element) childNode).getAttribute(NAME));
                    }
                }
            }
        } catch (Exception ex) {
            throw new RuntimeException("Error while loading the configurations.", ex);
        }
    }

    /**
     * Loads the brackets from the XML configuration
     * 
     * @param childNode
     */
    private void loadBrackets(Element childNode) {
        NodeList childList = childNode.getElementsByTagName(BRACKET);
        int childLength = childList.getLength();
        brackets = new String[childLength][2];

        for (int j = 0; j < childLength; j++) {
            Element subChildNode = (Element) childList.item(j);
            brackets[j][0] = subChildNode.getAttribute(LEFT);
            brackets[j][1] = subChildNode.getAttribute(RIGHT);
        }
    }

    /**
     * Loads the precedence of the binary operators
     * 
     * @param childNode
     */
    private void loadBinaryPrecedence(Element childNode) {
        if (binaryPrecedences == null) {
            binaryPrecedences = new HashMap<String, Integer>();
        }

        loadPrecedence(childNode, binaryPrecedences);
    }

    /**
     * Loads the precedence of the unary operators
     * 
     * @param childNode
     */
    private void loadUnaryPrecedence(Element childNode) {
        if (unaryPrecedences == null) {
            unaryPrecedences = new HashMap<String, Integer>();
        }

        loadPrecedence(childNode, unaryPrecedences);
    }

    /**
     * Loads the precedence of the functions
     * 
     * @param childNode
     */
    private void loadFunctionPrecedence(Element childNode) {
        if (unaryPrecedences == null) {
            unaryPrecedences = new HashMap<String, Integer>();
        }

        loadPrecedence(childNode, unaryPrecedences);
    }

    /**
     * Loads the precedence in the given map.
     * 
     * @param childNode
     *            the operators node
     * @param precedences
     *            the map.
     */
    private void loadPrecedence(Element childNode, Map<String, Integer> precedences) {
        NodeList childList = childNode.getElementsByTagName(OPERATOR);
        int childLength = childList.getLength();

        for (int j = 0; j < childLength; j++) {
            Element subChildNode = (Element) childList.item(j);
            String operator = subChildNode.getAttribute(NAME);
            String precedence = subChildNode.getAttribute(PRECEDENCE);
            precedences.put(operator, new Integer(Integer.parseInt(precedence)));
        }
    }

    /**
     * Builds the delimiters
     * 
     * @param childNode
     *            the node delimiters
     */
    private void buildDelimiters(Element childNode) {
        delimiters = buildArrayByAttribute(childNode, DELIMITER, NAME);
    }

    /**
     * Builds the operators
     * 
     * @param childNode
     *            the node for operators
     */
    private void buildBinaryOperators(Element childNode) {
        binaryOperators = buildArrayByAttribute(childNode, OPERATOR, NAME);
    }

    /**
     * Builds the unary operators
     * 
     * @param childNode
     *            the node for unary operators
     */
    private void buildUnaryOperators(Element childNode) {
        unaryOperators = buildArrayByAttribute(childNode, OPERATOR, NAME);
    }

    /**
     * Builds the functions
     * 
     * @param childNode
     *            the node for unary operators
     */
    private void buildFunctions(Element childNode) {
        functions = buildArrayByAttribute(childNode, OPERATOR, NAME);
    }

    /**
     * Builds the production rules
     * 
     * @param childNode
     *            the node for production rules
     */
    private void buildProductionRules(Element childNode) {
        NodeList childList = childNode.getElementsByTagName(PRODUCTION_RULE);
        int childLength = childList.getLength();
        productionRules = new IProductionRule[childLength];

        for (int j = 0; j < childLength; j++) {
            Element subChildNode = (Element) childList.item(j);
            productionRules[j] = new ProductionRule(subChildNode.getAttribute(NAME),
                    subChildNode.getAttribute(APPROACHABLE_PATTERN), subChildNode.getAttribute(ALLOWED_PATTERN));
        }
    }

    /**
     * Builds the the array of string for attribute passed by the argument for
     * the elements specified by the tag
     * 
     * @param childNode the node delimiters
     * @param attribute the attribute for which to build the string array.
     * @param tag the tag for which the elements are identified.
     * @return the string array.
     */
    private String[] buildArrayByAttribute(Element childNode, String tag, String attribute) {
        NodeList childList = childNode.getElementsByTagName(tag);
        int childLength = childList.getLength();
        String[] array = new String[childLength];

        for (int j = 0; j < childLength; j++) {
            Element subChildNode = (Element) childList.item(j);
            array[j] = subChildNode.getAttribute(attribute);
        }

        return array;
    }

    /**
     * Searches the element in the string array
     * 
     * @param array the string elements array
     * @param element the element to search
     * @return the index of element in array, <code>-1</code> if not found
     */
    private int search(String[] array, String element) {
        int index = -1;
        int length = array == null ? 0 : array.length;

        for (int i = 0; i < length; i++) {
            if (element == null) {
                if (array[i] == null) {
                    index = i;

                    break;
                }
            } else if (element.equals(array[i])) {
                index = i;

                break;
            }
        }

        return index;
    }

    /**
     * Runs the test for grammar.
     */
    public static void main(String[] args) {
        DefaultXMLGrammar grammar = new DefaultXMLGrammar();
        grammar.isOperator("+");
    }

}