org.apache.asterix.extension.grammar.GrammarExtensionMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.asterix.extension.grammar.GrammarExtensionMojo.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.asterix.extension.grammar;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.asterix.external.input.record.CharArrayRecord;
import org.apache.asterix.external.util.ExternalDataConstants;
import org.apache.commons.lang3.StringUtils;
import org.apache.hyracks.algebricks.common.utils.Pair;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

/**
 * a Mojo for creating a grammar extension
 */
@Mojo(name = "grammarix")
public class GrammarExtensionMojo extends AbstractMojo {

    private static final String PARSER_BEGIN = "PARSER_BEGIN";
    private static final String PARSER_END = "PARSER_END";
    private static final char OPEN_BRACE = '{';
    private static final char CLOSE_BRACE = '}';
    private static final char OPEN_ANGULAR = '<';
    private static final char CLOSE_ANGULAR = '>';
    private static final char OPEN_PAREN = '(';
    private static final char CLOSE_PAREN = ')';
    private static final char SEMICOLON = ';';
    private static final List<Character> SIG_SPECIAL_CHARS = Arrays
            .asList(new Character[] { '(', ')', ':', '<', '>', ';', '.' });
    private static final String KWCLASS = "class";
    private static final String KWIMPORT = "import";
    private static final String KWUNIMPORT = "unimport";
    private static final String KWPACKAGE = "package";
    private static final String NEWPRODUCTION = "@new";
    private static final String MERGEPRODUCTION = "@merge";
    private static final String OVERRIDEPRODUCTION = "@override";
    private static final String BEFORE = "before:";
    private static final String AFTER = "after:";
    private static final String REPLACE = "replace";
    private static final String WITH = "with";
    private static final List<String> KEYWORDS = Arrays
            .asList(new String[] { KWCLASS, KWIMPORT, KWPACKAGE, PARSER_BEGIN, PARSER_END });
    private static final List<String> EXTENSIONKEYWORDS = Arrays
            .asList(new String[] { KWIMPORT, KWUNIMPORT, NEWPRODUCTION, OVERRIDEPRODUCTION, MERGEPRODUCTION });
    private static final String REGEX_WS_DOT_SEMICOLON = "\\s|[.]|[;]";
    private static final String REGEX_WS_PAREN = "\\s|[(]|[)]";
    private static final String OPTIONS = "options";
    private CharArrayRecord record = new CharArrayRecord();
    private Position position = new Position();
    private Map<String, Pair<String, String>> extensibles = new HashMap<>();
    private Map<String, String[]> mergeElements = new HashMap<>();
    private List<Pair<String, String>> baseFinals = new ArrayList<>();
    private List<Pair<String, String>> extensionFinals = new ArrayList<>();
    private List<List<String>> imports = new ArrayList<>();
    private String baseClassName;
    private String baseClassDef;
    private String optionsBlock;
    private boolean read = false;
    private boolean shouldReplace = false;
    private String oldWord = null;
    private String newWord = null;

    @Parameter(property = "grammarix.base")
    private String base;

    @Parameter(property = "grammarix.gbase")
    private String gbase;

    @Parameter(property = "grammarix.gextension")
    private String gextension;

    @Parameter(property = "grammarix.output")
    private String output;

    @Parameter(property = "grammarix.packageName")
    private String packageName;

    @Parameter(property = "grammarix.parserClassName")
    private String parserClassName;
    private String lastIdentifier;

    @Override
    public void execute() throws MojoExecutionException {
        base = new File(base).getAbsolutePath();
        getLog().info("Base dir: " + base);
        getLog().info("Grammar-base: " + gbase);
        getLog().info("Grammar-extension: " + gextension);
        processBase();
        processExtension();
        generateOutput();
    }

