/*
 * Copyright (c) 2009-2010 Ken Wenzel and Mathias Doenitz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.fge.grappa.transform.process;

import com.github.fge.grappa.matchers.base.Matcher;
import com.github.fge.grappa.matchers.wrap.ProxyMatcher;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.transform.CodeBlock;
import com.google.common.base.Preconditions;
import me.qmx.jitescript.util.CodegenUtils;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LabelNode;
import com.github.fge.grappa.transform.runtime.CacheArguments;
import com.github.fge.grappa.transform.base.ParserClassNode;
import com.github.fge.grappa.transform.base.RuleMethod;

import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.Objects;

import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ARETURN;

/**
 * Wraps the method code with caching and proxying constructs.
 */
public final class CachingGenerator
    implements RuleMethodProcessor
{
    private ParserClassNode classNode;
    private RuleMethod method;
    private InsnList instructions;
    private AbstractInsnNode retInsn;
    private String cacheFieldName;

    @Override
    public boolean appliesTo(@Nonnull final ParserClassNode classNode,
        @Nonnull final RuleMethod method)
    {
        Objects.requireNonNull(classNode, "classNode");
        Objects.requireNonNull(method, "method");
        return method.hasCachedAnnotation();
    }

    @Override
    public void process(@Nonnull final ParserClassNode classNode,
        @Nonnull final RuleMethod method)
        throws Exception
    {
        Objects.requireNonNull(classNode, "classNode");
        Objects.requireNonNull(method, "method");
        // super methods have flag moved to the overriding method
        Preconditions.checkState(!method.isSuperMethod());

        this.classNode = classNode;
        this.method = method;
        instructions = method.instructions;
        retInsn = instructions.getLast();

        while (retInsn.getOpcode() != ARETURN)
            retInsn = retInsn.getPrevious();

        CodeBlock block;

        block = CodeBlock.newCodeBlock();
        generateCacheHitReturn(block);
        generateStoreNewProxyMatcher(block);

        instructions.insert(block.getInstructionList());

        block = CodeBlock.newCodeBlock();
        generateArmProxyMatcher(block);
        generateStoreInCache(block);

        instructions.insertBefore(retInsn, block.getInstructionList());
    }

    // if (<cache> != null) return <cache>;
    private void generateCacheHitReturn(final CodeBlock block)
    {
        generateGetFromCache(block);

        final LabelNode cacheMiss = new LabelNode();

        block.dup()
            .ifnull(cacheMiss)
            .areturn()
            .label(cacheMiss)
            .pop();
    }

    private void generateGetFromCache(final CodeBlock block)
    {
        final Type[] paramTypes = Type.getArgumentTypes(method.desc);
        cacheFieldName = findUnusedCacheFieldName();

        // if we have no parameters we use a simple Rule field as cache,
        // otherwise a HashMap
        final String cacheFieldDesc = paramTypes.length == 0
            ? CodegenUtils.ci(Rule.class)
            : CodegenUtils.ci(HashMap.class);
        final FieldNode field = new FieldNode(ACC_PRIVATE, cacheFieldName,
            cacheFieldDesc, null, null);

        classNode.fields.add(field);

        block.aload(0).getfield(classNode.name, cacheFieldName, cacheFieldDesc);

        if (paramTypes.length == 0)
            return; // if we have no parameters we are done

        // generate: if (<cache> == null) <cache> = new HashMap<Object, Rule>();

        final LabelNode alreadyInitialized = new LabelNode();

        block.dup()
            .ifnonnull(alreadyInitialized)
            .pop()
            .aload(0)
            .newobj(CodegenUtils.p(HashMap.class)).dup_x1().dup()
            .invokespecial(CodegenUtils.p(HashMap.class), "<init>",
                CodegenUtils.sig(void.class))
            .putfield(classNode.name, cacheFieldName, cacheFieldDesc)
            .label(alreadyInitialized);

        // if we have more than one parameter or the parameter is an array we
        // have to wrap with our Arguments class since we need to unroll all
        // inner arrays and apply custom hashCode(...) and equals(...)
        // implementations
        if (paramTypes.length > 1 || paramTypes[0].getSort() == Type.ARRAY) {
            // generate: push new Arguments(new Object[] {<params>})

            block.newobj(CodegenUtils.p(CacheArguments.class)).dup();

            generatePushNewParameterObjectArray(block, paramTypes);

            block.invokespecial(CodegenUtils.p(CacheArguments.class), "<init>",
                CodegenUtils.sig(void.class, Object[].class));
        } else {
            generatePushParameterAsObject(block, paramTypes, 0);
        }

        // generate: <hashMap>.get(...)

        block.dup().astore(method.maxLocals)
            .invokevirtual(CodegenUtils.p(HashMap.class), "get",
                CodegenUtils.sig(Object.class, Object.class));
    }

    private String findUnusedCacheFieldName()
    {
        String name = "cache$" + method.name;
        int i = 2;
        while (hasField(name))
            name = "cache$" + method.name + i++;
        return name;
    }

    private boolean hasField(final String fieldName)
    {
        for (final Object field : classNode.fields)
            if (fieldName.equals(((FieldNode) field).name))
                return true;

        return false;
    }

    private void generatePushNewParameterObjectArray(final CodeBlock block,
        final Type[] paramTypes)
    {
        block.bipush(paramTypes.length).anewarray(CodegenUtils.p(Object.class));

        for (int i = 0; i < paramTypes.length; i++) {
            block.dup().bipush(i);
            generatePushParameterAsObject(block, paramTypes, i);
            block.aastore();
        }
    }

    private static void generatePushParameterAsObject(final CodeBlock block,
        final Type[] paramTypes, int parameterNr)
    {
        switch (paramTypes[parameterNr++].getSort()) {
            case Type.BOOLEAN:
                block.iload(parameterNr)
                    .invokestatic(CodegenUtils.p(Boolean.class), "valueOf",
                        CodegenUtils.sig(Boolean.class, boolean.class));
                return;
            case Type.CHAR:
                block.iload(parameterNr)
                    .invokestatic(CodegenUtils.p(Character.class), "valueOf",
                    CodegenUtils.sig(Character.class, char.class));
                return;
            case Type.BYTE:
                block.iload(parameterNr)
                    .invokestatic(CodegenUtils.p(Byte.class), "valueOf",
                    CodegenUtils.sig(Byte.class, byte.class));
                return;
            case Type.SHORT:
                block.iload(parameterNr)
                    .invokestatic(CodegenUtils.p(Short.class), "valueOf",
                    CodegenUtils.sig(Short.class, short.class));
                return;
            case Type.INT:
                block.iload(parameterNr)
                    .invokestatic(CodegenUtils.p(Integer.class), "valueOf",
                    CodegenUtils.sig(Integer.class, int.class));
                return;
            case Type.FLOAT:
                block.fload(parameterNr)
                    .invokestatic(CodegenUtils.p(Float.class), "valueOf",
                    CodegenUtils.sig(Float.class, float.class));
                return;
            case Type.LONG:
                block.lload(parameterNr)
                    .invokestatic(CodegenUtils.p(Long.class), "valueOf",
                    CodegenUtils.sig(Long.class, long.class));
                return;
            case Type.DOUBLE:
                block.dload(parameterNr)
                    .invokestatic(CodegenUtils.p(Double.class), "valueOf",
                    CodegenUtils.sig(Double.class, double.class));
                return;
            case Type.ARRAY:
            case Type.OBJECT:
                block.aload(parameterNr);
                return;
            case Type.VOID:
            default:
                throw new IllegalStateException();
        }
    }

    // <cache> = new ProxyMatcher();
    private void generateStoreNewProxyMatcher(final CodeBlock block)
    {
        block.newobj(CodegenUtils.p(ProxyMatcher.class))
            .dup()
            .invokespecial(CodegenUtils.p(ProxyMatcher.class), "<init>",
                CodegenUtils.sig(void.class));

        generateStoreInCache(block);
    }

    // <proxyMatcher>.arm(<rule>)
    private static void generateArmProxyMatcher(final CodeBlock block)
    {
        block.dup_x1()
            .checkcast(CodegenUtils.p(Matcher.class))
            .invokevirtual(CodegenUtils.p(ProxyMatcher.class), "arm",
                CodegenUtils.sig(void.class, Matcher.class));
    }

    private void generateStoreInCache(final CodeBlock block)
    {
        final Type[] paramTypes = Type.getArgumentTypes(method.desc);

        block.dup();

        if (paramTypes.length == 0) {
            block.aload(0)
                .swap()
                .putfield(classNode.name, cacheFieldName,
                    CodegenUtils.ci(Rule.class));
            return;
        }

        block.aload(method.maxLocals)
            .swap()
            .aload(0)
            .getfield(classNode.name, cacheFieldName,
                CodegenUtils.ci(HashMap.class))
            .dup_x2()
            .pop()
            .invokevirtual(CodegenUtils.p(HashMap.class), "put",
                CodegenUtils.sig(Object.class, Object.class, Object.class))
            .pop();
    }
}


