/* 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: InstrProcessorST.java,v 1.1.1.1.2.3 2004/07/16 23:32:28 vlad_r Exp $ */ package com.vladium.emma.instr; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.Date; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import com.vladium.jcd.cls.ClassDef; import com.vladium.jcd.compiler.ClassWriter; import com.vladium.jcd.parser.ClassDefParser; import com.vladium.logging.Logger; import com.vladium.util.ByteArrayOStream; import com.vladium.util.Descriptors; import com.vladium.util.Files; import com.vladium.util.IPathEnumerator; import com.vladium.util.IProperties; //import com.vladium.util.Profiler; import com.vladium.util.Property; import com.vladium.util.asserts.$assert; import com.vladium.util.exception.Exceptions; //import com.vladium.utils.ObjectSizeProfiler; import com.vladium.emma.IAppConstants; import com.vladium.emma.IAppErrorCodes; import com.vladium.emma.EMMAProperties; import com.vladium.emma.EMMARuntimeException; import com.vladium.emma.data.CoverageOptions; import com.vladium.emma.data.CoverageOptionsFactory; import com.vladium.emma.data.DataFactory; import com.vladium.emma.data.IMetaData; // ---------------------------------------------------------------------------- /** * @author Vlad Roubtsov, (C) 2003 */ final class InstrProcessorST extends InstrProcessor implements IAppErrorCodes { // public: ................................................................ // TODO: performance of 'copy' mode could be improved by pushing dir filtering // all the way to the path enumerator [although dir listing is reasonably fast] // IPathEnumerator.IPathHandler: public final void handleArchiveStart (final File parentDir, final File archive, final Manifest manifest) { final Logger log = m_log; if (log.atTRACE2 ()) log.trace2 ("handleArchiveStart", "[" + parentDir + "] [" + archive + "]"); // TODO: pass manifest into this callback, if any // TODO: detect if manifest corresonds to a previously intrumented archive already ? if (DO_DEPENDS_CHECKING) { final File fullArchiveFile = Files.newFile (parentDir, archive); m_currentArchiveTS = fullArchiveFile.lastModified (); if ($assert.ENABLED) $assert.ASSERT (m_currentArchiveTS > 0, "invalid ts: " + m_currentArchiveTS); } if ((m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE)) { final Manifest outManifest = manifest != null ? new Manifest (manifest) // shallow copy : new Manifest (); // set some basic main attributes: final Attributes mainAttrs = outManifest.getMainAttributes (); if (manifest == null) mainAttrs.put (Attributes.Name.MANIFEST_VERSION, "1.0"); mainAttrs.put (new Attributes.Name ("Created-By"), IAppConstants.APP_NAME + " v" + IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG); // note: Manifest makes these 72-char-safe mainAttrs.put (Attributes.Name.IMPLEMENTATION_TITLE, "instrumented version of [" + archive.getAbsolutePath () + "]"); mainAttrs.put (Attributes.Name.SPECIFICATION_TITLE, "instrumented on " + new Date (m_timeStamp) + " [" + Property.getSystemFingerprint () + "]"); // TODO: remove entries related to signing if (m_outMode == OutMode.OUT_MODE_FULLCOPY) { // create an identically named artive in outdir/lib [the stream is // closed in the archive end event handler]: try { final OutputStream out = new FileOutputStream (getFullOutFile (parentDir, archive, IN_LIB)); m_archiveOut = outManifest != null ? new JarOutputStream (out, outManifest) : new JarOutputStream (out); } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } } else if (m_outMode == OutMode.OUT_MODE_OVERWRITE) { // create a temp file in the same dir [moved into the original one // in the archive end event handler]: m_origArchiveFile = Files.newFile (parentDir, archive); // length > 3: final String archiveName = Files.getFileName (archive) + IAppConstants.APP_NAME_LC; final String archiveExt = EMMAProperties.PROPERTY_TEMP_FILE_EXT; try { m_tempArchiveFile = Files.createTempFile (parentDir, archiveName, archiveExt); if (log.atTRACE2 ()) log.trace2 ("handleArchiveStart", "created temp archive [" + m_tempArchiveFile.getAbsolutePath () + "]"); final OutputStream out = new FileOutputStream (m_tempArchiveFile); m_archiveOut = outManifest != null ? new JarOutputStream (out, outManifest) : new JarOutputStream (out); } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } } } } public final void handleArchiveEntry (final JarInputStream in, final ZipEntry entry) { final Logger log = m_log; if (log.atTRACE2 ()) log.trace2 ("handleArchiveEntry", "[" + entry.getName () + "]"); final String name = entry.getName (); final String lcName = name.toLowerCase (); final boolean notcopymode = (m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE); boolean copyEntry = false; if (lcName.endsWith (".class")) { final String className = name.substring (0, name.length () - 6).replace ('/', '.'); // it is possible that a class with this name has already been processed; // however, we can't skip it here because there is no guarantee that // the runtime classpath will be identical to the instrumentation path // [the metadata will still contain only a single entry for a class with // this name: it is the responsibility of the user to ensure that both // files represent the same class; in the future I might use a more // robust internal strategy that uses something other than a class name // as a metadata key] if ((m_coverageFilter == null) || m_coverageFilter.included (className)) { InputStream clsin = null; try { File outFile = null; File fullOutFile = null; if (DO_DEPENDS_CHECKING) { // in 'copy' mode if (m_outMode == OutMode.OUT_MODE_COPY) { outFile = new File (className.replace ('.', File.separatorChar).concat (".class")); fullOutFile = getFullOutFile (null, outFile, IN_CLASSES); // if we already processed this class name within this instrumentor // run, skip duplicates in copy mode: if (m_mdata.hasDescriptor (Descriptors.javaNameToVMName (className))) return; // BUG_SF989071: using outFile here instead resulted in // a zero result regardless of whether the target existed or not final long outTimeStamp = fullOutFile.lastModified (); // 0 if 'fullOutFile' does not exist or if an I/O error occurs if (outTimeStamp > 0) { long inTimeStamp = entry.getTime (); // can return -1 if (inTimeStamp < 0) inTimeStamp = m_currentArchiveTS; // default to the archive file timestamp if ($assert.ENABLED) $assert.ASSERT (inTimeStamp > 0); if (inTimeStamp <= outTimeStamp) { if (log.atVERBOSE ()) log.verbose ("destination file [" + outFile + "] skipped: more recent than the source"); return; } } } } readZipEntry (in, entry); final ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); m_visitor.process (clsDef, m_outMode == OutMode.OUT_MODE_OVERWRITE, true, true, m_instrResult); if (m_instrResult.m_instrumented) { if ($assert.ENABLED) $assert.ASSERT (m_instrResult.m_descriptor != null, "no descriptor created for an instrumented class"); ++ m_classInstrs; // update metadata [if this class has not been seen before]: m_mdata.add (m_instrResult.m_descriptor, false); // class def modified: write it to an array and submit a write job m_baos.reset (); ClassWriter.writeClassTable (clsDef, m_baos); if (notcopymode) { // [destination is a zip entry] entry.setTime (m_timeStamp); addJob (new EntryWriteJob (m_archiveOut, m_baos.copyByteArray (), entry, false)); } else // copy mode { // [destination is a file] if (! DO_DEPENDS_CHECKING) // this block is just a complement to the one above (where fullOutFile is inited) { outFile = new File (className.replace ('.', File.separatorChar).concat (".class")); fullOutFile = getFullOutFile (null, outFile, IN_CLASSES); } addJob (new FileWriteJob (fullOutFile, m_baos.copyByteArray (), true)); } } else if (notcopymode) { // original class def already read into m_readbuf: // clone the array and submit an entry write job final byte [] data = new byte [m_readpos]; System.arraycopy (m_readbuf, 0, data, 0, data.length); ++ m_classCopies; entry.setTime (m_timeStamp); addJob (new EntryWriteJob (m_archiveOut, data, entry, true)); } } 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 (); } catch (Exception e) { // TODO: error code throw new EMMARuntimeException (e); } } } else { // copy excluded .class entries in full copy and overwrite modes: copyEntry = notcopymode; } } else { // copy non-.class entries in full copy and overwrite modes: copyEntry = notcopymode; // skipping these entries here is important: this is done as a complement // to Sun jar API workarounds as detailed in PathEnumerator.enumeratePathArchive(): if (copyEntry && name.equalsIgnoreCase ("META-INF/")) copyEntry = false; if (copyEntry && name.equalsIgnoreCase (JarFile.MANIFEST_NAME)) copyEntry = false; // TODO: skip signature-related entries (.SF and .RSA/.DSA/.PGP) } if (copyEntry) { try { readZipEntry (in, entry); final byte [] data = new byte [m_readpos]; System.arraycopy (m_readbuf, 0, data, 0, data.length); ++ m_classCopies; entry.setTime (m_timeStamp); addJob (new EntryWriteJob (m_archiveOut, data, entry, true)); } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } } } public final void handleArchiveEnd (final File parentDir, final File archive) { final Logger log = m_log; if (log.atTRACE2 ()) log.trace2 ("handleArchiveEnd", "[" + parentDir + "] [" + archive + "]"); m_currentArchiveTS = Long.MAX_VALUE; if ((m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE)) { try { drainJobQueue (); // drain the queue before closing the archive m_archiveOut.flush (); m_archiveOut.close (); m_archiveOut = null; } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } // in overwrite mode replace the original archive with the temp archive: if (m_outMode == OutMode.OUT_MODE_OVERWRITE) { if (! Files.renameFile (m_tempArchiveFile, m_origArchiveFile, true)) // overwrite the original archive { // TODO: disable temp file cleanup in this case so that the user // could do it manually later? // error code throw new EMMARuntimeException ("could not rename temporary file [" + m_tempArchiveFile + "] to [" + m_origArchiveFile + "]: make sure the original file is not locked and can be deleted"); } else { if (log.atTRACE2 ()) log.trace2 ("handleArchiveEnd", "renamed temp archive [" + m_tempArchiveFile.getAbsolutePath () + "] to [" + m_origArchiveFile + "]"); m_origArchiveFile = m_tempArchiveFile = null; } } } } public final void handleDirStart (final File pathDir, final File dir) { final Logger log = m_log; if (log.atTRACE2 ()) log.trace2 ("handleDirStart", "[" + pathDir + "] [" + dir + "]"); // in full copy mode, create all dirs here; in copy mode, do it as part // of writing each individual file: if (m_outMode == OutMode.OUT_MODE_FULLCOPY) { final File saveDir = new File (getFullOutDir (pathDir, IN_CLASSES), dir.getPath ()); createDir (saveDir, true); } } public final void handleFile (final File pathDir, final File file) { final Logger log = m_log; if (log.atTRACE2 ()) log.trace2 ("handleFile", "[" + pathDir + "] [" + file + "]"); final String name = file.getPath (); final String lcName = name.toLowerCase (); final boolean fullcopymode = (m_outMode == OutMode.OUT_MODE_FULLCOPY); final boolean mkdir = (m_outMode == OutMode.OUT_MODE_COPY); boolean copyFile = false; if (lcName.endsWith (".class")) { final String className = name.substring (0, name.length () - 6).replace (File.separatorChar, '.'); // it is possible that a class with this name has already been processed; // however, we can't skip it here because there is no guarantee that // the runtime classpath will be identical to the instrumentation path // [the metadata will still contain only a single entry for a class with // this name: it is the responsibility of the user to ensure that both // files represent the same class; in the future I might use a more // robust internal strategy that uses something other than a class name // as a metadata key] if ((m_coverageFilter == null) || m_coverageFilter.included (className)) { InputStream clsin = null; try { final File inFile = Files.newFile (pathDir, file.getPath ()); final File fullOutFile = getFullOutFile (pathDir, file, IN_CLASSES); if (DO_DEPENDS_CHECKING) { if (m_outMode == OutMode.OUT_MODE_COPY) { // if we already processed this class name within this instrumentor // run, skip duplicates in copy mode: if (m_mdata.hasDescriptor (Descriptors.javaNameToVMName (className))) return; // otherwise, instrument only if the dest file is out of date // wrt to the source file: final long outTimeStamp = fullOutFile.lastModified (); // 0 if 'fullOutFile' does not exist or if an I/O error occurs if (outTimeStamp > 0) { final long inTimeStamp = inFile.lastModified (); if (inTimeStamp <= outTimeStamp) { if (log.atVERBOSE ()) log.verbose ("destination file [" + fullOutFile + "] skipped: more recent that the source file"); return; } } } } readFile (inFile); ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); // in non-overwrite modes, bail if src file already instrumented: m_visitor.process (clsDef, m_outMode == OutMode.OUT_MODE_OVERWRITE, true, true, m_instrResult); if (m_instrResult.m_instrumented) { if ($assert.ENABLED) $assert.ASSERT (m_instrResult.m_descriptor != null, "no descriptor created for an instrumented class"); ++ m_classInstrs; // update metadata [if this class has not been seen before]: // ObjectSizeProfiler.SizeProfile profile = ObjectSizeProfiler.profile (m_instrResult.m_descriptor, true); // System.out.println (clsDef.getName () + " metadata:"); // System.out.println (profile.root ().dump (0.2)); m_mdata.add (m_instrResult.m_descriptor, false); // class def modified: write it to an array and submit a write job m_baos.reset (); ClassWriter.writeClassTable (clsDef, m_baos); clsDef = null; final byte [] outdata = m_baos.copyByteArray (); addJob (new FileWriteJob (fullOutFile, outdata, mkdir)); } else if (fullcopymode) { // original class def already read into m_readbuf: // clone the array and submit a file write job clsDef = null; final byte [] outdata = new byte [m_readpos]; System.arraycopy (m_readbuf, 0, outdata, 0, m_readpos); ++ m_classCopies; addJob (new FileWriteJob (fullOutFile, outdata, mkdir)); } } 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 (); } catch (Exception e) { // TODO: error code throw new EMMARuntimeException (e); } } } else { // copy excluded .class files in full copy mode: copyFile = fullcopymode; } } else { // copy non-.class files in full copy mode: copyFile = fullcopymode; } if (copyFile) { try { final File inFile = Files.newFile (pathDir, file.getPath ()); readFile (inFile); final byte [] data = new byte [m_readpos]; System.arraycopy (m_readbuf, 0, data, 0, data.length); ++ m_classCopies; final File outFile = getFullOutFile (pathDir, file, IN_CLASSES); addJob (new FileWriteJob (outFile, data, mkdir)); } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } } } public final void handleDirEnd (final File pathDir, final File dir) { final Logger log = m_log; if (log.atTRACE2 ()) log.trace2 ("handleDirEnd", "[" + pathDir + "] [" + dir + "]"); // in overwrite mode, flush the job queue before going to the next directory: if (m_outMode == OutMode.OUT_MODE_OVERWRITE) { try { drainJobQueue (); } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } } } // protected: ............................................................. protected void reset () { m_visitor = null; m_mdata = null; m_readbuf = null; m_baos = null; for (int j = 0; j < m_jobs.length; ++ j) m_jobs [j] = null; if (CLEANUP_TEMP_ARCHIVE_ON_ERRORS) { if (m_archiveOut != null) try { m_archiveOut.close (); } catch (Exception ignore) {} // unlock the file descriptor for deletion if (m_tempArchiveFile != null) m_tempArchiveFile.delete (); } m_archiveOut = null; m_origArchiveFile = null; m_tempArchiveFile = null; super.reset (); } protected void _run (final IProperties toolProperties) { final Logger log = m_log; final boolean verbose = log.atVERBOSE (); if (verbose) { log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID); // [assertion: m_instrPath != null] log.verbose ("instrumentation path:"); log.verbose ("{"); for (int p = 0; p < m_instrPath.length; ++ p) { final File f = m_instrPath [p]; final String nonexistent = f.exists () ? "" : "{nonexistent} "; log.verbose (" " + nonexistent + f.getAbsolutePath ()); } log.verbose ("}"); // [assertion: m_outMode != null] log.verbose ("instrumentation output mode: " + m_outMode); } else { log.info ("processing instrumentation path ..."); } RuntimeException failure = null; try { long start = System.currentTimeMillis (); m_timeStamp = start; // construct instr path enumerator [throws on illegal input only]: final IPathEnumerator enumerator = IPathEnumerator.Factory.create (m_instrPath, m_canonical, this); // create out dir(s): { if (m_outMode != OutMode.OUT_MODE_OVERWRITE) createDir (m_outDir, true); if ((m_outMode == OutMode.OUT_MODE_FULLCOPY)) { final File classesDir = Files.newFile (m_outDir, CLASSES); createDir (classesDir, false); // note: not using mkdirs() here final File libDir = Files.newFile (m_outDir, LIB); createDir (libDir, false); // note: not using mkdirs() here } } // get the data out settings [note: this is not conditioned on m_dumpRawData]: File mdataOutFile = m_mdataOutFile; Boolean mdataOutMerge = m_mdataOutMerge; { if (mdataOutFile == null) mdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_META_DATA_OUT_FILE, EMMAProperties.DEFAULT_META_DATA_OUT_FILE)); if (mdataOutMerge == null) { final String _dataOutMerge = toolProperties.getProperty (EMMAProperties.PROPERTY_META_DATA_OUT_MERGE, EMMAProperties.DEFAULT_META_DATA_OUT_MERGE.toString ()); mdataOutMerge = Property.toBoolean (_dataOutMerge) ? Boolean.TRUE : Boolean.FALSE; } } if (verbose) { log.verbose ("metadata output file: " + mdataOutFile.getAbsolutePath ()); log.verbose ("metadata output merge mode: " + mdataOutMerge); } // TODO: can also register an exit hook to clean up temp files, but this is low value // 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 // reset job queue position: m_jobPos = 0; m_currentArchiveTS = Long.MAX_VALUE; final CoverageOptions options = CoverageOptionsFactory.create (toolProperties); m_visitor = new InstrVisitor (options); // TODO: reuse this? m_mdata = DataFactory.newMetaData (options); // actual work is driven by the path enumerator: try { enumerator.enumerate (); drainJobQueue (); } catch (IOException ioe) { throw new EMMARuntimeException (INSTR_IO_FAILURE, ioe); } if (log.atINFO ()) { final long end = System.currentTimeMillis (); log.info ("instrumentation path processed in " + (end - start) + " ms"); log.info ("[" + m_classInstrs + " class(es) instrumented, " + m_classCopies + " resource(s) copied]"); } // persist metadata: try { // TODO: create an empty file earlier to catch any errors sooner? [to avoid scenarios where a user waits throught the entire instr run to find out the file could not be written to] if ($assert.ENABLED) $assert.ASSERT (mdataOutFile != null, "m_metadataOutFile is null"); if (verbose) { if (m_mdata != null) { log.verbose ("metadata contains " + m_mdata.size () + " entries"); } } if (m_mdata.isEmpty ()) { log.info ("no output created: metadata is empty"); } else { start = System.currentTimeMillis (); DataFactory.persist (m_mdata, mdataOutFile, mdataOutMerge.booleanValue ()); final long end = System.currentTimeMillis (); if (log.atINFO ()) { log.info ("metadata " + (mdataOutMerge.booleanValue () ? "merged into" : "written to") + " [" + mdataOutFile.getAbsolutePath () + "] {in " + (end - start) + " ms}"); } } } catch (IOException ioe) { throw new EMMARuntimeException (OUT_IO_FAILURE, new Object [] {mdataOutFile.getAbsolutePath ()}, ioe); } } catch (SecurityException se) { failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se); } catch (RuntimeException re) { failure = re; } finally { reset (); } if (failure != null) { if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES)) { throw new EMMARuntimeException (UNEXPECTED_FAILURE, new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK}, failure); } else throw failure; } } // package: ............................................................... InstrProcessorST () { m_jobs = new Job [JOB_QUEUE_SIZE]; m_instrResult = new InstrVisitor.InstrResult (); } static void writeFile (final byte [] data, final File outFile, final boolean mkdirs) throws IOException { RandomAccessFile raf = null; try { if (mkdirs) { final File parent = outFile.getParentFile (); if (parent != null) parent.mkdirs (); // no error checking here [errors will be throw below] } raf = new RandomAccessFile (outFile, "rw"); if (DO_RAF_EXTENSION) raf.setLength (data.length); raf.write (data); } finally { if (raf != null) raf.close (); // note: intentionally letting the exception percolate up } } static void writeZipEntry (final byte [] data, final ZipOutputStream out, final ZipEntry entry, final boolean isCopy) throws IOException { if (isCopy) { out.putNextEntry (entry); // reusing ' entry' is ok here because we are not changing the data try { out.write (data); } finally { out.closeEntry (); } } else { // need to compute the checksum which slows things down quite a bit: final ZipEntry entryCopy = new ZipEntry (entry.getName ()); entryCopy.setTime (entry.getTime ()); // avoid repeated calls to System.currentTimeMillis() inside the zip stream entryCopy.setMethod (ZipOutputStream.STORED); // [directory status is implicit in the name] entryCopy.setSize (data.length); entryCopy.setCompressedSize (data.length); final CRC32 crc = new CRC32 (); crc.update (data); entryCopy.setCrc (crc.getValue ()); out.putNextEntry (entryCopy); try { out.write (data); } finally { out.closeEntry (); } } } // private: ............................................................... private static abstract class Job { protected abstract void run () throws IOException; } // end of nested class private static final class FileWriteJob extends Job { protected void run () throws IOException { writeFile (m_data, m_outFile, m_mkdirs); m_data = null; } FileWriteJob (final File outFile, final byte [] data, final boolean mkdirs) { m_outFile = outFile; m_data = data; m_mkdirs = mkdirs; } final File m_outFile; final boolean m_mkdirs; byte [] m_data; } // end of nested class private static final class EntryWriteJob extends Job { protected void run () throws IOException { writeZipEntry (m_data, m_out, m_entry, m_isCopy); m_data = null; } EntryWriteJob (final ZipOutputStream out, final byte [] data, final ZipEntry entry, final boolean isCopy) { m_out = out; m_data = data; m_entry = entry; m_isCopy = isCopy; } final ZipOutputStream m_out; byte [] m_data; final ZipEntry m_entry; final boolean m_isCopy; } // end of nested class private void addJob (final Job job) throws FileNotFoundException, IOException { if (m_jobPos == JOB_QUEUE_SIZE) drainJobQueue (); m_jobs [m_jobPos ++] = job; } private void drainJobQueue () throws IOException { for (int j = 0; j < m_jobPos; ++ j) { final Job job = m_jobs [j]; if (job != null) // a guard just in case { m_jobs [j] = null; job.run (); } } m_jobPos = 0; } /* * 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)]; } } // internal run()-scoped state: private final Job [] m_jobs; private final InstrVisitor.InstrResult m_instrResult; private InstrVisitor m_visitor; private IMetaData m_mdata; private byte [] m_readbuf; private int m_readpos; private ByteArrayOStream m_baos; // TODO: code to guard this from becoming too large private int m_jobPos; private long m_currentArchiveTS; private File m_origArchiveFile, m_tempArchiveFile; private JarOutputStream m_archiveOut; private long m_timeStamp; private static final int BUF_SIZE = 32 * 1024; private static final int JOB_QUEUE_SIZE = 128; // a reasonable size chosen empirically after testing a few SCSI/IDE machines private static final boolean CLEANUP_TEMP_ARCHIVE_ON_ERRORS = true; private static final boolean DO_RAF_EXTENSION = true; private static final boolean DO_DEPENDS_CHECKING = true; private static final Class [] EXPECTED_FAILURES; // set in static { EXPECTED_FAILURES = new Class [] { EMMARuntimeException.class, IllegalArgumentException.class, IllegalStateException.class, }; } } // end of class // ----------------------------------------------------------------------------