/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard;

import proguard.classfile.attribute.annotation.visitor.*;
import proguard.classfile.attribute.visitor.*;
import proguard.classfile.visitor.*;
import proguard.util.*;

import java.util.List;

/**
 * This factory creates visitors to efficiently travel to specified classes and
 * class members.
 *
 * @author Eric Lafortune
 */
public class ClassSpecificationVisitorFactory
{
    /**
     * Constructs a ClassPoolVisitor to efficiently travel to the specified
     * classes, class members, and attributes.
     *
     * @param classSpecifications the list of ClassSpecification instances
     *                            that specify the classes and class members
     *                            to visit.
     * @param classVisitor        an optional ClassVisitor to be applied to
     *                            all classes.
     * @param memberVisitor       an optional MemberVisitor to be applied to
     *                            matching fields and methods.
     */
    public ClassPoolVisitor createClassPoolVisitor(List          classSpecifications,
                                                   ClassVisitor  classVisitor,
                                                   MemberVisitor memberVisitor)
    {
        return createClassPoolVisitor(classSpecifications,
                                      classVisitor,
                                      memberVisitor,
                                      memberVisitor,
                                      null);
    }


    /**
     * Constructs a ClassPoolVisitor to efficiently travel to the specified
     * classes and class members.
     *
     * @param classSpecifications the list of ClassSpecification instances
     *                            that specify the classes and class members
     *                            to visit.
     * @param classVisitor        an optional ClassVisitor to be applied to
     *                            all classes.
     * @param fieldVisitor        an optional MemberVisitor to be applied to
     *                            matching fields.
     * @param methodVisitor       an optional MemberVisitor to be applied to
     *                            matching methods.
     * @param attributeVisitor    an optional AttributeVisitor to be applied
     *                            to matching attributes.
     */
    public ClassPoolVisitor createClassPoolVisitor(List             classSpecifications,
                                                   ClassVisitor     classVisitor,
                                                   MemberVisitor    fieldVisitor,
                                                   MemberVisitor    methodVisitor,
                                                   AttributeVisitor attributeVisitor)
    {
        MultiClassPoolVisitor multiClassPoolVisitor = new MultiClassPoolVisitor();

        if (classSpecifications != null)
        {
            for (int index = 0; index < classSpecifications.size(); index++)
            {
                ClassSpecification classSpecification =
                    (ClassSpecification)classSpecifications.get(index);

                multiClassPoolVisitor.addClassPoolVisitor(
                    createClassPoolVisitor(classSpecification,
                                           classVisitor,
                                           fieldVisitor,
                                           methodVisitor,
                                           attributeVisitor,
                                           null));
            }
        }

        return multiClassPoolVisitor;
    }


