gt:             FileWriter writer = new FileWriter(fileName);
context: /* Copyright (c) 2002-2011 by XMLVM.org
   *
   * Project Info:  http://www.xmlvm.org
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation; either version 2.1 of the License, or
   * (at your option) any later version.
   *
   * This library 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 Lesser General Public
   * License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
   * USA.
   */
  
  package org.xmlvm.util.analytics;
  
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.xmlvm.Log;
  import org.xmlvm.util.analytics.data.Dependencies;
  import org.xmlvm.util.analytics.data.TypeHierarchy;
  import org.xmlvm.util.analytics.data.Util;
  import org.xmlvm.util.universalfile.UniversalFile;
  import org.xmlvm.util.universalfile.UniversalFileCreator;
  import org.xmlvm.util.universalfile.UniversalFileFilter;
  
  import com.android.dx.cf.code.ConcreteMethod;
  import com.android.dx.cf.code.Ropper;
  import com.android.dx.cf.direct.DirectClassFile;
  import com.android.dx.cf.direct.StdAttributeFactory;
  import com.android.dx.cf.iface.Method;
  import com.android.dx.cf.iface.MethodList;
  import com.android.dx.cf.iface.ParseException;
  import com.android.dx.dex.code.CatchHandlerList;
  import com.android.dx.dex.code.CatchTable;
  import com.android.dx.dex.code.CatchTable.Entry;
  import com.android.dx.dex.code.CstInsn;
  import com.android.dx.dex.code.DalvCode;
  import com.android.dx.dex.code.DalvInsn;
  import com.android.dx.dex.code.DalvInsnList;
  import com.android.dx.dex.code.Dop;
  import com.android.dx.dex.code.Dops;
  import com.android.dx.dex.code.HighRegisterPrefix;
  import com.android.dx.dex.code.PositionList;
  import com.android.dx.dex.code.RopTranslator;
  import com.android.dx.dex.code.SimpleInsn;
  import com.android.dx.rop.code.AccessFlags;
  import com.android.dx.rop.code.DexTranslationAdvice;
  import com.android.dx.rop.code.RopMethod;
  import com.android.dx.rop.code.TranslationAdvice;
  import com.android.dx.rop.cst.Constant;
  import com.android.dx.rop.cst.CstMemberRef;
  import com.android.dx.rop.cst.CstMethodRef;
  import com.android.dx.rop.type.Prototype;
  import com.android.dx.rop.type.StdTypeList;
  import com.android.dx.rop.type.TypeList;
  
  /**
   * The JDKAnalyzer is a swiss army knife for analyzing dependencies between
   * classes in a library. Given a black- and white-list of classes and packages, 
   * it analyzes the border betweteen these two groups of classes. All
   * dependencies between classes are mapped and can be written as a graph file,
   * for visual analysis.
   */
  public class JDKAnalyzer {
      public static final String         TAG               = JDKAnalyzer.class.getSimpleName();
      public static Map<String, Integer> badClassCount     = new HashMap<String, Integer>();
      public static final String         RESULTS_FILENAME  = "results.bin";
      public static final String         RESULTS2_FILENAME = "results2.bin";
      public static final String         GRAPH_FILENAME    = "fulldeps.gdf";
      final static String[]              GOOD_PACKAGES     = { "java.lang", "java.util", "java.math",
              "java.net", "java.io", "org.apache.harmony.luni.util" };
  
      /**
       * For manual classification override: Classes that will always be
       * classified as "good", regardless of their package.
       */
      public final static Set<String>    GOOD_CLASSES      = new HashSet<String>();
  
      /**
       * For manual classification override: Classes that will always be
       * classified as "bad", regardless of their package.
       */
      public final static Set<String>    BAD_CLASSES       = new HashSet<String>();
  
  
      public static void main(String[] args) {
          // The JDK JAR archive should be given as the argument.
          if (args.length != 1) {
              Log.error(TAG, "Invalid usage.");
              System.exit(-1);
          }
          fillGoodBadOverrides();
          String libraryPath = args[0];
  
          // Get a complete dependency map.
          Dependencies dependencies = loadDepencencies(libraryPath);
  
          writeDepsToGraphFile(dependencies);
  
          // Note(Sascha): The analysis showed, that in the JDK there are not a
          // lot of red/green mixed hierarchies. So for now, we skip this part.
          // It might become interesting for other libraries or for when the rules
          // change.
  
          // Compute the type hierarchy of the given set of classes.
          HierarchyAnalyzer hierarchyAnalyzer = new HierarchyAnalyzer(libraryPath);
          TypeHierarchy hierarchy = hierarchyAnalyzer.analyze();
  
          // First we determine the list of mock methods we need to insert into
          // green classes to prevent bubbling up of calls into red methods.
          Set<String> mockMethods = determineGreenMockMethods(dependencies, hierarchy);
  
          Pair<Set<String>, Set<String>> orangeLists = constructOrangeList(dependencies);
          Set<String> redList = constructRedList(dependencies, orangeLists.first);
          Set<String> greenList = constructGreenList(dependencies);
          writeSetToFile(orangeLists.first, "orange_classes.txt");
          writeSetToFile(orangeLists.second, "orange_details.txt");
          writeSetToFile(redList, "red.txt");
          writeSetToFile(greenList, "green.txt");
  
          // Find all good classes with bad references in them.
          // Log.debug(TAG, "Getting good classes with bad deps...");
          // Set<String> goodClassesWithBadDeps =
          // getGoodClassesWithBadReferences(dependencies);
          // Log.debug(TAG, "Found: " + goodClassesWithBadDeps.size());
  
          // Bad references that are referenced directly from good classes.
          // printDirectBadRefs();
      }
  
      private static void fillGoodBadOverrides() {
          // Problematic:
          // java.security.BasicPermission. Used by many different
          // classes and doing an import on all of java.security.
          // sun.misc.Unsafe: Very low-level functionality.
  
          GOOD_CLASSES.add("java.security.Permission");
          // Interfaces
          GOOD_CLASSES.add("java.security.Guard");
  
          // Super-class of a few good classes and not much functionality as it is
          // primarily abstract.
          GOOD_CLASSES.add("java.security.PermissionCollection");
  
          GOOD_CLASSES.add("java.security.BasicPermission");
          GOOD_CLASSES.add("java.security.PrivilegedAction");
          GOOD_CLASSES.add("java.security.PrivilegedActionException");
          GOOD_CLASSES.add("java.security.PrivilegedExceptionAction");
  
          // Interfaces from which good classes are created.
          // TODO(Sascha): Maybe do this automatically.
          GOOD_CLASSES.add("sun.reflect.LangReflectAccess");
          GOOD_CLASSES.add("sun.misc.JavaNetAccess");
          GOOD_CLASSES.add("sun.misc.JavaIOAccess");
          GOOD_CLASSES.add("org.xml.sax.EntityResolver");
          GOOD_CLASSES.add("sun.net.spi.nameservice.NameService");
          GOOD_CLASSES.add("org.xml.sax.ErrorHandler");
          GOOD_CLASSES.add("sun.misc.JavaIODeleteOnExitAccess");
          GOOD_CLASSES.add("sun.misc.SignalHandler");
          GOOD_CLASSES.add("sun.misc.JavaLangAccess");
          GOOD_CLASSES.add("sun.misc.JavaUtilJarAccess");
          GOOD_CLASSES.add("sun.misc.JavaIOFileDescriptorAccess");
          GOOD_CLASSES.add("sun.util.LocaleServiceProviderPool$LocalizedObjectGetter");
          GOOD_CLASSES.add("java.nio.channels.Channel");
  
          GOOD_CLASSES.add("org.apache.harmony.luni.internal.nls.Messages");
          GOOD_CLASSES.add("org.apache.harmony.math.internal.nls.Messages");
          GOOD_CLASSES.add("org.apache.harmony.regex.internal.nls.Messages");
          GOOD_CLASSES.add("org.apache.harmony.archive.internal.nls.Messages");
  
          BAD_CLASSES.add("java.util.jar.JarVerifier");
          BAD_CLASSES.add("java.lang.management.ManagementFactory");
          BAD_CLASSES.add("java.util.JapaneseImperialCalendar");
          BAD_CLASSES.add("java.lang.ClassLoader");
          BAD_CLASSES.add("java.net.URLClassLoader");
          BAD_CLASSES.add("java.net.URLClassLoader$SubURLClassLoader");
          BAD_CLASSES.add("java.net.FactoryURLClassLoader");
  
          // This one is a subclass of java.lang.ClassLoader, so it must go as
          // well.
          BAD_CLASSES.add("java.util.ResourceBundle$RBClassLoader");
  
          // Sub-class of bad class sun.misc.LRUCache.
          BAD_CLASSES.add("java.util.Scanner$1");
  
          // Sub-class of bad class org.apache.harmony.luni.util.ThreadLocalCache
          BAD_CLASSES.add("java.io.ObjectStreamClass$OSCThreadLocalCache");
          BAD_CLASSES.add("java.io.ObjectStreamClass$OSCThreadLocalCache$1");
  
          // DEX cannot parse this.
          BAD_CLASSES.add("org.apache.xerces.impl.xpath.regex.ParserForXMLSchema");
      }
  
      /**
       * Constructs a set of classes that can be completely removed.
       */
      private static Set<String> constructRedList(Dependencies dependencies, Set<String> orangeList) {
          Set<String> result = new HashSet<String>();
          for (String clazz : dependencies.keySet()) {
              // Red classes are all bad classes that are not orange.
              if (!isGoodClass(clazz) && !orangeList.contains(clazz)) {
                  result.add(clazz);
              }
  
          }
          return result;
      }
  
      private static Set<String> constructGreenList(Dependencies dependencies) {
          Set<String> result = new HashSet<String>();
          for (String clazz : dependencies.keySet()) {
              // Red classes are all bad classes that are not orange.
              if (isGoodClass(clazz)) {
                  result.add(clazz);
              }
          }
          return result;
      }
  
      /**
       * Constructs a list of <class/member> pairs that need to be mocked with
       * assertions, as these members will be accessed by the remaining good
       * classes.
       */
      private static Pair<Set<String>, Set<String>> constructOrangeList(Dependencies dependencies) {
          // These are just the orange class names. This can be used to remove
          // these classes from he red list.
          Set<String> orangeClasses = new HashSet<String>();
  
          // These are the details about the classes and members that need to be
          // mocked.
          Set<String> details = new HashSet<String>();
  
          for (String clazz : dependencies.keySet()) {
              if (!isGoodClass(clazz)) {
                  continue;
              }
  
              Dependencies.ClassDeps classDeps = dependencies.getDepsForClass(clazz);
              for (String method : classDeps.methodSet()) {
                  Dependencies.MethodDeps methodDeps = classDeps.getMethodDeps(method);
                  for (String dep : methodDeps.classSet()) {
                      // Only bad dependencies are relevant here.
                      // TODO: Add super-type relationship mocks.
                      if (isGoodClass(dep)) {
                          continue;
                      }
                      orangeClasses.add(dep);
                      for (String methodDep : methodDeps.getMethods(dep)) {
                          // MethodDep can be empty for e.g. the SUPER
                          // relationship, which we don't care about for the
                          // mocks at this point.
                          if (!methodDep.isEmpty()) {
                              details.add(dep + "::" + methodDep);
                          }
                      }
                  }
              }
          }
          return new Pair<Set<String>, Set<String>>(orangeClasses, details);
      }
  
      private static void writeDepsToGraphFile(Dependencies dependencies) {
          Log.debug(TAG, "Writing GDF file ...");
          StringBuilder nodeDef = new StringBuilder();
          StringBuilder edgeDef = new StringBuilder();
  
          nodeDef.append("nodedef> name,color,style\n");
          edgeDef.append("edgedef> node1,node2,method VARCHAR(32)\n");
  
          Set<String> allNodes = new HashSet<String>();
          for (String clazz : dependencies.keySet()) {
              // Only write out good classes.
              if (!isGoodClass(clazz)) {
                  continue;
              }
              boolean hasDepsToGraph = false;
              Dependencies.ClassDeps classDeps = dependencies.getDepsForClass(clazz);
              for (String method : classDeps.methodSet()) {
                  Dependencies.MethodDeps methodDeps = classDeps.getMethodDeps(method);
                  for (String dep : methodDeps.classSet()) {
                      // To reduce complexity in the graph, only map bad deps.
                      if (isGoodClass(dep)) {
                          continue;
                      }
                      for (String methodDep : methodDeps.getMethods(dep)) {
                          allNodes.add(dep);
                          edgeDef.append(clazz);
                          edgeDef.append(",");
                          edgeDef.append(dep);
                          edgeDef.append(",");
                          edgeDef.append(method + "->" + methodDep);
                          edgeDef.append("\n");
                          hasDepsToGraph = true;
                      }
                  }
              }
              if (hasDepsToGraph) {
                  allNodes.add(clazz);
              }
          }
  
          for (String node : allNodes) {
              nodeDef.append(node);
              if (isGoodClass(node)) {
                  nodeDef.append(",");
                  nodeDef.append("blue");
                  nodeDef.append(",");
                  nodeDef.append("1");
              } else {
                  nodeDef.append(",");
                  nodeDef.append("red");
                  nodeDef.append(",");
                  nodeDef.append("2");
              }
              nodeDef.append("\n");
          }
  
          FileWriter fw;
          try {
              fw = new FileWriter(GRAPH_FILENAME);
              fw.write(nodeDef.toString());
              fw.write(edgeDef.toString());
              fw.close();
              Log.debug(TAG, "Writing GDF file done.");
          } catch (IOException e) {
              Log.error(TAG, "Writing GDF file failed: " + e.getMessage());
          }
      }
  
      private static void printDirectBadRefs() {
          List<String> clazzes = new ArrayList<String>(badClassCount.keySet());
  
          Collections.sort(clazzes, new Comparator<String>() {
              @Override
              public int compare(String o1, String o2) {
                  return badClassCount.get(o1) - badClassCount.get(o2);
              }
          });
  
          Log.debug(TAG, "Top direct bad refs:");
          for (String badDep : clazzes) {
              int count = badClassCount.get(badDep);
              if (count > 10) {
                  Log.debug(TAG, count + " - " + badDep);
              }
          }
      }
  
      /**
       * Returns a list of classes that contain bad dependencies.
       */
      private static Set<String> getGoodClassesWithBadReferences(Dependencies data) {
          Set<String> result = new HashSet<String>();
  
          // We try to cache the result of this analysis on disk as well. If we
          // find this cache, we load it.
          File resultsFile = new File(RESULTS2_FILENAME);
          if (resultsFile.exists()) {
              Log.debug(TAG, "Attempting to read second result from " + RESULTS2_FILENAME);
              try {
                  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(resultsFile));
                  result = (Set<String>) ois.readObject();
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
                  return null;
              } catch (IOException e) {
                  e.printStackTrace();
                  return null;
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
                  return null;
              }
          } else {
              // If the cache could not be loaded, we perform the actual analysis
              // and store the result on disk for further calls.
              int goods = 0;
              for (String className : data.keySet()) {
                  if (isGoodClass(className)) {
                      goods++;
                      if (hasBadDep(className, data)) {
                          result.add(className);
                      } else {
                          Log.debug(TAG, "A good class without bad rep!");
                      }
                  }
              }
  
              Log.debug(TAG, "Good classes: " + goods);
              try {
                  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(resultsFile));
                  oos.writeObject(result);
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
                  return null;
              } catch (IOException e) {
                  e.printStackTrace();
                  return null;
              }
          }
          return result;
      }
  
      private static boolean hasBadDep(String className, Dependencies data) {
          boolean result = false;
          for (String dep : data.getAllDepsForClass(className)) {
              for (String goodDep : GOOD_PACKAGES) {
                  if (!dep.startsWith(goodDep)) {
                      if (!badClassCount.containsKey(dep)) {
                          badClassCount.put(dep, 0);
                      }
                      badClassCount.put(dep, badClassCount.get(dep) + 1);
                      result = true;
                      break;
                  }
              }
          }
          return result;
      }
  
      /**
       * Returns whether the given class name is a good class.
       */
      private static boolean isGoodClass(String className) {
          // First check the overrides.
          for (String good : GOOD_CLASSES) {
              if (className.equals(good)) {
                  return true;
              }
          }
          for (String bad : BAD_CLASSES) {
              if (className.equals(bad)) {
                  return false;
              }
          }
  
          for (String goodDep : GOOD_PACKAGES) {
              if (className.startsWith(goodDep)) {
                  return true;
              }
          }
          return false;
      }
  
      private static Dependencies loadDepencencies(String jdkFileName) {
          Dependencies dependencies = new Dependencies();
  
          // This is a shortcut, in case we already have the analytical data about
          // the dependencies loaded and stored on the file system. If so, we use
          // this data.
          File resultsFile = new File(RESULTS_FILENAME);
          if (resultsFile.exists()) {
              Log.debug(TAG, "Loading results from " + RESULTS_FILENAME + " ...");
              ObjectInputStream ois;
              try {
                  ois = new ObjectInputStream(new FileInputStream(resultsFile));
                  dependencies = (Dependencies) ois.readObject();
                  ois.close();
                  Log.debug(TAG, "Loaded results from file: " + dependencies.size());
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
                  return null;
              } catch (IOException e) {
                  e.printStackTrace();
                  return null;
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
                  return null;
              }
          } else {
              // If there is no stored result, we nee to analyze the classes
              // first.
              // First the class files are loaded.
              UniversalFile library = UniversalFileCreator.createDirectory(null, jdkFileName);
              UniversalFile[] classes = library.listFilesRecursively(new UniversalFileFilter() {
                  @Override
                  public boolean accept(UniversalFile file) {
                      return file.getName().toLowerCase().endsWith(".class");
                  }
              });
              Log.debug(TAG, "Analyzing " + classes.length + " classes.");
              final String basePath = library.getAbsolutePath();
  
              // We go through all the class files and get their dependencies.
              for (UniversalFile clazz : classes) {
                  String fileName = clazz.getRelativePath(basePath).replace('\\', '.');
                  Dependencies.ClassDeps classDeps = dependencies.getDepsForClass(fileName.substring(
                          0, fileName.length() - 6).replace(File.separatorChar, '.'));
                  getAllDependencies(clazz.getFileAsBytes(), fileName, classDeps);
              }
  
              // Store results on disk so we save time when executing this a
              // second time.
              try {
                  Log.debug(TAG, "Writing result to file " + RESULTS_FILENAME);
                  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
                          RESULTS_FILENAME));
                  oos.writeObject(dependencies);
                  oos.close();
                  Log.debug(TAG, "Done.");
              } catch (FileNotFoundException e) {
                  e.printStackTrace();
                  return null;
              } catch (IOException e) {
                  e.printStackTrace();
                  return null;
              }
          }
          return dependencies;
      }
  
      /**
       * Adds all dependencies of the given class to classDeps.
       */
      private static void getAllDependencies(byte[] bytes, String relativePath,
              Dependencies.ClassDeps classDeps) {
  
          Log.debug(TAG, relativePath);
          DirectClassFile classFile = new DirectClassFile(bytes, relativePath, false);
          classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
          try {
              classFile.getMagic();
          } catch (ParseException ex) {
              Log.warn(TAG, "Put to red-list as it couldn't be parsed: " + relativePath);
              BAD_CLASSES.add(classDeps.getClassName());
              return;
          }
  
          String superClassName = "";
          // This can happen for java.lang.Object.
          if (classFile.getSuperclass() != null) {
              superClassName = Util.parseClassName(
                      classFile.getSuperclass().getClassType().getClassName()).toString();
          }
  
          // Super Class
          if (!superClassName.isEmpty()) {
              Set<String> superClass = new HashSet<String>();
              superClass.add(superClassName.replace('/', '.'));
              classDeps.getMethodDeps("SUPER").addDependency(superClassName.replace('/', '.'), "");
          }
  
          // Interfaces
          TypeList interfaces = classFile.getInterfaces();
          if (interfaces.size() > 0) {
              Set<String> interfaceList = new HashSet<String>();
              for (int i = 0; i < interfaces.size(); ++i) {
                  interfaceList.add(Util.parseClassName(interfaces.getType(i).getClassName())
                          .toString());
                  classDeps.getMethodDeps("INTERFACES").addDependency(
                          Util.parseClassName(interfaces.getType(i).getClassName()).toString(), "");
              }
          }
  
          // Methods
          MethodList methods = classFile.getMethods();
          for (int i = 0; i < methods.size(); i++) {
              Method method = methods.get(i);
              // CstMethodRef methodRef = new
              // CstMethodRef(method.getDefiningClass(), method.getNat());
              // We shouldn't need to go through the signature. If the class is
              // not used in the code block, we can ignore it.
              // processSignature(methodRef, dependencies);
              processCode(getCode(method, classFile),
                      classDeps.getMethodDeps(method.getName().toHuman()));
          }
      }
  
      /**
       * Extracts all types from the method signature.
       */
      private static void processSignature(CstMethodRef methodRef, Set<String> dependencies) {
          Prototype prototype = methodRef.getPrototype();
  
          // Parameter types.
          StdTypeList parameters = prototype.getParameterTypes();
          for (int i = 0; i < parameters.size(); ++i) {
              String parameterType = parameters.get(i).toHuman();
              dependencies.add(parameterType);
          }
  
          // Return type.
          dependencies.add(prototype.getReturnType().getType().toHuman());
      }
  
      private static void processCode(DalvCode code, Dependencies.MethodDeps methodDeps) {
          if (code == null) {
              return;
          }
          // TODO: We don't need this, as it should be added by code blocks, if
          // the exception is actually used. If it's not used, we shouldn't care.
          // processCatchTable(code.getCatches(), dependencies);
  
          DalvInsnList instructions = code.getInsns();
          for (int i = 0; i < instructions.size(); ++i) {
              processInstruction(instructions.get(i), methodDeps);
          }
      }
  
      private static void processInstruction(DalvInsn instruction, Dependencies.MethodDeps methodDeps) {
          String opname = instruction.getOpcode().getName();
          if (opname.equals("instance-of") || opname.equals("const-class")) {
              CstInsn isaInsn = (CstInsn) instruction;
              // TODO: Do we need this?
              // dependencies.add(isaInsn.getConstant().toHuman());
          }
  
          // TODO: Do we need to add these?
          // RegisterSpecList registers = instruction.getRegisters();
          // for (int i = 0; i < registers.size(); ++i) {
          // RegisterSpec register = registers.get(i);
          // String descriptor = register.getType().getDescriptor();
          // String registerType = register.getType().toHuman();
          // // Sometimes a register type name starts with some info about the
          // // register. We need to cut this out.
          // if (descriptor.startsWith("N")) {
          // registerType = registerType.substring(registerType.indexOf('L') + 1);
          // }
          // methodDeps.addDependency(registerType, "TODO");
          // }
  
          if (instruction instanceof CstInsn) {
              CstInsn cstInsn = (CstInsn) instruction;
              if (isInvokeInstruction(cstInsn)) {
                  // Adds the class that a method references
                  CstMethodRef methodRef = (CstMethodRef) cstInsn.getConstant();
                  methodDeps.addDependency(methodRef.getDefiningClass().toHuman(), methodRef.getNat()
                          .getName().toHuman()
                          + ":" + methodRef.getPrototype().toString());
              } else {
                  Constant constant = cstInsn.getConstant();
                  if (constant instanceof CstMemberRef) {
                      CstMemberRef memberRef = (CstMemberRef) constant;
  
                      methodDeps.addDependency(memberRef.getDefiningClass().getClassType().toHuman(),
                              memberRef.getNat().getName().toHuman());
  
                      // dependencies.add(memberRef.getNat().getFieldType().getType().toHuman());
                  }
              }
          } else if (instruction instanceof HighRegisterPrefix) {
              HighRegisterPrefix highRegisterPrefix = (HighRegisterPrefix) instruction;
              SimpleInsn[] moveInstructions = highRegisterPrefix.getMoveInstructions();
              for (SimpleInsn moveInstruction : moveInstructions) {
                  // Recurse.
                  processInstruction(moveInstruction, methodDeps);
              }
          }
      }
  
      /**
       * Adds all exception types.
       */
      private static void processCatchTable(CatchTable catchTable,
              Map<String, Set<String>> dependencies) {
          if (catchTable.size() == 0) {
              return;
          }
          for (int i = 0; i < catchTable.size(); ++i) {
              Entry entry = catchTable.get(i);
              CatchHandlerList catchHandlers = entry.getHandlers();
              for (int j = 0; j < catchHandlers.size(); ++j) {
                  // TODO: Do we need these? Shouldn't they be handled by code, if
                  // accessed?
                  // dependencies.add(catchHandlers.get(j).getExceptionType().toHuman());
              }
          }
      }
  
      /**
       * Returns whether the given instruction is an invoke instruction that can
       * be handled by {@link #processInvokeInstruction(CstInsn)}.
       */
      private static boolean isInvokeInstruction(CstInsn cstInsn) {
          final Dop[] invokeInstructions = { Dops.INVOKE_VIRTUAL, Dops.INVOKE_VIRTUAL_RANGE,
                  Dops.INVOKE_STATIC, Dops.INVOKE_STATIC_RANGE, Dops.INVOKE_DIRECT,
                  Dops.INVOKE_DIRECT_RANGE, Dops.INVOKE_INTERFACE, Dops.INVOKE_INTERFACE_RANGE,
                  Dops.INVOKE_SUPER, Dops.INVOKE_SUPER_RANGE };
          for (Dop dop : invokeInstructions) {
              if (dop.equals(cstInsn.getOpcode())) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Extracts the code block from the given method of the given class, or
       * <code>null</code>, if method is native or abstract.
       */
      private static DalvCode getCode(Method method, DirectClassFile classFile) {
          boolean isNative = AccessFlags.isNative(method.getAccessFlags());
          boolean isStatic = AccessFlags.isStatic(method.getAccessFlags());
          boolean isAbstract = AccessFlags.isAbstract(method.getAccessFlags());
  
          if (isNative || isAbstract) {
              return null;
          }
  
          ConcreteMethod concrete = new ConcreteMethod(method, classFile, false, false);
          TranslationAdvice advice = DexTranslationAdvice.THE_ONE;
          RopMethod rmeth = Ropper.convert(concrete, advice);
          CstMethodRef meth = new CstMethodRef(method.getDefiningClass(), method.getNat());
          int paramSize = meth.getParameterWordCount(isStatic);
          DalvCode code = RopTranslator.translate(rmeth, PositionList.NONE, null, paramSize);
          DalvCode.AssignIndicesCallback callback = new DalvCode.AssignIndicesCallback() {
              public int getIndex(Constant cst) {
                  // Everything is at index 0!
                  return 0;
              }
          };
          code.assignIndices(callback);
          return code;
      }
  
      /**
       * Writes a set to a file. Each item will be on a single line in the
       * resulting file.
       */
      private static void writeSetToFile(Set<String> set, String fileName) {
          try {
  