com.opengamma.elsql.ElSqlParser.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.elsql.ElSqlParser.java

Source

/**
 * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.elsql;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

/**
 * A parse of elsql formatted SQL.
 * <p>
 * The parser reads the file line by line and creates the named fragments of SQL for later use.
 * The format is whitespace-aware, with indentation defining blocks (where curly braces would be used in Java).
 * <p>
 * This class is mutable and intended for use by a single thread.
 */
final class ElSqlParser {

    /**
     * The regex for @NAME(identifier).
     */
    private static final Pattern NAME_PATTERN = Pattern.compile("[ ]*[@]NAME[(]([A-Za-z0-9_]+)[)][ ]*");
    /**
     * The regex for @AND(identifier).
     */
    private static final Pattern AND_PATTERN = Pattern
            .compile("[ ]*[@]AND[(]([:][A-Za-z0-9_]+)" + "([ ]?[=][ ]?[A-Za-z0-9_]+)?" + "[)][ ]*");
    /**
     * The regex for @OR(identifier).
     */
    private static final Pattern OR_PATTERN = Pattern
            .compile("[ ]*[@]OR[(]([:][A-Za-z0-9_]+)" + "([ ]?[=][ ]?[A-Za-z0-9_]+)?" + "[)][ ]*");
    /**
     * The regex for @IF(identifier).
     */
    private static final Pattern IF_PATTERN = Pattern
            .compile("[ ]*[@]IF[(]([:][A-Za-z0-9_]+)" + "([ ]?[=][ ]?[A-Za-z0-9_]+)?" + "[)][ ]*");
    /**
     * The regex for @INCLUDE(key)
     */
    private static final Pattern INCLUDE_PATTERN = Pattern.compile("[@]INCLUDE[(]([:]?[A-Za-z0-9_]+)[)](.*)");
    /**
     * The regex for @PAGING(offsetVariable,fetchVariable)
     */
    private static final Pattern PAGING_PATTERN = Pattern
            .compile("[@]PAGING[(][:]([A-Za-z0-9_]+)[ ]?[,][ ]?[:]([A-Za-z0-9_]+)[)](.*)");
    /**
     * The regex for @OFFSETFETCH(offsetVariable,fetchVariable)
     */
    private static final Pattern OFFSET_FETCH_PATTERN = Pattern
            .compile("[@]OFFSETFETCH[(][:]([A-Za-z0-9_]+)[ ]?[,][ ]?[:]([A-Za-z0-9_]+)[)](.*)");
    /**
     * The regex for @FETCH(fetchVariable)
     */
    private static final Pattern FETCH_PATTERN = Pattern.compile("[@]FETCH[(][:]([A-Za-z0-9_]+)[)](.*)");
    /**
     * The regex for @FETCH(numberRows)
     */
    private static final Pattern FETCH_ROWS_PATTERN = Pattern.compile("[@]FETCH[(]([0-9]+)[)](.*)");
    /**
     * The regex for text :variable text
     */
    private static final Pattern VARIABLE_PATTERN = Pattern.compile("([^:])*([:][A-Za-z0-9_]+)(.*)");

    /**
     * The input.
     */
    private final List<Line> _lines = new ArrayList<Line>();
    /**
     * The parsed output.
     */
    private Map<String, NameSqlFragment> _namedFragments = new LinkedHashMap<String, NameSqlFragment>();

