package com.laytonsmith.abstraction; import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery; import com.laytonsmith.PureUtilities.Common.ReflectionUtils; import com.laytonsmith.abstraction.enums.EnumConvertor; import com.laytonsmith.annotations.abstractionenum; import com.laytonsmith.core.Prefs; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Set; /** * This class dynamically detects the server version being run, using various * checks as needed. * * */ public final class Implementation { //TODO: Ideally, this would not be static based, and instead, upon server startup, something would //pass an implementation of an EnvironmentProvider in, and that implement some number of methods //that could be used to hook in to the server. private Implementation() { } private static Implementation.Type serverType = null; private static boolean useAbstractEnumThread = true; /** * Sets whether or not we should verify enums when setServerType is called. * Defaults to true. * @param on */ public static void useAbstractEnumThread(boolean on){ useAbstractEnumThread = on; } /** * This method works like setServerType, except it does not check to * see that the server type wasn't already set. This should only be * used by the embedded tools or other meta code, not during normal * execution. This does not trigger the abstract enum thread. * @param type */ public static void forceServerType(Implementation.Type type){ serverType = type; } public static void setServerType(Implementation.Type type) { if (serverType == null) { serverType = type; } else { if (type != Type.TEST) { //This could potentially happen, but we don't care in the case that we //are testing, so don't error out here. (Failures may occur elsewhere though... :() throw new RuntimeException("Server type is already set! Cannot re-set!"); } } //Fire off our abstractionenum checks in a new Thread if (type != Type.TEST && type != Type.SHELL && useAbstractEnumThread) { Thread abstractionenumsThread; abstractionenumsThread = new Thread(new Runnable() { @Override public void run() { try { try { //Let the server startup data blindness go by first, so we display any error messages prominently, //since an Error is a case of very bad code that shouldn't have been released to begin with. Thread.sleep(15000); } catch (InterruptedException ex) { // } Set<Class<?>> abstractionenums = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(abstractionenum.class); for (Class c : abstractionenums) { abstractionenum annotation = (abstractionenum) c.getAnnotation(abstractionenum.class); if (EnumConvertor.class.isAssignableFrom(c)) { EnumConvertor<Enum, Enum> convertor; try { //Now, if this is not the current server type, skip it if (annotation.implementation() != serverType) { continue; } //Next, verify usage of the annotation (it is an error if not used properly) //All EnumConvertor subclasses should have public static getConvertor methods, let's grab it now Method m = c.getDeclaredMethod("getConvertor"); convertor = (EnumConvertor<Enum, Enum>) m.invoke(null); //Go through and check for a proper mapping both ways, from concrete to abstract, and vice versa. //At this point, if there is an error, it is only a warning, NOT an error. Class abstractEnum = annotation.forAbstractEnum(); Class concreteEnum = annotation.forConcreteEnum(); checkEnumConvertors(convertor, abstractEnum, concreteEnum, false); checkEnumConvertors(convertor, concreteEnum, abstractEnum, true); } catch (IllegalAccessException ex) { throw new Error(ex); } catch (IllegalArgumentException ex) { throw new Error(ex); } catch (InvocationTargetException ex) { throw new Error(ex); } catch (NoSuchMethodException ex) { throw new Error(serverType.getBranding() + ": The method with signature public static " + c.getName() + " getConvertor() was not found in " + c.getName() + " Please add the following code: \n" + "private static " + c.getName() + " instance;\n" + "public static " + c.getName() + " getConvertor(){\n" + "\tif(instance == null){\n" + "\t\tinstance = new " + c.getName() + "();\n" + "\t}\n" + "\treturn instance;\n" + "}\n" + "If you do not know what error is, please report this to the developers."); } } else { throw new Error("Only classes that extend EnumConvertor may use @abstractionenum. " + c.getName() + " does not, yet it uses the annotation."); } } } catch (Exception e) { boolean debugMode; try { debugMode = Prefs.DebugMode(); } catch(RuntimeException ex){ //Set it to true if we fail to load prefs, which can happen //with a buggy front end. debugMode = true; } if (debugMode) { //If we're in debug mode, sure, go ahead and print the stack trace, //but otherwise we don't want to bother the user. e.printStackTrace(); } } } }, "Abstraction Enum Verification Thread"); abstractionenumsThread.setPriority(Thread.MIN_PRIORITY); abstractionenumsThread.setDaemon(true); abstractionenumsThread.start(); } } private static void checkEnumConvertors(EnumConvertor convertor, Class to, Class from, boolean isToConcrete) { for (Object enumConst : from.getEnumConstants()) { ReflectionUtils.set(EnumConvertor.class, convertor, "useError", false); if (isToConcrete) { convertor.getConcreteEnum((Enum) enumConst); } else { convertor.getAbstractedEnum((Enum) enumConst); } ReflectionUtils.set(EnumConvertor.class, convertor, "useError", true); } } /** * These are all the supported server types */ public static enum Type { TEST("test-backend"), BUKKIT("CommandHelper"), SHELL("MethodScript"), SPONGE("CommandHelper"); //GLOWSTONE, //SINGLE_PLAYER private final String branding; /** * * @param branding This MUST be a universally acceptable folder name. */ private Type(String branding) { this.branding = branding; } /** * Returns the branding string for this implementation. * * @return */ public String getBranding() { return branding; } } /** * Returns the server type currently running * * @return */ public static Type GetServerType() { if (serverType == null) { throw new RuntimeException("Server type has not been set yet! Please call Implementation.setServerType with the appropriate implementation."); } return serverType; } }