import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import uk.ac.ntu.n0521366.wsyd.server.ServerSocial;
+import uk.ac.ntu.n0521366.wsyd.server.ServerChat;
import uk.ac.ntu.n0521366.wsyd.management.ServerManagement;
import uk.ac.ntu.n0521366.wsyd.client.ClientGUI;
/**
+ * Allow execution of multiple independent application classes in a single Java Virtual Machine under
+ * control of a single Launcher class.
+ *
+ * For testing and running large projects that contain more than one executable application class
+ * this launcher can be set as the Project's 'main' class to be executed when the Project us built
+ * and run.
+ *
+ * The launcher will create and start a Thread for each executable application class inside the
+ * same Java Virtual Machine that the launcher is executing in.
+ *
+ * The alternative is to manually launch each executable application class each time the project
+ * needs to be executed as a whole.
+ *
+ * This is extremely useful in development IDEs where the project properties will only accept a
+ * single ('main') class for execution when the project is run.
+ *
+ * It is also useful in packaging complete projects to make it easy for the end-user to start
+ * all the required executable classes without external operating-system specific shell scripts
+ * or other mechanisms.
*
* @author Eddie Berrisford-Lynch <dev@fun2be.me>
*/
public class Launcher {
-
+
+ enum EXECUTE { NO, YES};
+ /**
+ * Wrap an executable application class in a Runnable that can be instantiated in a separate Thread.
+ */
static class App implements Runnable {
private final Class<?> _classRef;
private final String[] _args;
+ private final EXECUTE _shouldExecute;
- public App(Class<?> classRef, String[] args) {
+ /**
+ * Build an executable application.
+ *
+ * @param classRef The application class (must contain the method: static void main(String[])
+ * @param args The command-line arguments to pass to the application's main() method
+ * @param execute Whether this application should be started
+ */
+ public App(Class<?> classRef, String[] args, EXECUTE execute) {
_classRef = classRef;
_args = args;
+ _shouldExecute = execute;
}
+ /**
+ * Instantiate an executable class and execute it.
+ *
+ * Execute the static main() method of an executable class in its own thread
+ * and run the class's application in a Thread of execution in the current
+ * Java Virtual Machine rather than as a stand-alone operating system process.
+ */
@Override
public void run() {
- try {
- ClassLoader cl = _classRef.getClassLoader();
- try {
- Object o = cl.loadClass(_classRef.getName()).newInstance();
- Class<?> newClass = o.getClass();
- Method m = newClass.getMethod("main", String[].class);
- m.invoke(null, (Object) this._args);
- } catch (ClassNotFoundException ex) {
- Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
- } catch (InstantiationException ex) {
+ if (this._shouldExecute == EXECUTE.YES) {
+ Logger.getLogger(Launcher.class.getName()).log(Level.INFO, MessageFormat.format("Starting app {0}", _classRef.getClass().getName()));
+ try {
+ // Use the class loader that knows how to load the classes imported by the application class
+ ClassLoader cl = _classRef.getClassLoader();
+ try {
+ Class<?> appClass = cl.loadClass(_classRef.getName()).newInstance().getClass();
+ Method main = appClass.getMethod("main", String[].class);
+ Logger.getLogger(App.class.getName()).log(Level.INFO, MessageFormat.format("Invoking {0}.main(_args)", _classRef.getName()));
+ // invoking a static (not instance) method so the object instance is null
+ main.invoke(null, (Object) this._args);
+ } catch (ClassNotFoundException | InstantiationException ex) {
+ Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
}
- } catch (NoSuchMethodException ex) {
- Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
- } catch (SecurityException ex) {
- Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
- } catch (IllegalAccessException ex) {
- Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
- } catch (IllegalArgumentException ex) {
- Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
- } catch (InvocationTargetException ex) {
- Logger.getLogger(Launcher.class.getName()).log(Level.SEVERE, null, ex);
}
-
+ else; // do nothing and return immediately
}
}
+
+ /**
+ * List of executable application classes to execute.
+ */
static ArrayList<App> apps;
+
+ /**
+ * Quantity of ClientGUI processes to start
+ */
+ static int clientQty = 1;
+ /**
+ * Execute each of the application classes in its own Thread.
+ *
+ * Use "--clients X" command-line option to set the number of ClientGUI applications to start.
+ *
+ * @param args the launcher's command-line arguments into each executable application's
+ * main() method.
+ */
public static void main(String[] args) {
-
+
apps = new ArrayList<>();
-
- apps.add(new App( ServerSocial.class, args));
- apps.add(new App(ServerManagement.class, args));
- for (int qty = 1; qty <= 1; qty++) {
- apps.add(new App(ClientGUI.class, args));
+
+ // possibly set number of clients to create from command line options
+ boolean getClients = false;
+ for (String arg: args)
+ if (getClients) {
+ clientQty = Integer.parseInt(arg);
+ break;
+ } else if (arg.equals("--clients")) {
+ getClients = true;
+ } else if (arg.equals("--help")) {
+ System.out.println(
+ "Usage: java -cp . uk.ac.ntu.n0521366.wsyd.Launcher [options]\n" +
+ " --help\tthis usage hint\n" +
+ " --clients\tnumber of clients to create\n" +
+ " --server\tIP address (or host name) of Social Server\n" +
+ " --announce\tUse multicast group announcements for host discovery\n"
+ );
+ return;
+ }
+
+ // create Runnable objects that encapsulate each of the executable application classes
+ apps.add(new App( ServerSocial.class, args, EXECUTE.YES));
+ apps.add(new App( ServerChat.class, args, EXECUTE.NO));
+ apps.add(new App(ServerManagement.class, args, EXECUTE.YES));
+ for (int qty = 1; qty <= clientQty; qty++) {
+ apps.add(new App(ClientGUI.class, args, EXECUTE.YES));
}
-
+
for (App app: apps) {
- System.out.println("Starting app " + app._classRef.getTypeName());
+ // start each executable application class in its own Thread
new Thread(app).start();
}
}
-
}