/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2015 Eric Lafortune @ GuardSquare
 *
 * 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.optimize;

import proguard.*;
import proguard.classfile.*;
import proguard.classfile.attribute.visitor.*;
import proguard.classfile.constant.visitor.*;
import proguard.classfile.editor.*;
import proguard.classfile.instruction.visitor.*;
import proguard.classfile.util.MethodLinker;
import proguard.classfile.visitor.*;
import proguard.evaluation.InvocationUnit;
import proguard.evaluation.value.*;
import proguard.optimize.evaluation.*;
import proguard.optimize.info.*;
import proguard.optimize.peephole.*;
import proguard.util.*;

import java.io.IOException;
import java.util.*;

/**
 * This class optimizes class pools according to a given configuration.
 *
 * @author Eric Lafortune
 */
public class Optimizer
{
    private static final String CLASS_MARKING_FINAL            = "class/marking/final";
    private static final String CLASS_UNBOXING_ENUM            = "class/unboxing/enum";
    private static final String CLASS_MERGING_VERTICAL         = "class/merging/vertical";
    private static final String CLASS_MERGING_HORIZONTAL       = "class/merging/horizontal";
    private static final String FIELD_REMOVAL_WRITEONLY        = "field/removal/writeonly";
    private static final String FIELD_MARKING_PRIVATE          = "field/marking/private";
    private static final String FIELD_PROPAGATION_VALUE        = "field/propagation/value";
    private static final String METHOD_MARKING_PRIVATE         = "method/marking/private";
    private static final String METHOD_MARKING_STATIC          = "method/marking/static";
    private static final String METHOD_MARKING_FINAL           = "method/marking/final";
    private static final String METHOD_REMOVAL_PARAMETER       = "method/removal/parameter";
    private static final String METHOD_PROPAGATION_PARAMETER   = "method/propagation/parameter";
    private static final String METHOD_PROPAGATION_RETURNVALUE = "method/propagation/returnvalue";
    private static final String METHOD_INLINING_SHORT          = "method/inlining/short";
    private static final String METHOD_INLINING_UNIQUE         = "method/inlining/unique";
    private static final String METHOD_INLINING_TAILRECURSION  = "method/inlining/tailrecursion";
    private static final String CODE_MERGING                   = "code/merging";
    private static final String CODE_SIMPLIFICATION_VARIABLE   = "code/simplification/variable";
    private static final String CODE_SIMPLIFICATION_ARITHMETIC = "code/simplification/arithmetic";
    private static final String CODE_SIMPLIFICATION_CAST       = "code/simplification/cast";
    private static final String CODE_SIMPLIFICATION_FIELD      = "code/simplification/field";
    private static final String CODE_SIMPLIFICATION_BRANCH     = "code/simplification/branch";
    private static final String CODE_SIMPLIFICATION_STRING     = "code/simplification/string";
    private static final String CODE_SIMPLIFICATION_ADVANCED   = "code/simplification/advanced";
    private static final String CODE_REMOVAL_ADVANCED          = "code/removal/advanced";
    private static final String CODE_REMOVAL_SIMPLE            = "code/removal/simple";
    private static final String CODE_REMOVAL_VARIABLE          = "code/removal/variable";
    private static final String CODE_REMOVAL_EXCEPTION         = "code/removal/exception";
    private static final String CODE_ALLOCATION_VARIABLE       = "code/allocation/variable";


    public static final String[] OPTIMIZATION_NAMES = new String[]
    {
        CLASS_MARKING_FINAL,
        CLASS_MERGING_VERTICAL,
        CLASS_MERGING_HORIZONTAL,
        FIELD_REMOVAL_WRITEONLY,
        FIELD_MARKING_PRIVATE,
        FIELD_PROPAGATION_VALUE,
        METHOD_MARKING_PRIVATE,
        METHOD_MARKING_STATIC,
        METHOD_MARKING_FINAL,
        METHOD_REMOVAL_PARAMETER,
        METHOD_PROPAGATION_PARAMETER,
        METHOD_PROPAGATION_RETURNVALUE,
        METHOD_INLINING_SHORT,
        METHOD_INLINING_UNIQUE,
        METHOD_INLINING_TAILRECURSION,
        CODE_MERGING,
        CODE_SIMPLIFICATION_VARIABLE,
        CODE_SIMPLIFICATION_ARITHMETIC,
        CODE_SIMPLIFICATION_CAST,
        CODE_SIMPLIFICATION_FIELD,
        CODE_SIMPLIFICATION_BRANCH,
        CODE_SIMPLIFICATION_STRING,
        CODE_SIMPLIFICATION_ADVANCED,
        CODE_REMOVAL_ADVANCED,
        CODE_REMOVAL_SIMPLE,
        CODE_REMOVAL_VARIABLE,
        CODE_REMOVAL_EXCEPTION,
        CODE_ALLOCATION_VARIABLE,
    };


    private final Configuration configuration;


    /**
     * Creates a new Optimizer.
     */
    public Optimizer(Configuration configuration)
    {
        this.configuration = configuration;
    }