package com.github.fge.grappa.transform.load;

import com.github.fge.grappa.transform.ParserTransformException;

import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * Wrapper to provide reflective access to a {@link ClassLoader}
 *
 *
Necessary in order to grant correct access to package private fields of
 * parser classes.

 */
public final class ReflectiveClassLoader
    implements AutoCloseable
{
    private static final String CLASSLOADER = "java.lang.ClassLoader";
    private static final String FIND_LOADED_CLASS = "findLoadedClass";
    private static final String DEFINE_CLASS = "defineClass";

    private final ClassLoader loader;
    private final Method findClass;
    private final Method loadClass;

    /**
     * Constructor
     *
     * @param loader the classloader to use
     */
    public ReflectiveClassLoader(final ClassLoader loader)
    {
        this.loader = Objects.requireNonNull(loader);

        try {
            final Class<?> loaderClass = Class.forName(CLASSLOADER);
            findClass = loaderClass.getDeclaredMethod(FIND_LOADED_CLASS,
                String.class);
            loadClass = loaderClass.getDeclaredMethod(DEFINE_CLASS,
                String.class, byte[].class, int.class, int.class);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            throw new ParserTransformException(
                "unable to find the needed methods", e);
        }

        final ParserTransformException exception = new ParserTransformException(
            "could not change the necessary access modifiers");

        try {
            findClass.setAccessible(true);
        } catch (SecurityException e) {
            exception.addSuppressed(e);
        }

        try {
            loadClass.setAccessible(true);
        } catch (SecurityException e) {
            exception.addSuppressed(e);
        }

        if (exception.getSuppressed().length > 0)
            throw exception;
    }

    /**
     * Returns the class with the given name if it has already been loaded by
     * the given class loader
     *
     *
If the class has not been loaded yet, this method returns {@code
     * null}.

     *
     * @param className the full name of the class to be loaded
     * @return the class instance, if found
     */
    @Nullable
    public Class<?> findClass(final String className)
    {
        Objects.requireNonNull(className);

        try {
            return (Class<?>) findClass.invoke(loader, className);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new ParserTransformException("unable to find class by name ("
                + className + ')', e);
        }
    }

    /**
     * Loads the class defined with the given name and bytecode using the given
     * class loader
     *
     *
Since package and class idendity includes the ClassLoader instance
     * used to load a class, we use reflection on the given class loader to
     * define generated classes.

     *
     *
If we used our own class loader (in order to be able to access the
     * protected "defineClass" method), we would likely still be able to load
     * generated classes; however, they would not have access to package-private
     * classes and members of their super classes.

     *
     * @param className the full name of the class to be loaded
     * @param bytecode the bytecode of the class to load
     * @return the class instance
     */
    public Class<?> loadClass(final String className, final byte[] bytecode)
    {
        Objects.requireNonNull(className);
        Objects.requireNonNull(bytecode);

        try {
            return (Class<?>) loadClass.invoke(loader, className, bytecode, 0,
                bytecode.length);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new ParserTransformException("unable to load class by name",
                e);
        }
    }

    @Override
    public void close()
    {
        final ParserTransformException exception = new ParserTransformException(
            "could not close classloader properly");
        try {
            findClass.setAccessible(false);
        } catch (SecurityException e) {
            exception.addSuppressed(e);
        }

        try {
            loadClass.setAccessible(false);
        } catch (SecurityException e) {
            exception.addSuppressed(e);
        }

        if (exception.getSuppressed().length > 0)
            throw exception;
    }
}


/*
 * Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed 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 com.github.fge.grappa.matchers.trie;

import javax.annotation.concurrent.Immutable;
import java.nio.CharBuffer;
import java.util.Arrays;

/**
 * The core of the trie
 *
 *
This class implements a trie node. It has two elements in it:

 *
 *

 *
a boolean telling whether this node matches a full word;

 *
a list of children trie nodes, if any.

 *

 *
 *
The children are indexed by the character they match (which means, in
 * effect, that a trie node has no characters "belonging" to him, and the root
 * node knows of all first characters there are to match).

 *
 * @since 1.0.0-beta.6
 */
@Immutable
public final class TrieNode
{
    private final boolean fullWord;

    private final char[] nextChars;
    private final TrieNode[] nextNodes;

    @SuppressWarnings("MethodCanBeVariableArityMethod")
    TrieNode(final boolean fullWord, final char[] nextChars,
        final TrieNode[] nextNodes)
    {
        this.fullWord = fullWord;
        this.nextChars = nextChars;
        this.nextNodes = nextNodes;
    }

    public int search(final String needle, final boolean ignoreCase)
    {
        return doSearch(CharBuffer.wrap(needle), fullWord ? 0 : -1, 0,
            ignoreCase);
    }

