/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
 *
 * 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.AllConstantVisitor;
import proguard.classfile.editor.*;
import proguard.classfile.instruction.visitor.*;
import proguard.classfile.util.MethodLinker;
import proguard.classfile.visitor.*;
import proguard.evaluation.*;
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_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_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_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_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 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 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       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 codeSimplificationAdvancedCounter   = new InstructionCounter();
        InstructionCounter deletedCounter                      = new InstructionCounter();
        InstructionCounter addedCounter                        = new InstructionCounter();
        MemberCounter      codeRemovalVariableCounter          = new MemberCounter();
        ExceptionCounter   codeRemovalExceptionCounter         = new ExceptionCounter();
        MemberCounter      codeAllocationVariableCounter       = new MemberCounter();
        MemberCounter      initializerFixCounter               = 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.
        programClassPool.classesAccept(
            new AllMethodVisitor(
            new AllAttributeVisitor(
            new AllInstructionVisitor(
            new DotClassClassVisitor(keepMarker)))));

        // We also keep all classes that are involved in Class.forName constructs.
        programClassPool.classesAccept(
            new AllConstantVisitor(
            new ClassForNameClassVisitor(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 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()));
        }

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

        // 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.
        ValueFactory valueFactory = new IdentifiedValueFactory();

        if (fieldPropagationValue      ||
            methodPropagationParameter ||
            methodPropagationReturnvalue)
        {
            // Fill out fields, method parameters, and return values, so they
            // can be propagated.
            InvocationUnit storingInvocationUnit =
                new StoringInvocationUnit(valueFactory,
                                          fieldPropagationValue,
                                          methodPropagationParameter,
                                          methodPropagationReturnvalue);

            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new PartialEvaluator(valueFactory, storingInvocationUnit, false))));

            // Count the constant fields and methods.
            programClassPool.classesAccept(
                new MultiClassVisitor(
                new ClassVisitor[]
                {
                    new AllFieldVisitor(
                    new ConstantMemberFilter(fieldPropagationValueCounter)),
                    new AllMethodVisitor(
                    new ConstantParameterFilter(methodPropagationParameterCounter)),
                    new AllMethodVisitor(
                    new ConstantMemberFilter(methodPropagationReturnvalueCounter)),
                }));
        }

        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.INTERNAL_ACC_STATIC,
                new MethodStaticizer(methodMarkingStaticCounter)))));
        }

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

        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())));
        }

//        // 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 AllConstantVisitor(
                new PackageVisibleMemberInvokingClassMarker()),
                new AllMethodVisitor(
                new MultiMemberVisitor(
                new MemberVisitor[]
                {
                    new PackageVisibleMemberContainingClassMarker(),
                    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 BackwardBranchMarker(),
                            new AccessMethodMarker(),
                        })),
                        new AllExceptionInfoVisitor(
                        new ExceptionHandlerConstantVisitor(
                        new ReferencedClassVisitor(
                        new CaughtClassMarker()))),
                    })),
                })),
            }));

        if (classMergingVertical)
        {
            // Merge classes into their superclasses or interfaces.
            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 (classMergingVertical ||
            classMergingHorizontal)
        {
            // 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 AllConstantVisitor(
                    new AccessFixer()));
            }
        }

        if (methodRemovalParameter ||
            classMergingVertical   ||
            classMergingHorizontal)
        {
            // Tweak the descriptors of duplicate initializers.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new DuplicateInitializerFixer(initializerFixCounter)));

            if (initializerFixCounter.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 ||
            methodMarkingPrivate)
        {
            // Make all non-private fields private, whereever possible.
            programClassPool.classesAccept(
                new AllFieldVisitor(
                new MemberAccessFilter(0, ClassConstants.INTERNAL_ACC_PRIVATE,
                new MemberPrivatizer(fieldMarkingPrivateCounter))));
        }

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

        if ((methodInliningUnique ||
             methodInliningShort  ||
             methodInliningTailrecursion) &&
            configuration.allowAccessModification)
        {
            // Fix the access flags of referenced classes and class members,
            // for MethodInliner.
            programClassPool.classesAccept(
                new AllConstantVisitor(
                new AccessFixer()));
        }

        if (methodRemovalParameter ||
            classMergingVertical   ||
            classMergingHorizontal ||
            methodMarkingPrivate)
        {
            // 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 (!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))));
        }
        else
        {
            // Clean up all unused local variables.
            programClassPool.classesAccept(
                new AllMethodVisitor(
                new AllAttributeVisitor(
                new VariableCleaner())));
        }

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

        int classMarkingFinalCount            = classMarkingFinalCounter           .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() - initializerFixCounter.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 codeSimplificationAdvancedCount   = codeSimplificationAdvancedCounter  .getCount();
        int codeRemovalCount                  = deletedCounter                     .getCount() - addedCounter.getCount();
        int codeRemovalVariableCount          = codeRemovalVariableCounter         .getCount();
        int codeRemovalExceptionCount         = codeRemovalExceptionCounter        .getCount();
        int codeAllocationVariableCount       = codeAllocationVariableCounter      .getCount();

        if (configuration.verbose)
        {
            System.out.println("  Number of finalized classes:                 " + classMarkingFinalCount            + disabled(classMarkingFinal));
            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 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 ||
               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 ||
               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)";
    }
}