    /**
     * Constructs a ClassPoolVisitor to efficiently travel to the specified
     * classes, class members, and attributes.
     *
     * @param classSpecification     the specifications of the class(es) and class
     *                               members to visit.
     * @param classVisitor           an optional ClassVisitor to be applied to
     *                               matching classes.
     * @param fieldVisitor           an optional MemberVisitor to be applied to
     *                               matching fields.
     * @param methodVisitor          an optional MemberVisitor to be applied to
     *                               matching methods.
     * @param attributeVisitor       an optional AttributeVisitor to be applied
     *                               to matching attributes.
     * @param variableStringMatchers an optional mutable list of
     *                               VariableStringMatcher instances that match
     *                               the wildcards.
     */
    protected ClassPoolVisitor createClassPoolVisitor(ClassSpecification classSpecification,
                                                      ClassVisitor       classVisitor,
                                                      MemberVisitor      fieldVisitor,
                                                      MemberVisitor      methodVisitor,
                                                      AttributeVisitor   attributeVisitor,
                                                      List               variableStringMatchers)
    {
        String className             = classSpecification.className;
        String annotationType        = classSpecification.annotationType;
        String extendsAnnotationType = classSpecification.extendsAnnotationType;
        String extendsClassName      = classSpecification.extendsClassName;

        // We explicitly need to match a wildcard class name, so it can be
        // referenced through its variable string matcher.
        if (className == null)
        {
            className = "**";
        }

        // We need to parse the class names before any class member names, to
        // make sure the list of variable string matchers is filled out in the
        // right order.
        StringMatcher annotationTypeMatcher = annotationType == null ? null :
            new ListParser(new ClassNameParser(variableStringMatchers)).parse(annotationType);

        StringMatcher classNameMatcher =
            new ListParser(new ClassNameParser(variableStringMatchers)).parse(className);

        StringMatcher extendsAnnotationTypeMatcher = extendsAnnotationType == null ? null :
            new ListParser(new ClassNameParser(variableStringMatchers)).parse(extendsAnnotationType);

        StringMatcher extendsClassNameMatcher = extendsClassName == null ? null :
            new ListParser(new ClassNameParser(variableStringMatchers)).parse(extendsClassName);

        // Combine both visitors.
        ClassVisitor combinedClassVisitor =
            createCombinedClassVisitor(classSpecification.attributeNames,
                                       classSpecification.fieldSpecifications,
                                       classSpecification.methodSpecifications,
                                       classVisitor,
                                       fieldVisitor,
                                       methodVisitor,
                                       attributeVisitor,
                                       variableStringMatchers);

        // If the class name has wildcards, only visit classes with matching names.
        if (extendsAnnotationType != null ||
            extendsClassName      != null ||
            containsWildCards(className))
        {
            combinedClassVisitor =
                new ClassNameFilter(classNameMatcher, combinedClassVisitor);

            // We'll have to visit all classes now.
            className = null;
        }

        // If specified, only visit classes with the right annotation.
        if (annotationType != null)
        {
            combinedClassVisitor =
                new AllAttributeVisitor(
                new AllAnnotationVisitor(
                new AnnotationTypeFilter(annotationTypeMatcher,
                new AnnotationToAnnotatedClassVisitor(combinedClassVisitor))));
        }

        // If specified, only visit classes with the right access flags.
        if (classSpecification.requiredSetAccessFlags   != 0 ||
            classSpecification.requiredUnsetAccessFlags != 0)
        {
            combinedClassVisitor =
                new ClassAccessFilter(classSpecification.requiredSetAccessFlags,
                                      classSpecification.requiredUnsetAccessFlags,
                                      combinedClassVisitor);
        }

        // If it's specified, start visiting from the extended class.
        if (extendsAnnotationType != null ||
            extendsClassName      != null)
        {
            // Start visiting from the extended class.
            combinedClassVisitor =
                new ClassHierarchyTraveler(false, false, false, true,
                                           combinedClassVisitor);

            // If specified, only visit extended classes with the right annotation.
            if (extendsAnnotationType != null)
            {
                combinedClassVisitor =
                    new AllAttributeVisitor(
                    new AllAnnotationVisitor(
                    new AnnotationTypeFilter(extendsAnnotationTypeMatcher,
                    new AnnotationToAnnotatedClassVisitor(combinedClassVisitor))));
            }

            // If specified, only visit extended classes with matching names.
            if (extendsClassName != null)
            {
                // If wildcarded, only visit extended classes with matching names.
                if (containsWildCards(extendsClassName))
                {
                    combinedClassVisitor =
                        new ClassNameFilter(extendsClassNameMatcher,
                                            combinedClassVisitor);
                }
                else
                {
                    // Start visiting from the named extended class.
                    className = extendsClassName;
                }
            }
        }

        // If specified, visit a single named class, otherwise visit all classes.
        return className != null ?
            new NamedClassVisitor(combinedClassVisitor, className) :
            new AllClassVisitor(combinedClassVisitor);
    }


