From 36e0c3e456a08e8b619e37d0e8f573d2c54b3bda Mon Sep 17 00:00:00 2001
From: chylex <info@chylex.com>
Date: Sun, 10 Jan 2016 21:07:34 +0100
Subject: [PATCH] Rewrite and update to 2.0 (fix headless env, add
 futureproofing, cleanup code)

---
 build.gradle                                  |   2 +-
 .../java/chylex/javacheck/Java7Checker.java   |   4 +-
 .../java/chylex/javacheck/Java8Checker.java   |   4 +-
 .../chylex/javacheck/JavaVersionChecker.java  |  48 +++++++
 .../javacheck/report/JavaCheckerReporter.java | 132 ++++++------------
 .../report/OutdatedJavaException.java         |   2 +-
 .../javacheck/util/ForgeCompatibility.java    | 100 +++++++++++++
 .../javacheck/test/JavaCheckerTestMod.java    |   4 +-
 8 files changed, 198 insertions(+), 98 deletions(-)
 create mode 100644 src/main/java/chylex/javacheck/JavaVersionChecker.java
 create mode 100644 src/main/java/chylex/javacheck/util/ForgeCompatibility.java

diff --git a/build.gradle b/build.gradle
index c3deaac..ba9dcce 100644
--- a/build.gradle
+++ b/build.gradle
@@ -19,7 +19,7 @@ group = "chylex.javacheck"
 ext.buildnumber = 0
 project.buildnumber = System.getenv('BUILD_NUMBER') == null ? "CUSTOM" : System.getenv('BUILD_NUMBER')
 
