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: runCommand.java,v 1.1.1.1.2.1 2004/07/16 23:32:03 vlad_r Exp $
8 */
9package com.vladium.emma;
10
11import java.io.File;
12import java.io.IOException;
13import java.util.jar.Attributes;
14import java.util.jar.JarFile;
15import java.util.jar.Manifest;
16
17import com.vladium.util.ClassLoaderResolver;
18import com.vladium.util.Strings;
19import com.vladium.util.args.IOptsParser;
20import com.vladium.util.asserts.$assert;
21import com.vladium.emma.rt.AppRunner;
22
23// ----------------------------------------------------------------------------
24/**
25 * @author Vlad Roubtsov, (C) 2003
26 */
27public
28final class runCommand extends Command
29{
30    // public: ................................................................
31
32
33    public synchronized void run ()
34    {
35        ClassLoader loader;
36        try
37        {
38            loader = ClassLoaderResolver.getClassLoader ();
39        }
40        catch (Throwable t)
41        {
42            loader = getClass ().getClassLoader ();
43        }
44
45        try
46        {
47            // process 'args':
48            {
49                final IOptsParser parser = getOptParser (loader);
50                final IOptsParser.IOpts parsedopts = parser.parse (m_args);
51
52                // check if usage is requested before checking args parse errors etc:
53                {
54                    final int usageRequestLevel = parsedopts.usageRequestLevel ();
55
56                    if (usageRequestLevel > 0)
57                    {
58                        usageexit (parser, usageRequestLevel, null);
59                        return;
60                    }
61                }
62
63                final IOptsParser.IOpt [] opts = parsedopts.getOpts ();
64
65                if (opts == null) // this means there were args parsing errors
66                {
67                    parsedopts.error (m_out, STDOUT_WIDTH);
68                    usageexit (parser, IOptsParser.SHORT_USAGE, null);
69                    return;
70                }
71
72                // process parsed args:
73                try
74                {
75                    for (int o = 0; o < opts.length; ++ o)
76                    {
77                        final IOptsParser.IOpt opt = opts [o];
78                        final String on = opt.getCanonicalName ();
79
80                        if (! processOpt (opt))
81                        {
82                            if ("cp".equals (on))
83                            {
84                                m_classpath = getListOptValue (opt, PATH_DELIMITERS, true);
85                            }
86                            else if ("jar".equals (on))
87                            {
88                                m_jarMode = true;
89                            }
90                            else if ("f".equals (on))
91                            {
92                                m_scanCoveragePath = getOptionalBooleanOptValue (opt);
93                            }
94                            else if ("sp".equals (on))
95                            {
96                                m_srcpath = getListOptValue (opt, PATH_DELIMITERS, true);
97                            }
98                            else if ("raw".equals (on))
99                            {
100                                m_dumpRawData = getOptionalBooleanOptValue (opt);
101                            }
102                            else if ("out".equals (on))
103                            {
104                                m_outFileName = opt.getFirstValue ();
105                            }
106                            else if ("merge".equals (on))
107                            {
108                                m_outDataMerge = getOptionalBooleanOptValue (opt) ? Boolean.TRUE : Boolean.FALSE;
109                            }
110                            else if ("r".equals (on))
111                            {
112                                m_reportTypes = Strings.merge (opt.getValues (), COMMA_DELIMITERS, true);
113                            }
114                            else if ("ix".equals (on))
115                            {
116                                m_ixpath = getListOptValue (opt, COMMA_DELIMITERS, true);
117                            }
118                        }
119                    }
120
121                    // user '-props' file property overrides:
122
123                    if (! processFilePropertyOverrides ()) return;
124
125                    // process prefixed opts:
126
127                    processCmdPropertyOverrides (parsedopts);
128                }
129                catch (IOException ioe)
130                {
131                    throw new EMMARuntimeException (IAppErrorCodes.ARGS_IO_FAILURE, ioe);
132                }
133
134
135                // process free args:
136                {
137                    final String [] freeArgs = parsedopts.getFreeArgs ();
138
139                    if (m_jarMode)
140                    {
141                        if ((freeArgs == null) || (freeArgs.length == 0))
142                        {
143                            usageexit (parser, IOptsParser.SHORT_USAGE, "missing jar file name");
144                            return;
145                        }
146
147                        if ($assert.ENABLED) $assert.ASSERT (freeArgs != null && freeArgs.length > 0, "invalid freeArgs");
148
149                        final File jarfile = new File (freeArgs [0]);
150                        final String jarMainClass;
151                        try
152                        {
153                            jarMainClass = openJarFile (jarfile); // the rest of free args are *not* ignored
154                        }
155                        catch (IOException ioe)
156                        {
157                            // TODO: is the right error code?
158                            throw new EMMARuntimeException (IAppErrorCodes.ARGS_IO_FAILURE, ioe);
159                        }
160
161                        if (jarMainClass == null)
162                        {
163                            exit (true, "failed to load Main-Class manifest attribute from [" + jarfile.getAbsolutePath () + "]", null, RC_UNEXPECTED);
164                            return;
165                        }
166
167                        if ($assert.ENABLED) $assert.ASSERT (jarMainClass != null, "invalid jarMainClass");
168
169                        m_appArgs = new String [freeArgs.length];
170                        System.arraycopy (freeArgs, 1, m_appArgs, 1, freeArgs.length - 1);
171                        m_appArgs [0] = jarMainClass;
172
173                        m_classpath = new String [] { jarfile.getPath () };
174                    }
175                    else
176                    {
177                        if ((freeArgs == null) || (freeArgs.length == 0))
178                        {
179                            usageexit (parser, IOptsParser.SHORT_USAGE, "missing application class name");
180                            return;
181                        }
182
183                        m_appArgs = freeArgs;
184                    }
185                }
186                // [m_appArgs: { mainclass, arg1, arg2, ... }]
187
188
189                // handle cmd line-level defaults and option interaction
190                {
191                    if (DEFAULT_TO_SYSTEM_CLASSPATH)
192                    {
193                        if (m_classpath == null)
194                        {
195                            // TODO" this is not guaranteed to work (in WebStart etc), so double check if I should remove this
196
197                            final String systemClasspath = System.getProperty ("java.class.path", "");
198                            if (systemClasspath.length () == 0)
199                            {
200                                // TODO: assume "." just like Sun JVMs in this case?
201                                usageexit (parser, IOptsParser.SHORT_USAGE, "could not infer coverage classpath from 'java.class.path'; use an explicit -cp option");
202                                return;
203                            }
204
205                            m_classpath = new String [] {systemClasspath};
206                        }
207                    }
208                    else
209                    {
210                        if (m_classpath == null)
211                        {
212                            usageexit (parser, IOptsParser.SHORT_USAGE, "either '-cp' or '-jar' option is required");
213                            return;
214                        }
215                    }
216
217                    // TODO: who owns setting this 'txt' default? most likely AppRunner
218                    if (m_reportTypes == null)
219                    {
220                        m_reportTypes = new String [] {"txt"};
221                    }
222                }
223            }
224
225            // run the app:
226            {
227                if ($assert.ENABLED) $assert.ASSERT (m_appArgs != null && m_appArgs.length > 0, "invalid m_appArgs");
228
229                final String [] appargs = new String [m_appArgs.length - 1];
230                System.arraycopy (m_appArgs, 1, appargs, 0, appargs.length);
231
232                final AppRunner processor = AppRunner.create (loader);
233                processor.setAppName (IAppConstants.APP_NAME); // for log prefixing
234
235                processor.setAppClass (m_appArgs [0], appargs);
236                processor.setCoveragePath (m_classpath, true); // TODO: an option to set 'canonical'?
237                processor.setScanCoveragePath (m_scanCoveragePath);
238                processor.setSourcePath (m_srcpath);
239                processor.setInclExclFilter (m_ixpath);
240                processor.setDumpSessionData (m_dumpRawData);
241                processor.setSessionOutFile (m_outFileName);
242                processor.setSessionOutMerge (m_outDataMerge);
243                if ($assert.ENABLED) $assert.ASSERT (m_reportTypes != null, "m_reportTypes no set");
244                processor.setReportTypes (m_reportTypes);
245                processor.setPropertyOverrides (m_propertyOverrides);
246
247                processor.run ();
248            }
249        }
250        catch (EMMARuntimeException yre)
251        {
252            // TODO: see below
253
254            exit (true, yre.getMessage (), yre, RC_UNEXPECTED); // does not return
255            return;
256        }
257        catch (Throwable t)
258        {
259            // TODO: embed: OS/JVM fingerprint, build #, etc
260            // TODO: save stack trace in a file and prompt user to send it to ...
261
262            exit (true, "unexpected failure: ", t, RC_UNEXPECTED); // does not return
263            return;
264        }
265
266        exit (false, null, null, RC_OK);
267    }
268
269    // protected: .............................................................
270
271
272    protected runCommand (final String usageToolName, final String [] args)
273    {
274        super (usageToolName, args);
275    }
276
277    protected void initialize ()
278    {
279        // TODO: clean up instance state
280
281        super.initialize ();
282    }
283
284    protected String usageArgsMsg ()
285    {
286        return "[options] class [args...] | -jar [options] jarfile [args...]";
287    }
288
289    // package: ...............................................................
290
291    // private: ...............................................................
292
293
294    private static String openJarFile (final File file)
295        throws IOException
296    {
297        JarFile jarfile = null;
298        try
299        {
300            jarfile = new JarFile (file, false);
301
302            final Manifest manifest = jarfile.getManifest ();
303            if (manifest == null) return null;
304
305            final Attributes attributes = manifest.getMainAttributes ();
306            if (attributes == null) return null;
307
308            final String jarMainClass = attributes.getValue (Attributes.Name.MAIN_CLASS);
309
310            return jarMainClass;
311        }
312        finally
313        {
314            if (jarfile != null) try { jarfile.close (); } catch (IOException ignore) {}
315        }
316    }
317
318
319    private String [] m_classpath, m_srcpath;
320    private boolean m_jarMode;
321    private boolean m_scanCoveragePath; // defaults to false
322    private String [] m_ixpath;
323    private String [] m_appArgs;
324    private boolean m_dumpRawData; // defaults to false
325    private String m_outFileName;
326    private Boolean m_outDataMerge;
327    private String [] m_reportTypes;
328
329    private static final boolean DEFAULT_TO_SYSTEM_CLASSPATH = false;
330
331} // end of class
332// ----------------------------------------------------------------------------