    /**
     * Constructs a ClassVisitor to efficiently delegate to the given ClassVisitor
     * and travel to the specified class members and attributes.
     * @param attributeNames         optional names (with wildcards) of class
     *                               attributes to visit.
     * @param fieldSpecifications    optional specifications of the fields to
     *                               visit.
     * @param methodSpecifications   optional specifications of the methods to
     *                               visit.
     * @param classVisitor           an optional ClassVisitor to be applied to
     *                               all classes.
     * @param fieldVisitor           an optional MemberVisitor to be applied to
     *                               matching fields.
     * @param methodVisitor          an optional MemberVisitor to be applied to
     *                               matching methods.
     * @param attributeVisitor       an optional AttributeVisitor to be applied
     *                               to matching attributes.
     * @param variableStringMatchers an optional mutable list of
     *                               VariableStringMatcher instances that match
     */
    protected ClassVisitor createCombinedClassVisitor(List             attributeNames,
                                                      List             fieldSpecifications,
                                                      List             methodSpecifications,
                                                      ClassVisitor     classVisitor,
                                                      MemberVisitor    fieldVisitor,
                                                      MemberVisitor    methodVisitor,
                                                      AttributeVisitor attributeVisitor,
                                                      List             variableStringMatchers)
    {
        // Don't visit any members if there aren't any member specifications.
        if (fieldSpecifications  == null)
        {
            fieldVisitor  = null;
        }

        if (methodSpecifications == null)
        {
            methodVisitor = null;
        }

        // The class visitor for classes and their members.
        MultiClassVisitor multiClassVisitor = new MultiClassVisitor();

        // If specified, let the class visitor visit the class itself.
        if (classVisitor != null)
        {
            // This class visitor may be the only one.
            if (fieldVisitor     == null &&
                methodVisitor    == null &&
                attributeVisitor == null)
            {
                return classVisitor;
            }

            multiClassVisitor.addClassVisitor(classVisitor);
        }

        // If specified, let the attribute visitor visit the class attributes.
        if (attributeVisitor != null)
        {
            // If specified, only visit attributes with the right names.
            if (attributeNames != null)
            {
                attributeVisitor =
                    new AttributeNameFilter(attributeNames, attributeVisitor);
            }

            multiClassVisitor.addClassVisitor(new AllAttributeVisitor(attributeVisitor));
        }

        // If specified, let the member info visitor visit the class members.
        if (fieldVisitor  != null ||
            methodVisitor != null)
        {
            ClassVisitor memberClassVisitor =
                createClassVisitor(fieldSpecifications,
                                   methodSpecifications,
                                   fieldVisitor,
                                   methodVisitor,
                                   attributeVisitor,
                                   variableStringMatchers);

            // This class visitor may be the only one.
            if (classVisitor == null)
            {
                return memberClassVisitor;
            }

            multiClassVisitor.addClassVisitor(memberClassVisitor);
        }

        return multiClassVisitor;
    }


    /**
     * Constructs a ClassVisitor to efficiently travel to the specified class
     * members.
     *
     * @param fieldSpecifications    the specifications of the fields to visit.
     * @param methodSpecifications   the specifications of the methods to visit.
     * @param fieldVisitor           an optional MemberVisitor to be applied to
     *                               matching fields.
     * @param methodVisitor          an optional MemberVisitor to be applied to
     *                               matching methods.
     * @param attributeVisitor       an optional AttributeVisitor to be applied
     *                               to matching attributes.
     * @param variableStringMatchers an optional mutable list of
     *                               VariableStringMatcher instances that match
     *                               the wildcards.
     */
    private ClassVisitor createClassVisitor(List             fieldSpecifications,
                                            List             methodSpecifications,
                                            MemberVisitor    fieldVisitor,
                                            MemberVisitor    methodVisitor,
                                            AttributeVisitor attributeVisitor,
                                            List             variableStringMatchers)
    {
        MultiClassVisitor multiClassVisitor = new MultiClassVisitor();

        addMemberVisitors(fieldSpecifications,  true,  multiClassVisitor, fieldVisitor,  attributeVisitor, variableStringMatchers);
        addMemberVisitors(methodSpecifications, false, multiClassVisitor, methodVisitor, attributeVisitor, variableStringMatchers);

        // Mark the class member in this class and in super classes.
        return new ClassHierarchyTraveler(true, true, false, false,
                                          multiClassVisitor);
    }


    /**
     * Adds elements to the given MultiClassVisitor, to apply the given
     * MemberVisitor to all class members that match the given List
     * of options (of the given type).
     */
    private void addMemberVisitors(List              memberSpecifications,
                                   boolean           isField,
                                   MultiClassVisitor multiClassVisitor,
                                   MemberVisitor     memberVisitor,
                                   AttributeVisitor  attributeVisitor,
                                   List              variableStringMatchers)
    {
        if (memberSpecifications != null)
        {
            for (int index = 0; index < memberSpecifications.size(); index++)
            {
                MemberSpecification memberSpecification =
                    (MemberSpecification)memberSpecifications.get(index);

                multiClassVisitor.addClassVisitor(
                    createClassVisitor(memberSpecification,
                                       isField,
                                       memberVisitor,
                                       attributeVisitor,
                                       variableStringMatchers));
            }
        }
    }


