public class MainApp {

	public static enum APP {
		B, C;
	}

	public static void main(String[] args) throws Exception,
			InterruptedException {
		if (args.length > 0) {
			APP app = APP.valueOf(args[0]);
			switch (app) {
			case B:
				performB();
				break;
			case C:
				performC();
				break;
			}
			return;
		}
		performA();
	}

	private static void performA() throws Exception {
		String javaBin = "java";
		String[] cmdArray = { javaBin, "-cp",
				System.getProperty("java.class.path"), MainApp.class.getName(),
				APP.B.name() };
		ProcessBuilder builder = new ProcessBuilder(cmdArray);
		builder.redirectErrorStream(true);
		final Process process = builder.start();

		process.getOutputStream().close();
		process.getErrorStream().close();

		// if we call this, the main app hangs, the output would be:
		// Before call child process
		// After call child process
		// the main program hangs here.
		// Never output - "Read stream finished."
		readViaInputStream(process);

		// if we call this, the main app hangs, the output would be:
		// Before call child process
		// After call child process
		// the main program hangs here.
		// Never output - "Read stream finished."
		// readViaBufferedReader(process);

		// if we call this, the main app still hangs, the output would be:
		// Before call child process
		// After call child process
		// Read stream finished.
		// ===> and the main program hang
		// readOutputViaAnotherThread(process);

		System.err.println("Read stream finished."); // never come here
	}

	private static void performB() throws Exception {
		System.out.println("Before call child process");
		String javaBin = "java";
		String[] cmdArray = { javaBin, "-cp",
				System.getProperty("java.class.path"), MainApp.class.getName(),
				APP.C.name() };
		ProcessBuilder builder = new ProcessBuilder(cmdArray);
		Process process = builder.start();

		process.getInputStream().close();
		process.getOutputStream().close();
		process.getErrorStream().close();

		System.out.println("After call child process");
		System.exit(0); // no difference with or without this line.
	}

	private static void performC() throws Exception {
		Thread thread = new Thread() {
			@Override
			public void run() {
				int i = 0;
				while (true) {
					try {
						Thread.sleep(60 * 2);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.err.println("child " + ++i);
				}
			}
		};
		thread.start();
		thread.join();

	}

	private static void readViaInputStream(final Process process)
			throws Exception {

		// System.err.println("exitValue: " + process.waitFor());
		InputStream is = process.getInputStream();
		int result;

		while ((result = is.read()) != -1) {
			System.err.println(result);
		}

		System.err.println("exitValue: " + process.waitFor());
	}

	private static void readViaBufferedReader(final Process process)
			throws Exception {
		BufferedReader in = new BufferedReader(new InputStreamReader(
				process.getInputStream(), "utf-8"));
		String result = "";
		while ((result = in.readLine()) != null) {
			System.err.println(result);
		}

		System.err.println("exitValue: " + process.waitFor());
	}

	private static void readOutputViaAnotherThread(final Process process)
			throws Exception {
		class ReadOutputStreamThread extends Thread {
			public void run() {

				running = true;
				try {
					BufferedReader in = new BufferedReader(
							new InputStreamReader(process.getInputStream(),
									"utf-8"));
					String result = "";
					while (running && (result = in.readLine()) != null) {
						System.err.println(result);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}

			};

			private volatile boolean running;

			public void shutdown() throws IOException {
				running = false;
				// this has no impact
				process.getInputStream().close();
				interrupt();
			}
		}

		ReadOutputStreamThread readOutputThread = new ReadOutputStreamThread();
		// if we set this readOutputThread as daemon, it works, but the thread
		// will remains run forever.
		// readOutputThread.setDaemon(true);
		readOutputThread.start();

		System.err.println("exitValue: " + process.waitFor());
		readOutputThread.shutdown();
		process.destroy();
	}
}

