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 */
9package com.vladium.emma.rt;
10
11import java.io.File;
12import java.io.FileInputStream;
13import java.io.FileNotFoundException;
14import java.io.IOException;
15import java.io.InputStream;
16import java.util.Map;
17import java.util.jar.JarInputStream;
18import java.util.jar.Manifest;
19import java.util.zip.ZipEntry;
20import java.util.zip.ZipInputStream;
21
22import com.vladium.jcd.cls.ClassDef;
23import com.vladium.jcd.parser.ClassDefParser;
24import com.vladium.logging.Logger;
25import com.vladium.util.ByteArrayOStream;
26import com.vladium.util.Files;
27import com.vladium.util.IPathEnumerator;
28import com.vladium.util.asserts.$assert;
29import com.vladium.emma.IAppErrorCodes;
30import com.vladium.emma.EMMARuntimeException;
31import com.vladium.emma.data.IMetaData;
32import com.vladium.emma.filter.IInclExclFilter;
33import com.vladium.emma.instr.InstrVisitor;
34
35// ----------------------------------------------------------------------------
36/**
37 * @author Vlad Roubtsov, (C) 2003
38 */
39public
40final 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// ----------------------------------------------------------------------------