    /**
     * Constructs a ClassPoolVisitor that conditionally applies the given
     * ClassPoolVisitor for all classes that match the given class
     * specification.
     */
    protected ClassPoolVisitor createClassTester(ClassSpecification classSpecification,
                                                 ClassPoolVisitor   classPoolVisitor,
                                                 List               variableStringMatchers)
    {
        ClassPoolClassVisitor classPoolClassVisitor =
            new ClassPoolClassVisitor(classPoolVisitor);

        // Parse the class condition.
        ClassPoolVisitor conditionalClassTester =
            createClassTester(classSpecification,
                              (ClassVisitor)classPoolClassVisitor,
                              variableStringMatchers);

        // The ClassPoolClassVisitor first needs to visit the class pool
        // and then its classes.
        return new MultiClassPoolVisitor(
               new ClassPoolVisitor[]
               {
                   classPoolClassVisitor,
                   conditionalClassTester
               });
    }


    /**
     * Constructs a ClassPoolVisitor that conditionally applies the given
     * ClassVisitor to all classes that match the given class specification.
     */
    protected ClassPoolVisitor createClassTester(ClassSpecification classSpecification,
                                                 ClassVisitor       classVisitor,
                                                 List               variableStringMatchers)
    {
        // Create a placeholder for the class visitor that tests class
        // members.
        MultiClassVisitor conditionalMemberTester =
            new MultiClassVisitor();

        // Parse the class condition.
        ClassPoolVisitor conditionalClassTester =
            createClassPoolVisitor(classSpecification,
                                   conditionalMemberTester,
                                   null,
                                   null,
                                   null,
                                   variableStringMatchers);

        // Parse the member conditions and add the result to the placeholder.
        conditionalMemberTester.addClassVisitor(
            createClassMemberTester(classSpecification.fieldSpecifications,
                                    classSpecification.methodSpecifications,
                                    classVisitor,
                                    variableStringMatchers));

        return conditionalClassTester;
    }


    /**
     * Constructs a ClassVisitor that conditionally applies the given
     * ClassVisitor to all classes that contain the given class members.
     */
    private ClassVisitor createClassMemberTester(List         fieldSpecifications,
                                                 List         methodSpecifications,
                                                 ClassVisitor classVisitor,
                                                 List         variableStringMatchers)
    {
        // Create a linked list of conditional visitors, for fields and for
        // methods.
        return createClassMemberTester(fieldSpecifications,
                                       true,
               createClassMemberTester(methodSpecifications,
                                       false,
                                       classVisitor, variableStringMatchers),
                                       variableStringMatchers);
    }


    /**
     * Constructs a ClassVisitor that conditionally applies the given
     * ClassVisitor to all classes that contain the given List of class
     * members (of the given type).
     */
    private ClassVisitor createClassMemberTester(List         memberSpecifications,
                                                 boolean      isField,
                                                 ClassVisitor classVisitor,
                                                 List         variableStringMatchers)
    {
        // Create a linked list of conditional visitors.
        if (memberSpecifications != null)
        {
            for (int index = 0; index < memberSpecifications.size(); index++)
            {
                MemberSpecification memberSpecification =
                    (MemberSpecification)memberSpecifications.get(index);

                classVisitor =
                    createClassVisitor(memberSpecification,
                                       isField,
                                       new MemberToClassVisitor(classVisitor),
                                       null,
                                       variableStringMatchers);
            }
        }

        return classVisitor;
    }


