summaryrefslogtreecommitdiffstats
path: root/testing/src/org/aspectj/internal/tools/ant/taskdefs/MainWrapper.java
blob: 55d8dbed10ed71d1906af81fd1298ec112e699bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/* *******************************************************************
 * Copyright (c) 1999-2001 Xerox Corporation, 
 *               2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/epl-v10.html 
 *  
 * Contributors: 
 *     Xerox/PARC     initial implementation 
 * ******************************************************************/



package org.aspectj.internal.tools.ant.taskdefs;

import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.aspectj.testing.util.LangUtil;

/** 
 * Wrapper to invoke class identified by setting VM argument.
 * Caller must set a system property "MainWrapper.classname"
 * to the fully-qualified name of the target class to invoke,
 * and the target class must be resolvable from the defining
 * class loader of this class.
 * VM argument name is available as <code>PROP_NAME</code>, but
 * is set by adding the following to the command line:
 * <code>-DMainWrapper.classname="fully.qualified.Classname"</code>.
 * This returns -1 if unable to load the main method,
 * 1 if the invoked method throws an exception, and 0 otherwise.
 */
public class MainWrapper {
    /** MUST set the fully-qualified name of class to invoke using
     * a VM property of this name 
     * tracked in Ajctest.java */
    public static final String PROP_NAME = "MainWrapper.classname";
    /** May set the path to a classes diretory, 
     * to interpret class names and load classes.
     * Tracked in Ajctest.java */
    public static final String CLASSDIR_NAME = "MainWrapper.classdir";

    /** to disable returning int via System.exit, set to boolean true value (todo: ignored) */
    public static final String SIGNAL_EXCEPTION_NAME = "MainWrapper.signalException";

    /** to disable returning via System.exit on first Throwable, set to boolean true value (todo: ignored) */
    public static final String FAIL_ON_EXCEPTION_NAME = "MainWrapper.failOnException";

    /** quit on first exception */ // todo public class controls - yuck 
    public static boolean FAIL_ON_EXCEPTION = true;

    /** signal number of exceptions with int return value */ 
    public static boolean SIGNAL_EXCEPTION = true;

    /** redirect messages for exceptions; if null, none printed */ 
    public static PrintStream OUT_STREAM = System.err;

    /** result accumulated, possibly from multiple threads */ 
    private static int result;

    /**
     * Run target class's main(args), doing a System.exit() with
     * a value > 0 for the number of Throwable that 
     *  the target class threw that
     * makes it through to a top-level ThreadGroup. (This is 
     * strictly speaking not correct since applications can live
     * after their exceptions stop a thread.)
     * Exit with a value < 0 if there were exceptions in loading
     * the target class.  Messages are printed to OUT_STREAM.
     */
    public static void main(String[] args) {
        String classname = "no property : " + PROP_NAME;
        Method main = null;
        // setup: this try block is for loading main method - return -1 if fail
        try {
            // access classname from jvm arg 
            classname = System.getProperty(PROP_NAME);
            // this will fail if the class is not available from this classloader
            Class cl = Class.forName(classname);
            final Class[] argTypes = new Class[] {String[].class};
            // will fail if no main method
            main = cl.getMethod("main", argTypes);
            if (!Modifier.isStatic(main.getModifiers())) {
                PrintStream outStream = OUT_STREAM;
                if (null != outStream) outStream.println("main is not static");
                result = -1;
            }
            // if user also request loading of all classes...
            String classesDir = System.getProperty(CLASSDIR_NAME);
            if ((null != classesDir) && (0 < classesDir.length())) {
                MainWrapper.loadAllClasses(new File(classesDir));
            }
        } catch (Throwable t) {
            if (1 != result) result--;
            reportException("setup Throwable invoking class " + classname, t);
        }
        // run: this try block is for running things - get Throwable from our thread here
        if ((null != main) && (0 == result)) {
            try {
                runInOurThreadGroup(main, args);
            } catch (Throwable t) {
                if (result > -1) {
                    result++;
                }
                reportException("run Throwable invoking class " + classname, t);
            }
        }
        if ((0 != result) && (SIGNAL_EXCEPTION)) {
            System.exit(result);
        }
    }

    static void runInOurThreadGroup(final Method main, final String[] args) {
        final String classname = main.getDeclaringClass().getName();
        ThreadGroup ourGroup = new ThreadGroup("MainWrapper ThreadGroup") {
                public void uncaughtException(Thread t, Throwable e) {
                    reportException("uncaughtException invoking " + classname, e);
                    result++;
                    if (FAIL_ON_EXCEPTION) {
                        System.exit((SIGNAL_EXCEPTION ? result : 0));
                    }
                }
            };
        Runnable runner = new Runnable() {
                public void run() {
                    try {
                        main.invoke(null, new Object[] {args});
                    } catch (InvocationTargetException e) {
                        result = -1;
                        reportException("InvocationTargetException invoking " + classname, e);
                    } catch (IllegalAccessException e) {
                        result = -1;
                        reportException("IllegalAccessException invoking " + classname, e);
                    }
                }
            };
        Thread newMain = new Thread(ourGroup, runner, "pseudo-main");
        newMain.start();
        try {
            newMain.join();
        } catch (InterruptedException e) {
            result = -1; // todo: InterruptedException might be benign - retry?
            reportException("Interrupted while waiting for to join " + newMain, e);
        }
    }

    /** 
     * Try to load all classes in a directory.
     * @throws Error if any failed
     */
    static protected void loadAllClasses(File classesDir) {
        if (null != classesDir) {
            String[] names = LangUtil.classesIn(classesDir);
            StringBuffer err = new StringBuffer();
            LangUtil.loadClasses(names, null, err);
            if (0 < err.length()) {
                throw new Error("MainWrapper Errors loading classes: " 
                                 + err.toString());
            }
        }
    }

    static void reportException(String context, Throwable t) {
        PrintStream outStream = OUT_STREAM;
        if (null != outStream) {
            while ((null != t) && 
                   (InvocationTargetException.class.isAssignableFrom(t.getClass()))) {
                t = ((InvocationTargetException) t).getTargetException();
            }
            outStream.println("  context: " + context);
            outStream.println("  message: " + t.getMessage());
            t.printStackTrace(outStream);
        }
    }

} // MainWrapper