-version = project.hasProperty("mavendir") ? "v1.3-b"+project.buildnumber : "MC-UNIVERSAL  v1.3"
+version = project.hasProperty("mavendir") ? "v2.0-b"+project.buildnumber : "MC-UNIVERSAL  v2.0"
 String archiveSuffix = (project.hasProperty("mavendir") ? "-" : "  ")+version+".jar"
 
 minecraft{
diff --git a/src/main/java/chylex/javacheck/Java7Checker.java b/src/main/java/chylex/javacheck/Java7Checker.java
index d30ad30..e77e4ea 100644
--- a/src/main/java/chylex/javacheck/Java7Checker.java
+++ b/src/main/java/chylex/javacheck/Java7Checker.java
@@ -6,10 +6,10 @@ import net.minecraft.launchwrapper.LaunchClassLoader;
 import org.apache.commons.lang3.JavaVersion;
 import chylex.javacheck.report.JavaCheckerReporter;
 
-public class Java7Checker implements ITweaker{
+public final class Java7Checker implements ITweaker{
 	@Override
 	public void injectIntoClassLoader(LaunchClassLoader classLoader){
-		JavaCheckerReporter.run(JavaVersion.JAVA_1_7);
+		JavaVersionChecker.run(JavaVersion.JAVA_1_7);
 	}
 	
 	@Override
diff --git a/src/main/java/chylex/javacheck/Java8Checker.java b/src/main/java/chylex/javacheck/Java8Checker.java
index 7b14c3a..16808f8 100644
--- a/src/main/java/chylex/javacheck/Java8Checker.java
+++ b/src/main/java/chylex/javacheck/Java8Checker.java
@@ -6,10 +6,10 @@ import net.minecraft.launchwrapper.LaunchClassLoader;
 import org.apache.commons.lang3.JavaVersion;
 import chylex.javacheck.report.JavaCheckerReporter;
 
-public class Java8Checker implements ITweaker{
+public final class Java8Checker implements ITweaker{
 	@Override
 	public void injectIntoClassLoader(LaunchClassLoader classLoader){
-		JavaCheckerReporter.run(JavaVersion.JAVA_1_8);
+		JavaVersionChecker.run(JavaVersion.JAVA_1_8);
 	}
 	
 	@Override
diff --git a/src/main/java/chylex/javacheck/JavaVersionChecker.java b/src/main/java/chylex/javacheck/JavaVersionChecker.java
new file mode 100644
index 0000000..db013f7
--- /dev/null
+++ b/src/main/java/chylex/javacheck/JavaVersionChecker.java
@@ -0,0 +1,48 @@
+package chylex.javacheck;
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
+import chylex.javacheck.report.JavaCheckerReporter;
+import chylex.javacheck.report.OutdatedJavaException;
+import chylex.javacheck.util.ForgeCompatibility;
+
+public final class JavaVersionChecker{
+	public static final String issueReportSite = "https://github.com/chylex/Java-Checker/issues";
+	
+	public static void run(JavaVersion minVersion){
+		try{
+			unsafeRun(minVersion);
+		}catch(OutdatedJavaException me){
+			throw me;
+		}catch(ShadingException up){
+			throw up;
+		}catch(Throwable t){
+			t.printStackTrace();
+			System.out.println("Detected an unexpected error in Java Version Checker, ignoring since trying to run the game is more important.");
+			System.out.println("If you crashed and happen to see this, please report the error above to: "+issueReportSite);
+		}
+	}
+	
+	private static void unsafeRun(JavaVersion minVersion){
+		if (minVersion == null || !SystemUtils.isJavaVersionAtLeast(minVersion)){
+			if (minVersion == null)minVersion = JavaVersion.JAVA_1_8; // debugging purposes
+			
+			JavaCheckerReporter.reportOutdatedJava(minVersion);
+			throw new OutdatedJavaException();
+		}
+		else{
+			if (isShaded() && !ForgeCompatibility.tryResetModState()){
+				throw new ShadingException();
+			}
+		}
+	}
+	
+	private static boolean isShaded(){
+		return !JavaCheckerReporter.class.getPackage().getName().equals("chylex.javacheck.report");
+	}
+	
+	public static class ShadingException extends RuntimeException{
+		public ShadingException(){
+			super("An exception happened when updating the coremod list, the mod you are shading Java Checker in will not run without it. Please, report the stack traces above to "+issueReportSite);
+		}
+	}
+}
diff --git a/src/main/java/chylex/javacheck/report/JavaCheckerReporter.java b/src/main/java/chylex/javacheck/report/JavaCheckerReporter.java
index 4aab9ef..abe6aaf 100644
--- a/src/main/java/chylex/javacheck/report/JavaCheckerReporter.java
+++ b/src/main/java/chylex/javacheck/report/JavaCheckerReporter.java
@@ -1,5 +1,6 @@
 package chylex.javacheck.report;
 import java.awt.Desktop;
+import java.awt.GraphicsEnvironment;
 import java.io.File;
 import java.util.List;
 import javax.swing.JEditorPane;
@@ -10,106 +11,55 @@ import javax.swing.event.HyperlinkEvent.EventType;
 import javax.swing.event.HyperlinkListener;
 import org.apache.commons.lang3.JavaVersion;
 import org.apache.commons.lang3.SystemUtils;
+import chylex.javacheck.util.ForgeCompatibility;
 
 public final class JavaCheckerReporter{
-	public static void run(JavaVersion minVersion){
-		if (minVersion == null || !SystemUtils.isJavaVersionAtLeast(minVersion)){
-			if (minVersion == null)minVersion = JavaVersion.JAVA_1_8;
-			
-			try{
-				Class relaunchLog = findRelaunchLog();
-				if (relaunchLog != null)relaunchLog.getMethod("severe",String.class,Object[].class).invoke(null,getConsoleReport(minVersion),new Object[0]);
-			}catch(Throwable t){}
-			
-			String style = "font-family:Dialog;font-size:12;font-weight:bold";
-			JEditorPane pane = new JEditorPane("text/html","<html><body style='"+style+"'>"+getWindowReport(minVersion)+"</body></html>");
-			pane.setBackground(new JLabel().getBackground());
-			pane.setEditable(false);
-			
-			pane.addHyperlinkListener(new HyperlinkListener(){
-				@Override
-				public void hyperlinkUpdate(HyperlinkEvent e){
-					if (e.getEventType() == EventType.ACTIVATED){
-						try{
-							if (Desktop.isDesktopSupported())Desktop.getDesktop().browse(e.getURL().toURI());
-						}catch(Exception ex){
-							ex.printStackTrace();
-						}
+	public static void reportOutdatedJava(JavaVersion minVersion){
+		String consoleReport = getConsoleReport(minVersion);
+		
+		if (!ForgeCompatibility.tryLog(consoleReport)){
+			System.out.println(consoleReport);
+		}
+		
+		if (!GraphicsEnvironment.isHeadless() && ForgeCompatibility.isClientSide()){
+			displayErrorPopup("Outdated Java",getHtmlReport(minVersion));
+		}
+	}
+	
+	private static void displayErrorPopup(String title, String contents){
+		JEditorPane pane = new JEditorPane("text/html","<html><body style='font-family:Dialog;font-size:12;font-weight:bold'>"+contents+"</body></html>");
+		pane.setBackground(new JLabel().getBackground());
+		pane.setEditable(false);
+		
+		pane.addHyperlinkListener(new HyperlinkListener(){
+			@Override
+			public void hyperlinkUpdate(HyperlinkEvent e){
+				if (e.getEventType() == EventType.ACTIVATED){
+					try{
+						if (Desktop.isDesktopSupported())Desktop.getDesktop().browse(e.getURL().toURI());
+					}catch(Exception ex){
+						ex.printStackTrace();
 					}
 				}
-			});
-			
-			JOptionPane.showMessageDialog(null,pane,"Outdated Java",JOptionPane.ERROR_MESSAGE);
-            throw new OutdatedJavaException();
-		}
-		else{
-			try{
-				Class cmm = findCoreModManager();
-				
-				List coremods = getListOrNullSafe(cmm,"getLoadedCoremods");
-				if (coremods == null)coremods = getListOrNullSafe(cmm,"getIgnoredMods");
-				
-				List reparsed = getListOrNullSafe(cmm,"getReparseableCoremods");
-				
-				String myFile = new File(JavaCheckerReporter.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getName();
-				coremods.remove(myFile);
-				reparsed.add(myFile);
-			}catch(Throwable t){
-				t.printStackTrace();
 			}
-		}
+		});
+		
+		JOptionPane.showMessageDialog(null,pane,title,JOptionPane.ERROR_MESSAGE);
 	}
 	
 	private static String getConsoleReport(JavaVersion minVersion){
-		return new StringBuilder(242).append("\n")
-		.append("\n!! DO NOT REPORT !!\n\n")
-		.append("One of the mods requires Java "+minVersion.toString()+" or newer, you are using ").append(SystemUtils.JAVA_VERSION).append(".\n")
-		.append("Visit https://java.com/download/ for the latest version.\n")
-		.append("Please, uninstall the old version first to prevent further issues.")
-		.append("\n\n!! DO NOT REPORT !!\n")
-		.toString();
+		return
+			"\n\n!! DO NOT REPORT !!\n\n"+
+			"One of the mods requires Java "+minVersion+" or newer, you are using "+SystemUtils.JAVA_VERSION+".\n"+
+			"Visit https://java.com/download/ for the latest version.\n"+
+			"Please, uninstall the old version first to prevent further issues."+
+			"\n\n!! DO NOT REPORT !!\n";
 	}
 	
-	private static String getWindowReport(JavaVersion minVersion){
-		return new StringBuilder(230)
-		.append("One of the mods requires Java "+minVersion.toString()+" or newer, you are using ").append(SystemUtils.JAVA_VERSION).append(".<br>")
-		.append("Visit <a href=\"https://java.com/download/\"><span style=\"color:blue\">https://java.com/download/</span></a> for the latest version.<br>")
-		.append("Please, uninstall the old version first to prevent further issues.")
-		.toString();
-	}
-	
-	private static Class findRelaunchLog() throws Throwable{
-		try{
-			return Class.forName("cpw.mods.fml.relauncher.FMLRelaunchLog");
-		}catch(ClassNotFoundException e){}
-		
-		try{
-			return Class.forName("net.minecraftforge.fml.relauncher.FMLRelaunchLog");
-		}catch(ClassNotFoundException e){}
-		
-		return null;
-	}
-	
-	private static Class findCoreModManager() throws Throwable{
-		try{
-			return Class.forName("cpw.mods.fml.relauncher.CoreModManager");
-		}catch(ClassNotFoundException e){}
-		
-		try{
-			return Class.forName("net.minecraftforge.fml.relauncher.CoreModManager");
-		}catch(ClassNotFoundException e){}
-		
-		return null;
-	}
-	
-	private static List getListOrNullSafe(Class cls, String methodName){
-		try{
-			return (List)cls.getMethod(methodName).invoke(null);
-		}catch(NoSuchMethodException e){
-		}catch(Throwable t){
-			t.printStackTrace();
-		}
-		
-		return null;
+	private static String getHtmlReport(JavaVersion minVersion){
+		return
+			"One of the mods requires Java "+minVersion+" or newer, you are using "+SystemUtils.JAVA_VERSION+".<br>"+
+			"Visit <a href=\"https://java.com/download/\"><span style=\"color:blue\">https://java.com/download/</span></a> for the latest version.<br>"+
+			"Please, uninstall the old version first to prevent further issues.";
 	}
 }
diff --git a/src/main/java/chylex/javacheck/report/OutdatedJavaException.java b/src/main/java/chylex/javacheck/report/OutdatedJavaException.java
index ddba08e..a31fb5f 100644
--- a/src/main/java/chylex/javacheck/report/OutdatedJavaException.java
+++ b/src/main/java/chylex/javacheck/report/OutdatedJavaException.java
@@ -2,7 +2,7 @@ package chylex.javacheck.report;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 
-public class OutdatedJavaException extends RuntimeException{
+public final class OutdatedJavaException extends RuntimeException{
 	public OutdatedJavaException(){
 		setStackTrace(new StackTraceElement[0]);
 	}
diff --git a/src/main/java/chylex/javacheck/util/ForgeCompatibility.java b/src/main/java/chylex/javacheck/util/ForgeCompatibility.java
new file mode 100644
index 0000000..1b8fa77
--- /dev/null
+++ b/src/main/java/chylex/javacheck/util/ForgeCompatibility.java
@@ -0,0 +1,100 @@
+package chylex.javacheck.util;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URISyntaxException;
+import java.util.List;
+
+public final class ForgeCompatibility{
+	public static boolean tryLog(String data){
+		try{
+			org.apache.logging.log4j.LogManager.getLogger("JavaChecker").log(org.apache.logging.log4j.Level.ERROR,data);
+			return true;
+		}catch(Throwable t){ // apache logging is not available
+			t.printStackTrace();
+		}
+		
+		try{
+			Class relaunchLog = findFMLClass("relaunch","FMLRelaunchLog");
+			Method logSevere = findMethod(relaunchLog,"severe",String.class,Object[].class);
+			
+			if (logSevere != null){
+				logSevere.invoke(null,data,new Object[0]);
+				return true;
+			}
+		}catch(Throwable t){ // relaunch log not available
+			t.printStackTrace();
+		}
+		
+		return false;
+	}
+	
+	public static boolean tryResetModState(){
+		try{
+			Class cmm = findFMLClass("relauncher","CoreModManager");
+			Method getCoremods = findMethodAlt(cmm,new String[]{ "getLoadedCoremods", "getIgnoredMods" });
+			Method getReparsed = findMethodAlt(cmm,new String[]{ "getReparseableCoremods" });
+			
+			if (getCoremods == null || getReparsed == null)return false;
+			
+			String myFile = getModFileName();
+			
+			((List)getCoremods.invoke(null)).remove(myFile);
+			((List)getReparsed.invoke(null)).add(myFile);
+			return true;
+		}catch(Throwable t){
+			t.printStackTrace();
+			return false;
+		}
+	}
+	
+	public static boolean isClientSide(){
+		try{
+			return Class.forName("net.minecraft.client.Minecraft") != null;
+		}catch(ClassNotFoundException e){
+			return false;
+		}catch(Throwable t){
+			t.printStackTrace();
+			return false;
+		}
+	}
+	
+	private static Class findFMLClass(String classPackage, String className){
+		String searchTarget = classPackage.isEmpty() ? className : classPackage+"."+className;
+		
+		try{
+			return Class.forName("cpw.mods.fml."+searchTarget);
+		}catch(ClassNotFoundException e){}
+		
+		try{
+			return Class.forName("net.minecraftforge.fml."+searchTarget);
+		}catch(ClassNotFoundException e){}
+		
+		return null;
+	}
+	
+	private static Method findMethod(Class cls, String methodName, Class...params){
+		if (cls == null)return null;
+		
+		try{
+			return cls.getMethod(methodName,params);
+		}catch(NoSuchMethodException e){}
+		
+		return null;
+	}
+	
+	private static Method findMethodAlt(Class cls, String[] methodNames, Class...params){
+		if (cls == null)return null;
+		
+		for(String methodName:methodNames){
+			try{
+				return cls.getMethod(methodName,params);
+			}catch(NoSuchMethodException e){}
+		}
+		
+		return null;
+	}
+	
+	private static String getModFileName() throws URISyntaxException{
+		return new File(ForgeCompatibility.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getName();
+	}
+}
diff --git a/src/main/test/chylex/javacheck/test/JavaCheckerTestMod.java b/src/main/test/chylex/javacheck/test/JavaCheckerTestMod.java
index b15c7e8..c2d7b31 100644
--- a/src/main/test/chylex/javacheck/test/JavaCheckerTestMod.java
+++ b/src/main/test/chylex/javacheck/test/JavaCheckerTestMod.java
@@ -2,12 +2,14 @@ package chylex.javacheck.test;
 import net.minecraftforge.fml.common.Mod;
 import net.minecraftforge.fml.common.Mod.EventHandler;
 import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+import chylex.javacheck.JavaVersionChecker;
 import chylex.javacheck.report.JavaCheckerReporter;
 
 @Mod(modid = "JavaCheckerTestMod")
 public class JavaCheckerTestMod{
 	@EventHandler
 	public void onPreInit(FMLPreInitializationEvent e){
-		JavaCheckerReporter.run(null);
+		System.out.println(JavaCheckerReporter.class.getPackage().getName());
+		JavaVersionChecker.run(null);
 	}
 }