content: /*
   * Copyright (C) 2010-2101 Alibaba Group Holding Limited.
   *
   * 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.alibaba.otter.shared.common.utils.sizeof;
  
  import java.lang.reflect.Array;
  import java.lang.reflect.Field;
  import java.lang.reflect.Modifier;
  import java.math.BigInteger;
  import java.security.AccessController;
  import java.security.PrivilegedActionException;
  import java.security.PrivilegedExceptionAction;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.IdentityHashMap;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.WeakHashMap;
  import java.util.jar.JarFile;
  
  // ----------------------------------------------------------------------------
  /**
   * This non-instantiable class presents an API for object sizing as described in the <a
   * href="http://www.javaworld.com/javaqa/2003-12/02-qa-1226-sizeof_p.html">article</a>. See individual methods for
   * details.
   * <P>
   * This implementation is J2SE 1.4+ only. You would need to code your own identity hashmap to port this to earlier Java
   * versions.
   * <P>
   * Security: this implementation uses AccessController.doPrivileged() so it could be granted privileges to access
   * non-public class fields separately from your main application code. The minimum set of permissions necessary for this
   * class to function correctly follows:
   * 
   * <pre>
   *    permission java.lang.RuntimePermission &quot;accessDeclaredMembers&quot;;
   *    permission java.lang.reflect.ReflectPermission &quot;suppressAccessChecks&quot;;
   * </pre>
   * 
   * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
   */
  public abstract class ObjectProfiler {
  
      // public: ................................................................
  
      // the following constants are physical sizes (in bytes) and are JVM-dependent:
      // [the current values are Ok for most 32-bit JVMs]
  
      public static final int OBJECT_SHELL_SIZE  = 8; // java.lang.Object shell
      // size in bytes
      public static final int OBJREF_SIZE        = 4;
      public static final int LONG_FIELD_SIZE    = 8;
      public static final int INT_FIELD_SIZE     = 4;
      public static final int SHORT_FIELD_SIZE   = 2;
      public static final int CHAR_FIELD_SIZE    = 2;
      public static final int BYTE_FIELD_SIZE    = 1;
      public static final int BOOLEAN_FIELD_SIZE = 1;
      public static final int DOUBLE_FIELD_SIZE  = 8;
      public static final int FLOAT_FIELD_SIZE   = 4;
  
      /**
       * Estimates the full size of the object graph rooted at 'obj'. Duplicate data instances are correctly accounted
       * for. The implementation is not recursive.
       * 
       * @param obj input object instance to be measured
       * @return 'obj' size [0 if 'obj' is null']
       */
      public static long sizeof(final Object obj) {
          if (null == obj || isSharedFlyweight(obj)) {
              return 0;
          }
  
          final IdentityHashMap visited = new IdentityHashMap(80000);
  
          try {
              return computeSizeof(obj, visited, CLASS_METADATA_CACHE);
          } catch (RuntimeException re) {
              // re.printStackTrace();//DEBUG
              return -1;
          } catch (NoClassDefFoundError ncdfe) {
              // BUG: throws "java.lang.NoClassDefFoundError: org.eclipse.core.resources.IWorkspaceRoot" when run in WSAD
              // 5
              // see
              // http://www.javaworld.com/javaforums/showflat.php?Cat=&Board=958763&Number=15235&page=0&view=collapsed&sb=5&o=
              // System.err.println(ncdfe);//DEBUG
              return -1;
          }
      }
  
      /**
       * Estimates the full size of the object graph rooted at 'obj' by pre-populating the "visited" set with the object
       * graph rooted at 'base'. The net effect is to compute the size of 'obj' by summing over all instance data
       * contained in 'obj' but not in 'base'.
       * 
       * @param base graph boundary [may not be null]
       * @param obj input object instance to be measured
       * @return 'obj' size [0 if 'obj' is null']
       */
      public static long sizedelta(final Object base, final Object obj) {
          if (null == obj || isSharedFlyweight(obj)) {
              return 0;
          }
          if (null == base) {
              throw new IllegalArgumentException("null input: base");
          }
  
          final IdentityHashMap visited = new IdentityHashMap(40000);
  
          try {
              computeSizeof(base, visited, CLASS_METADATA_CACHE);
              return visited.containsKey(obj) ? 0 : computeSizeof(obj, visited, CLASS_METADATA_CACHE);
          } catch (RuntimeException re) {
              // re.printStackTrace();//DEBUG
              return -1;
          } catch (NoClassDefFoundError ncdfe) {
              // BUG: throws "java.lang.NoClassDefFoundError: org.eclipse.core.resources.IWorkspaceRoot" when run in WSAD
              // 5
              // see
              // http://www.javaworld.com/javaforums/showflat.php?Cat=&Board=958763&Number=15235&page=0&view=collapsed&sb=5&o=
              // System.err.println(ncdfe);//DEBUG
              return -1;
          }
      }
  
      // protected: .............................................................
  
      // package: ...............................................................
  
      // private: ...............................................................
  
      /*
       * Internal class used to cache class metadata information.
       */
      private static final class ClassMetadata {
  
          ClassMetadata(final int primitiveFieldCount, final int shellSize, final Field[] refFields){
              m_primitiveFieldCount = primitiveFieldCount;
              m_shellSize = shellSize;
              m_refFields = refFields;
          }
  
          // all fields are inclusive of superclasses:
  
          final int     m_primitiveFieldCount;
  
          final int     m_shellSize;          // class shell size
  
          final Field[] m_refFields;          // cached non-static fields (made accessible)
  
      } // end of nested class
  
      private static final class ClassAccessPrivilegedAction implements PrivilegedExceptionAction {
  
          /** {@inheritDoc} */
          public Object run() throws Exception {
              return m_cls.getDeclaredFields();
          }
  
          void setContext(final Class cls) {
              m_cls = cls;
          }
  
          private Class m_cls;
  
      } // end of nested class
  
      private static final class FieldAccessPrivilegedAction implements PrivilegedExceptionAction {
  
          /** {@inheritDoc} */
          public Object run() throws Exception {
              m_field.setAccessible(true);
              return null;
          }
  
          void setContext(final Field field) {
              m_field = field;
          }
  
          private Field m_field;
  
      } // end of nested class
  
      private ObjectProfiler(){
      } // this class is not extendible
  
      /*
       * The main worker method for sizeof() and sizedelta().
       */
      private static long computeSizeof(Object obj, final IdentityHashMap visited,
                                        final Map /* <Class,ClassMetadata> */metadataMap) {
          // this uses depth-first traversal; the exact graph traversal algorithm
          // does not matter for computing the total size and this method could be
          // easily adjusted to do breadth-first instead (addLast() instead of
          // addFirst()),
          // however, dfs/bfs require max queue length to be the length of the
          // longest
          // graph path/width of traversal front correspondingly, so I expect
          // dfs to use fewer resources than bfs for most Java objects;
  
          if (null == obj || isSharedFlyweight(obj)) {
              return 0;
          }
  
          final LinkedList queue = new LinkedList();
  
          visited.put(obj, obj);
          queue.add(obj);
  
          long result = 0;
  
          final ClassAccessPrivilegedAction caAction = new ClassAccessPrivilegedAction();
          final FieldAccessPrivilegedAction faAction = new FieldAccessPrivilegedAction();
  
          while (!queue.isEmpty()) {
              obj = queue.removeFirst();
              final Class objClass = obj.getClass();
  
              int skippedBytes = skipClassDueToSunJVMBug(objClass);
              if (skippedBytes > 0) {
                  result += skippedBytes; // can't do better than that
                  continue;
              }
  
              if (objClass.isArray()) {
                  final int arrayLength = Array.getLength(obj);
                  final Class componentType = objClass.getComponentType();
  
                  result += sizeofArrayShell(arrayLength, componentType);
  
                  if (!componentType.isPrimitive()) {
                      // traverse each array slot:
                      for (int i = 0; i < arrayLength; ++i) {
                          final Object ref = Array.get(obj, i);
  
                          if ((ref != null) && !visited.containsKey(ref)) {
                              visited.put(ref, ref);
                              queue.addFirst(ref);
                          }
                      }
                  }
              } else { // the object is of a non-array type
                  final ClassMetadata metadata = getClassMetadata(objClass, metadataMap, caAction, faAction);
                  final Field[] fields = metadata.m_refFields;
  
                  result += metadata.m_shellSize;
  
                  // traverse all non-null ref fields:
                  for (int f = 0, fLimit = fields.length; f < fLimit; ++f) {
                      final Field field = fields[f];
  
                      final Object ref;
                      try { // to get the field value:
                          ref = field.get(obj);
                      } catch (Exception e) {
                          throw new RuntimeException("cannot get field [" + field.getName() + "] of class ["
                                                     + field.getDeclaringClass().getName() + "]: " + e.toString());
                      }
  
                      if ((ref != null) && !visited.containsKey(ref)) {
                          visited.put(ref, ref);
                          queue.addFirst(ref);
                      }
                  }
              }
          }
  
          return result;
      }
  
      /*
       * A helper method for manipulating a class metadata cache.
       */
      private static ClassMetadata getClassMetadata(final Class cls, final Map /* <Class,ClassMetadata> */metadataMap,
                                                    final ClassAccessPrivilegedAction caAction,
                                                    final FieldAccessPrivilegedAction faAction) {
          if (null == cls) {
              return null;
          }
  
          ClassMetadata result;
          synchronized (metadataMap) {
              result = (ClassMetadata) metadataMap.get(cls);
          }
          if (result != null) {
              return result;
          }
  
          int primitiveFieldCount = 0;
          int shellSize = OBJECT_SHELL_SIZE; // java.lang.Object shell
          final List /* Field */refFields = new LinkedList();
  
          final Field[] declaredFields;
          try {
              caAction.setContext(cls);
              declaredFields = (Field[]) AccessController.doPrivileged(caAction);
          } catch (PrivilegedActionException pae) {
              throw new RuntimeException("could not access declared fields of class " + cls.getName() + ": "
                                         + pae.getException());
          }
  
          for (int f = 0; f < declaredFields.length; ++f) {
              final Field field = declaredFields[f];
              if (Modifier.isStatic(field.getModifiers())) {
                  continue;
              }
              /*
               * Can't do that: HashMap data is transient, for example... if (Modifier.isTransient(field.getModifiers()))
               * { shellSize += OBJREF_SIZE; continue; }
               */
  
              final Class fieldType = field.getType();
              if (fieldType.isPrimitive()) {
                  // memory alignment ignored:
                  shellSize += sizeofPrimitiveType(fieldType);
                  ++primitiveFieldCount;
              } else {
                  // prepare for graph traversal later:
                  if (!field.isAccessible()) {
                      try {
                          faAction.setContext(field);
                          AccessController.doPrivileged(faAction);
                      } catch (PrivilegedActionException pae) {
                          throw new RuntimeException("could not make field " + field + " accessible: "
                                                     + pae.getException());
                      }
                  }
  
                  // memory alignment ignored:
                  shellSize += OBJREF_SIZE;
                  refFields.add(field);
              }
          }
  
          // recurse into superclass:
          final ClassMetadata superMetadata = getClassMetadata(cls.getSuperclass(), metadataMap, caAction, faAction);
          if (superMetadata != null) {
              primitiveFieldCount += superMetadata.m_primitiveFieldCount;
              shellSize += superMetadata.m_shellSize - OBJECT_SHELL_SIZE;
              refFields.addAll(Arrays.asList(superMetadata.m_refFields));
          }
  
          final Field[] _refFields = new Field[refFields.size()];
          refFields.toArray(_refFields);
  
          result = new ClassMetadata(primitiveFieldCount, shellSize, _refFields);
          synchronized (metadataMap) {
              metadataMap.put(cls, result);
          }
  
          return result;
      }
  
      /*
       * Computes the "shallow" size of an array instance.
       */
      private static int sizeofArrayShell(final int length, final Class componentType) {
          // this ignores memory alignment issues by design:
  
          final int slotSize = componentType.isPrimitive() ? sizeofPrimitiveType(componentType) : OBJREF_SIZE;
  
          return OBJECT_SHELL_SIZE + INT_FIELD_SIZE + OBJREF_SIZE + length * slotSize;
      }
  
      /*
       * Returns the JVM-specific size of a primitive type.
       */
      private static int sizeofPrimitiveType(final Class type) {
          if (type == int.class) {
              return INT_FIELD_SIZE;
          } else if (type == long.class) {
              return LONG_FIELD_SIZE;
          } else if (type == short.class) {
              return SHORT_FIELD_SIZE;
          } else if (type == byte.class) {
              return BYTE_FIELD_SIZE;
          } else if (type == boolean.class) {
              return BOOLEAN_FIELD_SIZE;
          } else if (type == char.class) {
              return CHAR_FIELD_SIZE;
          } else if (type == double.class) {
              return DOUBLE_FIELD_SIZE;
          } else if (type == float.class) {
              return FLOAT_FIELD_SIZE;
          } else {
              throw new IllegalArgumentException("not primitive: " + type);
          }
      }
  
      // class metadata cache:
      private static final Map CLASS_METADATA_CACHE = new WeakHashMap(101);
  
      static final Class[]     sunProblematicClasses;
      static final Map        /* <Class, Integer> */sunProblematicClassesSizes;
  
      static {
          Map classesSizes = new HashMap();
          classesSizes.put("java.lang.Class", Integers.valueOf(OBJECT_SHELL_SIZE));// not really a pb, but since this is
          // shared, so there's no point in going
          // further
          // 1.3+
          classesSizes.put("java.lang.Throwable", Integers.valueOf(OBJECT_SHELL_SIZE + 4 * OBJREF_SIZE));
          // 1.4+
          classesSizes.put("sun.reflect.UnsafeStaticFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticBooleanFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticByteFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticShortFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticIntegerFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticLongFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticCharacterFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticFloatFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticDoubleFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          classesSizes.put("sun.reflect.UnsafeStaticObjectFieldAccessorImpl", Integers.valueOf(OBJECT_SHELL_SIZE));// unknown
          // 1.5+
          classesSizes.put("java.lang.Enum", Integers.valueOf(OBJECT_SHELL_SIZE));// not really a pb, but since this is
          // shared, so there's no point in going
          // further
          classesSizes.put("sun.reflect.ConstantPool", Integers.valueOf(OBJECT_SHELL_SIZE + OBJECT_SHELL_SIZE));
          sunProblematicClassesSizes = Collections.unmodifiableMap(classesSizes);
  
          List classes = new ArrayList(sunProblematicClassesSizes.size());
          Iterator iter = sunProblematicClassesSizes.entrySet().iterator();
          while (iter.hasNext()) {
              Map.Entry entry = (Map.Entry) iter.next();
              String className = (String) entry.getKey();
              try {
                  classes.add(Class.forName(className));
              } catch (ClassNotFoundException cnfe) {
                  // } catch (ExceptionInInitializerError eiie) {
                  // } catch (NoClassDefFoundError ncdfe) {
                  // } catch (UnsatisfiedLinkError le) {
              } catch (LinkageError le) {
                  // BEA JRockit 1.4 also throws NoClassDefFoundError and UnsatisfiedLinkError
              }
          }
          sunProblematicClasses = (Class[]) classes.toArray(new Class[0]);
      }
  
      /**
       * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5012949 Implementation note: we can compare classes with ==
       * since they will always be loaded from the same ClassLoader (they are "low" in the hierarchy)
       */
      private static int skipClassDueToSunJVMBug(Class clazz) {
          for (int i = 0; i < sunProblematicClasses.length; ++i) {
              Class sunPbClass = sunProblematicClasses[i];
              if (clazz == sunPbClass) {
                  return ((Integer) sunProblematicClassesSizes.get(clazz.getName())).intValue();
              }
          }
          return 0;
      }
  
      /*
       * Very very incomplete, but better than nothing...
       */
      // See http://download.oracle.com/javase/7/docs/api/constant-values.html for JDK's String constants
      private static boolean isSharedFlyweight(Object obj) {
          if (obj == null) {
              return true;
          }
          if (obj == Boolean.TRUE || obj == Boolean.FALSE) {
              return true;
          }
          if (/* obj == Locale.ROOT || *//* Java 6 */
          obj == Locale.ENGLISH || obj == Locale.FRENCH || obj == Locale.GERMAN || obj == Locale.ITALIAN
                  || obj == Locale.JAPANESE || obj == Locale.KOREAN || obj == Locale.CHINESE
                  || obj == Locale.SIMPLIFIED_CHINESE || obj == Locale.TRADITIONAL_CHINESE || obj == Locale.FRANCE
                  || obj == Locale.GERMANY || obj == Locale.ITALY || obj == Locale.JAPAN || obj == Locale.KOREA
                  || obj == Locale.CHINA || obj == Locale.PRC || obj == Locale.TAIWAN || obj == Locale.UK
                  || obj == Locale.US || obj == Locale.CANADA || obj == Locale.CANADA_FRENCH) {
              return true;
          }
          if (obj == Collections.EMPTY_SET || obj == Collections.EMPTY_LIST || obj == Collections.EMPTY_MAP) {
              return true;
          }
          if (obj == BigInteger.ZERO || obj == BigInteger.ONE) {
              return true;
          }
          if (obj == System.in || obj == System.out || obj == System.err) {
              return true;
          }
          if (obj == String.CASE_INSENSITIVE_ORDER) {
              return true;
          }
          if (obj == JarFile.MANIFEST_NAME) {
              return true;
          }
          return false;
      }
  }
  
  final class Integers {
  
      private Integers(){
      }
  
      private static final int     cache_low  = -128;
      private static final int     cache_high = 127;
      private static final Integer cache[]    = new Integer[(cache_high - cache_low) + 1];
  
      static {
          for (int i = 0; i < cache.length; ++i) {
              cache[i] = new Integer(i + cache_low);
          }
      }
  
      /**
       * Returns a <tt>Integer</tt> instance representing the specified <tt>int</tt> value. If a new <tt>Integer</tt>
       * instance is not required, this method should generally be used in preference to the constructor
       * {@link #Integer(int)}, as this method is likely to yield significantly better space and time performance by
       * caching frequently requested values.
       * 
       * @param i an <code>int</code> value.
       * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
       * @since 1.5
       */
      public static Integer valueOf(int i) {
          if (i >= cache_low && i <= cache_high) { // must cache
              return cache[i - cache_low];
          } else {
              return new Integer(i);
          }
      }
  }
  