    /**
     * Core search method
     *
     *
This method uses a {@link CharBuffer} to perform searches, and changes
     * this buffer's position as the match progresses. The two other arguments
     * are the depth of the current search (ie the number of nodes visited
     * since root) and the index of the last node where a match was found (ie
     * the last node where {@link #fullWord} was true.

     *
     * @param buffer the charbuffer
     * @param matchedLength the last matched length (-1 if no match yet)
     * @param currentLength the current length walked by the trie
     * @return the length of the match found, -1 otherwise
     */
    private int doSearch(final CharBuffer buffer, final int matchedLength,
        final int currentLength, final boolean ignoreCase)
    {
        /*
         * Try and see if there is a possible match here; there is if "fullword"
         * is true, in this case the next "matchedLength" argument to a possible
         * child call will be the current length.
         */
        final int nextLength = fullWord ? currentLength : matchedLength;

        /*
         * If there is nothing left in the buffer, we have a match.
         */
        if (!buffer.hasRemaining())
            return nextLength;

        /*
         * OK, there is at least one character remaining, so pick it up and see
         * whether it is in the list of our children...
         */
        char c = buffer.get();
        int index = Arrays.binarySearch(nextChars, c);
        if (index < 0 && ignoreCase) {
            final boolean isUpper = Character.isUpperCase(c);
            final boolean isLower = Character.isLowerCase(c);
            if (isUpper != isLower) {
               c = isUpper ? Character.toLowerCase(c)
                   : Character.toUpperCase(c);
               index = Arrays.binarySearch(nextChars, c);
            }
        }

        /*
         * If not, we return the last good match; if yes, we call this same
         * method on the matching child node with the (possibly new) matched
         * length as an argument and a depth increased by 1.
         */
        if (index < 0)
            return nextLength;
        return nextNodes[index].doSearch(buffer, nextLength, currentLength + 1,
            ignoreCase);
    }
}


/*
 * Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed 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 com.github.fge.grappa.matchers.trie;

import javax.annotation.Nonnull;
import java.util.Objects;

/**
 * A builder for a {@link Trie}
 *
 * @since 1.0.0-beta.6
 */
public final class TrieBuilder
{
    int nrWords = 0;
    int maxLength = 0;
    final TrieNodeBuilder nodeBuilder
        = new TrieNodeBuilder();

    TrieBuilder()
    {
    }

    /**
     * Add one word to the trie
     *
     * @param word the word to add
     * @return this
     * @throws IllegalArgumentException word is empty
     */
    public TrieBuilder addWord(@Nonnull final String word)
    {
        Objects.requireNonNull(word);

        final int length = word.length();

        if (length == 0)
            throw new IllegalArgumentException("a trie cannot have empty "
                + "strings (use EMPTY instead)");
        nrWords++;
        maxLength = Math.max(maxLength, length);
        nodeBuilder.addWord(word);
        return this;
    }