    /**
     * Performs optimization of the given program class pool.
     */
    public boolean execute(ClassPool programClassPool,
                           ClassPool libraryClassPool) throws IOException
    {
        // Check if we have at least some keep commands.
        if (configuration.keep         == null &&
            configuration.applyMapping == null &&
            configuration.printMapping == null)
        {
            throw new IOException("You have to specify '-keep' options for the optimization step.");
        }

        // Create a matcher for filtering optimizations.
        StringMatcher filter = configuration.optimizations != null ?
            new ListParser(new NameParser()).parse(configuration.optimizations) :
            new ConstantMatcher(true);

        boolean classMarkingFinal            = filter.matches(CLASS_MARKING_FINAL);
        boolean classUnboxingEnum            = filter.matches(CLASS_UNBOXING_ENUM);
        boolean classMergingVertical         = filter.matches(CLASS_MERGING_VERTICAL);
        boolean classMergingHorizontal       = filter.matches(CLASS_MERGING_HORIZONTAL);
        boolean fieldRemovalWriteonly        = filter.matches(FIELD_REMOVAL_WRITEONLY);
        boolean fieldMarkingPrivate          = filter.matches(FIELD_MARKING_PRIVATE);
        boolean fieldPropagationValue        = filter.matches(FIELD_PROPAGATION_VALUE);
        boolean methodMarkingPrivate         = filter.matches(METHOD_MARKING_PRIVATE);
        boolean methodMarkingStatic          = filter.matches(METHOD_MARKING_STATIC);
        boolean methodMarkingFinal           = filter.matches(METHOD_MARKING_FINAL);
        boolean methodRemovalParameter       = filter.matches(METHOD_REMOVAL_PARAMETER);
        boolean methodPropagationParameter   = filter.matches(METHOD_PROPAGATION_PARAMETER);
        boolean methodPropagationReturnvalue = filter.matches(METHOD_PROPAGATION_RETURNVALUE);
        boolean methodInliningShort          = filter.matches(METHOD_INLINING_SHORT);
        boolean methodInliningUnique         = filter.matches(METHOD_INLINING_UNIQUE);
        boolean methodInliningTailrecursion  = filter.matches(METHOD_INLINING_TAILRECURSION);
        boolean codeMerging                  = filter.matches(CODE_MERGING);
        boolean codeSimplificationVariable   = filter.matches(CODE_SIMPLIFICATION_VARIABLE);
        boolean codeSimplificationArithmetic = filter.matches(CODE_SIMPLIFICATION_ARITHMETIC);
        boolean codeSimplificationCast       = filter.matches(CODE_SIMPLIFICATION_CAST);
        boolean codeSimplificationField      = filter.matches(CODE_SIMPLIFICATION_FIELD);
        boolean codeSimplificationBranch     = filter.matches(CODE_SIMPLIFICATION_BRANCH);
        boolean codeSimplificationString     = filter.matches(CODE_SIMPLIFICATION_STRING);
        boolean codeSimplificationAdvanced   = filter.matches(CODE_SIMPLIFICATION_ADVANCED);
        boolean codeRemovalAdvanced          = filter.matches(CODE_REMOVAL_ADVANCED);
        boolean codeRemovalSimple            = filter.matches(CODE_REMOVAL_SIMPLE);
        boolean codeRemovalVariable          = filter.matches(CODE_REMOVAL_VARIABLE);
        boolean codeRemovalException         = filter.matches(CODE_REMOVAL_EXCEPTION);
        boolean codeAllocationVariable       = filter.matches(CODE_ALLOCATION_VARIABLE);

        // Create counters to count the numbers of optimizations.
        ClassCounter       classMarkingFinalCounter            = new ClassCounter();
        ClassCounter       classUnboxingEnumCounter            = new ClassCounter();
        ClassCounter       classMergingVerticalCounter         = new ClassCounter();
        ClassCounter       classMergingHorizontalCounter       = new ClassCounter();
        MemberCounter      fieldRemovalWriteonlyCounter        = new MemberCounter();
        MemberCounter      fieldMarkingPrivateCounter          = new MemberCounter();
        MemberCounter      fieldPropagationValueCounter        = new MemberCounter();
        MemberCounter      methodMarkingPrivateCounter         = new MemberCounter();
        MemberCounter      methodMarkingStaticCounter          = new MemberCounter();
        MemberCounter      methodMarkingFinalCounter           = new MemberCounter();
        MemberCounter      methodRemovalParameterCounter       = new MemberCounter();
        MemberCounter      methodPropagationParameterCounter   = new MemberCounter();
        MemberCounter      methodPropagationReturnvalueCounter = new MemberCounter();
        InstructionCounter methodInliningShortCounter          = new InstructionCounter();
        InstructionCounter methodInliningUniqueCounter         = new InstructionCounter();
        InstructionCounter methodInliningTailrecursionCounter  = new InstructionCounter();
        InstructionCounter codeMergingCounter                  = new InstructionCounter();
        InstructionCounter codeSimplificationVariableCounter   = new InstructionCounter();
        InstructionCounter codeSimplificationArithmeticCounter = new InstructionCounter();
        InstructionCounter codeSimplificationCastCounter       = new InstructionCounter();
        InstructionCounter codeSimplificationFieldCounter      = new InstructionCounter();
        InstructionCounter codeSimplificationBranchCounter     = new InstructionCounter();
        InstructionCounter codeSimplificationStringCounter     = new InstructionCounter();
        InstructionCounter codeSimplificationAdvancedCounter   = new InstructionCounter();
        InstructionCounter deletedCounter                      = new InstructionCounter();
        InstructionCounter addedCounter                        = new InstructionCounter();
        MemberCounter      codeRemovalVariableCounter          = new MemberCounter();
        ExceptionCounter   codeRemovalExceptionCounter         = new ExceptionCounter();
        MemberCounter      codeAllocationVariableCounter       = new MemberCounter();
        MemberCounter      initializerFixCounter1              = new MemberCounter();
        MemberCounter      initializerFixCounter2              = new MemberCounter();

        // Some optimizations are required by other optimizations.
        codeSimplificationAdvanced =
            codeSimplificationAdvanced ||
            fieldPropagationValue      ||
            methodPropagationParameter ||
            methodPropagationReturnvalue;

        codeRemovalAdvanced =
            codeRemovalAdvanced   ||
            fieldRemovalWriteonly ||
            methodMarkingStatic   ||
            methodRemovalParameter;

        codeRemovalSimple =
            codeRemovalSimple ||
            codeSimplificationBranch;

        codeRemovalException =
            codeRemovalException ||
            codeRemovalAdvanced ||
            codeRemovalSimple;

        // Clean up any old visitor info.
        programClassPool.classesAccept(new ClassCleaner());
        libraryClassPool.classesAccept(new ClassCleaner());

        // Link all methods that should get the same optimization info.
        programClassPool.classesAccept(new BottomClassFilter(
                                       new MethodLinker()));
        libraryClassPool.classesAccept(new BottomClassFilter(
                                       new MethodLinker()));

        // Create a visitor for marking the seeds.
        KeepMarker keepMarker = new KeepMarker();
        ClassPoolVisitor classPoolvisitor =
            ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.keep,
                                                                    keepMarker,
                                                                    keepMarker,
                                                                    false,
                                                                    true,
                                                                    false);
        // Mark the seeds.
        programClassPool.accept(classPoolvisitor);
        libraryClassPool.accept(classPoolvisitor);

