1/**
2 * Copyright 2006-2017 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.objenesis.instantiator.basic;
17
18/*
19 * Copyright 2003,2004 The Apache Software Foundation
20 *
21 *  Licensed under the Apache License, Version 2.0 (the "License");
22 * you may not use this file except in compliance with the License.
23 * You may obtain a copy of the License at
24 *
25 *      http://www.apache.org/licenses/LICENSE-2.0
26 *
27 *  Unless required by applicable law or agreed to in writing, software
28 * distributed under the License is distributed on an "AS IS" BASIS,
29 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 * See the License for the specific language governing permissions and
31 * limitations under the License.
32 */
33
34import org.objenesis.ObjenesisException;
35
36import java.io.BufferedOutputStream;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.InputStream;
40import java.lang.reflect.Method;
41import java.security.AccessController;
42import java.security.PrivilegedAction;
43import java.security.ProtectionDomain;
44
45/**
46 * Helper class for ProxyObjectInstantiator. We can see the details of a class specification
47 * <a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html">here</a>
48 *
49 * @author Henri Tremblay
50 */
51public final class ClassDefinitionUtils {
52
53   public static final byte OPS_aload_0 = 42;
54   public static final byte OPS_invokespecial = -73; // has two bytes parameters
55   public static final byte OPS_return = -79;
56   public static final byte OPS_new = -69;
57   public static final byte OPS_dup = 89;
58   public static final byte OPS_areturn = -80;
59
60   public static final int CONSTANT_Utf8 = 1;
61   public static final int CONSTANT_Integer = 3;
62   public static final int CONSTANT_Float = 4;
63   public static final int CONSTANT_Long = 5;
64   public static final int CONSTANT_Double = 6;
65   public static final int CONSTANT_Class = 7;
66   public static final int CONSTANT_String = 8;
67   public static final int CONSTANT_Fieldref = 9;
68   public static final int CONSTANT_Methodref = 10;
69   public static final int CONSTANT_InterfaceMethodref = 11;
70   public static final int CONSTANT_NameAndType = 12;
71   public static final int CONSTANT_MethodHandle = 15;
72   public static final int CONSTANT_MethodType = 16;
73   public static final int CONSTANT_InvokeDynamic = 18;
74
75   public static final int ACC_PUBLIC = 0x0001; // Declared public; may be accessed from outside its package.
76   public static final int ACC_FINAL = 0x0010; // Declared final; no subclasses allowed.
77   public static final int ACC_SUPER = 0x0020; // Treat superclass methods specially when invoked by the invokespecial instruction.
78   public static final int ACC_INTERFACE = 0x0200; // Is an interface, not a class.
79   public static final int ACC_ABSTRACT = 0x0400; // Declared abstract; must not be instantiated.
80   public static final int ACC_SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code.
81   public static final int ACC_ANNOTATION = 0x2000; // Declared as an annotation type.
82   public static final int ACC_ENUM = 0x4000; // Declared as an enum type.
83
84   public static final byte[] MAGIC = { (byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe };
85   public static final byte[] VERSION = { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x31 }; // minor_version, major_version (Java 5)
86
87   private ClassDefinitionUtils() { }
88
89   private static Method DEFINE_CLASS;
90   private static final ProtectionDomain PROTECTION_DOMAIN;
91
92   static {
93      PROTECTION_DOMAIN = AccessController.doPrivileged(new PrivilegedAction<ProtectionDomain>() {
94         public ProtectionDomain run() {
95            return ClassDefinitionUtils.class.getProtectionDomain();
96         }
97      });
98
99      AccessController.doPrivileged(new PrivilegedAction<Object>() {
100         public Object run() {
101            try {
102               Class<?> loader = Class.forName("java.lang.ClassLoader"); // JVM crash w/o this
103               DEFINE_CLASS = loader.getDeclaredMethod("defineClass",
104                  new Class[]{ String.class,
105                     byte[].class,
106                     Integer.TYPE,
107                     Integer.TYPE,
108                     ProtectionDomain.class });
109               DEFINE_CLASS.setAccessible(true);
110            } catch (ClassNotFoundException e) {
111               throw new ObjenesisException(e);
112            } catch (NoSuchMethodException e) {
113               throw new ObjenesisException(e);
114            }
115            return null;
116         }
117      });
118   }
119
120   /**
121    * Define a class in the provided class loader from the array of bytes. Inspired by cglib
122    * <code>ReflectUtils.defineClass</code>
123    *
124    * @param <T> type of the class returned
125    * @param className class name in the format <code>org.objenesis.MyClass</code>
126    * @param b bytes representing the class
127    * @param loader the class loader where the class will be loaded
128    * @return the newly loaded class
129    * @throws Exception whenever something goes wrong
130    */
131   @SuppressWarnings("unchecked")
132   public static <T> Class<T> defineClass(String className, byte[] b, ClassLoader loader)
133      throws Exception {
134      Object[] args = new Object[]{className, b, new Integer(0), new Integer(b.length), PROTECTION_DOMAIN };
135      Class<T> c = (Class<T>) DEFINE_CLASS.invoke(loader, args);
136      // Force static initializers to run.
137      Class.forName(className, true, loader);
138      return c;
139   }
140
141   /**
142    * Read the bytes of a class from the classpath
143    *
144    * @param className full class name including the package
145    * @return the bytes representing the class
146    * @throws IllegalArgumentException if the class is longer than 2500 bytes
147    * @throws IOException if we fail to read the class
148    */
149   public static byte[] readClass(String className) throws IOException {
150      // convert to a resource
151      className = classNameToResource(className);
152
153      byte[] b = new byte[2500]; // I'm assuming that I'm reading class that are not too big
154
155      int length;
156
157      InputStream in = ClassDefinitionUtils.class.getClassLoader().getResourceAsStream(className);
158      try {
159         length = in.read(b);
160      }
161      finally {
162         in.close();
163      }
164
165      if(length >= 2500) {
166         throw new IllegalArgumentException("The class is longer that 2500 bytes which is currently unsupported");
167      }
168
169      byte[] copy = new byte[length];
170      System.arraycopy(b, 0, copy, 0, length);
171      return copy;
172   }
173
174   /**
175    * Write all class bytes to a file.
176    *
177    * @param fileName file where the bytes will be written
178    * @param bytes bytes representing the class
179    * @throws IOException if we fail to write the class
180    */
181   public static void writeClass(String fileName, byte[] bytes) throws IOException {
182      BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(fileName));
183      try {
184         out.write(bytes);
185      }
186      finally {
187         out.close();
188      }
189   }
190
191   /**
192    * Will convert a class name to its name in the class definition format (e.g {@code org.objenesis.EmptyClass}
193    * becomes {@code org/objenesis/EmptyClass})
194    *
195    * @param className full class name including the package
196    * @return the internal name
197    */
198   public static String classNameToInternalClassName(String className) {
199      return className.replace('.', '/');
200   }
201
202   /**
203    * Will convert a class name to its class loader resource name (e.g {@code org.objenesis.EmptyClass}
204    * becomes {@code org/objenesis/EmptyClass.class})
205    *
206    * @param className full class name including the package
207    * @return the resource name
208    */
209   public static String classNameToResource(String className) {
210      return classNameToInternalClassName(className) + ".class";
211   }
212
213   /**
214    * Check if this class already exists in the class loader and return it if it does
215    *
216    * @param <T> type of the class returned
217    * @param classLoader Class loader where to search the class
218    * @param className Class name with full path
219    * @return the class if it already exists or null
220    */
221   @SuppressWarnings("unchecked")
222   public static <T> Class<T> getExistingClass(ClassLoader classLoader, String className) {
223      try {
224         return (Class<T>) Class.forName(className, true, classLoader);
225      }
226      catch (ClassNotFoundException e) {
227         return null;
228      }
229   }
230}
231