import java.io.ByteArrayOutputStream; 
import java.io.File; 
import java.io.IOException; 
import java.io.PrintStream; 
import java.nio.file.Files; 
import java.nio.file.Path; 
import java.nio.file.Paths; 
import java.nio.file.StandardCopyOption; 
import java.nio.file.StandardOpenOption; 
import java.text.DateFormat; 
import java.text.SimpleDateFormat; 
import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.Collections; 
import java.util.Date; 
import java.util.Iterator; 
import java.util.List; 
import java.util.Map; 
import java.util.Set; 
import java.util.stream.Collectors; 
import java.util.stream.Stream; 

public class TestRestartWithNewJre {
	private static String logFile = "p1.log"; 
	private static String oldJreHome = null; 
	private static String newJreHome = null; 

	public static void main(String[] args) { 
		TestRestartWithNewJre t = new TestRestartWithNewJre(); 

		try { 
			//get params 
			setParams(args); 

			//delete logfiles 
			new File( logFile ).delete(); 

			//log params and all system properties 
			log("args: " + Arrays.asList(args)); 
			dumpMap("properties: ", (Map) System.getProperties() ); 
			dumpMap("env: ", System.getenv() ); 

			// restart with alternate JRE or rename the old one 
			t.start(); 
		} catch (IOException e) { 
			log("IOException found", e); 
			log("plese check first which file is locked..... (process sleep for 1h)"); 

			try { 
				Thread.sleep(1000 * 60 * 60); 
			} catch (InterruptedException e1) { 
				log("sleep interrupted", e1); 
			} 
		} catch (Exception e) { 
			log("error", e); 
		} 
	} 


	private void start() throws IOException { 

		// if new jre home is given, restart this job with the new JRE and put the reference to the old JRE as parameter "-oldjre" (for renaming later on) 
		if( newJreHome != null ) { 
			File newJreHomeDir = new File(newJreHome); 
			if( newJreHomeDir.exists() && newJreHomeDir.isDirectory() ) { 
				List<String> cmdItems = new ArrayList<String>(); 
				String javaExecutable = getJavaExe(newJreHomeDir); 
				cmdItems.add(javaExecutable); 
				cmdItems.add("-classpath"); 
				String jarName = getJarName(); 
				cmdItems.add(jarName); 
				cmdItems.add(this.getClass().getName()); 
				cmdItems.add("-oldjre"); 
				String jreHome = System.getProperty("java.home"); 
				cmdItems.add(jreHome); 
				cmdItems.add("-logfile"); 
				cmdItems.add("p2.log"); 

				invokeNewProcess(newJreHome, cmdItems); 
				System.exit(1); 
			} else { 
				log("new JRE home '" + newJreHome + "' does not exist"); 
				System.exit(1); 
			} 

		} else if( oldJreHome != null ) { 
			log("sleep for 2 seconds first, before trying to rename old/unused JRE-home"); 
			try { 
				Thread.sleep(1000 * 2); 
			} catch (InterruptedException e1) { 
				log("sleep interrupted", e1); 
			} 

			// if old/unused jre home is given, try to rename it 
			File oldJreHomeDir = new File(oldJreHome); 
			if( oldJreHomeDir.exists() && oldJreHomeDir.isDirectory() ) { 
				File targetPath = new File(oldJreHomeDir.getParentFile(), oldJreHomeDir.getName() + "-" + System.currentTimeMillis()); 

				log("try to move '" + oldJreHome + "' to '" + targetPath.getAbsolutePath() + "'"); 

				Path movedTargetPath = Files.move(oldJreHomeDir.toPath(), targetPath.toPath(), StandardCopyOption.ATOMIC_MOVE); 
				if( movedTargetPath.toFile().exists() ) { 
					log("rename '" + oldJreHome + "' to '" + movedTargetPath.toString() + "' done with success"); 
				} else { 
					log("rename '" + oldJreHome + "' to '" + movedTargetPath.toString() + "' failed"); 
				} 
			} else { 
				log("wrong parameter -oldjre '" + oldJreHome + "', path does not exist"); 
				System.exit(1); 
			} 
		} 


	} 


	/* 
	 * get params 
	 */ 
	private static void setParams(String[] args) { 
		for( int i=0; i<args.length; i++ ) { 
			switch( args[i] ) { 
			case "-oldjre": 
				if( i+1 < args.length ) { 
					oldJreHome = args[++i]; 
				} 
				break; 
			case "-newjre": 
				if( i+1 < args.length ) { 
					newJreHome = args[++i]; 
				} 
				break; 
			case "-logfile": 
				if( i+1 < args.length ) { 
					logFile = args[++i]; 
				} 
				break; 
			} 
		} 
	} 