        // All library classes and library class members remain unchanged.
        libraryClassPool.classesAccept(keepMarker);
        libraryClassPool.classesAccept(new AllMemberVisitor(keepMarker));

        // We also keep all classes that are involved in .class constructs.
        // We're not looking at enum classes though, so they can be simplified.
        programClassPool.classesAccept(
            new ClassAccessFilter(0, ClassConstants.ACC_ENUM,
            new AllMethodVisitor(
            new AllAttributeVisitor(
            new AllInstructionVisitor(
            new DotClassClassVisitor(keepMarker))))));

        // We also keep all classes that are accessed dynamically.
        programClassPool.classesAccept(
            new AllConstantVisitor(
            new ConstantTagFilter(ClassConstants.CONSTANT_String,
            new ReferencedClassVisitor(keepMarker))));

        // We also keep all class members that are accessed dynamically.
        programClassPool.classesAccept(
            new AllConstantVisitor(
            new ConstantTagFilter(ClassConstants.CONSTANT_String,
            new ReferencedMemberVisitor(keepMarker))));

        // We also keep all bootstrap method signatures.
        programClassPool.classesAccept(
            new ClassVersionFilter(ClassConstants.CLASS_VERSION_1_7,
            new AllAttributeVisitor(
            new AttributeNameFilter(ClassConstants.ATTR_BootstrapMethods,
            new AllBootstrapMethodInfoVisitor(
            new BootstrapMethodHandleTraveler(
            new MethodrefTraveler(
            new ReferencedMemberVisitor(keepMarker))))))));

        // We also keep all bootstrap method arguments that point to methods.
        // These arguments are typically the method handles for
        // java.lang.invoke.LambdaMetafactory#metafactory, which provides the
        // implementations for closures.
        programClassPool.classesAccept(
            new ClassVersionFilter(ClassConstants.CLASS_VERSION_1_7,
            new AllAttributeVisitor(
            new AttributeNameFilter(ClassConstants.ATTR_BootstrapMethods,
            new AllBootstrapMethodInfoVisitor(
            new BootstrapMethodArgumentVisitor(
            new MethodrefTraveler(
            new ReferencedMemberVisitor(keepMarker))))))));

        // We also keep all classes (and their methods) returned by dynamic
        // method invocations. They may return dynamic implementations of
        // interfaces that otherwise appear unused.
        programClassPool.classesAccept(
            new ClassVersionFilter(ClassConstants.CLASS_VERSION_1_7,
            new AllConstantVisitor(
            new DynamicReturnedClassVisitor(
            new MultiClassVisitor(new ClassVisitor[]
            {
                keepMarker,
                new AllMemberVisitor(keepMarker)
            })))));

        // Attach some optimization info to all classes and class members, so
        // it can be filled out later.
        programClassPool.classesAccept(new ClassOptimizationInfoSetter());

        programClassPool.classesAccept(new AllMemberVisitor(
                                       new MemberOptimizationInfoSetter()));

        if (configuration.assumeNoSideEffects != null)
        {
            // Create a visitor for marking methods that don't have any side effects.
            NoSideEffectMethodMarker noSideEffectMethodMarker = new NoSideEffectMethodMarker();
            ClassPoolVisitor noClassPoolvisitor =
                ClassSpecificationVisitorFactory.createClassPoolVisitor(configuration.assumeNoSideEffects,
                                                                        null,
                                                                        noSideEffectMethodMarker);

            // Mark the seeds.
            programClassPool.accept(noClassPoolvisitor);
            libraryClassPool.accept(noClassPoolvisitor);
        }

        if (classMarkingFinal)
        {
            // Make classes final, whereever possible.
            programClassPool.classesAccept(
                new ClassFinalizer(classMarkingFinalCounter));
        }