    private void generateOutput() throws MojoExecutionException {
        File outputFile = prepareOutputFile();
        try (BufferedWriter writer = Files.newBufferedWriter(outputFile.toPath(), StandardCharsets.UTF_8)) {
            // Options
            if (optionsBlock != null) {
                writer.write(OPTIONS);
                writer.write(optionsBlock);
            }
            writer.newLine();

            // Parser Begin
            writer.write(PARSER_BEGIN);
            writer.write(OPEN_PAREN);
            writer.write(parserClassName);
            writer.write(CLOSE_PAREN);
            writer.newLine();
            writer.newLine();

            // Package
            writer.write(KWPACKAGE);
            writer.write(" ");
            writer.write(packageName);
            writer.write(SEMICOLON);
            writer.newLine();
            writer.newLine();

            // Imports
            List<String> importList = new ArrayList<>();
            for (List<String> importTokens : imports) {
                importList.add(importToString(importTokens));
            }
            Collections.sort(importList);
            for (String importStatement : importList) {
                writer.write(importStatement);
                writer.newLine();
            }

            writer.newLine();

            // Class definition
            writer.write(baseClassDef.replaceAll(baseClassName, parserClassName));
            writer.newLine();

            // Parser End
            writer.write(PARSER_END);
            writer.write(OPEN_PAREN);
            writer.write(parserClassName);
            writer.write(CLOSE_PAREN);
            writer.newLine();
            writer.newLine();

            // Extinsibles
            for (Entry<String, Pair<String, String>> entry : extensibles.entrySet()) {
                writer.newLine();
                String signature = entry.getKey();
                if (mergeElements.containsKey(signature)) {
                    writer.write("// Merged Non-terminal");
                    writer.newLine();
                }
                writer.write(extensibleSignatureToOutput(signature));
                writer.newLine();
                if (mergeElements.containsKey(signature)) {
                    merge(writer, entry.getValue(), mergeElements.get(signature));
                } else {
                    writer.write(entry.getValue().first);
                    writer.newLine();
                    if (entry.getValue().second != null) {
                        writer.write(entry.getValue().second);
                        writer.newLine();
                    }
                }
            }

            for (Pair<String, String> element : extensionFinals) {
                writer.write(toOutput(element.first));
                writer.newLine();
                writer.write(element.second);
                writer.newLine();
            }

            for (Pair<String, String> element : baseFinals) {
                writer.write(toOutput(element.first));
                writer.newLine();
                writer.write(element.second);
                writer.newLine();
            }

        } catch (Exception e) {
            getLog().error(e);
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    private String extensibleSignatureToOutput(String signature) {
        StringBuilder aString = new StringBuilder();
        String[] tokens = signature.split(" ");
        aString.append(tokens[0]);
        for (int i = 1; i < tokens.length; i++) {
            if (tokens[i - 1].charAt(tokens[i - 1].length() - 1) == CLOSE_PAREN
                    || (!SIG_SPECIAL_CHARS.contains(tokens[i].charAt(0))
                            && !SIG_SPECIAL_CHARS.contains(tokens[i - 1].charAt(tokens[i - 1].length() - 1)))) {
                aString.append(" ");
            }
            aString.append(tokens[i]);
        }
        return aString.toString();
    }

    private String toOutput(String signature) {
        if (signature.indexOf(OPEN_ANGULAR) == 0) {
            // a final
            StringBuilder aString = new StringBuilder();
            aString.append(signature.substring(0, signature.indexOf(CLOSE_ANGULAR) + 1));
            aString.append('\n');
            aString.append(signature.substring(signature.indexOf(CLOSE_ANGULAR) + 1));
            return aString.toString();
        } else {
            return signature;
        }
    }

    private void merge(BufferedWriter writer, Pair<String, String> baseBlocks, String[] extensions)
            throws IOException, MojoExecutionException {
        String errorMessage = "Merged base node doesn't conform to expected mergable node structure";
        int block1Open = baseBlocks.first.indexOf(OPEN_BRACE);
        int block1Close = baseBlocks.first.lastIndexOf(CLOSE_BRACE);
        // first block
        writer.write(OPEN_BRACE);
        if (extensions[0] != null) {
            writer.write(extensions[0]);
        }
        writer.write(baseBlocks.first.substring(block1Open + 1, block1Close));
        if (extensions[1] != null) {
            writer.write(extensions[1]);
        }
        writer.write(CLOSE_BRACE);
        writer.newLine();
        // second block
        writer.write(OPEN_BRACE);
        writer.newLine();
        if (extensions[2] != null) {
            writer.write(extensions[2]);
        }
        String innerBlock2String = null;
        if (baseBlocks.second != null) {
            BufferedReader blockReader = stringToReader(baseBlocks.second);
            Position blockPosition = new Position();
            blockPosition.index = 0;
            blockPosition.line = blockReader.readLine();
            while (blockPosition.line != null
                    && (blockPosition.line.trim().length() == 0 || blockPosition.line.indexOf(OPEN_BRACE) < 0)) {
                if (blockPosition.line.trim().length() > 0) {
                    writer.write(blockPosition.line);
                    writer.newLine();
                }
                blockPosition.line = blockReader.readLine();
            }
            if (blockPosition.line == null) {
                throw new MojoExecutionException(errorMessage);
            }
            int block2Open = blockPosition.line.indexOf(OPEN_BRACE);
            blockPosition.line = blockPosition.line.substring(block2Open + 1);
            while (blockPosition.line != null
                    && (blockPosition.line.trim().length() == 0 || blockPosition.line.indexOf(OPEN_PAREN) < 0)) {
                if (blockPosition.line.trim().length() > 0) {
                    writer.write(blockPosition.line);
                    writer.newLine();
                }
                blockPosition.line = blockReader.readLine();
            }
            if (blockPosition.line == null) {
                throw new MojoExecutionException(errorMessage);
            }
            int innerBlock1Open = blockPosition.line.indexOf(OPEN_PAREN);
            writer.write("  ");
            writer.write(OPEN_PAREN);
            blockPosition.index = innerBlock1Open;
            readBlock(blockReader, OPEN_PAREN, CLOSE_PAREN, blockPosition);
            String innerBlock1String = record.toString();
            writer.newLine();
            writer.write("    ");
            writer.write(innerBlock1String.substring(innerBlock1String.indexOf(OPEN_PAREN) + 1,
                    innerBlock1String.lastIndexOf(CLOSE_PAREN)).trim());
            writer.newLine();
            record.reset();
            // read second inner block
            blockPosition.line = blockReader.readLine();
            while (blockPosition.line != null && blockPosition.line.trim().length() == 0) {
                blockPosition.line = blockReader.readLine();
            }
            int innerBlock2Open = blockPosition.line.indexOf(OPEN_BRACE);
            if (innerBlock2Open < 0) {
                throw new MojoExecutionException(errorMessage);
            }
            blockPosition.index = innerBlock2Open;
            readBlock(blockReader, OPEN_BRACE, CLOSE_BRACE, blockPosition);
            innerBlock2String = record.toString();
            record.reset();
        }
        if (extensions[3] != null) {
            writer.write(extensions[3]);
        }
        writer.newLine();
        writer.write("  ");
        writer.write(CLOSE_PAREN);
        writer.newLine();
        writer.write("  ");
        writer.write(OPEN_BRACE);
        if (extensions[4] != null) {
            writer.write(extensions[4]);
        }
        if (innerBlock2String != null) {
            writer.newLine();
            writer.write("  ");
            writer.write(innerBlock2String.substring(innerBlock2String.indexOf(OPEN_BRACE) + 1,
                    innerBlock2String.lastIndexOf(CLOSE_BRACE)).trim());
            writer.newLine();
        }
        if (extensions[5] != null) {
            writer.write(extensions[5]);
        }
        // Close inner second block
        writer.write("  ");
        writer.write(CLOSE_BRACE);
        writer.newLine();
        // Close second block
        writer.write(CLOSE_BRACE);
        writer.newLine();
    }

    class Position {
        String line;
        int index;
    }

    private void readBlock(BufferedReader reader, char start, char end) throws IOException, MojoExecutionException {
        readBlock(reader, start, end, position);
    }

    private void readBlock(BufferedReader reader, char start, char end, Position position)
            throws IOException, MojoExecutionException {
        record.reset();
        char[] chars = position.line.toCharArray();
        if (chars[position.index] != start) {
            throw new MojoExecutionException(
                    "attempt to read a non terminal that doesn't start with a brace. @Line: " + position.line);
        }
        boolean prevCharEscape = false;
        boolean inString = false;
        boolean hasFinished = false;
        int depth = 0;
        int bufferPosn = position.index;
        int bufferLength = chars.length;
        do {
            int startPosn = bufferPosn;
            if (bufferPosn >= bufferLength) {
                startPosn = bufferPosn = 0;
                record.append("\n".toCharArray());
                position.line = reader.readLine();
                chars = position.line.toCharArray();
                bufferLength = chars.length;
            }
            for (; bufferPosn < bufferLength; ++bufferPosn) {
                if (inString) {
                    // we are in a string, we only care about the string end
                    if (chars[bufferPosn] == ExternalDataConstants.QUOTE && !prevCharEscape) {
                        inString = false;
                    }
                    if (prevCharEscape) {
                        prevCharEscape = false;
                    } else {
                        prevCharEscape = chars[bufferPosn] == ExternalDataConstants.ESCAPE;
                    }
                } else {
                    if (chars[bufferPosn] == ExternalDataConstants.QUOTE) {
                        inString = true;
                    } else if (chars[bufferPosn] == start) {
                        depth += 1;
                    } else if (chars[bufferPosn] == end) {
                        depth -= 1;
                        if (depth == 0) {
                            hasFinished = true;
                            bufferPosn++;
                            position.index = bufferPosn;
                            break;
                        }
                    }
                }
            }
            int appendLength = bufferPosn - startPosn;
            if (appendLength > 0) {
                record.append(chars, startPosn, appendLength);
            }
        } while (!hasFinished);
        record.endRecord();
    }

    private void processBase() throws MojoExecutionException {
        try (BufferedReader reader = Files.newBufferedReader(Paths.get(base, gbase), StandardCharsets.UTF_8)) {
            StringBuilder identifier = new StringBuilder();
            while ((position.line = reader.readLine()) != null) {
                if (position.line.trim().startsWith("//")) {
                    // skip comments
                    continue;
                }
                String[] tokens = position.line.split(REGEX_WS_PAREN);
                position.index = 0;
                int openBraceIndex = position.line.indexOf(OPEN_BRACE);
                int openAngularIndex = position.line.trim().indexOf(OPEN_ANGULAR);
                if (tokens.length > 0 && identifier.length() == 0 && KEYWORDS.contains(tokens[0])) {
                    handleSpecialToken(tokens[0], reader);
                } else if (openBraceIndex >= 0 && openAngularIndex < 0) {
                    String beforeBrace = position.line.substring(0, openBraceIndex);
                    if (beforeBrace.trim().length() > 0) {
                        identifier.append(beforeBrace);
                    } else if (identifier.length() == 0) {
                        identifier.append(lastIdentifier);
                    }
                    position.index = openBraceIndex;
                    readBlock(reader, OPEN_BRACE, CLOSE_BRACE);
                    // if next non-white space character is an open brace, then  we need to append
                    addExtensibleProduction(identifier);
                } else if (openAngularIndex == 0) {
                    position.index = position.line.indexOf(OPEN_ANGULAR);
                    readFinalProduction(identifier, reader);
                    addFinalProduction(identifier, baseFinals);
                } else if (identifier.length() > 0 || position.line.trim().length() > 0) {
                    identifier.append(position.line);
                    identifier.append('\n');
                }
            }
        } catch (Exception e) {
            getLog().error(e);
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    private void handleSpecialToken(String token, BufferedReader reader)
            throws IOException, MojoExecutionException {
        switch (token) {
        case PARSER_BEGIN:
            // parser begin. duh!
            parserBegin(reader);
            break;
        case PARSER_END:
            // parser end
            parserEnd(reader);
            break;
        case KWCLASS:
            // class declaration
            handleClassDeclaration(reader);
            break;
        case KWPACKAGE:
            // package declaration
            skipPackageDeclaration(reader);
            break;
        case KWIMPORT:
            handleImport(reader);
            // import statement
            break;
        default:
            break;
        }
    }

    private void addFinalProduction(StringBuilder identifier, List<Pair<String, String>> finals) {
        String sig = toSignature(identifier.toString());
        finals.add(new Pair<String, String>(sig, record.toString()));
        record.reset();
        identifier.setLength(0);
        lastIdentifier = null;
    }

    private void handleImport(BufferedReader reader) throws IOException {
        // will not work on two imports on a single line
        ArrayList<String> importList = new ArrayList<>();
        String[] tokens = position.line.split(REGEX_WS_DOT_SEMICOLON);
        importList.addAll(Arrays.asList(tokens));
        while (position.line.indexOf(SEMICOLON) < 0) {
            position.line = reader.readLine();
            tokens = position.line.split(REGEX_WS_DOT_SEMICOLON);
            importList.addAll(Arrays.asList(tokens));
        }
        imports.add(importList);
    }

    private void handleUnImport(BufferedReader reader) throws IOException {
        ArrayList<String> importList = new ArrayList<>();
        String[] tokens = position.line.split(REGEX_WS_DOT_SEMICOLON);
        importList.addAll(Arrays.asList(tokens));
        while (position.line.indexOf(SEMICOLON) < 0) {
            position.line = reader.readLine();
            tokens = position.line.split(REGEX_WS_DOT_SEMICOLON);
            importList.addAll(Arrays.asList(tokens));
        }
        // remove from imports
        Iterator<List<String>> it = imports.iterator();
        while (it.hasNext()) {
            List<String> anImport = it.next();
            if (anImport.size() == importList.size()) {
                boolean equals = true;
                for (int i = 1; i < anImport.size(); i++) {
                    if (!anImport.get(i).equals(importList.get(i))) {
                        equals = false;
                        break;
                    }
                }
                if (equals) {
                    it.remove();
                }
            }
        }
    }

    private String importToString(List<String> importTokens) {
        return "import " + StringUtils.join(importTokens.subList(1, importTokens.size()), '.') + ";";
    }

    private void skipPackageDeclaration(BufferedReader reader) throws IOException {
        while (position.line.indexOf(SEMICOLON) < 0) {
            position.line = reader.readLine();
        }
    }

    private void handleClassDeclaration(BufferedReader reader) throws IOException, MojoExecutionException {
        StringBuilder parserDef = new StringBuilder();
        int classPosition = position.line.indexOf(KWCLASS);
        int startIndex = position.line.indexOf(OPEN_BRACE, classPosition);
        if (startIndex < 0) {
            position.line = position.line.substring(classPosition).replace(baseClassName, parserClassName);
            while (startIndex < 0) {
                parserDef.append(position.line);
                parserDef.append('\n');
                position.line = reader.readLine();
                startIndex = position.line.indexOf(OPEN_BRACE);
            }
            parserDef.append(position.line, 0, startIndex);
        } else {
            parserDef.append(position.line, classPosition, startIndex);
        }
        position.index = startIndex;
        readBlock(reader, OPEN_BRACE, CLOSE_BRACE);
        String classBody = record.toString();
        parserDef.append(classBody);
        parserDef.append('\n');
        baseClassDef = parserDef.toString();
    }

    private void parserEnd(BufferedReader reader) throws IOException {
        int endIndex = position.line.indexOf(CLOSE_PAREN, position.index);
        while (endIndex < 0) {
            position.line = reader.readLine();
            endIndex = position.line.indexOf(CLOSE_PAREN);
        }
        position.index = endIndex;
    }

    private void parserBegin(BufferedReader reader) throws IOException {
        StringBuilder aStringBuilder = new StringBuilder();
        int startIndex = position.line.indexOf(OPEN_PAREN, position.index);
        while (startIndex < 0) {
            position.line = reader.readLine();
            startIndex = position.line.indexOf(OPEN_PAREN);
        }
        int endIndex = position.line.indexOf(CLOSE_PAREN, startIndex);
        if (endIndex < 0) {
            // start and end on different lines
            position.line = position.line.substring(startIndex + 1);
            while (endIndex < 0) {
                aStringBuilder.append(position.line);
                position.line = reader.readLine();
                endIndex = position.line.indexOf(CLOSE_PAREN);
            }
            aStringBuilder.append(position.line, 0, endIndex);
        } else {
            // start and end on the same line
            aStringBuilder.append(position.line.substring(startIndex + 1, endIndex));
        }
        position.index = endIndex;
        baseClassName = aStringBuilder.toString().trim();
    }

    private void readFinalProduction(StringBuilder identifier, BufferedReader reader)
            throws IOException, MojoExecutionException {
        int blockStart = position.line.indexOf(OPEN_BRACE);
        if (blockStart < 0) {
            position.line = position.line.substring(position.index);
            while (blockStart < 0) {
                identifier.append(position.line);
                identifier.append('\n');
                position.line = reader.readLine();
                blockStart = position.line.indexOf(OPEN_BRACE);
            }
            identifier.append(position.line.substring(0, blockStart));
        } else {
            identifier.append(position.line.substring(position.index, blockStart));
        }
        position.index = blockStart;
        readBlock(reader, OPEN_BRACE, CLOSE_BRACE);
    }

    private void addExtensibleProduction(StringBuilder identifier) {
        if (identifier.toString().trim().equals(OPTIONS)) {
            optionsBlock = record.toString();
        } else {
            String sig = toSignature(identifier.toString());
            Pair<String, String> pair = extensibles.get(sig);
            if (pair == null) {
                pair = new Pair<>(record.toString(), null);
                extensibles.put(sig, pair);
            } else {
                pair.second = record.toString();
            }
            lastIdentifier = identifier.toString();
        }
        record.reset();
        identifier.setLength(0);
    }

    private void processExtension() throws MojoExecutionException {
        try (BufferedReader reader = Files.newBufferedReader(Paths.get(base, gextension), StandardCharsets.UTF_8)) {
            StringBuilder identifier = new StringBuilder();
            String nextOperation = OVERRIDEPRODUCTION;
            while (read || (position.line = reader.readLine()) != null) {
                read = false;
                if (position.line.trim().startsWith("//")) {
                    // skip comments
                    continue;
                }
                String[] tokens = position.line.split(REGEX_WS_PAREN);
                position.index = 0;
                int openBraceIndex = position.line.indexOf(OPEN_BRACE);
                int openAngularIndex = position.line.trim().indexOf(OPEN_ANGULAR);
                if (tokens.length > 0 && identifier.length() == 0 && EXTENSIONKEYWORDS.contains(tokens[0])) {
                    switch (tokens[0]) {
                    case KWIMPORT:
                        handleImport(reader);
                        break;
                    case KWUNIMPORT:
                        handleUnImport(reader);
                        break;
                    case NEWPRODUCTION:
                        nextOperation = NEWPRODUCTION;
                        break;
                    case MERGEPRODUCTION:
                        nextOperation = MERGEPRODUCTION;
                        shouldReplace = shouldReplace(tokens);
                        break;
                    case OVERRIDEPRODUCTION:
                        nextOperation = OVERRIDEPRODUCTION;
                        break;
                    default:
                        break;
                    }
                } else if (openBraceIndex >= 0 && openAngularIndex < 0) {
                    String beforeBrace = position.line.substring(0, openBraceIndex);
                    if (beforeBrace.trim().length() > 0) {
                        identifier.append(beforeBrace);
                    } else if (identifier.length() == 0) {
                        identifier.append(lastIdentifier);
                    }
                    position.index = openBraceIndex;
                    switch (nextOperation) {
                    case NEWPRODUCTION:
                        handleNew(identifier, reader);
                        break;
                    case OVERRIDEPRODUCTION:
                        handleOverride(identifier, reader);
                        break;
                    case MERGEPRODUCTION:
                        handleMerge(identifier, reader);
                        break;
                    default:
                        throw new MojoExecutionException("Malformed extention file");
                    }
                    nextOperation = NEWPRODUCTION;
                } else if (openAngularIndex == 0) {
                    if (nextOperation != NEWPRODUCTION) {
                        throw new MojoExecutionException("Can only add new REGEX production kind");
                    }
                    position.index = position.line.indexOf(OPEN_ANGULAR);
                    readFinalProduction(identifier, reader);
                    addFinalProduction(identifier, extensionFinals);
                } else if (identifier.length() > 0 || position.line.trim().length() > 0) {
                    identifier.append(position.line);
                    identifier.append('\n');
                }
            }
        } catch (Exception e) {
            getLog().error(e);
            throw new MojoExecutionException(e.getMessage(), e);
        }
    }

    private boolean shouldReplace(String[] tokens) throws MojoExecutionException {
        boolean replace = false;
        if (tokens.length == 5) {
            if (tokens[1].equals(REPLACE) && tokens[3].equals(WITH)) {
                shouldReplace = true;
                oldWord = tokens[2];
                newWord = tokens[4];
            } else {
                throw new MojoExecutionException("Allowed syntax after @merge: <REPLACE> oldWord <WITH> newWord");
            }
        }
        return replace;
    }

    private void handleOverride(StringBuilder identifier, BufferedReader reader)
            throws MojoExecutionException, IOException {
        readBlock(reader, OPEN_BRACE, CLOSE_BRACE);
        Pair<String, String> pair = new Pair<>(record.toString(), null);
        String sig = toSignature(identifier.toString());
        extensibles.put(sig, pair);
        record.reset();
        identifier.setLength(0);
        // will read ahead of loop cycle
        read = true;
        position.index = 0;
        position.line = reader.readLine();
        while (position.line != null && position.line.trim().length() == 0) {
            position.line = reader.readLine();
        }
        int openBraceIndex = position.line.indexOf(OPEN_BRACE);
        if (openBraceIndex > -1) {
            // consume
            read = false;
            position.index = openBraceIndex;
            readBlock(reader, OPEN_BRACE, CLOSE_BRACE);
            pair.second = record.toString();
            record.reset();
        }
    }

    private void handleNew(StringBuilder identifier, BufferedReader reader)
            throws MojoExecutionException, IOException {
        String sig = toSignature(identifier.toString());
        if (extensibles.containsKey(sig)) {
            throw new MojoExecutionException(identifier.toString() + " already exists in base grammar");
        }
        handleOverride(identifier, reader);
    }

    private void handleMerge(StringBuilder identifier, BufferedReader reader)
            throws MojoExecutionException, IOException {
        String sig = toSignature(identifier.toString());
        if (!extensibles.containsKey(sig)) {
            throw new MojoExecutionException(identifier.toString() + " doesn't exist in base grammar");
        } else if (shouldReplace) {
            Pair<String, String> baseMethods = extensibles.get(sig);
            baseMethods.first = baseMethods.first.replaceAll(oldWord, newWord);
            baseMethods.second = baseMethods.second.replaceAll(oldWord, newWord);
            shouldReplace = false;
        }
        String[] amendments = new String[6];
        mergeElements.put(sig, amendments);
        // we don't need the identifier anymore
        identifier.setLength(0);
        readBlock(reader, OPEN_BRACE, CLOSE_BRACE);
        String block = record.toString();
        extractBeforeAndAfter(block, amendments, 0, 1);
        record.reset();
        position.index = 0;
        position.line = reader.readLine();
        while (position.line != null && position.line.trim().length() == 0) {
            position.line = reader.readLine();
        }
        int openBraceIndex = position.line.indexOf(OPEN_BRACE);
        if (openBraceIndex > -1) {
            position.index = openBraceIndex;
            readBlock(reader, OPEN_BRACE, CLOSE_BRACE);
        } else {
            throw new MojoExecutionException("merge element doesn't have a second block");
        }
        block = record.toString();
        BufferedReader blockReader = stringToReader(block);
        String line = blockReader.readLine();
        while (line != null && line.indexOf(OPEN_BRACE) < 0) {
            line = blockReader.readLine();
        }
        if (line == null) {
            throw new MojoExecutionException("merge element doesn't have a correct second block");
        }
        line = line.substring(line.indexOf(OPEN_BRACE) + 1);
        while (line != null && line.trim().length() == 0) {
            line = blockReader.readLine();
        }
        if (line == null) {
            throw new MojoExecutionException("merge element doesn't have a correct second block");
        }
        int openParenIndex = line.indexOf(OPEN_PAREN);
        if (openParenIndex < 0) {
            throw new MojoExecutionException("second block in merge element doesn't have a correct () block");
        }
        Position blockPosition = new Position();
        blockPosition.line = line;
        blockPosition.index = openParenIndex;
        readBlock(blockReader, OPEN_PAREN, CLOSE_PAREN, blockPosition);
        extractBeforeAndAfter(record.toString(), amendments, 2, 3);
        record.reset();
        // process third block:
        blockPosition.index = 0;
        blockPosition.line = blockReader.readLine();
        while (blockPosition.line != null && blockPosition.line.trim().length() == 0) {
            blockPosition.line = blockReader.readLine();
        }
        openBraceIndex = blockPosition.line.indexOf(OPEN_BRACE);
        if (openBraceIndex > -1) {
            blockPosition.index = openBraceIndex;
            readBlock(blockReader, OPEN_BRACE, CLOSE_BRACE, blockPosition);
        } else {
            throw new MojoExecutionException("merge element doesn't have a second block");
        }
        extractBeforeAndAfter(record.toString(), amendments, 4, 5);
        record.reset();
    }

    private void extractBeforeAndAfter(String block, String[] amendments, int beforeIndex, int afterIndex) {
        int before = block.indexOf(BEFORE);
        int after = block.indexOf(AFTER);
        if (before >= 0) {
            // before exists
            amendments[beforeIndex] = block.substring(before + BEFORE.length(),
                    (after >= 0) ? after : block.length() - 1);
            if (amendments[beforeIndex].trim().length() == 0) {
                amendments[beforeIndex] = null;
            }
        }
        if (after >= 0) {
            // after exists
            amendments[afterIndex] = block.substring(after + AFTER.length(), block.length() - 1);
            if (amendments[afterIndex].trim().length() == 0) {
                amendments[afterIndex] = null;
            }
        }
    }

    private BufferedReader stringToReader(String aString) {
        InputStream is = new ByteArrayInputStream(aString.getBytes(StandardCharsets.UTF_8));
        return new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
    }

    private File prepareOutputFile() throws MojoExecutionException {
        // write output
        File outputFile = new File(base, output);
        if (outputFile.exists() && (!outputFile.delete())) {
            throw new MojoExecutionException("Unable to delete file " + output);
        }
        try {
            outputFile.getParentFile().mkdirs();
            if (!outputFile.createNewFile()) {
                throw new MojoExecutionException("Unable to create file " + output);
            }
        } catch (IOException ioe) {
            throw new MojoExecutionException("Unable to create file " + output, ioe);
        }
        return outputFile;
    }

    private String toSignature(String identifier) {
        StringBuilder aString = new StringBuilder();
        char[] chars = identifier.toCharArray();
        int index = 0;
        boolean first = true;
        while (index < chars.length) {
            int begin;
            while (Character.isWhitespace(chars[index])) {
                index++;
                if (index == chars.length) {
                    break;
                }
            }
            if (index == chars.length) {
                break;
            }
            begin = index;
            while (!Character.isWhitespace(chars[index])) {
                index++;
                if ((index == chars.length) || SIG_SPECIAL_CHARS.contains(chars[index - 1])
                        || ((index < chars.length - 1) && SIG_SPECIAL_CHARS.contains(chars[index]))) {
                    break;
                }
            }
            if (!first) {
                aString.append(' ');
            }
            aString.append(new String(chars, begin, index - begin));
            first = false;
        }
        return aString.toString();
    }
}