All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fitbur.mockito.objenesis.instantiator.basic.ClassDefinitionUtils Maven / Gradle / Ivy

There is a newer version: 1.0.0
Show newest version
/**
 * Copyright 2006-2016 the original author or authors.
 *
 * 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.fitbur.mockito.objenesis.instantiator.basic;

/*
 * Copyright 2003,2004 The Apache Software Foundation
 *
 *  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.
 */

import com.fitbur.mockito.objenesis.ObjenesisException;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;

/**
 * Helper class for ProxyObjectInstantiator. We can see the details of a class specification
 * here
 *
 * @author Henri Tremblay
 */
public final class ClassDefinitionUtils {

   public static final byte OPS_aload_0 = 42;
   public static final byte OPS_invokespecial = -73; // has two bytes parameters
   public static final byte OPS_return = -79;
   public static final byte OPS_new = -69;
   public static final byte OPS_dup = 89;
   public static final byte OPS_areturn = -80;

   public static final int CONSTANT_Utf8 = 1;
   public static final int CONSTANT_Integer = 3;
   public static final int CONSTANT_Float = 4;
   public static final int CONSTANT_Long = 5;
   public static final int CONSTANT_Double = 6;
   public static final int CONSTANT_Class = 7;
   public static final int CONSTANT_String = 8;
   public static final int CONSTANT_Fieldref = 9;
   public static final int CONSTANT_Methodref = 10;
   public static final int CONSTANT_InterfaceMethodref = 11;
   public static final int CONSTANT_NameAndType = 12;
   public static final int CONSTANT_MethodHandle = 15;
   public static final int CONSTANT_MethodType = 16;
   public static final int CONSTANT_InvokeDynamic = 18;

   public static final int ACC_PUBLIC = 0x0001; // Declared public; may be accessed from outside its package.
   public static final int ACC_FINAL = 0x0010; // Declared final; no subclasses allowed.
   public static final int ACC_SUPER = 0x0020; // Treat superclass methods specially when invoked by the invokespecial instruction.
   public static final int ACC_INTERFACE = 0x0200; // Is an interface, not a class.
   public static final int ACC_ABSTRACT = 0x0400; // Declared abstract; must not be instantiated.
   public static final int ACC_SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code.
   public static final int ACC_ANNOTATION = 0x2000; // Declared as an annotation type.
   public static final int ACC_ENUM = 0x4000; // Declared as an enum type.

   public static final byte[] MAGIC = { (byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe };
   public static final byte[] VERSION = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x31 }; // minor_version, major_version (Java 5)

   private ClassDefinitionUtils() { }

   private static Method DEFINE_CLASS;
   private static final ProtectionDomain PROTECTION_DOMAIN;

   static {
      PROTECTION_DOMAIN = AccessController.doPrivileged(new PrivilegedAction() {
         public ProtectionDomain run() {
            return ClassDefinitionUtils.class.getProtectionDomain();
         }
      });

      AccessController.doPrivileged(new PrivilegedAction() {
         public Object run() {
            try {
               Class loader = Class.forName("java.lang.ClassLoader"); // JVM crash w/o this
               DEFINE_CLASS = loader.getDeclaredMethod("defineClass",
                  new Class[]{ String.class,
                     byte[].class,
                     Integer.TYPE,
                     Integer.TYPE,
                     ProtectionDomain.class });
               DEFINE_CLASS.setAccessible(true);
            } catch (ClassNotFoundException e) {
               throw new ObjenesisException(e);
            } catch (NoSuchMethodException e) {
               throw new ObjenesisException(e);
            }
            return null;
         }
      });
   }

   /**
    * Define a class in the provided class loader from the array of bytes. Inspired by cglib
    * ReflectUtils.defineClass
    *
    * @param  type of the class returned
    * @param className class name in the format com.fitbur.mockito.objenesis.MyClass
    * @param b bytes representing the class
    * @param loader the class loader where the class will be loaded
    * @return the newly loaded class
    * @throws Exception whenever something goes wrong
    */
   @SuppressWarnings("unchecked")
   public static  Class defineClass(String className, byte[] b, ClassLoader loader)
      throws Exception {
      Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN };
      Class c = (Class) DEFINE_CLASS.invoke(loader, args);
      // Force static initializers to run.
      Class.forName(className, true, loader);
      return c;
   }

   /**
    * Read the bytes of a class from the classpath
    *
    * @param className full class name including the package
    * @return the bytes representing the class
    * @throws IllegalArgumentException if the class is longer than 2500 bytes
    * @throws IOException if we fail to read the class
    */
   public static byte[] readClass(String className) throws IOException {
      // convert to a resource
      className = classNameToResource(className);

      byte[] b = new byte[2500]; // I'm assuming that I'm reading class that are not too big

      int length;

      InputStream in = ClassDefinitionUtils.class.getClassLoader().getResourceAsStream(className);
      try {
         length = in.read(b);
      }
      finally {
         in.close();
      }

      if(length >= 2500) {
         throw new IllegalArgumentException("The class is longer that 2500 bytes which is currently unsupported");
      }

      byte[] copy = new byte[length];
      System.arraycopy(b, 0, copy, 0, length);
      return copy;
   }

   /**
    * Write all class bytes to a file.
    *
    * @param fileName file where the bytes will be written
    * @param bytes bytes representing the class
    * @throws IOException if we fail to write the class
    */
   public static void writeClass(String fileName, byte[] bytes) throws IOException {
      BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileName));
      try {
         out.write(bytes);
      }
      finally {
         out.close();
      }
   }

   /**
    * Will convert a class name to its name in the class definition format (e.g {@code com.fitbur.mockito.objenesis.EmptyClass}
    * becomes {@code com.fitbur.mockito.objenesis/EmptyClass})
    *
    * @param className full class name including the package
    * @return the internal name
    */
   public static String classNameToInternalClassName(String className) {
      return className.replace('.', '/');
   }

   /**
    * Will convert a class name to its class loader resource name (e.g {@code com.fitbur.mockito.objenesis.EmptyClass}
    * becomes {@code com.fitbur.mockito.objenesis/EmptyClass.class})
    *
    * @param className full class name including the package
    * @return the resource name
    */
   public static String classNameToResource(String className) {
      return classNameToInternalClassName(className) + ".class";
   }

   /**
    * Check if this class already exists in the class loader and return it if it does
    *
    * @param  type of the class returned
    * @param classLoader Class loader where to search the class
    * @param className Class name with full path
    * @return the class if it already exists or null
    */
   @SuppressWarnings("unchecked")
   public static  Class getExistingClass(ClassLoader classLoader, String className) {
      try {
         return (Class) Class.forName(className, true, classLoader);
      }
      catch (ClassNotFoundException e) {
         return null;
      }
   }
}