    public Trie build()
    {
        return new Trie(this);
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed 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 com.github.fge.grappa.transform;

import com.github.fge.grappa.parsers.BaseParser;
import com.github.fge.grappa.transform.base.ParserClassNode;
import com.github.fge.grappa.transform.base.RuleMethod;
import com.github.fge.grappa.transform.generate.ActionClassGenerator;
import com.github.fge.grappa.transform.generate.ClassNodeBootstrap;
import com.github.fge.grappa.transform.generate.ConstructorGenerator;
import com.github.fge.grappa.transform.generate.VarInitClassGenerator;
import com.github.fge.grappa.transform.load.ClassLoaderList;
import com.github.fge.grappa.transform.load.ReflectiveClassLoader;
import com.github.fge.grappa.transform.process.BodyWithSuperCallReplacer;
import com.github.fge.grappa.transform.process.CachingGenerator;
import com.github.fge.grappa.transform.process.ImplicitActionsConverter;
import com.github.fge.grappa.transform.process.InstructionGraphCreator;
import com.github.fge.grappa.transform.process.InstructionGroupCreator;
import com.github.fge.grappa.transform.process.InstructionGroupPreparer;
import com.github.fge.grappa.transform.process.LabellingGenerator;
import com.github.fge.grappa.transform.process.ReturnInstructionUnifier;
import com.github.fge.grappa.transform.process.RuleMethodProcessor;
import com.github.fge.grappa.transform.process.RuleMethodRewriter;
import com.github.fge.grappa.transform.process.SuperCallRewriter;
import com.github.fge.grappa.transform.process.UnusedLabelsRemover;
import com.github.fge.grappa.transform.process.VarFramingGenerator;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableList;
import org.objectweb.asm.ClassWriter;

import java.util.List;
import java.util.Objects;

import static com.github.fge.grappa.misc.AsmUtils.getExtendedParserClassName;

/**
 * A replacement for {@link ParserTransformer}
 *
 * @param <V> type parameter of the stack values of the parser class
 * @param
 type parmaeter for the parser class
 */
@Beta
// TODO: generics can be better
public final class ParserGenerator<V, P extends BaseParser<V>>
{
    private static final List<RuleMethodProcessor> PROCESSORS;

    static {
        PROCESSORS = ImmutableList.of(
            new UnusedLabelsRemover(),
            new ReturnInstructionUnifier(),
            new InstructionGraphCreator(),
            new ImplicitActionsConverter(),
            new InstructionGroupCreator(),
            new InstructionGroupPreparer(),
            new ActionClassGenerator(false),
            new VarInitClassGenerator(false),
            new RuleMethodRewriter(),
            new SuperCallRewriter(),
            new BodyWithSuperCallReplacer(),
            new VarFramingGenerator(),
            new LabellingGenerator(),
            new CachingGenerator()
        );
    }

    private final Class
 parserClass;
    private final ClassLoaderList classLoaders;

    public ParserGenerator(final Class
 parserClass,
        final ClassLoaderList classLoaders)
    {
        this.parserClass = Objects.requireNonNull(parserClass);
        this.classLoaders = classLoaders;
    }

    public Class<? extends P> transformParser()
        throws Exception
    {
        Objects.requireNonNull(parserClass, "parserClass");
        // first check whether we did not already create and load the extension
        // of the given parser class
        final String name = getExtendedParserClassName(parserClass.getName());

        final Class<?> extendedClass;

        try (
            final ReflectiveClassLoader loader
                = new ReflectiveClassLoader(parserClass.getClassLoader());
        ) {
            extendedClass = loader.findClass(name);
        }

        final Class<?> ret = extendedClass != null
            ? extendedClass
            : extendParserClass().getExtendedClass();
        return (Class<? extends P>) ret;
    }

    /**
     * Dump the bytecode of a transformed parser class
     *
     *
This method will run all bytecode transformations on the parser class
     * then return a dump of the bytecode as a byte array.

     *
     * @return a bytecode dump
     *
     * @throws Exception FIXME
     */
    // TODO: poor exception specification
    public byte[] getByteCode()
        throws Exception
    {
        final ParserClassNode node = extendParserClass();
        return node.getClassCode();
    }

    private ParserClassNode extendParserClass()
        throws Exception
    {
        final ParserClassNode classNode = new ParserClassNode(parserClass);
        new ClassNodeBootstrap(classNode, classLoaders).process();
        runMethodTransformers(classNode);
        new ConstructorGenerator().process(classNode);
        defineExtendedParserClass(classNode);
        return classNode;
    }

    // TODO: poor exception handling again
    private static void runMethodTransformers(final ParserClassNode classNode)
        throws Exception
    {
        // TODO: comment above may be right, but it's still dangerous
        // iterate through all rule methods
        // since the ruleMethods map on the classnode is a treemap we get the
        // methods sorted by name which puts all super methods first (since they
        // are prefixed with one or more '$')
        for (final RuleMethod ruleMethod: classNode.getRuleMethods().values()) {
            if (ruleMethod.hasDontExtend())
                continue;

            for (final RuleMethodProcessor methodProcessor: PROCESSORS)
                if (methodProcessor.appliesTo(classNode, ruleMethod))
                    methodProcessor.process(classNode, ruleMethod);
        }

        for (final RuleMethod ruleMethod: classNode.getRuleMethods().values())
            if (!ruleMethod.isGenerationSkipped())
                classNode.methods.add(ruleMethod);
    }

    private static void defineExtendedParserClass(final ParserClassNode node)
    {
        final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        node.accept(writer);
        node.setClassCode(writer.toByteArray());

        final String className = node.name.replace('/', '.');
        final byte[] bytecode = node.getClassCode();

        final ClassLoader classLoader = node.getParentClass().getClassLoader();
        final Class<?> extendedClass;

        try (
            final ReflectiveClassLoader loader
                = new ReflectiveClassLoader(classLoader);
        ) {
            extendedClass = loader.loadClass(className, bytecode);
        }

        node.setExtendedClass(extendedClass);
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed 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 com.github.fge.grappa.support;

/**
 * Simple specialization of a {@link Var} for StringBuilders.
 * Provides a few convenience helper methods.
 */
// TODO: value can be null, replace with empty StringBuilder
public class StringBuilderVar
    extends Var<StringBuilder>
{
    /**
     * Initializes a new StringVar with a null initial value.
     */
    public StringBuilderVar()
    {
    }

    /**
     * Initializes a new StringBuilderVar with the given initial StringBuilder instance.
     *
     * @param value the initial value
     */
    public StringBuilderVar(final StringBuilder value)
    {
        super(value);
    }

    /**
     * Returns true if the wrapped string is either null or empty.
     *
     * @return true if the wrapped string is either null or empty
     */
    public boolean isEmpty()
    {
        return get() == null || get().length() == 0;
    }

    /**
     * @return the String representation of the underlying StringBuilder.
     */
    public String getString()
    {
        return get() == null ? "" : get().toString();
    }

    /**
     * @return the char[] representation of the underlying StringBuilder.
     */
    public char[] getChars()
    {
        if (get() == null)
            return new char[0];
        final StringBuilder sb = get();
        final char[] buf = new char[sb.length()];
        sb.getChars(0, buf.length, buf, 0);
        return buf;
    }

    /**
     * Appends the given string.
     * If this instance is currently uninitialized the given string is used for initialization.
     *
     * @param text the text to append
     * @return true
     */
    public boolean append(final String text)
    {
        if (get() == null)
            return set(new StringBuilder(text));
        get().append(text);
        return true;
    }

    /**
     * Appends the given string.
     * If this instance is currently uninitialized the given string is used for initialization.
     *
     * @param text the text to append
     * @return this instance
     */
    public StringBuilderVar appended(final String text)
    {
        append(text);
        return this;
    }

    /**
     * Appends the given char.
     * If this instance is currently uninitialized the given char is used for initialization.
     *
     * @param c the char to append
     * @return true
     */
    public boolean append(final char c)
    {
        if (get() == null)
            return set(new StringBuilder().append(c));
        get().append(c);
        return true;
    }

    /**
     * Appends the given char.
     * If this instance is currently uninitialized the given char is used for initialization.
     *
     * @param c the char to append
     * @return this instance
     */
    public StringBuilderVar appended(final char c)
    {
        append(c);
        return this;
    }

    /**
     * Clears the contents of the wrapped StringBuilder.
     * If the instance is currently unintialized this method does nothing.
     *
     * @return true
     */
    public boolean clearContents()
    {
        if (get() != null)
            get().setLength(0);
        return true;
    }

    /**
     * Clears the contents of the wrapped StringBuilder.
     * If the instance is currently unintialized this method does nothing.
     *
     * @return this instance
     */
    public StringBuilderVar contentsCleared()
    {
        if (get() != null)
            get().setLength(0);
        return this;
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed 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 com.github.fge.grappa.misc;

import com.google.common.base.Optional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * A simple container holding a reference to another object.
 *
 * @param <T>
 */
public class Reference<T>
{
    @SuppressWarnings("InstanceVariableMayNotBeInitialized")
    private T value;

    /**
     * Create a new Reference with a null value.
     */
    public Reference()
    {
    }

    /**
     * Create a new Reference to the given value object.
     *
     * @param value the value object
     */
    public Reference(@Nullable final T value)
    {
        this.value = value;
    }

    /**
     * Sets this references value field to null.
     *
     * @return true
     */
    public final boolean clear()
    {
        return set(null);
    }

    /**
     * Sets this references value object to the given instance.
     *
     * @param value the value
     * @return true
     */
    public final boolean set(@Nullable final T value)
    {
        this.value = value;
        return true;
    }

    /**
     * Retrieves this references value object.
     *
     * @return the target
     */
    @Nullable
    public final T get()
    {
        return value;
    }

    /**
     * Retrieves the non null value stored by this var
     *
     * @return the value if non null
     * @throws IllegalStateException value is null
     *
     * @since 1.0.0-beta.10
     */
    @Nonnull
    public final T getNonnull()
    {
        // See javadoc for Guava's Optional; this throws IllegalStateException
        // if value is null
        return Optional.fromNullable(value).get();
    }

    /**
     * Replaces this references value with the given one.
     *
     * @param value the new value
     * @return the previous value
     */
    public final T getAndSet(final T value)
    {
        final T ret = this.value;
        this.value = value;
        return ret;
    }

    /**
     * @return true if this Reference holds a non-null value
     */
    public final boolean isSet()
    {
        return value != null;
    }
}


/*
 * Copyright (c) 2009-2011 Ken Wenzel and Mathias Doenitz
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.fge.grappa.misc;

import com.github.fge.grappa.parsers.BaseParser;
import com.github.fge.grappa.run.context.ContextAware;
import com.github.fge.grappa.support.Var;
import com.github.fge.grappa.transform.ClassCache;
import com.github.fge.grappa.transform.LoadingOpcode;
import me.qmx.jitescript.util.CodegenUtils;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Objects;

public final class AsmUtils
{
    private static final ClassCache CACHE = ClassCache.INSTANCE;

    private AsmUtils()
    {
    }

    public static String getExtendedParserClassName(
        final String parserClassName)
    {
        Objects.requireNonNull(parserClassName, "parserClassName");
        return parserClassName + "$$grappa";
    }

    /**
     * Get the class equivalent to an ASM {@link Type}
     *
     * @param type the type
     * @return the matching class
     */
    public static Class<?> getClassForType(final Type type)
    {
        Objects.requireNonNull(type, "type");
        switch (type.getSort()) {
            case Type.BOOLEAN:
                return boolean.class;
            case Type.BYTE:
                return byte.class;
            case Type.CHAR:
                return char.class;
            case Type.DOUBLE:
                return double.class;
            case Type.FLOAT:
                return float.class;
            case Type.INT:
                return int.class;
            case Type.LONG:
                return long.class;
            case Type.SHORT:
                return short.class;
            case Type.VOID:
                return void.class;
            case Type.OBJECT:
            case Type.ARRAY:
                return CACHE.loadClass(type.getInternalName());
                //return getClassForInternalName(type.getInternalName());
        }
        throw new IllegalStateException(); // should be unreachable
    }

    public static Field getClassField(final String classInternalName,
        final String fieldName)
    {
        Objects.requireNonNull(classInternalName, "classInternalName");
        Objects.requireNonNull(fieldName, "fieldName");
        final Class<?> c = CACHE.loadClass(classInternalName);
        //final Class<?> c = getClassForInternalName(classInternalName);
        Class<?> current = c;
        while (current != Object.class) {
            for (final Field field: current.getDeclaredFields())
                if (field.getName().equals(fieldName))
                    return field;
            current = current.getSuperclass();
        }
        throw new RuntimeException("Field '" + fieldName + "' not found in '"
            + c.getCanonicalName() + "' or any superclass");
    }

    public static Method getClassMethod(final String classInternalName,
        final String methodName, final String methodDesc)
    {
        Objects.requireNonNull(classInternalName, "classInternalName");
        Objects.requireNonNull(methodName, "methodName");
        Objects.requireNonNull(methodDesc, "methodDesc");

        final Class<?> c = CACHE.loadClass(classInternalName);
        //final Class<?> c = getClassForInternalName(classInternalName);
        final Type[] types = Type.getArgumentTypes(methodDesc);
        final Class<?>[] argTypes = new Class<?>[types.length];

        for (int i = 0; i < types.length; i++)
            argTypes[i] = getClassForType(types[i]);

        final Method method = findMethod(c, methodName, argTypes);
        if (method == null) {
            throw new RuntimeException("Method '" + methodName
                + "' with descriptor '" + methodDesc + "' not found in '"
                + c + "' or any supertype");
        }
        return method;
    }

    @Nullable
    private static Method findMethod(final Class<?> clazz,
        final String methodName, final Class<?>[] argTypes)
    {
        if (clazz == null)
            return null;

        try {
            return clazz.getDeclaredMethod(methodName, argTypes);
        } catch (NoSuchMethodException ignored) {
            Method ret;
            ret = findMethod(clazz.getSuperclass(), methodName, argTypes);
            if (ret != null)
                return ret;

            for (final Class<?> interfaceClass : clazz.getInterfaces()) {
                ret = findMethod(interfaceClass, methodName, argTypes);
                if (ret != null)
                    return ret;
            }
        }
        // TODO: is this actually reachable?
        return null;
    }

    public static Constructor<?> getClassConstructor(
        final String classInternalName, final String constructorDesc)
    {
        Objects.requireNonNull(classInternalName, "classInternalName");
        Objects.requireNonNull(constructorDesc, "constructorDesc");

        final Class<?> c = CACHE.loadClass(classInternalName);
        //final Class<?> c = getClassForInternalName(classInternalName);
        final Type[] types = Type.getArgumentTypes(constructorDesc);
        final Class<?>[] argTypes = new Class<?>[types.length];

        for (int i = 0; i < types.length; i++)
            argTypes[i] = getClassForType(types[i]);

        try {
            return c.getDeclaredConstructor(argTypes);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("Constructor with descriptor '"
                + constructorDesc + "' not found in '" + c, e);
        }
    }

    public static InsnList createArgumentLoaders(final String methodDescriptor)
    {
        Objects.requireNonNull(methodDescriptor, "methodDescriptor");

        final InsnList instructions = new InsnList();
        final Type[] types = Type.getArgumentTypes(methodDescriptor);

        int opcode;
        VarInsnNode node;

        for (int i = 0; i < types.length; i++) {
            opcode = LoadingOpcode.forType(types[i]);
            node = new VarInsnNode(opcode, i + 1);
            instructions.add(node);
        }
        return instructions;
    }

    /**
     * Determines whether the class with the given descriptor is assignable to
     * the given type.
     *
     * @param classInternalName the class descriptor
     * @param type the type
     * @return true if the class with the given descriptor is assignable to the
     * given type
     */
    public static boolean isAssignableTo(final String classInternalName,
        final Class<?> type)
    {
        Objects.requireNonNull(classInternalName, "classInternalName");
        Objects.requireNonNull(type, "type");

        final Class<?> c = CACHE.loadClass(classInternalName);
        //final Class<?> c = getClassForInternalName(classInternalName);
        return type.isAssignableFrom(c);
    }

    public static boolean isBooleanValueOfZ(final AbstractInsnNode insn)
    {
        Objects.requireNonNull(insn, "insn");
        if (insn.getOpcode() != Opcodes.INVOKESTATIC)
            return false;
        final MethodInsnNode mi = (MethodInsnNode) insn;
        return isBooleanValueOfZ(mi.owner, mi.name, mi.desc);
    }

    public static boolean isBooleanValueOfZ(final String methodOwner,
        final String methodName, final String methodDesc)
    {
        Objects.requireNonNull(methodOwner, "methodOwner");
        Objects.requireNonNull(methodName, "methodName");
        Objects.requireNonNull(methodDesc, "methodDesc");

        return CodegenUtils.p(Boolean.class).equals(methodOwner)
            && "valueOf".equals(methodName)
            && CodegenUtils.sig(Boolean.class, boolean.class)
                .equals(methodDesc);
    }

    public static boolean isActionRoot(final AbstractInsnNode insn)
    {
        Objects.requireNonNull(insn, "insn");
        if (insn.getOpcode() != Opcodes.INVOKESTATIC)
            return false;
        final MethodInsnNode mi = (MethodInsnNode) insn;
        return isActionRoot(mi.owner, mi.name);
    }

    public static boolean isActionRoot(final String methodOwner,
        final String methodName)
    {
        Objects.requireNonNull(methodOwner, "methodOwner");
        Objects.requireNonNull(methodName, "methodName");
        return "ACTION".equals(methodName)
            && isAssignableTo(methodOwner, BaseParser.class);
    }

    public static boolean isVarRoot(final AbstractInsnNode insn)
    {
        Objects.requireNonNull(insn, "insn");
        if (insn.getOpcode() != Opcodes.INVOKESPECIAL)
            return false;
        final MethodInsnNode mi = (MethodInsnNode) insn;
        return isVarRoot(mi.owner, mi.name, mi.desc);
    }

    public static boolean isVarRoot(final String methodOwner,
        final String methodName, final String methodDesc)
    {
        Objects.requireNonNull(methodOwner, "methodOwner");
        Objects.requireNonNull(methodName, "methodName");
        Objects.requireNonNull(methodDesc, "methodDesc");

        return "<init>".equals(methodName)
            && CodegenUtils.sig(void.class, Object.class).equals(methodDesc)
            && isAssignableTo(methodOwner, Var.class);
    }

    public static boolean isCallOnContextAware(final AbstractInsnNode insn)
    {
        Objects.requireNonNull(insn, "insn");
        if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL
            && insn.getOpcode() != Opcodes.INVOKEINTERFACE)
            return false;
        final MethodInsnNode mi = (MethodInsnNode) insn;
        return isAssignableTo(mi.owner, ContextAware.class);
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed 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 com.github.fge.grappa.transform;

import com.github.fge.grappa.transform.base.ParserClassNode;
import com.github.fge.grappa.transform.base.RuleMethod;
import com.github.fge.grappa.transform.load.ReflectiveClassLoader;
import com.github.fge.grappa.transform.generate.ActionClassGenerator;
import com.github.fge.grappa.transform.generate.ClassNodeInitializer;
import com.github.fge.grappa.transform.generate.ConstructorGenerator;
import com.github.fge.grappa.transform.generate.VarInitClassGenerator;
import com.github.fge.grappa.transform.process.BodyWithSuperCallReplacer;
import com.github.fge.grappa.transform.process.CachingGenerator;
import com.github.fge.grappa.transform.process.ImplicitActionsConverter;
import com.github.fge.grappa.transform.process.InstructionGraphCreator;
import com.github.fge.grappa.transform.process.InstructionGroupCreator;
import com.github.fge.grappa.transform.process.InstructionGroupPreparer;
import com.github.fge.grappa.transform.process.LabellingGenerator;
import com.github.fge.grappa.transform.process.ReturnInstructionUnifier;
import com.github.fge.grappa.transform.process.RuleMethodProcessor;
import com.github.fge.grappa.transform.process.RuleMethodRewriter;
import com.github.fge.grappa.transform.process.SuperCallRewriter;
import com.github.fge.grappa.transform.process.UnusedLabelsRemover;
import com.github.fge.grappa.transform.process.VarFramingGenerator;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import org.objectweb.asm.ClassWriter;

import java.util.List;
import java.util.Objects;

import static com.github.fge.grappa.misc.AsmUtils.getExtendedParserClassName;

public final class ParserTransformer
{
    private ParserTransformer()
    {
    }

    // TODO: remove "synchronized" here
    // TODO: move elsewhere
    public static synchronized <T> Class<? extends T> transformParser(
        final Class<T> parserClass)
        throws Exception
    {
        Objects.requireNonNull(parserClass, "parserClass");
        // first check whether we did not already create and load the extension
        // of the given parser class
        final String name = getExtendedParserClassName(parserClass.getName());

        final Class<?> extendedClass;

        try (
            final ReflectiveClassLoader loader
                = new ReflectiveClassLoader(parserClass.getClassLoader());
        ) {
            extendedClass = loader.findClass(name);
        }

        final Class<?> ret = extendedClass != null
            ? extendedClass
            : extendParserClass(parserClass).getExtendedClass();
        return (Class<? extends T>) ret;
    }

    /**
     * Dump the bytecode of a transformed parser class
     *
     *
This method will run all bytecode transformations on the parser class
     * then return a dump of the bytecode as a byte array.

     *
     * @param parserClass the parser class
     * @return a bytecode dump
     *
     * @throws Exception FIXME
     * @see #extendParserClass(Class)
     */
    // TODO: poor exception specification
    public static byte[] getByteCode(final Class<?> parserClass)
        throws Exception
    {
        final ParserClassNode node = extendParserClass(parserClass);
        return node.getClassCode();
    }

    @VisibleForTesting
    public static ParserClassNode extendParserClass(final Class<?> parserClass)
        throws Exception
    {
        final ParserClassNode classNode = new ParserClassNode(parserClass);
        new ClassNodeInitializer().process(classNode);
        runMethodTransformers(classNode);
        new ConstructorGenerator().process(classNode);
        defineExtendedParserClass(classNode);
        return classNode;
    }

    // TODO: poor exception handling again
    private static void runMethodTransformers(final ParserClassNode classNode)
        throws Exception
    {
        final List<RuleMethodProcessor> methodProcessors
            = createRuleMethodProcessors();

        // TODO: comment above may be right, but it's still dangerous
        // iterate through all rule methods
        // since the ruleMethods map on the classnode is a treemap we get the
        // methods sorted by name which puts all super methods first (since they
        // are prefixed with one or more '$')
        for (final RuleMethod ruleMethod: classNode.getRuleMethods().values()) {
            if (ruleMethod.hasDontExtend())
                continue;

            for (final RuleMethodProcessor methodProcessor : methodProcessors)
                if (methodProcessor.appliesTo(classNode, ruleMethod))
                    methodProcessor.process(classNode, ruleMethod);
        }

        for (final RuleMethod ruleMethod: classNode.getRuleMethods().values())
            if (!ruleMethod.isGenerationSkipped())
                classNode.methods.add(ruleMethod);
    }

    private static List<RuleMethodProcessor> createRuleMethodProcessors()
    {
        return ImmutableList.of(
            new UnusedLabelsRemover(),
            new ReturnInstructionUnifier(),
            new InstructionGraphCreator(),
            new ImplicitActionsConverter(),
            new InstructionGroupCreator(),
            new InstructionGroupPreparer(),
            new ActionClassGenerator(false),
            new VarInitClassGenerator(false),
            new RuleMethodRewriter(),
            new SuperCallRewriter(),
            new BodyWithSuperCallReplacer(),
            new VarFramingGenerator(),
            new LabellingGenerator(),
            new CachingGenerator()
        );
    }

    private static void defineExtendedParserClass(final ParserClassNode node)
    {
        final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        node.accept(writer);
        node.setClassCode(writer.toByteArray());

        final String className = node.name.replace('/', '.');
        final byte[] bytecode = node.getClassCode();

        final ClassLoader classLoader = node.getParentClass().getClassLoader();
        final Class<?> extendedClass;

        try (
            final ReflectiveClassLoader loader
                = new ReflectiveClassLoader(classLoader);
        ) {
            extendedClass = loader.loadClass(className, bytecode);
        }

        node.setExtendedClass(extendedClass);
    }
}


package com.github.fge.grappa.run.trace;

import com.github.fge.grappa.buffers.InputBuffer;
import com.github.fge.grappa.exceptions.GrappaException;
import com.github.fge.grappa.matchers.base.Matcher;
import com.github.fge.grappa.run.ParseEventListener;
import com.github.fge.grappa.run.context.MatcherContext;
import com.github.fge.grappa.run.events.MatchFailureEvent;
import com.github.fge.grappa.run.events.MatchSuccessEvent;
import com.github.fge.grappa.run.events.PostParseEvent;
import com.github.fge.grappa.run.events.PreMatchEvent;
import com.github.fge.grappa.run.events.PreParseEvent;

import javax.annotation.ParametersAreNonnullByDefault;
import java.io.BufferedWriter;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * The tracing listener
 *
 *
This parsing event listener will collect all match failures and successes,
 * and maintain parent/child relationships between matches.

 *
 *
The information is made as condensed as possible for performance reasons,
 * since parsing a big file can produce several millions of parsing nodes.

 *
 *
The output of this listener is a zip archive which can then be used in
 * the debugger.

 *
 * @param <V> type parameter of the parser's stack values
 */
@ParametersAreNonnullByDefault
public final class TracingListener<V>
    extends ParseEventListener<V>
{
    private static final Map<String, ?> ENV
        = Collections.singletonMap("create", "true");
    private static final String NODE_PATH = "/nodes.csv";
    private static final String MATCHERS_PATH = "/matchers.csv";
    private static final String INPUT_TEXT_PATH = "/input.txt";
    private static final String INFO_PATH = "/info.csv";

    private InputBuffer inputBuffer = null;
    private long startTime = 0L;
    private int nrLines = 0;
    private int nrChars = 0;
    private int nrCodePoints = 0;

    private final Map<Matcher, MatcherDescriptor> matcherDescriptors
        = new IdentityHashMap<>();

    private final Map<Matcher, Integer> matcherIds = new IdentityHashMap<>();
    private int nextMatcherId = 0;

    private final Map<Integer, Integer> nodeIds = new HashMap<>();
    private int nextNodeId = 0;

    private final Map<Integer, Integer> prematchMatcherIds = new HashMap<>();
    private final Map<Integer, Integer> prematchIndices = new HashMap<>();
    private final Map<Integer, Long> prematchTimes = new HashMap<>();

    private final Path zipPath;
    private final Path nodeFile;
    private final BufferedWriter writer;
    private final StringBuilder sb = new StringBuilder();

    /**
     * Constructor
     *
     * @param zipPath path to the zip to be generated
     * @param delete delete the zip if it already exists
     * @throws IOException file already exists and {@code delete} is false; or
     * failed to open the zip for writing
     */
    public TracingListener(final Path zipPath, final boolean delete)
        throws IOException
    {
        this.zipPath = zipPath;
        if (delete)
            Files.deleteIfExists(zipPath);
        nodeFile = Files.createTempFile("nodes", ".csv");
        writer = Files.newBufferedWriter(nodeFile, UTF_8);
    }

    @Override
    public void beforeParse(final PreParseEvent<V> event)
    {
        nodeIds.put(-1, -1);
        inputBuffer = event.getContext().getInputBuffer();
        nrChars = inputBuffer.length();
        nrLines = inputBuffer.getLineCount();
        startTime = System.currentTimeMillis();
    }

    @Override
    public void beforeMatch(final PreMatchEvent<V> event)
    {
        final MatcherContext<V> context = event.getContext();
        final Matcher matcher = context.getMatcher();

        Integer id = matcherIds.get(matcher);
        if (id == null) {
            //noinspection UnnecessaryBoxing
            id = Integer.valueOf(nextMatcherId);
            matcherIds.put(matcher, id);
            matcherDescriptors.put(matcher,
                new MatcherDescriptor(nextMatcherId, matcher));
            nextMatcherId++;
        }

        final int level = context.getLevel();

        nodeIds.put(level, nextNodeId);
        nextNodeId++;

        prematchMatcherIds.put(level, id);
        final int startIndex = Math.min(nrChars, context.getCurrentIndex());
        prematchIndices.put(level, startIndex);
        prematchTimes.put(level, System.nanoTime());
    }

    @SuppressWarnings({ "AutoBoxing", "AutoUnboxing" })
    @Override
    public void matchSuccess(final MatchSuccessEvent<V> event)
    {
        final long endTime = System.nanoTime();
        final MatcherContext<V> context = event.getContext();
        final int level = context.getLevel();

        final Integer parentNodeId = nodeIds.get(level - 1);
        final Integer nodeId = nodeIds.get(level);

        final int startIndex = prematchIndices.get(level);
        final int endIndex
            = Math.min(nrChars, context.getCurrentIndex());

        final Integer matcherId = prematchMatcherIds.get(level);

        final long time = endTime - prematchTimes.get(level);

        // Write:
        // parent;id;level;success;matcherId;start;end;time
        sb.setLength(0);
        sb.append(parentNodeId).append(';')
            .append(nodeId).append(';')
            .append(level).append(";1;")
            .append(matcherId).append(';')
            .append(startIndex).append(';')
            .append(endIndex).append(';')
            .append(time).append('\n');
        try {
            writer.append(sb);
        } catch (IOException e) {
            throw cleanup(e);
        }
    }

    @SuppressWarnings({ "AutoBoxing", "AutoUnboxing" })
    @Override
    public void matchFailure(final MatchFailureEvent<V> event)
    {
        final long endTime = System.nanoTime();
        final MatcherContext<V> context = event.getContext();
        final int level = context.getLevel();

        final Integer parentNodeId = nodeIds.get(level - 1);
        final Integer nodeId = nodeIds.get(level);

        final int startIndex = prematchIndices.get(level);
        final int endIndex = context.getCurrentIndex();

        final Integer matcherId = prematchMatcherIds.get(level);

        final long time = endTime - prematchTimes.get(level);

        // Write:
        // parent;id;level;success;matcherId;start;end;time
        sb.setLength(0);
        sb.append(parentNodeId).append(';')
            .append(nodeId).append(';')
            .append(level).append(";0;")
            .append(matcherId).append(';')
            .append(startIndex).append(';')
            .append(endIndex).append(';')
            .append(time).append('\n');
        try {
            writer.append(sb);
        } catch (IOException e) {
            throw cleanup(e);
        }
    }

    @Override
    public void afterParse(final PostParseEvent<V> event)
    {
        try {
            writer.flush();
            writer.close();
        } catch (IOException e) {
            throw cleanup(e);
        }

        final URI uri = URI.create("jar:" + zipPath.toUri());

        try (
            final FileSystem zipfs = FileSystems.newFileSystem(uri, ENV);
        ) {
            Files.move(nodeFile, zipfs.getPath(NODE_PATH));
            copyInputText(zipfs);
            copyMatcherInfo(zipfs);
            copyParseInfo(zipfs);
        } catch (IOException e) {
            throw cleanup(e);
        }
    }

    private void copyInputText(final FileSystem zipfs)
        throws IOException
    {
        final Path path = zipfs.getPath(INPUT_TEXT_PATH);

        final String s = inputBuffer.extract(0, nrChars);
        nrCodePoints = s.codePointCount(0, nrChars);

        try (
            final BufferedWriter writer = Files.newBufferedWriter(path, UTF_8);
        ) {
            writer.write(s);
            writer.flush();
        }
    }

    private void copyMatcherInfo(final FileSystem zipfs)
    {
        final Path path = zipfs.getPath(MATCHERS_PATH);

        try (
            final BufferedWriter writer = Files.newBufferedWriter(path, UTF_8);
        ) {
            for (final MatcherDescriptor descriptor:
                matcherDescriptors.values()) {
                sb.setLength(0);
                sb.append(descriptor.getId()).append(';')
                    .append(descriptor.getClassName()).append(';')
                    .append(descriptor.getType()).append(';')
                    .append(descriptor.getName()).append('\n');
                writer.append(sb);
            }
            writer.flush();
        } catch (IOException e) {
            throw cleanup(e);
        }
    }

    // MUST be called after copyInputText!
    private void copyParseInfo(final FileSystem zipfs)
        throws IOException
    {
        final Path path = zipfs.getPath(INFO_PATH);
        try (

            final BufferedWriter writer = Files.newBufferedWriter(path, UTF_8);
        ) {
            sb.setLength(0);
            sb.append(startTime).append(';')
                .append(prematchIndices.size()).append(';')
                .append(nextMatcherId).append(';')
                .append(nrLines).append(';')
                .append(nrChars).append(';')
                .append(nrCodePoints).append(';')
                .append(nextNodeId).append('\n');
            writer.append(sb);
            writer.flush();
        }
    }

    private GrappaException cleanup(final IOException e)
    {
        final GrappaException ret
            = new GrappaException("failed to write event", e);
        try {
            writer.close();
        } catch (IOException e2) {
            ret.addSuppressed(e2);
        }

        try {
            Files.deleteIfExists(nodeFile);
        } catch (IOException e3) {
            ret.addSuppressed(e3);
        }

        return ret;
    }
}


/*
 * Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.com>
 *
 * Licensed 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 com.github.fge.grappa.transform.hash;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.FieldNode;
import com.github.fge.grappa.transform.base.InstructionGroup;
import com.github.fge.grappa.transform.process.InstructionGroupPreparer;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;
import java.util.Objects;

/**
 * Hashing for an {@link InstructionGroup}
 *
 *
Not very pretty, that one.

 *
 *
The only entry point, {@link #hash(InstructionGroup, String)}, will hash
 * the entire instructions and field out of the instruction group, then set
 * the name of the instruction group appropriately.

 *
 * @see InstructionGroupPreparer
 */
//TODO: use more than 16 chars; means updating all string-based bytecode tests
@NotThreadSafe
public final class InstructionGroupHasher
    extends MethodVisitor
{
    private static final BaseEncoding BASE_ENCODING
        = BaseEncoding.base32Hex();
    private static final HashFunction SHA1 = Hashing.sha1();

    private final String className;
    private final InstructionGroup group;

    private final Hasher hasher;
    private final LabelListFunnel labelFunnel = new LabelListFunnel();

    /**
     * Generate a hash of the group, use it to name it
     *
     * @param group the instruction group
     * @param className this group's parent class name
     */
    public static void hash(@Nonnull final InstructionGroup group,
        @Nonnull final String className)
    {
        final InstructionGroupHasher groupHasher
            = new InstructionGroupHasher(group, className);
        final String name = groupHasher.hashAndGetName();
        group.setName(name);
    }

    private InstructionGroupHasher(@Nonnull final InstructionGroup group,
        @Nonnull final String className)
    {
        super(Opcodes.ASM5);
        this.group = Objects.requireNonNull(group);
        this.className = Objects.requireNonNull(className);
        hasher = SHA1.newHasher();
    }

    // TODO: not very nice :/
    private String hashAndGetName()
    {
        group.getInstructions().accept(this);
        for (final FieldNode node: group.getFields())
            hasher.putObject(node, FieldNodeFunnel.INSTANCE);
        final byte[] hash = new byte[10];
        hasher.hash().writeBytesTo(hash, 0, 10);
        final String prefix = group.getRoot().isActionRoot()
            ? "Action$" : "VarInit$";
        return prefix + BASE_ENCODING.encode(hash);
    }

    @Override
    public void visitInsn(final int opcode)
    {
        hasher.putInt(opcode);
    }

    @Override
    public void visitIntInsn(final int opcode, final int operand)
    {
        hasher.putInt(opcode).putInt(operand);
    }

    @Override
    public void visitVarInsn(final int opcode, final int var)
    {
        hasher.putInt(opcode).putInt(var);
        /*
         * (copied from original code) Make sure the names of identical actions
         * differ if thet are defined in different parent classes
         *
         * TODO: why var == 0? What does it mean?
         */
        if (opcode == Opcodes.ALOAD && var == 0)
            hasher.putUnencodedChars(className);
    }

    @Override
    public void visitTypeInsn(final int opcode, final String type)
    {
        hasher.putInt(opcode).putUnencodedChars(type);
    }

    @Override
    public void visitFieldInsn(final int opcode, final String owner,
        final String name, final String desc)
    {
        hasher.putInt(opcode).putUnencodedChars(owner)
            .putUnencodedChars(name).putUnencodedChars(desc);
    }

    @Override
    public void visitMethodInsn(final int opcode, final String owner,
        final String name, final String desc, final boolean itf)
    {
        hasher.putInt(opcode).putUnencodedChars(owner)
            .putUnencodedChars(name).putUnencodedChars(desc);
    }

    @Override
    public void visitJumpInsn(final int opcode, final Label label)
    {
        hasher.putInt(opcode).putObject(label, labelFunnel);
    }

    @Override
    public void visitLabel(final Label label)
    {
        hasher.putObject(label, labelFunnel);
    }

    @Override
    public void visitLdcInsn(final Object cst)
    {
        hasher.putObject(cst, LdcInsnFunnel.INSTANCE);
    }

    @Override
    public void visitIincInsn(final int var, final int increment)
    {
        hasher.putInt(var).putInt(increment);
    }

    @Override
    public void visitTableSwitchInsn(final int min, final int max,
        final Label dflt, final Label... labels)
    {
        hasher.putInt(min).putInt(max).putObject(dflt, labelFunnel);
        for (final Label label: labels)
            hasher.putObject(label, labelFunnel);
    }

    @Override
    public void visitLookupSwitchInsn(final Label dflt, final int[] keys,
        final Label[] labels)
    {
        hasher.putObject(dflt, labelFunnel);
        for (int i = 0; i < keys.length; i++)
            hasher.putInt(keys[i]).putObject(labels[i], labelFunnel);
    }

    @Override
    public void visitMultiANewArrayInsn(final String desc, final int dims)
    {
        hasher.putUnencodedChars(desc).putInt(dims);
    }

    @Override
    public void visitTryCatchBlock(final Label start, final Label end,
        final Label handler, final String type)
    {
        hasher.putObject(start, labelFunnel).putObject(end, labelFunnel)
            .putObject(handler, labelFunnel).putUnencodedChars(type);
    }
}


/*
 * Copyright (C) 2009-2011 Mathias Doenitz
 *
 * Licensed 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 com.github.fge.grappa.matchers.wrap;

import com.github.fge.grappa.matchers.MatcherType;
import com.github.fge.grappa.matchers.base.Matcher;
import com.github.fge.grappa.rules.Rule;
import com.github.fge.grappa.run.context.MatcherContext;
import com.github.fge.grappa.support.Var;

import java.util.List;
import java.util.Objects;

/**
 * Special wrapping matcher that manages the creation and destruction of
 * execution frames for a number of action vars
 *
 * @see Var
 */
// TODO: use delegation pattern
public final class VarFramingMatcher
    implements Matcher
{
    private final Matcher inner;
    private final Var<?>[] variables;

    public VarFramingMatcher(final Rule inner, final Var<?>[] variables)
    {
        this.inner = Objects.requireNonNull((Matcher) inner, "inner");
        this.variables = Objects.requireNonNull(variables, "variables");
    }

    @Override
    public MatcherType getType()
    {
        return inner.getType();
    }

    @Override
    public <V> boolean match(final MatcherContext<V> context)
    {
        for (final Var<?> var: variables)
            var.enterFrame();

        final boolean matched = inner.match(context);

        for (final Var<?> var : variables)
            var.exitFrame();

        return matched;
    }

    // GraphNode

    @Override
    public List<Matcher> getChildren()
    {
        return inner.getChildren();
    }

    // Rule

    @Override
    public Rule label(final String label)
    {
        return new VarFramingMatcher(inner.label(label), variables);
    }

    // Matcher

    @Override
    public String getLabel()
    {
        return inner.getLabel();
    }

    @Override
    public boolean hasCustomLabel()
    {
        return inner.hasCustomLabel();
    }

    @Override
    public <V> MatcherContext<V> getSubContext(final MatcherContext<V> context)
    {
        final MatcherContext<V> subContext = inner.getSubContext(context);
         // we need to inject ourselves here otherwise we get cut out
        subContext.setMatcher(this);
        return subContext;
    }

    @Override
    public String toString()
    {
        return inner.toString();
    }

    /**
     * Retrieves the innermost Matcher that is not a VarFramingMatcher.
     *
     * @param matcher the matcher to unwrap
     * @return the given instance if it is not a VarFramingMatcher, otherwise the innermost Matcher
     */
    public static Matcher unwrap(final Matcher matcher)
    {
        if (!(matcher instanceof VarFramingMatcher))
            return matcher;
        return unwrap(((VarFramingMatcher) matcher).inner);
    }
}