1 /* 2 * Copyright (c) 2002-2011 LWJGL Project 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'LWJGL' nor the names of 17 * its contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package org.lwjgl.util.mapped; 33 34 import org.lwjgl.LWJGLUtil; 35 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.lang.reflect.InvocationTargetException; 39 import java.lang.reflect.Method; 40 import java.net.URLClassLoader; 41 42 /** 43 * This classloader is responsible for applying the bytecode transformation to mapped objects. 44 * The transformation can either be applied using a Java agent, or with the convenient {@link #fork} method. 45 * 46 * @author Riven 47 */ 48 public class MappedObjectClassLoader extends URLClassLoader { 49 50 static final String MAPPEDOBJECT_PACKAGE_PREFIX = MappedObjectClassLoader.class.getPackage().getName() + "."; 51 52 static boolean FORKED; 53 54 /** 55 * Forks the specified class containing a main method, passing the specified arguments. See 56 * {@link org.lwjgl.test.mapped.TestMappedObject} for example usage. 57 * 58 * @param mainClass the class containing the main method 59 * @param args the arguments to pass 60 * 61 * @return true if the fork was successful. 62 */ fork(Class<?> mainClass, String[] args)63 public static boolean fork(Class<?> mainClass, String[] args) { 64 if ( FORKED ) { 65 return false; 66 } 67 68 FORKED = true; 69 70 try { 71 MappedObjectClassLoader loader = new MappedObjectClassLoader(mainClass); 72 loader.loadMappedObject(); 73 74 Class<?> replacedMainClass = loader.loadClass(mainClass.getName()); 75 Method mainMethod = replacedMainClass.getMethod("main", String[].class); 76 mainMethod.invoke(null, new Object[] { args }); 77 } catch (InvocationTargetException exc) { 78 Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), exc.getCause()); 79 } catch (Throwable cause) { 80 throw new Error("failed to fork", cause); 81 } 82 83 return true; 84 } 85 MappedObjectClassLoader(Class<?> mainClass)86 private MappedObjectClassLoader(Class<?> mainClass) { 87 super(((URLClassLoader)mainClass.getClassLoader()).getURLs()); 88 } 89 loadMappedObject()90 protected synchronized Class<?> loadMappedObject() throws ClassNotFoundException { 91 final String name = MappedObject.class.getName(); 92 String className = name.replace('.', '/'); 93 94 byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class"))); 95 96 long t0 = System.nanoTime(); 97 bytecode = MappedObjectTransformer.transformMappedObject(bytecode); 98 long t1 = System.nanoTime(); 99 total_time_transforming += (t1 - t0); 100 101 if ( MappedObjectTransformer.PRINT_ACTIVITY ) 102 printActivity(className, t0, t1); 103 104 Class<?> clazz = super.defineClass(name, bytecode, 0, bytecode.length); 105 resolveClass(clazz); 106 return clazz; 107 } 108 109 private static long total_time_transforming; 110 111 @Override loadClass(String name, boolean resolve)112 protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 113 if ( name.startsWith("java.") 114 || name.startsWith("javax.") 115 || name.startsWith("sun.") 116 || name.startsWith("sunw.") 117 || name.startsWith("org.objectweb.asm.") 118 ) 119 return super.loadClass(name, resolve); 120 121 final String className = name.replace('.', '/'); 122 final boolean inThisPackage = name.startsWith(MAPPEDOBJECT_PACKAGE_PREFIX); 123 124 if ( inThisPackage && ( 125 name.equals(MappedObjectClassLoader.class.getName()) 126 || name.equals((MappedObjectTransformer.class.getName())) 127 || name.equals((CacheUtil.class.getName())) 128 ) ) 129 return super.loadClass(name, resolve); 130 131 byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class"))); 132 133 // Classes in this package do not get transformed, but need to go through here because we have transformed MappedObject. 134 if ( !(inThisPackage && name.substring(MAPPEDOBJECT_PACKAGE_PREFIX.length()).indexOf('.') == -1) ) { 135 long t0 = System.nanoTime(); 136 final byte[] newBytecode = MappedObjectTransformer.transformMappedAPI(className, bytecode); 137 long t1 = System.nanoTime(); 138 139 total_time_transforming += (t1 - t0); 140 141 if ( bytecode != newBytecode ) { 142 bytecode = newBytecode; 143 if ( MappedObjectTransformer.PRINT_ACTIVITY ) 144 printActivity(className, t0, t1); 145 } 146 } 147 148 Class<?> clazz = super.defineClass(name, bytecode, 0, bytecode.length); 149 if ( resolve ) 150 resolveClass(clazz); 151 return clazz; 152 } 153 printActivity(final String className, final long t0, final long t1)154 private static void printActivity(final String className, final long t0, final long t1) { 155 final StringBuilder msg = new StringBuilder(MappedObjectClassLoader.class.getSimpleName() + ": " + className); 156 157 if ( MappedObjectTransformer.PRINT_TIMING ) 158 msg.append("\n\ttransforming took " + (t1 - t0) / 1000 + " micros (total: " + (total_time_transforming / 1000 / 1000) + "ms)"); 159 160 LWJGLUtil.log(msg); 161 } 162 readStream(InputStream in)163 private static byte[] readStream(InputStream in) { 164 byte[] bytecode = new byte[256]; 165 int len = 0; 166 try { 167 while ( true ) { 168 if ( bytecode.length == len ) 169 bytecode = copyOf(bytecode, len * 2); 170 int got = in.read(bytecode, len, bytecode.length - len); 171 if ( got == -1 ) 172 break; 173 len += got; 174 } 175 } catch (IOException exc) { 176 // stop! 177 } finally { 178 try { 179 in.close(); 180 } catch (IOException exc) { 181 // ignore... 182 } 183 } 184 return copyOf(bytecode, len); 185 } 186 copyOf(byte[] original, int newLength)187 private static byte[] copyOf(byte[] original, int newLength) { 188 byte[] copy = new byte[newLength]; 189 System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); 190 return copy; 191 } 192 193 }