001 package serp.bytecode; 002 003 import java.io.*; 004 import java.util.*; 005 006 import serp.bytecode.lowlevel.*; 007 import serp.bytecode.visitor.*; 008 import serp.util.*; 009 010 /** 011 * The Project represents a working set of classes. It caches parsed 012 * bytecode and is responsible for bytecode class creation. Currently 013 * changes made in one class are <strong>not</strong> reflected in other 014 * classes, though this will be an option in the future. 015 * 016 * <p>Bytecode that has been parsed is held in a cache so that retrieving 017 * a class with the same name multiple times always returns the same 018 * {@link BCClass} instance.</p> 019 * 020 * <p>A future goal is to eventually have facilities for traversing jars 021 * or directory structures to find classes that meet a given criteria (such 022 * as implementing a given interface, etc) and to perform operations on entire 023 * projects, similar to aspect-oriented programming.</p> 024 * 025 * @author Abe White 026 */ 027 public class Project implements VisitAcceptor { 028 private final String _name; 029 private final HashMap _cache = new HashMap(); 030 private final NameCache _names = new NameCache(); 031 032 /** 033 * Default constructor. 034 */ 035 public Project() { 036 this(null); 037 } 038 039 /** 040 * Construct a named project. 041 */ 042 public Project(String name) { 043 _name = name; 044 } 045 046 /** 047 * Return the project name, or null if unset. 048 */ 049 public String getName() { 050 return _name; 051 } 052 053 /** 054 * Return the name cache, which includes utilities for converting names 055 * from internal to external form and vice versa. 056 */ 057 public NameCache getNameCache() { 058 return _names; 059 } 060 061 /** 062 * Load a class with the given name. 063 * 064 * @see #loadClass(String,ClassLoader) 065 */ 066 public BCClass loadClass(String name) { 067 return loadClass(name, null); 068 } 069 070 /** 071 * Load the bytecode for the class with the given name. 072 * If a {@link BCClass} with the given name already exists in this project, 073 * it will be returned. Otherwise, a new {@link BCClass} will be created 074 * with the given name and returned. If the name represents an existing 075 * type, the returned instance will contain the parsed bytecode for 076 * that type. If the name is of a primitive or array type, the returned 077 * instance will act accordingly. 078 * 079 * @param name the name of the class, including package 080 * @param loader the class loader to use to search for an existing 081 * class with the given name; if null defaults to the 082 * context loader of the current thread 083 * @throws RuntimeException on parse error 084 */ 085 public BCClass loadClass(String name, ClassLoader loader) { 086 // convert to proper Class.forName() form 087 name = _names.getExternalForm(name, false); 088 089 BCClass cached = checkCache(name); 090 if (cached != null) 091 return cached; 092 093 // check for existing type 094 if (loader == null) 095 loader = Thread.currentThread().getContextClassLoader(); 096 try { 097 return loadClass(Strings.toClass(name, loader)); 098 } catch (Exception e) { 099 } 100 101 String componentName = _names.getComponentName(name); 102 BCClass ret = new BCClass(this); 103 if (componentName != null) 104 ret.setState(new ArrayState(name, componentName)); 105 else { 106 ret.setState(new ObjectState(_names)); 107 ret.setName(name); 108 ret.setSuperclass(Object.class); 109 } 110 cache(name, ret); 111 return ret; 112 } 113 114 /** 115 * Load the bytecode for the given class. 116 * If a {@link BCClass} with the name of the given class already exists in 117 * this project, it will be returned. Otherwise, the bytecode of the given 118 * class will be parsed and returned as a new {@link BCClass}. If the 119 * given class is an array or primitive type, the returned instance will 120 * act accordingly. 121 * 122 * @param type the class to parse 123 * @throws RuntimeException on parse error 124 */ 125 public BCClass loadClass(Class type) { 126 BCClass cached = checkCache(type.getName()); 127 if (cached != null) 128 return cached; 129 130 BCClass ret = new BCClass(this); 131 if (type.isPrimitive()) 132 ret.setState(new PrimitiveState(type, _names)); 133 else if (type.isArray()) 134 ret.setState(new ArrayState(type.getName(), _names.getExternalForm 135 (type.getComponentType().getName(), false))); 136 else { 137 ret.setState(new ObjectState(_names)); 138 try { 139 ret.read(type); 140 } catch (IOException ioe) { 141 throw new RuntimeException(ioe.toString()); 142 } 143 } 144 cache(type.getName(), ret); 145 return ret; 146 } 147 148 /** 149 * Load the bytecode from the given class file. 150 * If this project already contains the class in the given file, it will 151 * be returned. Otherwise a new {@link BCClass} will be created from the 152 * given bytecode. 153 * 154 * @throws RuntimeException on parse error 155 */ 156 public BCClass loadClass(File classFile) { 157 return loadClass(classFile, null); 158 } 159 160 /** 161 * Load the bytecode from the given class file. 162 * If this project already contains the class in the given file, it will 163 * be returned. Otherwise a new {@link BCClass} will be created from the 164 * given bytecode. 165 * 166 * @throws RuntimeException on parse error 167 */ 168 public BCClass loadClass(File classFile, ClassLoader loader) { 169 // parse the bytecode from the file 170 BCClass ret = new BCClass(this); 171 ret.setState(new ObjectState(_names)); 172 try { 173 ret.read(classFile, loader); 174 } catch (IOException ioe) { 175 throw new RuntimeException(ioe.toString()); 176 } 177 178 String name = ret.getName(); 179 BCClass cached = checkCache(name); 180 if (cached != null) 181 return cached; 182 183 cache(name, ret); 184 return ret; 185 } 186 187 /** 188 * Load the bytecode from the given stream. 189 * If this project already contains the class in the given stream, 190 * it will be returned. Otherwise a new {@link BCClass} will be created 191 * from the given bytecode. 192 * 193 * @throws RuntimeException on parse error 194 */ 195 public BCClass loadClass(InputStream in) { 196 return loadClass(in, null); 197 } 198 199 /** 200 * Load the bytecode from the given stream. 201 * If this project already contains the class in the given stream, 202 * it will be returned. Otherwise a new {@link BCClass} will be created 203 * from the given bytecode. 204 * 205 * @throws RuntimeException on parse error 206 */ 207 public BCClass loadClass(InputStream in, ClassLoader loader) { 208 BCClass ret = new BCClass(this); 209 ret.setState(new ObjectState(_names)); 210 try { 211 ret.read(in, loader); 212 } catch (IOException ioe) { 213 throw new RuntimeException(ioe.toString()); 214 } 215 216 String name = ret.getName(); 217 BCClass cached = checkCache(name); 218 if (cached != null) 219 return cached; 220 221 cache(name, ret); 222 return ret; 223 } 224 225 /** 226 * Import the given bytecode from another project. If a {@link BCClass} 227 * with the same name already exists in this project, it will be returned. 228 * Otherwise, a new {@link BCClass} will be created from the 229 * information in the given class. 230 */ 231 public BCClass loadClass(BCClass bc) { 232 String name = bc.getName(); 233 BCClass cached = checkCache(name); 234 if (cached != null) 235 return cached; 236 237 BCClass ret = new BCClass(this); 238 if (bc.isPrimitive()) 239 ret.setState(new PrimitiveState(bc.getType(), _names)); 240 else if (bc.isArray()) 241 ret.setState(new ArrayState(bc.getName(), bc.getComponentName())); 242 else { 243 ret.setState(new ObjectState(_names)); 244 ret.read(bc); 245 } 246 247 cache(name, ret); 248 return ret; 249 } 250 251 /** 252 * Clears all classes from this project. 253 */ 254 public void clear() { 255 Collection values = _cache.values(); 256 BCClass bc; 257 for (Iterator itr = values.iterator(); itr.hasNext();) { 258 bc = (BCClass) itr.next(); 259 itr.remove(); 260 bc.invalidate(); 261 } 262 _names.clear(); 263 } 264 265 /** 266 * Remove a class from this project. After removal, the result of any 267 * further operations on the class is undefined. 268 * 269 * @return true if the class belonged to this project, false otherwise 270 */ 271 public boolean removeClass(String type) { 272 return removeClass(checkCache(type)); 273 } 274 275 /** 276 * Remove a class from this project. After removal, the result of any 277 * further operations on the class is undefined. 278 * 279 * @return true if the class belonged to this project, false otherwise 280 */ 281 public boolean removeClass(Class type) { 282 if (type == null) 283 return false; 284 return removeClass(checkCache(type.getName())); 285 } 286 287 /** 288 * Remove a class from this project. After removal, the result of any 289 * further operations on the class is undefined. 290 * 291 * @return true if the class belonged to this project, false otherwise 292 */ 293 public boolean removeClass(BCClass type) { 294 if (type == null) 295 return false; 296 if (!removeFromCache(type.getName(), type)) 297 return false; 298 type.invalidate(); 299 return true; 300 } 301 302 /** 303 * Return all loaded classes in the project. 304 */ 305 public BCClass[] getClasses() { 306 Collection values = _cache.values(); 307 return (BCClass[]) values.toArray(new BCClass[values.size()]); 308 } 309 310 /** 311 * Return true if the project already contains the given class. 312 */ 313 public boolean containsClass(String type) { 314 return _cache.containsKey(type); 315 } 316 317 /** 318 * Return true if the project already contains the given class. 319 */ 320 public boolean containsClass(Class type) { 321 return (type == null) ? false : containsClass(type.getName()); 322 } 323 324 /** 325 * Return true if the project already contains the given class. 326 */ 327 public boolean containsClass(BCClass type) { 328 return (type == null) ? false : containsClass(type.getName()); 329 } 330 331 public void acceptVisit(BCVisitor visit) { 332 visit.enterProject(this); 333 BCClass[] classes = getClasses(); 334 for (int i = 0; i < classes.length; i++) 335 classes[i].acceptVisit(visit); 336 visit.exitProject(this); 337 } 338 339 /** 340 * Renames the given class within this project. Used internally by 341 * {@link BCClass} instances when their name is modified. 342 * 343 * @throws IllegalStateException if a class with the new name already exists 344 */ 345 void renameClass(String oldName, String newName, BCClass bc) { 346 if (oldName.equals(newName)) 347 return; 348 349 BCClass cached = (BCClass) checkCache(newName); 350 if (cached != null) 351 throw new IllegalStateException("A class with name " + newName + 352 " already exists in this project"); 353 354 removeFromCache(oldName, bc); 355 cache(newName, bc); 356 } 357 358 /** 359 * Check the cache for a loaded type. 360 */ 361 private BCClass checkCache(String name) { 362 return (BCClass) _cache.get(name); 363 } 364 365 /** 366 * Cache a class. 367 */ 368 private void cache(String name, BCClass bc) { 369 _cache.put(name, bc); 370 } 371 372 /** 373 * Remove a cached class. 374 */ 375 private boolean removeFromCache(String name, BCClass bc) { 376 BCClass rem = (BCClass) checkCache(name); 377 if (rem != bc) 378 return false; 379 _cache.remove(name); 380 return true; 381 } 382 }