	/* 
	 * get the name of this jar (for restart with alternate jre) 
	 */ 
	private String getJarName() { 
		return this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); 
	} 

	/* 
	 * determine the path/name of the java executable for the given JRE home (the alternate jre for restart !) 
	 */ 
	private String getJavaExe(final File javaHomeDir) throws IOException { 
		final Path start = javaHomeDir.toPath(); 
		final int maxDepth = 5; 
		final StringBuilder sb = new StringBuilder(""); 
		try (Stream<Path> stream = Files.find(start, maxDepth, (path, attr) -> ((String.valueOf(path).endsWith("java.exe")||String.valueOf(path).endsWith("java"))&&path.toFile().isFile()&&(path.toFile().canExecute())))) { 
			sb.append( stream.sorted().map(String::valueOf).collect(Collectors.joining(";")) ); 
		} 
		String javaExe[] = sb.toString().split(";"); 
		if( javaExe.length != 1 ) { 
			throw new RuntimeException("java executable not found: " + Arrays.asList(javaExe)); 
		} 
		File javaExeFile = new File(javaExe[0]); 
		boolean javaExecutableFound = javaExeFile != null && javaExeFile.exists() && javaExeFile.isFile() && javaExeFile.canRead() && javaExeFile.canExecute(); 
		if( !javaExecutableFound ) { 
			throw new RuntimeException("java file not executable: " + javaExeFile.getAbsolutePath()); 
		} 
		log("getJavaExe: " + javaExeFile.getCanonicalPath() ); 
		return javaExeFile.getCanonicalPath(); 
	} 

	/* 
	 * invoke the new process (with alternate JRE) 
	 */ 
	private void invokeNewProcess(String newJavaHome, List<String> cmdItems) throws IOException { 
		log("try to start process: " + cmdItems); 
		final ProcessBuilder pb = new ProcessBuilder(cmdItems); 
		Map<String,String> pbEnv = pb.environment(); 
		if( newJavaHome != null ) { 
			pbEnv.clear(); 
			pbEnv.put("JRE_HOME", newJavaHome); 
			pbEnv.put("JAVA_HOME", newJavaHome); 
		} 
		dumpMap("env for new process: ", pbEnv); 

		pb.redirectErrorStream(true); 
		pb.redirectInput(); 
		final Process process = pb.start();	
		log("process is started: " + process.isAlive()); 
	} 

	/* 
	 * log the current map (env or system properties) 
	 */ 
	private static void dumpMap(final String label, final Map<String,String> map) { 
		Set<String> keys = map.keySet(); 
		java.util.List<String> sortedKeys = new ArrayList<String>(keys); 
		Collections.sort(sortedKeys); 
		StringBuilder buf = new StringBuilder(label).append(System.lineSeparator()); 
		for (Iterator<String> iter = sortedKeys.iterator(); iter.hasNext();) { 
			String key = iter.next(); 
			String value = map.get(key); 
			buf.append("\t" + key + "=" + value).append(System.lineSeparator()); 
		} 
		log(buf.toString()); 
	} 

	/* 
	 * log msg 
	 */ 
	private static void log(String msg) { 
		log(msg, null); 
	} 

	/* 
	 * log msg and exception (if not null) 
	 */ 
	private static void log(String msg, Exception ex ) { 
		final DateFormat df = new SimpleDateFormat("yyyyMMdd HH:mm:ss SSS "); 
		String pid = " PID: " + getPID() + ", "; 
		try { 

			Files.write(Paths.get(logFile), (df.format(new Date()) + pid + msg + System.lineSeparator()).getBytes("UTF-8"),StandardOpenOption.CREATE,StandardOpenOption.APPEND); 
			if( ex != null ) { 
				ByteArrayOutputStream bo = new ByteArrayOutputStream(); 
				PrintStream ps = new PrintStream(bo); 
				ex.printStackTrace(ps); 
				Files.write(Paths.get(logFile), (df.format(new Date()) + pid + bo.toString() + System.lineSeparator()).getBytes("UTF-8"),StandardOpenOption.CREATE,StandardOpenOption.APPEND); 
			}	
		} catch (Exception e) { 
			//ignore 
		} 
	} 

	/* 
	 * determin the current PID 
	 */ 
	private static long getPID() { 
		try { 
			String processName = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); 
			return Long.parseLong(processName.split("@")[0]); 
		} catch( Exception ex ) { 
			return -999; 
		} 
	}	
}
