package jaci.openrio.toast.core.loader; import jaci.openrio.toast.core.ToastBootstrap; import jaci.openrio.toast.lib.profiler.Profiler; import jaci.openrio.toast.lib.profiler.ProfilerSection; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; /** * The class that handles runtime patching of classes. This allows for Simulations to happen * in development environments by patching WPILib's classes. This functions as a replacement for the System * Class Loader during Module Loading */ public class ClassPatcher extends URLClassLoader { public ClassPatcher() { this(ClassLoader.getSystemClassLoader()); } public ClassPatcher(ClassLoader parent) { super(new URL[0], parent); } /** * Patch a class for the given name. This is called whenever Class.forName is called, or Class.newInstance. This is * responsible for loading and defining patches if they exist, otherwise loading the standard file or throwing * an exception if it does not exist. */ @Override public Class<?> loadClass(String name) throws ClassNotFoundException { String simulation = "assets/toast/patches/" + name.replace(".", "/") + ".sim"; String patch = "assets/toast/patches/" + name.replace(".", "/") + ".pat"; InputStream stream = null; if (this.getResource(simulation) != null) stream = this.getResourceAsStream(simulation); if (this.getResource(patch) != null) stream = this.getResourceAsStream(patch); if (stream != null) { try { byte[] buf = new byte[50000]; int len = stream.read(buf); Method m = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); m.setAccessible(true); m.invoke(getParent(), name, buf, 0, len); } catch (Exception e) { ToastBootstrap.toastLogger.error("Could not load Patch: " + name); ToastBootstrap.toastLogger.exception(e); } } return super.loadClass(name); } /** * Identify the patches from assets/toast/patches/patches.txt */ public void identifyPatches(boolean sim) { try { ProfilerSection section = Profiler.INSTANCE.section("Patcher"); section.start("Identify"); ArrayList<String> patches = new ArrayList<>(); InputStream is = getResourceAsStream("assets/toast/patches/patches.txt"); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line = null; while ((line = reader.readLine()) != null) { patches.add(line); } reader.close(); section.stop("Identify"); for (String s : patches) { try { if (s.endsWith(".sim") && sim) { section.section("Simulation").start(s); loadClass(s.replace(".sim", "").replace("/", ".")); section.section("Simulation").stop(s); } else if (s.endsWith(".pat")) { section.section("Global").start(s); loadClass(s.replace(".pat", "").replace("/", ".")); section.section("Global").stop(s); } } catch (Exception e) { ToastBootstrap.toastLogger.error("Could not load Simulation Patch: " + s); ToastBootstrap.toastLogger.exception(e); } } } catch (Exception e) { ToastBootstrap.toastLogger.error("Could not load patches. Ignoring..."); } } }