/** Performs obfuscation of the given program class pool. */
  public void 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 obfuscation step.");
    }

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

    // If the class member names have to correspond globally,
    // link all class members in all classes, otherwise
    // link all non-private methods in all class hierarchies.
    ClassVisitor memberInfoLinker =
        configuration.useUniqueClassMemberNames
            ? (ClassVisitor) new AllMemberVisitor(new MethodLinker())
            : (ClassVisitor) new BottomClassFilter(new MethodLinker());

    programClassPool.classesAccept(memberInfoLinker);
    libraryClassPool.classesAccept(memberInfoLinker);

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

    // All library classes and library class members keep their names.
    libraryClassPool.classesAccept(nameMarker);
    libraryClassPool.classesAccept(new AllMemberVisitor(nameMarker));

    // Mark attributes that have to be kept.
    AttributeUsageMarker requiredAttributeUsageMarker = new AttributeUsageMarker();

    AttributeVisitor optionalAttributeUsageMarker =
        configuration.keepAttributes == null
            ? null
            : new AttributeNameFilter(
                new ListParser(new NameParser()).parse(configuration.keepAttributes),
                requiredAttributeUsageMarker);

    programClassPool.classesAccept(
        new AllAttributeVisitor(
            true,
            new RequiredAttributeFilter(
                requiredAttributeUsageMarker, optionalAttributeUsageMarker)));

    // Remove the attributes that can be discarded. Note that the attributes
    // may only be discarded after the seeds have been marked, since the
    // configuration may rely on annotations.
    programClassPool.classesAccept(new AttributeShrinker());

    // Apply the mapping, if one has been specified. The mapping can
    // override the names of library classes and of library class members.
    if (configuration.applyMapping != null) {
      WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);

      MappingReader reader = new MappingReader(configuration.applyMapping);

      MappingProcessor keeper =
          new MultiMappingProcessor(
              new MappingProcessor[] {
                new MappingKeeper(programClassPool, warningPrinter),
                new MappingKeeper(libraryClassPool, null),
              });

      reader.pump(keeper);

      // Print out a summary of the warnings if necessary.
      int mappingWarningCount = warningPrinter.getWarningCount();
      if (mappingWarningCount > 0) {
        System.err.println(
            "Warning: there were "
                + mappingWarningCount
                + " kept classes and class members that were remapped anyway.");
        System.err.println(
            "         You should adapt your configuration or edit the mapping file.");

        if (!configuration.ignoreWarnings) {
          System.err.println("         If you are sure this remapping won't hurt,");
          System.err.println(
              "         you could try your luck using the '-ignorewarnings' option.");
          throw new IOException("Please correct the above warnings first.");
        }
      }
    }

    // Come up with new names for all classes.
    DictionaryNameFactory classNameFactory =
        configuration.classObfuscationDictionary != null
            ? new DictionaryNameFactory(configuration.classObfuscationDictionary, null)
            : null;

    DictionaryNameFactory packageNameFactory =
        configuration.packageObfuscationDictionary != null
            ? new DictionaryNameFactory(configuration.packageObfuscationDictionary, null)
            : null;

    programClassPool.classesAccept(
        new ClassObfuscator(
            programClassPool,
            classNameFactory,
            packageNameFactory,
            configuration.useMixedCaseClassNames,
            configuration.keepPackageNames,
            configuration.flattenPackageHierarchy,
            configuration.repackageClasses,
            configuration.allowAccessModification));

    // Come up with new names for all class members.
    NameFactory nameFactory = new SimpleNameFactory();

    if (configuration.obfuscationDictionary != null) {
      nameFactory = new DictionaryNameFactory(configuration.obfuscationDictionary, nameFactory);
    }

    WarningPrinter warningPrinter = new WarningPrinter(System.err, configuration.warn);

    // Maintain a map of names to avoid [descriptor - new name - old name].
    Map descriptorMap = new HashMap();

    // Do the class member names have to be globally unique?
    if (configuration.useUniqueClassMemberNames) {
      // Collect all member names in all classes.
      programClassPool.classesAccept(
          new AllMemberVisitor(
              new MemberNameCollector(configuration.overloadAggressively, descriptorMap)));

      // Assign new names to all members in all classes.
      programClassPool.classesAccept(
          new AllMemberVisitor(
              new MemberObfuscator(
                  configuration.overloadAggressively, nameFactory, descriptorMap)));
    } else {
      // Come up with new names for all non-private class members.
      programClassPool.classesAccept(
          new MultiClassVisitor(
              new ClassVisitor[] {
                // Collect all private member names in this class and down
                // the hierarchy.
                new ClassHierarchyTraveler(
                    true,
                    false,
                    false,
                    true,
                    new AllMemberVisitor(
                        new MemberAccessFilter(
                            ClassConstants.INTERNAL_ACC_PRIVATE,
                            0,
                            new MemberNameCollector(
                                configuration.overloadAggressively, descriptorMap)))),

                // Collect all non-private member names anywhere in the hierarchy.
                new ClassHierarchyTraveler(
                    true,
                    true,
                    true,
                    true,
                    new AllMemberVisitor(
                        new MemberAccessFilter(
                            0,
                            ClassConstants.INTERNAL_ACC_PRIVATE,
                            new MemberNameCollector(
                                configuration.overloadAggressively, descriptorMap)))),

                // Assign new names to all non-private members in this class.
                new AllMemberVisitor(
                    new MemberAccessFilter(
                        0,
                        ClassConstants.INTERNAL_ACC_PRIVATE,
                        new MemberObfuscator(
                            configuration.overloadAggressively, nameFactory, descriptorMap))),

                // Clear the collected names.
                new MapCleaner(descriptorMap)
              }));

      // Come up with new names for all private class members.
      programClassPool.classesAccept(
          new MultiClassVisitor(
              new ClassVisitor[] {
                // Collect all member names in this class.
                new AllMemberVisitor(
                    new MemberNameCollector(configuration.overloadAggressively, descriptorMap)),

                // Collect all non-private member names higher up the hierarchy.
                new ClassHierarchyTraveler(
                    false,
                    true,
                    true,
                    false,
                    new AllMemberVisitor(
                        new MemberAccessFilter(
                            0,
                            ClassConstants.INTERNAL_ACC_PRIVATE,
                            new MemberNameCollector(
                                configuration.overloadAggressively, descriptorMap)))),

                // Assign new names to all private members in this class.
                new AllMemberVisitor(
                    new MemberAccessFilter(
                        ClassConstants.INTERNAL_ACC_PRIVATE,
                        0,
                        new MemberObfuscator(
                            configuration.overloadAggressively, nameFactory, descriptorMap))),

                // Clear the collected names.
                new MapCleaner(descriptorMap)
              }));
    }

    // Some class members may have ended up with conflicting names.
    // Come up with new, globally unique names for them.
    NameFactory specialNameFactory = new SpecialNameFactory(new SimpleNameFactory());

    // Collect a map of special names to avoid
    // [descriptor - new name - old name].
    Map specialDescriptorMap = new HashMap();

    programClassPool.classesAccept(
        new AllMemberVisitor(
            new MemberSpecialNameFilter(
                new MemberNameCollector(
                    configuration.overloadAggressively, specialDescriptorMap))));

    libraryClassPool.classesAccept(
        new AllMemberVisitor(
            new MemberSpecialNameFilter(
                new MemberNameCollector(
                    configuration.overloadAggressively, specialDescriptorMap))));

    // Replace conflicting non-private member names with special names.
    programClassPool.classesAccept(
        new MultiClassVisitor(
            new ClassVisitor[] {
              // Collect all private member names in this class and down
              // the hierarchy.
              new ClassHierarchyTraveler(
                  true,
                  false,
                  false,
                  true,
                  new AllMemberVisitor(
                      new MemberAccessFilter(
                          ClassConstants.INTERNAL_ACC_PRIVATE,
                          0,
                          new MemberNameCollector(
                              configuration.overloadAggressively, descriptorMap)))),

              // Collect all non-private member names in this class and
              // higher up the hierarchy.
              new ClassHierarchyTraveler(
                  true,
                  true,
                  true,
                  false,
                  new AllMemberVisitor(
                      new MemberAccessFilter(
                          0,
                          ClassConstants.INTERNAL_ACC_PRIVATE,
                          new MemberNameCollector(
                              configuration.overloadAggressively, descriptorMap)))),

              // Assign new names to all conflicting non-private members
              // in this class and higher up the hierarchy.
              new ClassHierarchyTraveler(
                  true,
                  true,
                  true,
                  false,
                  new AllMemberVisitor(
                      new MemberAccessFilter(
                          0,
                          ClassConstants.INTERNAL_ACC_PRIVATE,
                          new MemberNameConflictFixer(
                              configuration.overloadAggressively,
                              descriptorMap,
                              warningPrinter,
                              new MemberObfuscator(
                                  configuration.overloadAggressively,
                                  specialNameFactory,
                                  specialDescriptorMap))))),

              // Clear the collected names.
              new MapCleaner(descriptorMap)
            }));

    // Replace conflicting private member names with special names.
    // This is only possible if those names were kept or mapped.
    programClassPool.classesAccept(
        new MultiClassVisitor(
            new ClassVisitor[] {
              // Collect all member names in this class.
              new AllMemberVisitor(
                  new MemberNameCollector(configuration.overloadAggressively, descriptorMap)),

              // Collect all non-private member names higher up the hierarchy.
              new ClassHierarchyTraveler(
                  false,
                  true,
                  true,
                  false,
                  new AllMemberVisitor(
                      new MemberAccessFilter(
                          0,
                          ClassConstants.INTERNAL_ACC_PRIVATE,
                          new MemberNameCollector(
                              configuration.overloadAggressively, descriptorMap)))),

              // Assign new names to all conflicting private members in this
              // class.
              new AllMemberVisitor(
                  new MemberAccessFilter(
                      ClassConstants.INTERNAL_ACC_PRIVATE,
                      0,
                      new MemberNameConflictFixer(
                          configuration.overloadAggressively,
                          descriptorMap,
                          warningPrinter,
                          new MemberObfuscator(
                              configuration.overloadAggressively,
                              specialNameFactory,
                              specialDescriptorMap)))),

              // Clear the collected names.
              new MapCleaner(descriptorMap)
            }));

    // Print out any warnings about member name conflicts.
    int warningCount = warningPrinter.getWarningCount();
    if (warningCount > 0) {
      System.err.println(
          "Warning: there were " + warningCount + " conflicting class member name mappings.");
      System.err.println("         Your configuration may be inconsistent.");

      if (!configuration.ignoreWarnings) {
        System.err.println("         If you are sure the conflicts are harmless,");
        System.err.println("         you could try your luck using the '-ignorewarnings' option.");
        throw new IOException("Please correct the above warnings first.");
      }
    }

    // Print out the mapping, if requested.
    if (configuration.printMapping != null) {
      PrintStream ps =
          isFile(configuration.printMapping)
              ? new PrintStream(
                  new BufferedOutputStream(new FileOutputStream(configuration.printMapping)))
              : System.out;

      // Print out items that will be removed.
      programClassPool.classesAcceptAlphabetically(new MappingPrinter(ps));

      if (ps != System.out) {
        ps.close();
      }
    }

    // Actually apply the new names.
    programClassPool.classesAccept(new ClassRenamer());
    libraryClassPool.classesAccept(new ClassRenamer());

    // Update all references to these new names.
    programClassPool.classesAccept(new ClassReferenceFixer(false));
    libraryClassPool.classesAccept(new ClassReferenceFixer(false));
    programClassPool.classesAccept(new MemberReferenceFixer());

    // Make package visible elements public or protected, if obfuscated
    // classes are being repackaged aggressively.
    if (configuration.repackageClasses != null && configuration.allowAccessModification) {
      programClassPool.classesAccept(new AllConstantVisitor(new AccessFixer()));
    }

    // Rename the source file attributes, if requested.
    if (configuration.newSourceFileAttribute != null) {
      programClassPool.classesAccept(new SourceFileRenamer(configuration.newSourceFileAttribute));
    }

    // Mark NameAndType constant pool entries that have to be kept
    // and remove the other ones.
    programClassPool.classesAccept(new NameAndTypeUsageMarker());
    programClassPool.classesAccept(new NameAndTypeShrinker());

    // Mark Utf8 constant pool entries that have to be kept
    // and remove the other ones.
    programClassPool.classesAccept(new Utf8UsageMarker());
    programClassPool.classesAccept(new Utf8Shrinker());
  }
  /**
   * Initializes the classes in the given program class pool and library class pool, performs some
   * basic checks, and shrinks the library class pool.
   */
  public void execute(ClassPool programClassPool, ClassPool libraryClassPool) throws IOException {
    int originalLibraryClassPoolSize = libraryClassPool.size();

    // Perform a basic check on the keep options in the configuration.
    WarningPrinter keepClassMemberNotePrinter = new WarningPrinter(System.out, configuration.note);

    new KeepClassMemberChecker(keepClassMemberNotePrinter)
        .checkClassSpecifications(configuration.keep);

    // Construct a reduced library class pool with only those library
    // classes whose hierarchies are referenced by the program classes.
    // We can't do this if we later have to come up with the obfuscated
    // class member names that are globally unique.
    ClassPool reducedLibraryClassPool =
        configuration.useUniqueClassMemberNames ? null : new ClassPool();

    WarningPrinter classReferenceWarningPrinter =
        new WarningPrinter(System.err, configuration.warn);
    WarningPrinter dependencyWarningPrinter = new WarningPrinter(System.err, configuration.warn);

    // Initialize the superclass hierarchies for program classes.
    programClassPool.classesAccept(
        new ClassSuperHierarchyInitializer(
            programClassPool, libraryClassPool, classReferenceWarningPrinter, null));

    // Initialize the superclass hierarchy of all library classes, without
    // warnings.
    libraryClassPool.classesAccept(
        new ClassSuperHierarchyInitializer(
            programClassPool, libraryClassPool, null, dependencyWarningPrinter));

    // Initialize the class references of program class members and
    // attributes. Note that all superclass hierarchies have to be
    // initialized for this purpose.
    WarningPrinter memberReferenceWarningPrinter =
        new WarningPrinter(System.err, configuration.warn);

    programClassPool.classesAccept(
        new ClassReferenceInitializer(
            programClassPool,
            libraryClassPool,
            classReferenceWarningPrinter,
            memberReferenceWarningPrinter,
            null));

    if (reducedLibraryClassPool != null) {
      // Collect the library classes that are directly referenced by
      // program classes, without introspection.
      programClassPool.classesAccept(
          new ReferencedClassVisitor(
              new LibraryClassFilter(new ClassPoolFiller(reducedLibraryClassPool))));

      // Reinitialize the superclass hierarchies of referenced library
      // classes, this time with warnings.
      reducedLibraryClassPool.classesAccept(
          new ClassSuperHierarchyInitializer(
              programClassPool, libraryClassPool, classReferenceWarningPrinter, null));
    }

    // Initialize the Class.forName references.
    WarningPrinter dynamicClassReferenceNotePrinter =
        new WarningPrinter(System.out, configuration.note);
    WarningPrinter classForNameNotePrinter = new WarningPrinter(System.out, configuration.note);

    programClassPool.classesAccept(
        new AllMethodVisitor(
            new AllAttributeVisitor(
                new AllInstructionVisitor(
                    new DynamicClassReferenceInitializer(
                        programClassPool,
                        libraryClassPool,
                        dynamicClassReferenceNotePrinter,
                        null,
                        classForNameNotePrinter,
                        createClassNoteExceptionMatcher(configuration.keep))))));

    // Initialize the Class.get[Declared]{Field,Method} references.
    WarningPrinter getMemberNotePrinter = new WarningPrinter(System.out, configuration.note);

    programClassPool.classesAccept(
        new AllMethodVisitor(
            new AllAttributeVisitor(
                new AllInstructionVisitor(
                    new DynamicMemberReferenceInitializer(
                        programClassPool,
                        libraryClassPool,
                        getMemberNotePrinter,
                        createClassMemberNoteExceptionMatcher(configuration.keep, true),
                        createClassMemberNoteExceptionMatcher(configuration.keep, false))))));

    // Initialize other string constant references, if requested.
    if (configuration.adaptClassStrings != null) {
      programClassPool.classesAccept(
          new ClassNameFilter(
              configuration.adaptClassStrings,
              new AllConstantVisitor(
                  new StringReferenceInitializer(programClassPool, libraryClassPool))));
    }

    // Print various notes, if specified.
    WarningPrinter fullyQualifiedClassNameNotePrinter =
        new WarningPrinter(System.out, configuration.note);
    WarningPrinter descriptorKeepNotePrinter = new WarningPrinter(System.out, configuration.note);

    new FullyQualifiedClassNameChecker(
            programClassPool, libraryClassPool, fullyQualifiedClassNameNotePrinter)
        .checkClassSpecifications(configuration.keep);

    new DescriptorKeepChecker(programClassPool, libraryClassPool, descriptorKeepNotePrinter)
        .checkClassSpecifications(configuration.keep);

    // Initialize the class references of library class members.
    if (reducedLibraryClassPool != null) {
      // Collect the library classes that are referenced by program
      // classes, directly or indirectly, with or without introspection.
      programClassPool.classesAccept(
          new ReferencedClassVisitor(
              new LibraryClassFilter(
                  new ClassHierarchyTraveler(
                      true,
                      true,
                      true,
                      false,
                      new LibraryClassFilter(new ClassPoolFiller(reducedLibraryClassPool))))));

      // Initialize the class references of referenced library
      // classes, without warnings.
      reducedLibraryClassPool.classesAccept(
          new ClassReferenceInitializer(
              programClassPool, libraryClassPool, null, null, dependencyWarningPrinter));

      // Reset the library class pool.
      libraryClassPool.clear();

      // Copy the library classes that are referenced directly by program
      // classes and the library classes that are referenced by referenced
      // library classes.
      reducedLibraryClassPool.classesAccept(
          new MultiClassVisitor(
              new ClassVisitor[] {
                new ClassHierarchyTraveler(
                    true,
                    true,
                    true,
                    false,
                    new LibraryClassFilter(new ClassPoolFiller(libraryClassPool))),
                new ReferencedClassVisitor(
                    new LibraryClassFilter(
                        new ClassHierarchyTraveler(
                            true,
                            true,
                            true,
                            false,
                            new LibraryClassFilter(new ClassPoolFiller(libraryClassPool)))))
              }));
    } else {
      // Initialize the class references of all library class members.
      libraryClassPool.classesAccept(
          new ClassReferenceInitializer(
              programClassPool, libraryClassPool, null, null, dependencyWarningPrinter));
    }

    // Initialize the subclass hierarchies.
    programClassPool.classesAccept(new ClassSubHierarchyInitializer());
    libraryClassPool.classesAccept(new ClassSubHierarchyInitializer());

    // Share strings between the classes, to reduce heap memory usage.
    programClassPool.classesAccept(new StringSharer());
    libraryClassPool.classesAccept(new StringSharer());

    // Print out a summary of the notes, if necessary.
    int fullyQualifiedNoteCount = fullyQualifiedClassNameNotePrinter.getWarningCount();
    if (fullyQualifiedNoteCount > 0) {
      System.out.println(
          "Note: there were " + fullyQualifiedNoteCount + " references to unknown classes.");
      System.out.println("      You should check your configuration for typos.");
    }

    int descriptorNoteCount = descriptorKeepNotePrinter.getWarningCount();
    if (descriptorNoteCount > 0) {
      System.out.println(
          "Note: there were "
              + descriptorNoteCount
              + " unkept descriptor classes in kept class members.");
      System.out.println("      You should consider explicitly keeping the mentioned classes");
      System.out.println("      (using '-keep').");
    }

    int dynamicClassReferenceNoteCount = dynamicClassReferenceNotePrinter.getWarningCount();
    if (dynamicClassReferenceNoteCount > 0) {
      System.out.println(
          "Note: there were "
              + dynamicClassReferenceNoteCount
              + " unresolved dynamic references to classes or interfaces.");
      System.err.println("      You should check if you need to specify additional program jars.");
    }

    int classForNameNoteCount = classForNameNotePrinter.getWarningCount();
    if (classForNameNoteCount > 0) {
      System.out.println(
          "Note: there were "
              + classForNameNoteCount
              + " class casts of dynamically created class instances.");
      System.out.println(
          "      You might consider explicitly keeping the mentioned classes and/or");
      System.out.println("      their implementations (using '-keep').");
    }

    int getmemberNoteCount = getMemberNotePrinter.getWarningCount();
    if (getmemberNoteCount > 0) {
      System.out.println(
          "Note: there were "
              + getmemberNoteCount
              + " accesses to class members by means of introspection.");
      System.out.println(
          "      You should consider explicitly keeping the mentioned class members");
      System.out.println("      (using '-keep' or '-keepclassmembers').");
    }

    // Print out a summary of the warnings, if necessary.
    int classReferenceWarningCount = classReferenceWarningPrinter.getWarningCount();
    if (classReferenceWarningCount > 0) {
      System.err.println(
          "Warning: there were "
              + classReferenceWarningCount
              + " unresolved references to classes or interfaces.");
      System.err.println(
          "         You may need to specify additional library jars (using '-libraryjars').");

      if (configuration.skipNonPublicLibraryClasses) {
        System.err.println(
            "         You may also have to remove the option '-skipnonpubliclibraryclasses'.");
      }
    }

    int dependencyWarningCount = dependencyWarningPrinter.getWarningCount();
    if (dependencyWarningCount > 0) {
      System.err.println(
          "Warning: there were "
              + dependencyWarningCount
              + " instances of library classes depending on program classes.");
      System.err.println(
          "         You must avoid such dependencies, since the program classes will");
      System.err.println("         be processed, while the library classes will remain unchanged.");
    }

    int memberReferenceWarningCount = memberReferenceWarningPrinter.getWarningCount();
    if (memberReferenceWarningCount > 0) {
      System.err.println(
          "Warning: there were "
              + memberReferenceWarningCount
              + " unresolved references to program class members.");
      System.err.println("         Your input classes appear to be inconsistent.");
      System.err.println("         You may need to recompile them and try again.");
      System.err.println("         Alternatively, you may have to specify the option ");
      System.err.println("         '-dontskipnonpubliclibraryclassmembers'.");

      if (configuration.skipNonPublicLibraryClasses) {
        System.err.println(
            "         You may also have to remove the option '-skipnonpubliclibraryclasses'.");
      }
    }

    if ((classReferenceWarningCount > 0
            || dependencyWarningCount > 0
            || memberReferenceWarningCount > 0)
        && !configuration.ignoreWarnings) {
      throw new IOException("Please correct the above warnings first.");
    }

    if ((configuration.note == null || !configuration.note.isEmpty())
        && (configuration.warn != null && configuration.warn.isEmpty()
            || configuration.ignoreWarnings)) {
      System.out.println("Note: You're ignoring all warnings!");
    }

    // Discard unused library classes.
    if (configuration.verbose) {
      System.out.println("Ignoring unused library classes...");
      System.out.println("  Original number of library classes: " + originalLibraryClassPoolSize);
      System.out.println("  Final number of library classes:    " + libraryClassPool.size());
    }
  }