/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id: ClassPathProcessorST.java,v 1.1.1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $ */ package com.vladium.emma.rt; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Map; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import com.vladium.jcd.cls.ClassDef; import com.vladium.jcd.parser.ClassDefParser; import com.vladium.logging.Logger; import com.vladium.util.ByteArrayOStream; import com.vladium.util.Files; import com.vladium.util.IPathEnumerator; import com.vladium.util.asserts.$assert; import com.vladium.emma.IAppErrorCodes; import com.vladium.emma.EMMARuntimeException; import com.vladium.emma.data.IMetaData; import com.vladium.emma.filter.IInclExclFilter; import com.vladium.emma.instr.InstrVisitor; // ---------------------------------------------------------------------------- /** * @author Vlad Roubtsov, (C) 2003 */ public final class ClassPathProcessorST implements IPathEnumerator.IPathHandler, IAppErrorCodes { // public: ................................................................ public void run () { long start = System.currentTimeMillis (); // construct instr path enumerator [throws on illegal input only]: final IPathEnumerator enumerator = IPathEnumerator.Factory.create (m_path, m_canonical, this); // allocate I/O buffers: m_readbuf = new byte [BUF_SIZE]; // don't reuse this across run() calls to reset it to the original size m_readpos = 0; m_baos = new ByteArrayOStream (BUF_SIZE); // don't reuse this across run() calls to reset it to the original size if (m_log.atINFO ()) { m_log.info ("processing classpath ..."); } // actual work is driven by the path enumerator: try { enumerator.enumerate (); } catch (IOException ioe) { throw new EMMARuntimeException (INSTR_IO_FAILURE, ioe); } if (m_log.atINFO ()) { final long end = System.currentTimeMillis (); m_log.info ("[" + m_classCount + " class(es) processed in " + (end - start) + " ms]"); } } // IPathEnumerator.IPathHandler: public void handleArchiveStart (final File parentDir, final File archive, final Manifest manifest) { m_archiveFile = Files.newFile (parentDir, archive.getPath ()); } public void handleArchiveEntry (final JarInputStream in, final ZipEntry entry) { if (m_log.atTRACE2 ()) m_log.trace2 ("handleArchiveEntry", "[" + entry.getName () + "]"); final String name = entry.getName (); final String lcName = name.toLowerCase (); if (lcName.endsWith (".class")) { final String className = name.substring (0, name.length () - 6).replace ('/', '.'); if ((m_coverageFilter == null) || m_coverageFilter.included (className)) { String srcURL = null; InputStream clsin = null; try { readZipEntry (in, entry); srcURL = "jar:".concat (m_archiveFile.toURL ().toExternalForm ()).concat ("!/").concat (name); } catch (FileNotFoundException fnfe) { // ignore: this should never happen if ($assert.ENABLED) { fnfe.printStackTrace (System.out); } } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } finally { if (clsin != null) try { clsin.close (); clsin = null; } catch (Exception e) { // TODO: error code throw new EMMARuntimeException (e); } } // [original class def read into m_readbuf] try { ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); if (! clsDef.isInterface ()) ++ m_classCount; m_visitor.process (clsDef, false, false, true, m_instrResult); // get metadata only clsDef = null; boolean cacheClassDef = true; if (m_instrResult.m_descriptor != null) { // do not overwrite existing descriptors to support "first // in the classpath wins" semantics: if (! m_mdata.add (m_instrResult.m_descriptor, false)) cacheClassDef = false; } if (cacheClassDef && (m_cache != null)) { final byte [] bytes = new byte [m_readpos]; System.arraycopy (m_readbuf, 0, bytes, 0, m_readpos); m_cache.put (className, new ClassPathCacheEntry (bytes, srcURL)); } } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } } } } public void handleArchiveEnd (final File parentDir, final File archive) { m_archiveFile = null; } public void handleDirStart (final File pathDir, final File dir) { // do nothing } public void handleFile (final File pathDir, final File file) { if (m_log.atTRACE2 ()) m_log.trace2 ("handleFile", "[" + pathDir + "] [" + file + "]"); final String name = file.getPath (); final String lcName = name.toLowerCase (); if (lcName.endsWith (".class")) { final String className = name.substring (0, name.length () - 6).replace (File.separatorChar, '.'); if ((m_coverageFilter == null) || m_coverageFilter.included (className)) { String srcURL = null; InputStream clsin = null; try { final File inFile = Files.newFile (pathDir, file.getPath ()); readFile (inFile); srcURL = inFile.toURL ().toExternalForm (); } catch (FileNotFoundException fnfe) { // ignore: this should never happen if ($assert.ENABLED) { fnfe.printStackTrace (System.out); } } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } finally { if (clsin != null) try { clsin.close (); clsin = null; } catch (Exception e) { // TODO: error code throw new EMMARuntimeException (e); } } // [original class def read into m_readbuf] try { ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); if (! clsDef.isInterface ()) ++ m_classCount; m_visitor.process (clsDef, false, false, true, m_instrResult); // get metadata only clsDef = null; boolean cacheClassDef = true; if (m_instrResult.m_descriptor != null) { // do not overwrite existing descriptors to support "first // in the classpath wins" semantics: if (! m_mdata.add (m_instrResult.m_descriptor, false)) cacheClassDef = false; } if (cacheClassDef && (m_cache != null)) { final byte [] bytes = new byte [m_readpos]; System.arraycopy (m_readbuf, 0, bytes, 0, m_readpos); m_cache.put (className, new ClassPathCacheEntry (bytes, srcURL)); } } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } } } } public void handleDirEnd (final File pathDir, final File dir) { // do nothing } // protected: ............................................................. // package: ............................................................... /* * null 'cache' indicates to only populate the metadata and not bother with * caching instrumented class defs */ ClassPathProcessorST (final File [] path, final boolean canonical, final IMetaData mdata, final IInclExclFilter filter, final Map cache) { if (path == null) throw new IllegalArgumentException ("null input: path"); if (mdata == null) throw new IllegalArgumentException ("null input: mdata"); m_path = path; m_canonical = canonical; m_mdata = mdata; m_coverageFilter = filter; m_cache = cache; // can be null m_visitor = new InstrVisitor (mdata.getOptions ()); m_instrResult = new InstrVisitor.InstrResult (); m_log = Logger.getLogger (); } // private: ............................................................... /* * Reads into m_readbuf (m_readpos is updated correspondingly) */ private void readFile (final File file) throws IOException { final int length = (int) file.length (); ensureReadCapacity (length); InputStream in = null; try { in = new FileInputStream (file); int totalread = 0; for (int read; (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0; totalread += read); m_readpos = totalread; } finally { if (in != null) try { in.close (); } catch (Exception ignore) {} } } /* * Reads into m_readbuf (m_readpos is updated correspondingly) */ private void readZipEntry (final ZipInputStream in, final ZipEntry entry) throws IOException { final int length = (int) entry.getSize (); // can be -1 if unknown if (length >= 0) { ensureReadCapacity (length); int totalread = 0; for (int read; (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0; totalread += read); m_readpos = totalread; } else { ensureReadCapacity (BUF_SIZE); m_baos.reset (); for (int read; (read = in.read (m_readbuf)) >= 0; m_baos.write (m_readbuf, 0, read)); m_readbuf = m_baos.copyByteArray (); m_readpos = m_readbuf.length; } } private void ensureReadCapacity (final int capacity) { if (m_readbuf.length < capacity) { final int readbuflen = m_readbuf.length; m_readbuf = null; m_readbuf = new byte [Math.max (readbuflen << 1, capacity)]; } } private final File [] m_path; // never null private final boolean m_canonical; private final IMetaData m_mdata; // never null private final IInclExclFilter m_coverageFilter; // can be null private final InstrVisitor m_visitor; private final InstrVisitor.InstrResult m_instrResult; private final Map /* classJavaName:String -> ClassPathCacheEntry */ m_cache; // can be null private final Logger m_log; // this class is instantiated and used on a single thread private int m_classCount; private byte [] m_readbuf; private int m_readpos; private ByteArrayOStream m_baos; // TODO: code to guard this from becoming too large private File m_archiveFile; private static final int BUF_SIZE = 32 * 1024; } // end of class // ----------------------------------------------------------------------------