    /**
     * Creates the parser.
     * 
     * @param lines  the lines, not null
     */
    ElSqlParser(List<String> lines) {
        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i);
            _lines.add(new Line(line, i + 1));
        }
    }

    //-------------------------------------------------------------------------
    Map<String, NameSqlFragment> parse() {
        rejectTabs();
        parseNamedSections();
        return _namedFragments;
    }

    private void rejectTabs() {
        for (Line line : _lines) {
            if (line.containsTab()) {
                throw new IllegalArgumentException("Tab character not permitted: " + line);
            }
        }
    }

    private void parseNamedSections() {
        ContainerSqlFragment containerFragment = new ContainerSqlFragment();
        parseContainerSection(containerFragment, _lines.listIterator(), -1);
    }

    private void parseContainerSection(ContainerSqlFragment container, ListIterator<Line> lineIterator,
            int indent) {
        while (lineIterator.hasNext()) {
            Line line = lineIterator.next();
            if (line.isComment()) {
                lineIterator.remove();
                continue;
            }
            if (line.indent() <= indent) {
                lineIterator.previous();
                return;
            }
            String trimmed = line.lineTrimmed();
            if (trimmed.startsWith("@NAME")) {
                Matcher nameMatcher = NAME_PATTERN.matcher(trimmed);
                if (nameMatcher.matches() == false) {
                    throw new IllegalArgumentException("@NAME found with invalid format: " + line);
                }
                NameSqlFragment nameFragment = new NameSqlFragment(nameMatcher.group(1));
                parseContainerSection(nameFragment, lineIterator, line.indent());
                if (nameFragment.getFragments().size() == 0) {
                    throw new IllegalArgumentException("@NAME found with no subsequent indented lines: " + line);
                }
                container.addFragment(nameFragment);
                _namedFragments.put(nameFragment.getName(), nameFragment);

            } else if (indent < 0) {
                throw new IllegalArgumentException(
                        "Invalid fragment found at root level, only @NAME is permitted: " + line);

            } else if (trimmed.startsWith("@PAGING")) {
                Matcher pagingMatcher = PAGING_PATTERN.matcher(trimmed);
                if (pagingMatcher.matches() == false) {
                    throw new IllegalArgumentException("@PAGING found with invalid format: " + line);
                }
                PagingSqlFragment whereFragment = new PagingSqlFragment(pagingMatcher.group(1),
                        pagingMatcher.group(2));
                parseContainerSection(whereFragment, lineIterator, line.indent());
                if (whereFragment.getFragments().size() == 0) {
                    throw new IllegalArgumentException("@PAGING found with no subsequent indented lines: " + line);
                }
                container.addFragment(whereFragment);

            } else if (trimmed.startsWith("@WHERE")) {
                if (trimmed.equals("@WHERE") == false) {
                    throw new IllegalArgumentException("@WHERE found with invalid format: " + line);
                }
                WhereSqlFragment whereFragment = new WhereSqlFragment();
                parseContainerSection(whereFragment, lineIterator, line.indent());
                if (whereFragment.getFragments().size() == 0) {
                    throw new IllegalArgumentException("@WHERE found with no subsequent indented lines: " + line);
                }
                container.addFragment(whereFragment);

            } else if (trimmed.startsWith("@AND")) {
                Matcher andMatcher = AND_PATTERN.matcher(trimmed);
                if (andMatcher.matches() == false) {
                    throw new IllegalArgumentException("@AND found with invalid format: " + line);
                }
                AndSqlFragment andFragment = new AndSqlFragment(andMatcher.group(1),
                        StringUtils.strip(andMatcher.group(2), " ="));
                parseContainerSection(andFragment, lineIterator, line.indent());
                if (andFragment.getFragments().size() == 0) {
                    throw new IllegalArgumentException("@AND found with no subsequent indented lines: " + line);
                }
                container.addFragment(andFragment);

            } else if (trimmed.startsWith("@OR")) {
                Matcher orMatcher = OR_PATTERN.matcher(trimmed);
                if (orMatcher.matches() == false) {
                    throw new IllegalArgumentException("@OR found with invalid format: " + line);
                }
                OrSqlFragment orFragment = new OrSqlFragment(orMatcher.group(1),
                        StringUtils.strip(orMatcher.group(2), " ="));
                parseContainerSection(orFragment, lineIterator, line.indent());
                if (orFragment.getFragments().size() == 0) {
                    throw new IllegalArgumentException("@OR found with no subsequent indented lines: " + line);
                }
                container.addFragment(orFragment);

            } else if (trimmed.startsWith("@IF")) {
                Matcher ifMatcher = IF_PATTERN.matcher(trimmed);
                if (ifMatcher.matches() == false) {
                    throw new IllegalArgumentException("@IF found with invalid format: " + line);
                }
                IfSqlFragment ifFragment = new IfSqlFragment(ifMatcher.group(1),
                        StringUtils.strip(ifMatcher.group(2), " ="));
                parseContainerSection(ifFragment, lineIterator, line.indent());
                if (ifFragment.getFragments().size() == 0) {
                    throw new IllegalArgumentException("@IF found with no subsequent indented lines: " + line);
                }
                container.addFragment(ifFragment);

            } else {
                parseLine(container, line);
            }
        }
    }

    private void parseLine(ContainerSqlFragment container, Line line) {
        String trimmed = line.lineTrimmed();
        if (trimmed.length() == 0) {
            return;
        }
        if (trimmed.contains("@INCLUDE")) {
            parseIncludeTag(container, line);

        } else if (trimmed.contains("@LIKE")) {
            parseLikeTag(container, line);

        } else if (trimmed.contains("@OFFSETFETCH")) {
            parseOffsetFetchTag(container, line);

        } else if (trimmed.contains("@FETCH")) {
            parseFetchTag(container, line);

        } else if (trimmed.startsWith("@")) {
            throw new IllegalArgumentException("Unknown tag at start of line: " + line);

        } else {
            TextSqlFragment textFragment = new TextSqlFragment(trimmed);
            container.addFragment(textFragment);
        }
    }

    private void parseIncludeTag(ContainerSqlFragment container, Line line) {
        String trimmed = line.lineTrimmed();
        int pos = trimmed.indexOf("@INCLUDE");
        TextSqlFragment textFragment = new TextSqlFragment(trimmed.substring(0, pos));
        Matcher includeMatcher = INCLUDE_PATTERN.matcher(trimmed.substring(pos));
        if (includeMatcher.matches() == false) {
            throw new IllegalArgumentException("@INCLUDE found with invalid format: " + line);
        }
        IncludeSqlFragment includeFragment = new IncludeSqlFragment(includeMatcher.group(1));
        String remainder = includeMatcher.group(2);
        container.addFragment(textFragment);
        container.addFragment(includeFragment);

        Line subLine = new Line(remainder, line.lineNumber());
        parseLine(container, subLine);
    }

    private void parseLikeTag(ContainerSqlFragment container, Line line) {
        String trimmed = line.lineTrimmed();
        int pos = trimmed.indexOf("@LIKE");
        TextSqlFragment beforeTextFragment = new TextSqlFragment(trimmed.substring(0, pos));
        trimmed = trimmed.substring(pos + 5);
        String content = trimmed;
        int end = trimmed.indexOf("@ENDLIKE");
        String remainder = "";
        if (end >= 0) {
            content = trimmed.substring(0, end);
            remainder = trimmed.substring(end + 8);
        }
        TextSqlFragment contentTextFragment = new TextSqlFragment(content);
        Matcher matcher = VARIABLE_PATTERN.matcher(content);
        if (matcher.matches() == false) {
            throw new IllegalArgumentException("@LIKE found with invalid format: " + line);
        }
        LikeSqlFragment likeFragment = new LikeSqlFragment(matcher.group(2));
        container.addFragment(beforeTextFragment);
        container.addFragment(likeFragment);
        likeFragment.addFragment(contentTextFragment);

        Line subLine = new Line(remainder, line.lineNumber());
        parseLine(container, subLine);
    }

    private void parseOffsetFetchTag(ContainerSqlFragment container, Line line) {
        String trimmed = line.lineTrimmed();
        int pos = trimmed.indexOf("@OFFSETFETCH");
        TextSqlFragment textFragment = new TextSqlFragment(trimmed.substring(0, pos));
        String offsetVariable = "paging_offset";
        String fetchVariable = "paging_fetch";
        String remainder = trimmed.substring(pos + 12);
        if (trimmed.substring(pos).startsWith("@OFFSETFETCH(")) {
            Matcher matcher = OFFSET_FETCH_PATTERN.matcher(trimmed.substring(pos));
            if (matcher.matches() == false) {
                throw new IllegalArgumentException("@OFFSETFETCH found with invalid format: " + line);
            }
            offsetVariable = matcher.group(1);
            fetchVariable = matcher.group(2);
            remainder = matcher.group(3);
        }
        OffsetFetchSqlFragment pagingFragment = new OffsetFetchSqlFragment(offsetVariable, fetchVariable);
        container.addFragment(textFragment);
        container.addFragment(pagingFragment);

        Line subLine = new Line(remainder, line.lineNumber());
        parseLine(container, subLine);
    }

    private void parseFetchTag(ContainerSqlFragment container, Line line) {
        String trimmed = line.lineTrimmed();
        int pos = trimmed.indexOf("@FETCH");
        TextSqlFragment textFragment = new TextSqlFragment(trimmed.substring(0, pos));
        String fetchVariable = "paging_fetch";
        String remainder = trimmed.substring(pos + 6);
        if (trimmed.substring(pos).startsWith("@FETCH(")) {
            Matcher matcher = FETCH_PATTERN.matcher(trimmed.substring(pos));
            Matcher matcherRows = FETCH_ROWS_PATTERN.matcher(trimmed.substring(pos));
            if (matcher.matches()) {
                fetchVariable = matcher.group(1);
                remainder = matcher.group(2);
            } else if (matcherRows.matches()) {
                fetchVariable = matcherRows.group(1);
                remainder = matcherRows.group(2);
            } else {
                throw new IllegalArgumentException("@FETCH found with invalid format: " + line);
            }
        }
        OffsetFetchSqlFragment pagingFragment = new OffsetFetchSqlFragment(fetchVariable);
        container.addFragment(textFragment);
        container.addFragment(pagingFragment);

        Line subLine = new Line(remainder, line.lineNumber());
        parseLine(container, subLine);
    }

    //-------------------------------------------------------------------------
    static final class Line {
        private final String _line;
        private final String _trimmed;
        private final int _lineNumber;

        Line(String line, int lineNumber) {
            _line = line;
            _trimmed = line.trim();
            _lineNumber = lineNumber;
        }

        String line() {
            return _line;
        }

        String lineTrimmed() {
            return _trimmed;
        }

        int lineNumber() {
            return _lineNumber;
        }

        boolean containsTab() {
            return _line.contains("\t");
        }

        boolean isComment() {
            return _trimmed.startsWith("--") || _trimmed.length() == 0;
        }

        int indent() {
            for (int i = 0; i < _line.length(); i++) {
                if (_line.charAt(i) != ' ') {
                    return i;
                }
            }
            return _line.length();
        }

        @Override
        public String toString() {
            return "Line " + lineNumber();
        }
    }

}