        if (methodMarkingFinal)
        {
            // Make methods final, whereever possible.
            programClassPool.classesAccept(
                new ClassAccessFilter(0, ClassConstants.ACC_INTERFACE,
                new AllMethodVisitor(
                new MethodFinalizer(methodMarkingFinalCounter))));
        }

        if (fieldRemovalWriteonly)
        {
            // Mark all fields that are write-only.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new AllInstructionVisitor(
                new ReadWriteFieldMarker()))));

            // Count the write-only fields.
            programClassPool.classesAccept(
                new AllFieldVisitor(
                new WriteOnlyFieldFilter(fieldRemovalWriteonlyCounter)));
        }
        else
        {
            // Mark all fields as read/write.
            programClassPool.classesAccept(
                new AllFieldVisitor(
                new ReadWriteFieldMarker()));
        }

        if (classUnboxingEnum)
        {
             ClassCounter counter = new ClassCounter();

            // Mark all final enums that qualify as simple enums.
            programClassPool.classesAccept(
                new ClassAccessFilter(ClassConstants.ACC_FINAL |
                                      ClassConstants.ACC_ENUM, 0,
                new SimpleEnumClassChecker()));

            // Count the preliminary number of simple enums.
            programClassPool.classesAccept(
                new SimpleEnumFilter(counter));

            // Only continue checking simple enums if there are any candidates.
            if (counter.getCount() > 0)
            {
                // Unmark all simple enums that are explicitly used as objects.
                programClassPool.classesAccept(
                    new SimpleEnumUseChecker());

                // Count the definitive number of simple enums.
                programClassPool.classesAccept(
                    new SimpleEnumFilter(classUnboxingEnumCounter));

                // Only start handling simple enums if there are any.
                if (classUnboxingEnumCounter.getCount() > 0)
                {
                    // Simplify the use of the enum classes in code.
                    programClassPool.classesAccept(
                        new AllMethodVisitor(
                        new AllAttributeVisitor(
                        new SimpleEnumUseSimplifier())));

                    // Simplify the static initializers of simple enum classes.
                    programClassPool.classesAccept(
                        new SimpleEnumFilter(
                        new SimpleEnumClassSimplifier()));

                    // Simplify the use of the enum classes in descriptors.
                    programClassPool.classesAccept(
                        new SimpleEnumDescriptorSimplifier());

                    // Update references to class members with simple enum classes.
                    programClassPool.classesAccept(new MemberReferenceFixer());
                }
            }
        }

        // Mark all used parameters, including the 'this' parameters.
        programClassPool.classesAccept(
            new AllMethodVisitor(
            new OptimizationInfoMemberFilter(
            new ParameterUsageMarker(!methodMarkingStatic,
                                     !methodRemovalParameter))));

        // Mark all classes that have static initializers.
        programClassPool.classesAccept(new StaticInitializerContainingClassMarker());

        // Mark all methods that have side effects.
        programClassPool.accept(new SideEffectMethodMarker());

