1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. 2 * 3 * This program and the accompanying materials are made available under 4 * the terms of the Common Public License v1.0 which accompanies this distribution, 5 * and is available at http://www.eclipse.org/legal/cpl-v10.html 6 * 7 * $Id: ClassPathProcessorST.java,v 1.1.1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $ 8 */ 9 package com.vladium.emma.rt; 10 11 import java.io.File; 12 import java.io.FileInputStream; 13 import java.io.FileNotFoundException; 14 import java.io.IOException; 15 import java.io.InputStream; 16 import java.util.Map; 17 import java.util.jar.JarInputStream; 18 import java.util.jar.Manifest; 19 import java.util.zip.ZipEntry; 20 import java.util.zip.ZipInputStream; 21 22 import com.vladium.jcd.cls.ClassDef; 23 import com.vladium.jcd.parser.ClassDefParser; 24 import com.vladium.logging.Logger; 25 import com.vladium.util.ByteArrayOStream; 26 import com.vladium.util.Files; 27 import com.vladium.util.IPathEnumerator; 28 import com.vladium.util.asserts.$assert; 29 import com.vladium.emma.IAppErrorCodes; 30 import com.vladium.emma.EMMARuntimeException; 31 import com.vladium.emma.data.IMetaData; 32 import com.vladium.emma.filter.IInclExclFilter; 33 import com.vladium.emma.instr.InstrVisitor; 34 35 // ---------------------------------------------------------------------------- 36 /** 37 * @author Vlad Roubtsov, (C) 2003 38 */ 39 public 40 final class ClassPathProcessorST implements IPathEnumerator.IPathHandler, IAppErrorCodes 41 { 42 // public: ................................................................ 43 44 public void run () 45 { 46 long start = System.currentTimeMillis (); 47 48 // construct instr path enumerator [throws on illegal input only]: 49 final IPathEnumerator enumerator = IPathEnumerator.Factory.create (m_path, m_canonical, this); 50 51 // allocate I/O buffers: 52 m_readbuf = new byte [BUF_SIZE]; // don't reuse this across run() calls to reset it to the original size 53 m_readpos = 0; 54 m_baos = new ByteArrayOStream (BUF_SIZE); // don't reuse this across run() calls to reset it to the original size 55 56 if (m_log.atINFO ()) 57 { 58 m_log.info ("processing classpath ..."); 59 } 60 61 // actual work is driven by the path enumerator: 62 try 63 { 64 enumerator.enumerate (); 65 } 66 catch (IOException ioe) 67 { 68 throw new EMMARuntimeException (INSTR_IO_FAILURE, ioe); 69 } 70 71 if (m_log.atINFO ()) 72 { 73 final long end = System.currentTimeMillis (); 74 75 m_log.info ("[" + m_classCount + " class(es) processed in " + (end - start) + " ms]"); 76 } 77 } 78 79 // IPathEnumerator.IPathHandler: 80 81 public void handleArchiveStart (final File parentDir, final File archive, final Manifest manifest) 82 { 83 m_archiveFile = Files.newFile (parentDir, archive.getPath ()); 84 } 85 86 public void handleArchiveEntry (final JarInputStream in, final ZipEntry entry) 87 { 88 if (m_log.atTRACE2 ()) m_log.trace2 ("handleArchiveEntry", "[" + entry.getName () + "]"); 89 90 final String name = entry.getName (); 91 final String lcName = name.toLowerCase (); 92 93 if (lcName.endsWith (".class")) 94 { 95 final String className = name.substring (0, name.length () - 6).replace ('/', '.'); 96 97 if ((m_coverageFilter == null) || m_coverageFilter.included (className)) 98 { 99 String srcURL = null; 100 InputStream clsin = null; 101 try 102 { 103 readZipEntry (in, entry); 104 105 srcURL = "jar:".concat (m_archiveFile.toURL ().toExternalForm ()).concat ("!/").concat (name); 106 } 107 catch (FileNotFoundException fnfe) 108 { 109 // ignore: this should never happen 110 if ($assert.ENABLED) 111 { 112 fnfe.printStackTrace (System.out); 113 } 114 } 115 catch (IOException ioe) 116 { 117 // TODO: error code 118 throw new EMMARuntimeException (ioe); 119 } 120 finally 121 { 122 if (clsin != null) 123 try 124 { 125 clsin.close (); 126 clsin = null; 127 } 128 catch (Exception e) 129 { 130 // TODO: error code 131 throw new EMMARuntimeException (e); 132 } 133 } 134 135 // [original class def read into m_readbuf] 136 137 try 138 { 139 ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); 140 if (! clsDef.isInterface ()) ++ m_classCount; 141 142 m_visitor.process (clsDef, false, false, true, m_instrResult); // get metadata only 143 clsDef = null; 144 145 boolean cacheClassDef = true; 146 147 if (m_instrResult.m_descriptor != null) 148 { 149 // do not overwrite existing descriptors to support "first 150 // in the classpath wins" semantics: 151 152 if (! m_mdata.add (m_instrResult.m_descriptor, false)) 153 cacheClassDef = false; 154 } 155 156 if (cacheClassDef && (m_cache != null)) 157 { 158 final byte [] bytes = new byte [m_readpos]; 159 System.arraycopy (m_readbuf, 0, bytes, 0, m_readpos); 160 161 m_cache.put (className, new ClassPathCacheEntry (bytes, srcURL)); 162 } 163 } 164 catch (IOException ioe) 165 { 166 // TODO: error code 167 throw new EMMARuntimeException (ioe); 168 } 169 } 170 } 171 } 172 173 public void handleArchiveEnd (final File parentDir, final File archive) 174 { 175 m_archiveFile = null; 176 } 177 178 179 public void handleDirStart (final File pathDir, final File dir) 180 { 181 // do nothing 182 } 183 184 public void handleFile (final File pathDir, final File file) 185 { 186 if (m_log.atTRACE2 ()) m_log.trace2 ("handleFile", "[" + pathDir + "] [" + file + "]"); 187 188 final String name = file.getPath (); 189 final String lcName = name.toLowerCase (); 190 191 if (lcName.endsWith (".class")) 192 { 193 final String className = name.substring (0, name.length () - 6).replace (File.separatorChar, '.'); 194 195 if ((m_coverageFilter == null) || m_coverageFilter.included (className)) 196 { 197 String srcURL = null; 198 InputStream clsin = null; 199 try 200 { 201 final File inFile = Files.newFile (pathDir, file.getPath ()); 202 readFile (inFile); 203 204 srcURL = inFile.toURL ().toExternalForm (); 205 } 206 catch (FileNotFoundException fnfe) 207 { 208 // ignore: this should never happen 209 if ($assert.ENABLED) 210 { 211 fnfe.printStackTrace (System.out); 212 } 213 } 214 catch (IOException ioe) 215 { 216 // TODO: error code 217 throw new EMMARuntimeException (ioe); 218 } 219 finally 220 { 221 if (clsin != null) 222 try 223 { 224 clsin.close (); 225 clsin = null; 226 } 227 catch (Exception e) 228 { 229 // TODO: error code 230 throw new EMMARuntimeException (e); 231 } 232 } 233 234 // [original class def read into m_readbuf] 235 236 try 237 { 238 ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); 239 if (! clsDef.isInterface ()) ++ m_classCount; 240 241 m_visitor.process (clsDef, false, false, true, m_instrResult); // get metadata only 242 clsDef = null; 243 244 245 boolean cacheClassDef = true; 246 247 if (m_instrResult.m_descriptor != null) 248 { 249 // do not overwrite existing descriptors to support "first 250 // in the classpath wins" semantics: 251 252 if (! m_mdata.add (m_instrResult.m_descriptor, false)) 253 cacheClassDef = false; 254 } 255 256 if (cacheClassDef && (m_cache != null)) 257 { 258 final byte [] bytes = new byte [m_readpos]; 259 System.arraycopy (m_readbuf, 0, bytes, 0, m_readpos); 260 261 m_cache.put (className, new ClassPathCacheEntry (bytes, srcURL)); 262 } 263 } 264 catch (IOException ioe) 265 { 266 // TODO: error code 267 throw new EMMARuntimeException (ioe); 268 } 269 } 270 } 271 } 272 273 public void handleDirEnd (final File pathDir, final File dir) 274 { 275 // do nothing 276 } 277 278 // protected: ............................................................. 279 280 // package: ............................................................... 281 282 283 /* 284 * null 'cache' indicates to only populate the metadata and not bother with 285 * caching instrumented class defs 286 */ 287 ClassPathProcessorST (final File [] path, final boolean canonical, 288 final IMetaData mdata, final IInclExclFilter filter, 289 final Map cache) 290 { 291 if (path == null) throw new IllegalArgumentException ("null input: path"); 292 if (mdata == null) throw new IllegalArgumentException ("null input: mdata"); 293 294 m_path = path; 295 m_canonical = canonical; 296 m_mdata = mdata; 297 m_coverageFilter = filter; 298 m_cache = cache; // can be null 299 m_visitor = new InstrVisitor (mdata.getOptions ()); 300 m_instrResult = new InstrVisitor.InstrResult (); 301 302 m_log = Logger.getLogger (); 303 } 304 305 // private: ............................................................... 306 307 308 /* 309 * Reads into m_readbuf (m_readpos is updated correspondingly) 310 */ 311 private void readFile (final File file) 312 throws IOException 313 { 314 final int length = (int) file.length (); 315 316 ensureReadCapacity (length); 317 318 InputStream in = null; 319 try 320 { 321 in = new FileInputStream (file); 322 323 int totalread = 0; 324 for (int read; 325 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0; 326 totalread += read); 327 m_readpos = totalread; 328 } 329 finally 330 { 331 if (in != null) try { in.close (); } catch (Exception ignore) {} 332 } 333 } 334 335 /* 336 * Reads into m_readbuf (m_readpos is updated correspondingly) 337 */ 338 private void readZipEntry (final ZipInputStream in, final ZipEntry entry) 339 throws IOException 340 { 341 final int length = (int) entry.getSize (); // can be -1 if unknown 342 343 if (length >= 0) 344 { 345 ensureReadCapacity (length); 346 347 int totalread = 0; 348 for (int read; 349 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0; 350 totalread += read); 351 m_readpos = totalread; 352 } 353 else 354 { 355 ensureReadCapacity (BUF_SIZE); 356 357 m_baos.reset (); 358 for (int read; (read = in.read (m_readbuf)) >= 0; m_baos.write (m_readbuf, 0, read)); 359 360 m_readbuf = m_baos.copyByteArray (); 361 m_readpos = m_readbuf.length; 362 } 363 } 364 365 private void ensureReadCapacity (final int capacity) 366 { 367 if (m_readbuf.length < capacity) 368 { 369 final int readbuflen = m_readbuf.length; 370 m_readbuf = null; 371 m_readbuf = new byte [Math.max (readbuflen << 1, capacity)]; 372 } 373 } 374 375 376 private final File [] m_path; // never null 377 private final boolean m_canonical; 378 private final IMetaData m_mdata; // never null 379 private final IInclExclFilter m_coverageFilter; // can be null 380 private final InstrVisitor m_visitor; 381 private final InstrVisitor.InstrResult m_instrResult; 382 private final Map /* classJavaName:String -> ClassPathCacheEntry */ m_cache; // can be null 383 384 private final Logger m_log; // this class is instantiated and used on a single thread 385 386 private int m_classCount; 387 388 private byte [] m_readbuf; 389 private int m_readpos; 390 private ByteArrayOStream m_baos; // TODO: code to guard this from becoming too large 391 392 private File m_archiveFile; 393 394 private static final int BUF_SIZE = 32 * 1024; 395 396 } // end of class 397 // ----------------------------------------------------------------------------