| /* 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: InstrProcessor.java,v 1.1.1.1.2.3 2004/07/17 16:57:14 vlad_r Exp $ |
| */ |
| package com.vladium.emma.instr; |
| |
| import java.io.File; |
| |
| import com.vladium.util.Files; |
| import com.vladium.util.IConstants; |
| 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.Processor; |
| import com.vladium.emma.filter.IInclExclFilter; |
| |
| // ---------------------------------------------------------------------------- |
| /* |
| * This class was not meant to be public by design. It is made to to work around |
| * access bugs in reflective invocations. |
| */ |
| /** |
| * @author Vlad Roubtsov, (C) 2003 |
| */ |
| public |
| abstract class InstrProcessor extends Processor |
| implements IPathEnumerator.IPathHandler |
| { |
| // public: ................................................................ |
| |
| |
| public static final String PROPERTY_EXCLUDE_SYNTHETIC_METHODS = "instr.exclude_synthetic_methods"; |
| public static final String PROPERTY_EXCLUDE_BRIDGE_METHODS = "instr.exclude_bridge_methods"; |
| public static final String PROPERTY_DO_SUID_COMPENSATION = "instr.do_suid_compensation"; |
| |
| public static final String DEFAULT_EXCLUDE_SYNTHETIC_METHODS = "true"; |
| public static final String DEFAULT_EXCLUDE_BRIDGE_METHODS = "true"; |
| public static final String DEFAULT_DO_SUID_COMPENSATION = "true"; |
| |
| public static InstrProcessor create () |
| { |
| return new InstrProcessorST (); |
| } |
| |
| /** |
| * |
| * @param path [null is equivalent to an empty array] |
| * @param canonical |
| */ |
| public synchronized final void setInstrPath (final String [] path, final boolean canonical) |
| { |
| if ((path == null) || (path.length == 0)) |
| m_instrPath = IConstants.EMPTY_FILE_ARRAY; |
| else |
| m_instrPath = Files.pathToFiles (path, canonical); |
| |
| m_canonical = canonical; |
| } |
| |
| public synchronized final void setDependsMode (final boolean enable) |
| { |
| m_dependsMode = enable; |
| } |
| |
| /** |
| * |
| * @param specs [null is equivalent to no filtering (everything is included)] |
| */ |
| public synchronized final void setInclExclFilter (final String [] specs) |
| { |
| if (specs == null) |
| m_coverageFilter = null; |
| else |
| m_coverageFilter = IInclExclFilter.Factory.create (specs); |
| } |
| |
| /** |
| * |
| * @param fileName [null unsets the previous override setting] |
| */ |
| public synchronized final void setMetaOutFile (final String fileName) |
| { |
| if (fileName == null) |
| m_mdataOutFile = null; |
| else |
| { |
| final File _file = new File (fileName); |
| |
| if (_file.exists () && ! _file.isFile ()) |
| throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]"); |
| |
| m_mdataOutFile = _file; |
| } |
| } |
| |
| /** |
| * |
| * @param merge [null unsets the previous override setting] |
| */ |
| public synchronized final void setMetaOutMerge (final Boolean merge) |
| { |
| m_mdataOutMerge = merge; |
| } |
| |
| /** |
| * |
| * @param dir [null unsets the previous setting] |
| */ |
| public synchronized final void setInstrOutDir (final String dir) |
| { |
| if (dir == null) |
| m_outDir = null; |
| else |
| { |
| final File _outDir = new File (dir); |
| if (_outDir.exists () && ! _outDir.isDirectory ()) |
| throw new IllegalArgumentException ("not a directory: [" + _outDir.getAbsolutePath () + "]"); |
| |
| m_outDir = _outDir; |
| } |
| } |
| |
| /** |
| * |
| * @param mode [may not be null] |
| */ |
| public synchronized final void setOutMode (final OutMode mode) |
| { |
| if (mode == null) |
| throw new IllegalArgumentException ("null input: mode"); |
| |
| m_outMode = mode; |
| } |
| |
| // protected: ............................................................. |
| |
| |
| protected InstrProcessor () |
| { |
| m_dependsMode = true; |
| } |
| |
| |
| protected void validateState () |
| { |
| super.validateState (); |
| |
| if ((m_instrPath == null) || (m_instrPath.length == 0)) |
| throw new IllegalStateException ("instrumentation path not set"); |
| |
| // [m_coverageFilter can be null] |
| |
| if (m_outMode == null) |
| throw new IllegalStateException ("output mode not set"); |
| |
| if (m_outMode != OutMode.OUT_MODE_OVERWRITE) |
| { |
| if (m_outDir == null) |
| throw new IllegalStateException ("output directory not set"); |
| |
| // for non-overwrite modes output directory must not overlap |
| // with the instr path: |
| |
| // [the logic below does not quite catch all possibilities due to |
| // Class-Path: manifest attributes and dir nesting, but it should |
| // intercept most common mistakes] |
| |
| if ($assert.ENABLED) |
| { |
| $assert.ASSERT (m_outDir != null, "m_outDir = null"); |
| $assert.ASSERT (m_instrPath != null, "m_instrPath = null"); |
| } |
| |
| final File canonicalOutDir = Files.canonicalizeFile (m_outDir); |
| final File [] canonicalInstrPath; |
| |
| if (m_canonical) |
| canonicalInstrPath = m_instrPath; |
| else |
| { |
| canonicalInstrPath = new File [m_instrPath.length]; |
| |
| for (int ip = 0; ip < canonicalInstrPath.length; ++ ip) |
| { |
| canonicalInstrPath [ip] = Files.canonicalizeFile (m_instrPath [ip]); |
| } |
| } |
| |
| // FR_SF988785: detect if the user attempted to use a parent of m_outDir as one of |
| // the input directories (prevents spurious "already instrumented" errors) |
| |
| final int instrPathLength = canonicalInstrPath.length; |
| for (File dir = canonicalOutDir; dir != null; dir = dir.getParentFile ()) // getParentFile() does no real I/O |
| { |
| for (int ip = 0; ip < instrPathLength; ++ ip) |
| { |
| if (dir.equals (canonicalInstrPath [ip])) |
| throw new IllegalStateException ("output directory [" + canonicalOutDir + "] cannot be one of the instrumentation path directories (or a child thereof)"); |
| } |
| } |
| } |
| |
| // [m_mdataOutFile can be null] |
| // [m_mdataOutMerge can be null] |
| } |
| |
| protected void reset () |
| { |
| m_classCopies = m_classInstrs = 0; |
| } |
| |
| protected final void createDir (final File dir, final boolean mkall) |
| throws EMMARuntimeException |
| { |
| if (mkall) |
| { |
| if (! dir.mkdirs () && ! dir.exists ()) |
| throw new EMMARuntimeException (IAppErrorCodes.OUT_MKDIR_FAILURE, new Object [] {dir.getAbsolutePath ()}); |
| } |
| else |
| { |
| if (! dir.mkdir () && ! dir.exists ()) |
| throw new EMMARuntimeException (IAppErrorCodes.OUT_MKDIR_FAILURE, new Object [] {dir.getAbsolutePath ()}); |
| } |
| } |
| |
| protected final File getFullOutDir (final File pathDir, final boolean isClass) |
| { |
| if (m_outMode == OutMode.OUT_MODE_OVERWRITE) |
| { |
| return pathDir; |
| } |
| else if (m_outMode == OutMode.OUT_MODE_COPY) |
| { |
| return m_outDir; |
| } |
| else if (m_outMode == OutMode.OUT_MODE_FULLCOPY) |
| { |
| return isClass ? Files.newFile (m_outDir, CLASSES) : Files.newFile (m_outDir, LIB); |
| } |
| else throw new IllegalStateException ("invalid out mode state: " + m_outMode); |
| } |
| |
| protected final File getFullOutFile (final File pathDir, final File file, final boolean isClass) |
| { |
| return Files.newFile (getFullOutDir (pathDir, isClass), file.getPath ()); |
| } |
| |
| |
| // caller-settable state [scoped to this runner instance]: |
| |
| protected File [] m_instrPath; // required to be non-null/non-empty for run() |
| protected boolean m_dependsMode; |
| protected boolean m_canonical; |
| protected IInclExclFilter m_coverageFilter; // can be null for run() |
| protected OutMode m_outMode; // required to be set for run() |
| protected File m_outDir; // required to be non-null for run(), unless output mode is 'overwrite' |
| protected File m_mdataOutFile; // user override; can be null for run() |
| protected Boolean m_mdataOutMerge; // user override; can be null for run() |
| |
| // internal run()-scoped state: |
| |
| protected int m_classCopies, m_classInstrs; |
| |
| protected static final String CLASSES = "classes"; |
| protected static final String LIB = "lib"; |
| protected static final boolean IN_CLASSES = true; |
| protected static final boolean IN_LIB = ! IN_CLASSES; |
| |
| // package: ............................................................... |
| |
| // TODO: access level [public to workaround Sun's bugs in access level in reflective invocations] |
| public static final class OutMode |
| { |
| public static final OutMode OUT_MODE_COPY = new OutMode ("copy"); |
| public static final OutMode OUT_MODE_FULLCOPY = new OutMode ("fullcopy"); |
| public static final OutMode OUT_MODE_OVERWRITE = new OutMode ("overwrite"); |
| |
| public String getName () |
| { |
| return m_name; |
| } |
| |
| public String toString () |
| { |
| return m_name; |
| } |
| |
| public static OutMode nameToMode (final String name) |
| { |
| if (OUT_MODE_COPY.m_name.equals (name)) |
| return OUT_MODE_COPY; |
| else if (OUT_MODE_FULLCOPY.m_name.equals (name)) |
| return OUT_MODE_FULLCOPY; |
| else if (OUT_MODE_OVERWRITE.m_name.equals (name)) |
| return OUT_MODE_OVERWRITE; |
| |
| return null; |
| } |
| |
| private OutMode (final String name) |
| { |
| m_name = name; |
| } |
| |
| |
| private final String m_name; |
| |
| } // end of nested class |
| |
| // private: ............................................................... |
| |
| } // end of class |
| // ---------------------------------------------------------------------------- |