content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.preload.classdataretrieval.hprof;
  
  import com.android.ddmlib.Client;
  import com.android.ddmlib.ClientData;
  import com.android.ddmlib.ClientData.IHprofDumpHandler;
  import com.android.preload.classdataretrieval.ClassDataRetriever;
  import com.android.preload.ui.NullProgressMonitor;
  import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
  import com.android.tools.perflib.heap.ClassObj;
  import com.android.tools.perflib.heap.Queries;
  import com.android.tools.perflib.heap.Snapshot;
  
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileOutputStream;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.Set;
  
  public class Hprof implements ClassDataRetriever {
  
      private static GeneralHprofDumpHandler hprofHandler;
  
      public static void init() {
          synchronized(Hprof.class) {
              if (hprofHandler == null) {
                  ClientData.setHprofDumpHandler(hprofHandler = new GeneralHprofDumpHandler());
              }
          }
      }
  
      public static File doHprof(Client client, int timeout) {
          GetHprof gh = new GetHprof(client, timeout);
          return gh.get();
      }
  
      /**
       * Return a map of class names to class-loader names derived from the hprof dump.
       *
       * @param hprofLocalFile
       */
      public static Map<String, String> analyzeHprof(File hprofLocalFile) throws Exception {
          Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprofLocalFile));
  
          Map<String, Set<ClassObj>> classes = Queries.classes(snapshot, null);
          Map<String, String> retValue = new HashMap<String, String>();
          for (Map.Entry<String, Set<ClassObj>> e : classes.entrySet()) {
              for (ClassObj c : e.getValue()) {
                  String cl = c.getClassLoader() == null ? null : c.getClassLoader().toString();
                  String cName = c.getClassName();
                  int aDepth = 0;
                  while (cName.endsWith("[]")) {
                      cName = cName.substring(0, cName.length()-2);
                      aDepth++;
                  }
                  String newName = transformPrimitiveClass(cName);
                  if (aDepth > 0) {
                      // Need to use kind-a descriptor syntax. If it was transformed, it is primitive.
                      if (newName.equals(cName)) {
                          newName = "L" + newName + ";";
                      }
                      for (int i = 0; i < aDepth; i++) {
                          newName = "[" + newName;
                      }
                  }
                  retValue.put(newName, cl);
              }
          }
  
          // Free up memory.
          snapshot.dispose();
  
          return retValue;
      }
  
      private static Map<String, String> primitiveMapping;
  
      static {
          primitiveMapping = new HashMap<>();
          primitiveMapping.put("boolean", "Z");
          primitiveMapping.put("byte", "B");
          primitiveMapping.put("char", "C");
          primitiveMapping.put("double", "D");
          primitiveMapping.put("float", "F");
          primitiveMapping.put("int", "I");
          primitiveMapping.put("long", "J");
          primitiveMapping.put("short", "S");
          primitiveMapping.put("void", "V");
      }
  
      private static String transformPrimitiveClass(String name) {
          String rep = primitiveMapping.get(name);
          if (rep != null) {
              return rep;
          }
          return name;
      }
  
      private static class GetHprof implements IHprofDumpHandler {
  
          private File target;
          private long timeout;
          private Client client;
  
          public GetHprof(Client client, long timeout) {
              this.client = client;
              this.timeout = timeout;
          }
  
          public File get() {
              synchronized (this) {
                  hprofHandler.addHandler(this);
                  client.dumpHprof();
                  if (target == null) {
                      try {
                          wait(timeout);
                      } catch (Exception e) {
                          System.out.println(e);
                      }
                  }
              }
  
              hprofHandler.removeHandler(this);
              return target;
          }
  
          private void wakeUp() {
              synchronized (this) {
                  notifyAll();
              }
          }
  
          @Override
          public void onEndFailure(Client arg0, String arg1) {
              System.out.println("GetHprof.onEndFailure");
              if (client == arg0) {
                  wakeUp();
              }
          }
  
          private static File createTargetFile() {
              try {
                  return File.createTempFile("ddms", ".hprof");
              } catch (Exception e) {
                  throw new RuntimeException(e);
              }
          }
  
          @Override
          public void onSuccess(String arg0, Client arg1) {
              System.out.println("GetHprof.onSuccess");
              if (client == arg1) {
                  try {
                      target = createTargetFile();
                      arg1.getDevice().getSyncService().pullFile(arg0,
                              target.getAbsoluteFile().toString(), new NullProgressMonitor());
                  } catch (Exception e) {
                      if (target != null) {
                          target.delete();
                      }
                      e.printStackTrace();
                      target = null;
                  }
                  wakeUp();
              }
          }
  
          @Override
          public void onSuccess(byte[] arg0, Client arg1) {
              System.out.println("GetHprof.onSuccess");
              if (client == arg1) {
                  try {
                      target = createTargetFile();
                      BufferedOutputStream out =
                              new BufferedOutputStream(new FileOutputStream(target));
                      out.write(arg0);
                      out.close();
                  } catch (Exception e) {
                      if (target != null) {
                          target.delete();
                      }
                      e.printStackTrace();
                      target = null;
                  }
                  wakeUp();
              }
          }
      }
  
      private int timeout;
  
      public Hprof(int timeout) {
          this.timeout = timeout;
      }
  
      @Override
      public Map<String, String> getClassData(Client client) {
          File hprofLocalFile = Hprof.doHprof(client, timeout);
          if (hprofLocalFile == null) {
              throw new RuntimeException("Failed getting dump...");
          }
          System.out.println("Dump file is " + hprofLocalFile);
  
          try {
              return analyzeHprof(hprofLocalFile);
          } catch (Exception e) {
              throw new RuntimeException(e);
          } finally {
              hprofLocalFile.delete();
          }
      }
  }
  