    /**
     * Creates a new ClassVisitor to efficiently travel to the specified class
     * members and attributes.
     *
     * @param memberSpecification    the specification of the class member(s) to
     *                               visit.
     * @param memberVisitor          the MemberVisitor to be applied to matching
     *                               class member(s).
     * @param variableStringMatchers a mutable list of VariableStringMatcher
     *                               instances that match the wildcards.
     */
    private ClassVisitor createClassVisitor(MemberSpecification memberSpecification,
                                            boolean             isField,
                                            MemberVisitor       memberVisitor,
                                            AttributeVisitor    attributeVisitor,
                                            List                variableStringMatchers)
    {
        String annotationType = memberSpecification.annotationType;
        String name           = memberSpecification.name;
        String descriptor     = memberSpecification.descriptor;
        List   attributeNames = memberSpecification.attributeNames;

        // We need to parse the names before the descriptors, to make sure the
        // list of variable string matchers is filled out in the right order.
        StringMatcher annotationTypeMatcher = annotationType == null ? null :
            new ListParser(new ClassNameParser(variableStringMatchers)).parse(annotationType);

        StringMatcher nameMatcher = name == null ? null :
            new ListParser(new NameParser(variableStringMatchers)).parse(name);

        StringMatcher descriptorMatcher = descriptor == null ? null :
            new ListParser(new ClassNameParser(variableStringMatchers)).parse(descriptor);

        StringMatcher attributesMatcher = attributeNames == null ? null :
            new ListParser(new NameParser(variableStringMatchers)).parse(attributeNames);

        // If specified, let the attribute visitor visit the class member
        // attributes.
        if (attributeVisitor != null)
        {
            // If specified, only visit attributes with the right names.
            if (attributesMatcher != null)
            {
                attributeVisitor =
                    new AttributeNameFilter(attributesMatcher, attributeVisitor);
            }

            memberVisitor =
                new MultiMemberVisitor(
                new MemberVisitor[]
                {
                    memberVisitor,
                    new AllAttributeVisitor(attributeVisitor)
                });
        }

        // If specified, only visit class members with the right annotation.
        if (memberSpecification.annotationType != null)
        {
            memberVisitor =
                new AllAttributeVisitor(
                new AllAnnotationVisitor(
                new AnnotationTypeFilter(annotationTypeMatcher,
                new AnnotationToAnnotatedMemberVisitor(memberVisitor))));
        }

        // If any access flags are specified, only visit matching class members.
        if (memberSpecification.requiredSetAccessFlags   != 0 ||
            memberSpecification.requiredUnsetAccessFlags != 0)
        {
            memberVisitor =
                new MemberAccessFilter(memberSpecification.requiredSetAccessFlags,
                                       memberSpecification.requiredUnsetAccessFlags,
                                       memberVisitor);
        }

        // Are the name and descriptor fully specified?
        if (name       != null       &&
            descriptor != null       &&
            !containsWildCards(name) &&
            !containsWildCards(descriptor))
        {
            // Somewhat more efficiently, visit a single named class member.
            return isField ?
                new NamedFieldVisitor(name, descriptor, memberVisitor) :
                new NamedMethodVisitor(name, descriptor, memberVisitor);
        }

        // If specified, only visit class members with the right descriptors.
        if (descriptorMatcher != null)
        {
            memberVisitor =
                new MemberDescriptorFilter(descriptorMatcher, memberVisitor);
        }

        // If specified, only visit class members with the right names.
        if (name != null)
        {
            memberVisitor =
                new MemberNameFilter(nameMatcher, memberVisitor);
        }

        // Visit all class members, filtering the matching ones.
        return isField ?
            new AllFieldVisitor(memberVisitor) :
            new AllMethodVisitor(memberVisitor);
    }


    // Small utility methods.

    /**
     * Returns whether the given string contains a wild card.
     */
    private boolean containsWildCards(String string)
    {
        return string != null &&
            (string.indexOf('!')   >= 0 ||
             string.indexOf('*')   >= 0 ||
             string.indexOf('?')   >= 0 ||
             string.indexOf('%')   >= 0 ||
             string.indexOf(',')   >= 0 ||
             string.indexOf("///") >= 0 ||
             containsWildCardReferences(string));
    }


    /**
     * Returns whether the given string contains a numeric reference to a
     * wild card ("<n>").
     */
    private boolean containsWildCardReferences(String string)
    {
        int openIndex = string.indexOf('<');
        if (openIndex < 0)
        {
            return false;
        }

        int closeIndex = string.indexOf('>', openIndex + 1);
        if (closeIndex < 0)
        {
            return false;
        }

        try
        {
            Integer.parseInt(string.substring(openIndex + 1, closeIndex));
        }
        catch (NumberFormatException e)
        {
            return false;
        }

        return true;
    }
}