//        System.out.println("Optimizer.execute: before evaluation simplification");
//        programClassPool.classAccept("abc/Def", new NamedMethodVisitor("abc", null, new ClassPrinter()));

        // Perform partial evaluation for filling out fields, method parameters,
        // and method return values, so they can be propagated.
        if (fieldPropagationValue      ||
            methodPropagationParameter ||
            methodPropagationReturnvalue)
        {
            // We'll create values to be stored with fields, method parameters,
            // and return values.
            ValueFactory valueFactory         = new ParticularValueFactory();
            ValueFactory detailedValueFactory = new DetailedValueFactory();

            InvocationUnit storingInvocationUnit =
                new StoringInvocationUnit(valueFactory,
                                          fieldPropagationValue,
                                          methodPropagationParameter,
                                          methodPropagationReturnvalue);

            // Evaluate synthetic classes in more detail, notably to propagate
            // the arrays of the classes generated for enum switch statements.
            programClassPool.classesAccept(
                new ClassAccessFilter(ClassConstants.ACC_SYNTHETIC, 0,
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new PartialEvaluator(detailedValueFactory, storingInvocationUnit, false)))));

            // Evaluate non-synthetic classes.
            programClassPool.classesAccept(
                new ClassAccessFilter(0, ClassConstants.ACC_SYNTHETIC,
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new PartialEvaluator(valueFactory, storingInvocationUnit, false)))));

            if (fieldPropagationValue)
            {
                // Count the constant fields.
                programClassPool.classesAccept(
                    new AllFieldVisitor(
                    new ConstantMemberFilter(fieldPropagationValueCounter)));
            }

            if (methodPropagationParameter)
            {
                // Count the constant method parameters.
                programClassPool.classesAccept(
                    new AllMethodVisitor(
                    new ConstantParameterFilter(methodPropagationParameterCounter)));
            }

            if (methodPropagationReturnvalue)
            {
                // Count the constant method return values.
                programClassPool.classesAccept(
                    new AllMethodVisitor(
                    new ConstantMemberFilter(methodPropagationReturnvalueCounter)));
            }

            if (classUnboxingEnumCounter.getCount() > 0)
            {
                // Propagate the simple enum constant counts.
                programClassPool.classesAccept(
                    new SimpleEnumFilter(
                    new SimpleEnumArrayPropagator()));
            }

            if (codeSimplificationAdvanced)
            {
                // Fill out constants into the arrays of synthetic classes,
                // notably the arrays of the classes generated for enum switch
                // statements.
                InvocationUnit loadingInvocationUnit =
                    new LoadingInvocationUnit(valueFactory,
                                              fieldPropagationValue,
                                              methodPropagationParameter,
                                              methodPropagationReturnvalue);

                programClassPool.classesAccept(
                    new ClassAccessFilter(ClassConstants.ACC_SYNTHETIC, 0,
                    new AllMethodVisitor(
                    new AllAttributeVisitor(
                    new PartialEvaluator(valueFactory, loadingInvocationUnit, false)))));
            }
        }

        // Perform partial evaluation again, now loading any previously stored
        // values for fields, method parameters, and method return values.
        ValueFactory valueFactory = new IdentifiedValueFactory();

        InvocationUnit loadingInvocationUnit =
            new LoadingInvocationUnit(valueFactory,
                                      fieldPropagationValue,
                                      methodPropagationParameter,
                                      methodPropagationReturnvalue);

        if (codeSimplificationAdvanced)
        {
            // Simplify based on partial evaluation, propagating constant
            // field values, method parameter values, and return values.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new EvaluationSimplifier(
                new PartialEvaluator(valueFactory, loadingInvocationUnit, false),
                codeSimplificationAdvancedCounter))));
        }

        if (codeRemovalAdvanced)
        {
            // Remove code based on partial evaluation, also removing unused
            // parameters from method invocations, and making methods static
            // if possible.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new EvaluationShrinker(
                new PartialEvaluator(valueFactory, loadingInvocationUnit, !codeSimplificationAdvanced),
                deletedCounter, addedCounter))));
        }

        if (methodRemovalParameter)
        {
            // Shrink the parameters in the method descriptors.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new OptimizationInfoMemberFilter(
                new MethodDescriptorShrinker())));
        }

        if (methodMarkingStatic)
        {
            // Make all non-static methods that don't require the 'this'
            // parameter static.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new OptimizationInfoMemberFilter(
                new MemberAccessFilter(0, ClassConstants.ACC_STATIC,
                new MethodStaticizer(methodMarkingStaticCounter)))));
        }

        if (methodRemovalParameter)
        {
            // Fix all references to class members.
            // This operation also updates the stack sizes.
            programClassPool.classesAccept(
                new MemberReferenceFixer());

            // Remove unused bootstrap method arguments.
            programClassPool.classesAccept(
                new AllAttributeVisitor(
                new AllBootstrapMethodInfoVisitor(
                new BootstrapMethodArgumentShrinker())));
        }

        if (methodRemovalParameter ||
            methodMarkingPrivate   ||
            methodMarkingStatic)
        {
            // Remove all unused parameters from the byte code, shifting all
            // remaining variables.
            // This operation also updates the local variable frame sizes.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new ParameterShrinker(methodRemovalParameterCounter))));
        }
        else if (codeRemovalAdvanced)
        {
            // Just update the local variable frame sizes.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new StackSizeUpdater())));
        }

        if (methodRemovalParameter &&
            methodRemovalParameterCounter.getCount() > 0)
        {
            // Tweak the descriptors of duplicate initializers, due to removed
            // method parameters.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new DuplicateInitializerFixer(initializerFixCounter1)));

            if (initializerFixCounter1.getCount() > 0)
            {
                // Fix all invocations of tweaked initializers.
                programClassPool.classesAccept(
                    new AllMethodVisitor(
                    new AllAttributeVisitor(
                    new DuplicateInitializerInvocationFixer(addedCounter))));

                // Fix all references to tweaked initializers.
                programClassPool.classesAccept(new MemberReferenceFixer());
            }
        }

        //// Specializing the class member descriptors seems to increase the
        //// class file size, on average.
        //// Specialize all class member descriptors.
        //programClassPool.classesAccept(new AllMemberVisitor(
        //                               new OptimizationInfoMemberFilter(
        //                               new MemberDescriptorSpecializer())));
        //
        //// Fix all references to classes, for MemberDescriptorSpecializer.
        //programClassPool.classesAccept(new AllMemberVisitor(
        //                               new OptimizationInfoMemberFilter(
        //                               new ClassReferenceFixer(true))));

        // Mark all classes with package visible members.
        // Mark all exception catches of methods.
        // Count all method invocations.
        // Mark super invocations and other access of methods.
        programClassPool.classesAccept(
            new MultiClassVisitor(
            new ClassVisitor[]
            {
                new PackageVisibleMemberContainingClassMarker(),
                new AllConstantVisitor(
                new PackageVisibleMemberInvokingClassMarker()),
                new AllMethodVisitor(
                new MultiMemberVisitor(
                new MemberVisitor[]
                {
                    new AllAttributeVisitor(
                    new MultiAttributeVisitor(
                    new AttributeVisitor[]
                    {
                        new CatchExceptionMarker(),
                        new AllInstructionVisitor(
                        new MultiInstructionVisitor(
                        new InstructionVisitor[]
                        {
                            new InstantiationClassMarker(),
                            new InstanceofClassMarker(),
                            new DotClassMarker(),
                            new MethodInvocationMarker(),
                            new SuperInvocationMarker(),
                            new DynamicInvocationMarker(),
                            new BackwardBranchMarker(),
                            new AccessMethodMarker(),
                        })),
                        new AllExceptionInfoVisitor(
                        new ExceptionHandlerConstantVisitor(
                        new ReferencedClassVisitor(
                        new CaughtClassMarker()))),
                    })),
                })),
            }));

        if (classMergingVertical)
        {
            // Merge subclasses up into their superclasses or
            // merge interfaces down into their implementing classes.
            programClassPool.classesAccept(
                new VerticalClassMerger(configuration.allowAccessModification,
                                        configuration.mergeInterfacesAggressively,
                                        classMergingVerticalCounter));
        }

        if (classMergingHorizontal)
        {
            // Merge classes into their sibling classes.
            programClassPool.classesAccept(
                new HorizontalClassMerger(configuration.allowAccessModification,
                                          configuration.mergeInterfacesAggressively,
                                          classMergingHorizontalCounter));
        }

        if (classMergingVerticalCounter  .getCount() > 0 ||
            classMergingHorizontalCounter.getCount() > 0)
        {
            // Clean up inner class attributes to avoid loops.
            programClassPool.classesAccept(new RetargetedInnerClassAttributeRemover());

            // Update references to merged classes.
            programClassPool.classesAccept(new TargetClassChanger());
            programClassPool.classesAccept(new ClassReferenceFixer(true));
            programClassPool.classesAccept(new MemberReferenceFixer());

            if (configuration.allowAccessModification)
            {
                // Fix the access flags of referenced merged classes and their
                // class members.
                programClassPool.classesAccept(
                    new AccessFixer());
            }

            // Fix the access flags of the inner classes information.
            programClassPool.classesAccept(
                new AllAttributeVisitor(
                new AllInnerClassesInfoVisitor(
                new InnerClassesAccessFixer())));

            // Tweak the descriptors of duplicate initializers, due to merged
            // parameter classes.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new DuplicateInitializerFixer(initializerFixCounter2)));

            if (initializerFixCounter2.getCount() > 0)
            {
                // Fix all invocations of tweaked initializers.
                programClassPool.classesAccept(
                    new AllMethodVisitor(
                    new AllAttributeVisitor(
                    new DuplicateInitializerInvocationFixer(addedCounter))));

                // Fix all references to tweaked initializers.
                programClassPool.classesAccept(new MemberReferenceFixer());
            }
        }

        if (methodInliningUnique)
        {
            // Inline methods that are only invoked once.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new MethodInliner(configuration.microEdition,
                                  configuration.allowAccessModification,
                                  true,
                                  methodInliningUniqueCounter))));
        }

        if (methodInliningShort)
        {
            // Inline short methods.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new MethodInliner(configuration.microEdition,
                                  configuration.allowAccessModification,
                                  false,
                                  methodInliningShortCounter))));
        }

        if (methodInliningTailrecursion)
        {
            // Simplify tail recursion calls.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new TailRecursionSimplifier(methodInliningTailrecursionCounter))));
        }

        if (fieldMarkingPrivate ||
            methodMarkingPrivate)
        {
            // Mark all class members that can not be made private.
            programClassPool.classesAccept(
                new NonPrivateMemberMarker());
        }

        if (fieldMarkingPrivate)
        {
            // Make all non-private fields private, whereever possible.
            programClassPool.classesAccept(
                new ClassAccessFilter(0, ClassConstants.ACC_INTERFACE,
                new AllFieldVisitor(
                new MemberAccessFilter(0, ClassConstants.ACC_PRIVATE,
                new MemberPrivatizer(fieldMarkingPrivateCounter)))));
        }

        if (methodMarkingPrivate)
        {
            // Make all non-private methods private, whereever possible.
            programClassPool.classesAccept(
                new ClassAccessFilter(0, ClassConstants.ACC_INTERFACE,
                new AllMethodVisitor(
                new MemberAccessFilter(0, ClassConstants.ACC_PRIVATE,
                new MemberPrivatizer(methodMarkingPrivateCounter)))));
        }

        if ((methodInliningUniqueCounter       .getCount() > 0 ||
             methodInliningShortCounter        .getCount() > 0 ||
             methodInliningTailrecursionCounter.getCount() > 0) &&
            configuration.allowAccessModification)
        {
            // Fix the access flags of referenced classes and class members,
            // for MethodInliner.
            programClassPool.classesAccept(
                new AccessFixer());
        }

        if (methodRemovalParameterCounter .getCount() > 0 ||
            classMergingVerticalCounter   .getCount() > 0 ||
            classMergingHorizontalCounter .getCount() > 0 ||
            methodMarkingPrivateCounter   .getCount() > 0 )
        {
            // Fix invocations of interface methods, of methods that have become
            // non-abstract or private, and of methods that have moved to a
            // different package.
            programClassPool.classesAccept(
                new AllMemberVisitor(
                new AllAttributeVisitor(
                new MethodInvocationFixer())));
        }

        if (codeMerging)
        {
            // Share common blocks of code at branches.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new GotoCommonCodeReplacer(codeMergingCounter))));
        }

        // Create a branch target marker and a code attribute editor that can
        // be reused for all code attributes.
        BranchTargetFinder  branchTargetFinder  = new BranchTargetFinder();
        CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();

        List peepholeOptimizations = new ArrayList();
        if (codeSimplificationVariable)
        {
            // Peephole optimizations involving local variables.
            peepholeOptimizations.add(
                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
                                                 InstructionSequenceConstants.VARIABLE,
                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationVariableCounter));
        }

        if (codeSimplificationArithmetic)
        {
            // Peephole optimizations involving arithmetic operations.
            peepholeOptimizations.add(
                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
                                                 InstructionSequenceConstants.ARITHMETIC,
                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationArithmeticCounter));
        }

        if (codeSimplificationCast)
        {
            // Peephole optimizations involving cast operations.
            peepholeOptimizations.add(
                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
                                                 InstructionSequenceConstants.CAST,
                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationCastCounter));
        }

        if (codeSimplificationField)
        {
            // Peephole optimizations involving fields.
            peepholeOptimizations.add(
                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
                                                 InstructionSequenceConstants.FIELD,
                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationFieldCounter));
        }

        if (codeSimplificationBranch)
        {
            // Peephole optimizations involving branches.
            peepholeOptimizations.add(
                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
                                                 InstructionSequenceConstants.BRANCH,
                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationBranchCounter));

            // Include optimization of branches to branches and returns.
            peepholeOptimizations.add(
                new GotoGotoReplacer(codeAttributeEditor, codeSimplificationBranchCounter));
            peepholeOptimizations.add(
                new GotoReturnReplacer(codeAttributeEditor, codeSimplificationBranchCounter));
        }

        if (codeSimplificationString)
        {
            // Peephole optimizations involving branches.
            peepholeOptimizations.add(
                new InstructionSequencesReplacer(InstructionSequenceConstants.CONSTANTS,
                                                 InstructionSequenceConstants.STRING,
                                                 branchTargetFinder, codeAttributeEditor, codeSimplificationStringCounter));
        }

        if (!peepholeOptimizations.isEmpty())
        {
            // Convert the list into an array.
            InstructionVisitor[] peepholeOptimizationsArray =
                new InstructionVisitor[peepholeOptimizations.size()];
            peepholeOptimizations.toArray(peepholeOptimizationsArray);

            // Perform the peephole optimisations.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new PeepholeOptimizer(branchTargetFinder, codeAttributeEditor,
                new MultiInstructionVisitor(
                peepholeOptimizationsArray)))));
        }

        if (codeRemovalException)
        {
            // Remove unnecessary exception handlers.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new UnreachableExceptionRemover(codeRemovalExceptionCounter))));
        }

        if (codeRemovalSimple)
        {
            // Remove unreachable code.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new UnreachableCodeRemover(deletedCounter))));
        }

        if (codeRemovalVariable)
        {
            // Remove all unused local variables.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new VariableShrinker(codeRemovalVariableCounter))));
        }

        if (codeAllocationVariable)
        {
            // Optimize the variables.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new VariableOptimizer(false, codeAllocationVariableCounter))));
        }


        // Remove unused constants.
        programClassPool.classesAccept(
            new ConstantPoolShrinker());

        int classMarkingFinalCount            = classMarkingFinalCounter           .getCount();
        int classUnboxingEnumCount            = classUnboxingEnumCounter           .getCount();
        int classMergingVerticalCount         = classMergingVerticalCounter        .getCount();
        int classMergingHorizontalCount       = classMergingHorizontalCounter      .getCount();
        int fieldRemovalWriteonlyCount        = fieldRemovalWriteonlyCounter       .getCount();
        int fieldMarkingPrivateCount          = fieldMarkingPrivateCounter         .getCount();
        int fieldPropagationValueCount        = fieldPropagationValueCounter       .getCount();
        int methodMarkingPrivateCount         = methodMarkingPrivateCounter        .getCount();
        int methodMarkingStaticCount          = methodMarkingStaticCounter         .getCount();
        int methodMarkingFinalCount           = methodMarkingFinalCounter          .getCount();
        int methodRemovalParameterCount       = methodRemovalParameterCounter      .getCount() - methodMarkingStaticCounter.getCount() - initializerFixCounter1.getCount() - initializerFixCounter2.getCount();
        int methodPropagationParameterCount   = methodPropagationParameterCounter  .getCount();
        int methodPropagationReturnvalueCount = methodPropagationReturnvalueCounter.getCount();
        int methodInliningShortCount          = methodInliningShortCounter         .getCount();
        int methodInliningUniqueCount         = methodInliningUniqueCounter        .getCount();
        int methodInliningTailrecursionCount  = methodInliningTailrecursionCounter .getCount();
        int codeMergingCount                  = codeMergingCounter                 .getCount();
        int codeSimplificationVariableCount   = codeSimplificationVariableCounter  .getCount();
        int codeSimplificationArithmeticCount = codeSimplificationArithmeticCounter.getCount();
        int codeSimplificationCastCount       = codeSimplificationCastCounter      .getCount();
        int codeSimplificationFieldCount      = codeSimplificationFieldCounter     .getCount();
        int codeSimplificationBranchCount     = codeSimplificationBranchCounter    .getCount();
        int codeSimplificationStringCount     = codeSimplificationStringCounter    .getCount();
        int codeSimplificationAdvancedCount   = codeSimplificationAdvancedCounter  .getCount();
        int codeRemovalCount                  = deletedCounter                     .getCount() - addedCounter.getCount();
        int codeRemovalVariableCount          = codeRemovalVariableCounter         .getCount();
        int codeRemovalExceptionCount         = codeRemovalExceptionCounter        .getCount();
        int codeAllocationVariableCount       = codeAllocationVariableCounter      .getCount();

        // Forget about constant fields, parameters, and return values, if they
        // didn't lead to any useful optimizations. We want to avoid fruitless
        // additional optimization passes.
        if (codeSimplificationAdvancedCount == 0)
        {
            fieldPropagationValueCount        = 0;
            methodPropagationParameterCount   = 0;
            methodPropagationReturnvalueCount = 0;
        }

        if (configuration.verbose)
        {
            System.out.println("  Number of finalized classes:                 " + classMarkingFinalCount            + disabled(classMarkingFinal));
            System.out.println("  Number of unboxed enum classes:              " + classUnboxingEnumCount            + disabled(classUnboxingEnum));
            System.out.println("  Number of vertically merged classes:         " + classMergingVerticalCount         + disabled(classMergingVertical));
            System.out.println("  Number of horizontally merged classes:       " + classMergingHorizontalCount       + disabled(classMergingHorizontal));
            System.out.println("  Number of removed write-only fields:         " + fieldRemovalWriteonlyCount        + disabled(fieldRemovalWriteonly));
            System.out.println("  Number of privatized fields:                 " + fieldMarkingPrivateCount          + disabled(fieldMarkingPrivate));
            System.out.println("  Number of inlined constant fields:           " + fieldPropagationValueCount        + disabled(fieldPropagationValue));
            System.out.println("  Number of privatized methods:                " + methodMarkingPrivateCount         + disabled(methodMarkingPrivate));
            System.out.println("  Number of staticized methods:                " + methodMarkingStaticCount          + disabled(methodMarkingStatic));
            System.out.println("  Number of finalized methods:                 " + methodMarkingFinalCount           + disabled(methodMarkingFinal));
            System.out.println("  Number of removed method parameters:         " + methodRemovalParameterCount       + disabled(methodRemovalParameter));
            System.out.println("  Number of inlined constant parameters:       " + methodPropagationParameterCount   + disabled(methodPropagationParameter));
            System.out.println("  Number of inlined constant return values:    " + methodPropagationReturnvalueCount + disabled(methodPropagationReturnvalue));
            System.out.println("  Number of inlined short method calls:        " + methodInliningShortCount          + disabled(methodInliningShort));
            System.out.println("  Number of inlined unique method calls:       " + methodInliningUniqueCount         + disabled(methodInliningUnique));
            System.out.println("  Number of inlined tail recursion calls:      " + methodInliningTailrecursionCount  + disabled(methodInliningTailrecursion));
            System.out.println("  Number of merged code blocks:                " + codeMergingCount                  + disabled(codeMerging));
            System.out.println("  Number of variable peephole optimizations:   " + codeSimplificationVariableCount   + disabled(codeSimplificationVariable));
            System.out.println("  Number of arithmetic peephole optimizations: " + codeSimplificationArithmeticCount + disabled(codeSimplificationArithmetic));
            System.out.println("  Number of cast peephole optimizations:       " + codeSimplificationCastCount       + disabled(codeSimplificationCast));
            System.out.println("  Number of field peephole optimizations:      " + codeSimplificationFieldCount      + disabled(codeSimplificationField));
            System.out.println("  Number of branch peephole optimizations:     " + codeSimplificationBranchCount     + disabled(codeSimplificationBranch));
            System.out.println("  Number of string peephole optimizations:     " + codeSimplificationStringCount     + disabled(codeSimplificationString));
            System.out.println("  Number of simplified instructions:           " + codeSimplificationAdvancedCount   + disabled(codeSimplificationAdvanced));
            System.out.println("  Number of removed instructions:              " + codeRemovalCount                  + disabled(codeRemovalAdvanced));
            System.out.println("  Number of removed local variables:           " + codeRemovalVariableCount          + disabled(codeRemovalVariable));
            System.out.println("  Number of removed exception blocks:          " + codeRemovalExceptionCount         + disabled(codeRemovalException));
            System.out.println("  Number of optimized local variable frames:   " + codeAllocationVariableCount       + disabled(codeAllocationVariable));
        }

        return classMarkingFinalCount            > 0 ||
               classUnboxingEnumCount            > 0 ||
               classMergingVerticalCount         > 0 ||
               classMergingHorizontalCount       > 0 ||
               fieldRemovalWriteonlyCount        > 0 ||
               fieldMarkingPrivateCount          > 0 ||
               methodMarkingPrivateCount         > 0 ||
               methodMarkingStaticCount          > 0 ||
               methodMarkingFinalCount           > 0 ||
               fieldPropagationValueCount        > 0 ||
               methodRemovalParameterCount       > 0 ||
               methodPropagationParameterCount   > 0 ||
               methodPropagationReturnvalueCount > 0 ||
               methodInliningShortCount          > 0 ||
               methodInliningUniqueCount         > 0 ||
               methodInliningTailrecursionCount  > 0 ||
               codeMergingCount                  > 0 ||
               codeSimplificationVariableCount   > 0 ||
               codeSimplificationArithmeticCount > 0 ||
               codeSimplificationCastCount       > 0 ||
               codeSimplificationFieldCount      > 0 ||
               codeSimplificationBranchCount     > 0 ||
               codeSimplificationStringCount     > 0 ||
               codeSimplificationAdvancedCount   > 0 ||
               codeRemovalCount                  > 0 ||
               codeRemovalVariableCount          > 0 ||
               codeRemovalExceptionCount         > 0 ||
               codeAllocationVariableCount       > 0;
    }


    /**
     * Returns a String indicating whether the given flag is enabled or
     * disabled.
     */
    private String disabled(boolean flag)
    {
        return flag ? "" : "   (disabled)";
    }


    /**
     * Returns a String indicating whether the given flags are enabled or
     * disabled.
     */
    private String disabled(boolean flag1, boolean flag2)
    {
        return flag1 && flag2 ? "" :
               flag1 || flag2 ? "   (partially disabled)" :
                                "   (disabled)";
    }
}
