/**
   * Extracts a list of exceptions of field or method names for which not to print notes, from the
   * keep configuration.
   */
  private StringMatcher createClassMemberNoteExceptionMatcher(
      List noteExceptions, boolean isField) {
    if (noteExceptions != null) {
      List noteExceptionNames = new ArrayList();
      for (int index = 0; index < noteExceptions.size(); index++) {
        KeepClassSpecification keepClassSpecification =
            (KeepClassSpecification) noteExceptions.get(index);
        List memberSpecifications =
            isField
                ? keepClassSpecification.fieldSpecifications
                : keepClassSpecification.methodSpecifications;

        if (memberSpecifications != null) {
          for (int index2 = 0; index2 < memberSpecifications.size(); index2++) {
            MemberSpecification memberSpecification =
                (MemberSpecification) memberSpecifications.get(index2);

            String memberName = memberSpecification.name;
            if (memberName != null) {
              noteExceptionNames.add(memberName);
            }
          }
        }
      }

      if (noteExceptionNames.size() > 0) {
        return new ListParser(new ClassNameParser()).parse(noteExceptionNames);
      }
    }

    return null;
  }
  /**
   * Extracts a list of exceptions of classes for which not to print notes, from the keep
   * configuration.
   */
  private StringMatcher createClassNoteExceptionMatcher(List noteExceptions) {
    if (noteExceptions != null) {
      List noteExceptionNames = new ArrayList(noteExceptions.size());
      for (int index = 0; index < noteExceptions.size(); index++) {
        KeepClassSpecification keepClassSpecification =
            (KeepClassSpecification) noteExceptions.get(index);
        if (keepClassSpecification.markClasses) {
          // If the class itself is being kept, it's ok.
          String className = keepClassSpecification.className;
          if (className != null) {
            noteExceptionNames.add(className);
          }

          // If all of its extensions are being kept, it's ok too.
          String extendsClassName = keepClassSpecification.extendsClassName;
          if (extendsClassName != null) {
            noteExceptionNames.add(extendsClassName);
          }
        }
      }

      if (noteExceptionNames.size() > 0) {
        return new ListParser(new ClassNameParser()).parse(noteExceptionNames);
      }
    }

    return null;
  }
  /** 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 = StringMatcherUtil.matchesString(filter, CLASS_MARKING_FINAL);
    boolean classUnboxingEnum = StringMatcherUtil.matchesString(filter, CLASS_UNBOXING_ENUM);
    boolean classMergingVertical = StringMatcherUtil.matchesString(filter, CLASS_MERGING_VERTICAL);
    boolean classMergingHorizontal =
        StringMatcherUtil.matchesString(filter, CLASS_MERGING_HORIZONTAL);
    boolean fieldRemovalWriteonly =
        StringMatcherUtil.matchesString(filter, FIELD_REMOVAL_WRITEONLY);
    boolean fieldMarkingPrivate = StringMatcherUtil.matchesString(filter, FIELD_MARKING_PRIVATE);
    boolean fieldPropagationValue =
        StringMatcherUtil.matchesString(filter, FIELD_PROPAGATION_VALUE);
    boolean methodMarkingPrivate = StringMatcherUtil.matchesString(filter, METHOD_MARKING_PRIVATE);
    boolean methodMarkingStatic = StringMatcherUtil.matchesString(filter, METHOD_MARKING_STATIC);
    boolean methodMarkingFinal = StringMatcherUtil.matchesString(filter, METHOD_MARKING_FINAL);
    boolean methodRemovalParameter =
        StringMatcherUtil.matchesString(filter, METHOD_REMOVAL_PARAMETER);
    boolean methodPropagationParameter =
        StringMatcherUtil.matchesString(filter, METHOD_PROPAGATION_PARAMETER);
    boolean methodPropagationReturnvalue =
        StringMatcherUtil.matchesString(filter, METHOD_PROPAGATION_RETURNVALUE);
    boolean methodInliningShort = StringMatcherUtil.matchesString(filter, METHOD_INLINING_SHORT);
    boolean methodInliningUnique = StringMatcherUtil.matchesString(filter, METHOD_INLINING_UNIQUE);
    boolean methodInliningTailrecursion =
        StringMatcherUtil.matchesString(filter, METHOD_INLINING_TAILRECURSION);
    boolean codeMerging = StringMatcherUtil.matchesString(filter, CODE_MERGING);
    boolean codeSimplificationVariable =
        StringMatcherUtil.matchesString(filter, CODE_SIMPLIFICATION_VARIABLE);
    boolean codeSimplificationArithmetic =
        StringMatcherUtil.matchesString(filter, CODE_SIMPLIFICATION_ARITHMETIC);
    boolean codeSimplificationCast =
        StringMatcherUtil.matchesString(filter, CODE_SIMPLIFICATION_CAST);
    boolean codeSimplificationField =
        StringMatcherUtil.matchesString(filter, CODE_SIMPLIFICATION_FIELD);
    boolean codeSimplificationBranch =
        StringMatcherUtil.matchesString(filter, CODE_SIMPLIFICATION_BRANCH);
    boolean codeSimplificationString =
        StringMatcherUtil.matchesString(filter, CODE_SIMPLIFICATION_STRING);
    boolean codeSimplificationAdvanced =
        StringMatcherUtil.matchesString(filter, CODE_SIMPLIFICATION_ADVANCED);
    boolean codeRemovalAdvanced = StringMatcherUtil.matchesString(filter, CODE_REMOVAL_ADVANCED);
    boolean codeRemovalSimple = StringMatcherUtil.matchesString(filter, CODE_REMOVAL_SIMPLE);
    boolean codeRemovalVariable = StringMatcherUtil.matchesString(filter, CODE_REMOVAL_VARIABLE);
    boolean codeRemovalException = StringMatcherUtil.matchesString(filter, CODE_REMOVAL_EXCEPTION);
    boolean codeAllocationVariable =
        StringMatcherUtil.matchesString(filter, 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;
  }