1 16 17 package org.cojen.util; 18 19 import java.io.ByteArrayOutputStream ; 20 import java.io.File ; 21 import java.io.FileOutputStream ; 22 import java.io.IOException ; 23 import java.io.OutputStream ; 24 import java.lang.ref.SoftReference ; 25 import java.util.HashSet ; 26 import java.util.Map ; 27 import java.util.Random ; 28 import java.util.Set ; 29 30 import org.cojen.classfile.ClassFile; 31 32 50 public class ClassInjector { 51 private static final boolean DEBUG; 52 53 static { 54 DEBUG = Boolean.getBoolean("org.cojen.util.ClassInjector.DEBUG"); 55 } 56 57 private static final Random cRandom = new Random (); 58 59 private static Map cLoaders = new WeakIdentityMap(); 61 62 66 public static ClassInjector create() { 67 return create(null, null); 68 } 69 70 84 public static ClassInjector create(String prefix, ClassLoader parent) { 85 return create(prefix, parent, false); 86 } 87 88 102 public static ClassInjector createExplicit(String name, ClassLoader parent) { 103 if (name == null) { 104 throw new IllegalArgumentException ("Explicit class name not provided"); 105 } 106 return create(name, parent, true); 107 } 108 109 private static ClassInjector create(String prefix, ClassLoader parent, boolean explicit) { 110 if (prefix == null) { 111 prefix = ClassInjector.class.getName(); 112 } 113 if (parent == null) { 114 parent = ClassInjector.class.getClassLoader(); 115 if (parent == null) { 116 parent = ClassLoader.getSystemClassLoader(); 117 } 118 } 119 120 String name = explicit ? prefix : null; 121 Loader loader; 122 123 synchronized (cRandom) { 124 getLoader: { 125 if (parent instanceof Loader) { 126 loader = (Loader) parent; 129 break getLoader; 130 } 131 SoftReference ref = (SoftReference ) cLoaders.get(parent); 132 if (ref != null) { 133 loader = (Loader) ref.get(); 134 if (loader != null && loader.isValid()) { 135 break getLoader; 136 } 137 ref.clear(); 138 } 139 loader = parent == null ? new Loader() : new Loader(parent); 140 cLoaders.put(parent, new SoftReference (loader)); 141 } 142 143 if (explicit) { 144 reserveCheck: { 145 for (int i=0; i<2; i++) { 146 if (loader.reserveName(name)) { 147 try { 148 loader.loadClass(name); 149 } catch (ClassNotFoundException e) { 150 break reserveCheck; 151 } 152 } 153 if (i > 0) { 154 throw new IllegalStateException 155 ("Class name already reserved: " + name); 156 } 157 loader = parent == null ? new Loader() : new Loader(parent); 159 } 160 161 cLoaders.put(parent, new SoftReference (loader)); 163 } 164 } else { 165 for (int tryCount = 0; tryCount < 1000; tryCount++) { 166 name = null; 167 168 long ID = cRandom.nextInt(); 169 170 switch (tryCount) { 173 case 0: 174 ID &= 0xffL; 175 break; 176 case 1: 177 ID &= 0xffffL; 178 break; 179 default: 180 ID &= 0xffffffffL; 181 break; 182 } 183 184 name = prefix + '$' + ID; 185 186 if (!loader.reserveName(name)) { 187 continue; 188 } 189 190 try { 191 loader.loadClass(name); 192 } catch (ClassNotFoundException e) { 193 break; 194 } catch (LinkageError e) { 195 } 196 } 197 } 198 } 199 200 if (name == null) { 201 throw new InternalError ("Unable to create unique class name"); 202 } 203 204 return new ClassInjector(name, loader); 205 } 206 207 private final String mName; 208 private final Loader mLoader; 209 210 private ByteArrayOutputStream mData; 211 private Class mClass; 212 213 private ClassInjector(String name, Loader loader) { 214 mName = name; 215 mLoader = loader; 216 } 217 218 221 public String getClassName() { 222 return mName; 223 } 224 225 231 public OutputStream openStream() throws IllegalStateException { 232 if (mClass != null) { 233 throw new IllegalStateException ("New class has already been defined"); 234 } 235 ByteArrayOutputStream data = mData; 236 if (data != null) { 237 throw new IllegalStateException ("Stream already opened"); 238 } 239 mData = data = new ByteArrayOutputStream (); 240 return data; 241 } 242 243 250 public Class defineClass(ClassFile cf) { 251 try { 252 cf.writeTo(openStream()); 253 } catch (IOException e) { 254 throw new InternalError (e.toString()); 255 } 256 return getNewClass(); 257 } 258 259 264 public Class getNewClass() throws IllegalStateException , ClassFormatError { 265 if (mClass != null) { 266 return mClass; 267 } 268 ByteArrayOutputStream data = mData; 269 if (data == null) { 270 throw new IllegalStateException ("Class not defined yet"); 271 } 272 273 byte[] bytes = data.toByteArray(); 274 275 if (DEBUG) { 276 File file = new File (mName.replace('.', '/') + ".class"); 277 try { 278 File tempDir = new File (System.getProperty("java.io.tmpdir")); 279 file = new File (tempDir, file.getPath()); 280 } catch (SecurityException e) { 281 } 282 try { 283 file.getParentFile().mkdirs(); 284 System.out.println("ClassInjector writing to " + file); 285 OutputStream out = new FileOutputStream (file); 286 out.write(bytes); 287 out.close(); 288 } catch (Exception e) { 289 e.printStackTrace(); 290 } 291 } 292 293 mClass = mLoader.define(mName, bytes); 294 mData = null; 295 return mClass; 296 } 297 298 private static final class Loader extends ClassLoader { 299 private Set mReservedNames = new HashSet (); 300 301 Loader(ClassLoader parent) { 302 super(parent); 303 } 304 305 Loader() { 306 super(); 307 } 308 309 synchronized boolean reserveName(String name) { 312 return mReservedNames.add(name); 313 } 314 315 synchronized boolean isValid() { 316 return mReservedNames.size() < 100; 319 } 320 321 Class define(String name, byte[] b) { 322 Class clazz = defineClass(name, b, 0, b.length); 323 resolveClass(clazz); 324 return clazz; 325 } 326 } 327 } 328 | Popular Tags |