I am currently making a Minecraft Mod Loader.
package spideyzac;
import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.opengl.Display;
import spideyzac.Module;
public class Client {
public static String name = "Pizza Mod Loader", version = "1";
public static CopyOnWriteArrayList<Module> modules = new CopyOnWriteArrayList<Module>();
public static void startup() {
Display.setTitle(name + " v" + version);
}
public static void onKey(int key) {
for (Module m : modules) {
if (m.keyCode == key) {
m.toggle();
}
}
}
public static void update() {
for (Module m : modules) {
if (m.isEnabled()) {
m.ifEnabled();
} else {
m.ifNotEnabled();
}
}
}
}
As you can see above I have a class called Client. Startup is called when the Minecraft game launches. Now I have a folder named Mods and when startup is called I need to load the mods from the mods folder into the ArrayList named modules. More in depth, each Mod will be have a Main class which inherits this Module class
package spideyzac;
import org.lwjgl.input.Keyboard;
import net.minecraft.client.Minecraft;
public class Module {
public static int keyCode;
public static String name;
public static boolean enabled;
public Minecraft mc = Minecraft.getMinecraft();
Module (int keyCode, String name) {
this.keyCode = keyCode;
this.name = name;
}
public void onEnable() {
}
public void onDisable() {
}
public void toggle() {
enabled = !enabled;
if (enabled) {
onEnable();
} else {
onDisable();
}
}
public void ifEnabled() {
}
public void ifNotEnabled() {
}
public static boolean isEnabled() {
return enabled;
}
public static int getKeyCode() {
return keyCode;
}
public static boolean checkKey(int key) {
return Keyboard.isKeyDown(key);
}
}
So when startup is called, I need to go through each Mod in the mods folder and add a new instance of the module's Main class to the ArrayList modules.
There is a class named URLClassLoader for this.
As the name says, it can load classes from URLs:
File[] files=new File("Mods").listFiles();
URL[] urls=new URL[files.length];
for(int i=0;i<files.length;i++){
urls[i]=files[i].toURI().toURL();
}
URLClassLoader modLoader=new URLClassLoader(urls);
In one line:
URLClassLoader modLoader=new URLClassLoader(Stream.of(new File("Mods").list()).map(file->file.toURI().toURL()).toArray(URL[]::new));
You can also create one ClassLoader per JAR.
File[] files=new File("Mods").listFiles();
for(int i=0;i<files.length;i++){
URL url=files[i].toURI().toURL();
URLClassLoader modLoader=new URLClassLoader(url);
}
You can then load classes if you know the package name:
Class<?> cl=modLoader.loadClass("fully.qualified.name");
Object o=cl.getDeclaredConstructor().newInstance();
if(o instanceof Module){
modules.add((Module)o);
}
If you don't know the class names, you can use a library called Reflections in order to get the Class objects.
I am trying to develop a dynamic Factory Class, where Factory Class does not know the factories, I will put the code so that the question is clearer.
App.java:
package br.com.factory;
public class App {
public static void main(String[] args) {
Product product = ProductFactory.createProduct("Xiaomi", "MI XX");
if (product != null) {
System.out.println(product.getName());
}
}
}
Product.java:
package br.com.factory;
public interface Product {
public String getName();
}
ProductFactory.java:
package br.com.factory;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class ProductFactory {
private static HashMap<String, Map<String, Supplier<Product>>> registries = new HashMap<>();
private ProductFactory(){}
static {
ClassLoaderInitializer.initialize(ProductSupplier.class);
}
public static Product createProduct(String manufacturer, String model) {
Map<String, Supplier<Product>> manufacturers = registries.getOrDefault(manufacturer, null);
if (manufacturers != null){
Supplier<Product> suppliers = manufacturers.getOrDefault(model, null);
if (suppliers != null) {
return suppliers.get();
}
}
return null;
}
public static void registerFactory(String manufacturer, String model, Supplier<Product> supplier) {
registries
.computeIfAbsent(manufacturer, p -> new HashMap<>())
.putIfAbsent(model, supplier);
}
}
ProductSupplier.java:
package br.com.factory;
import java.util.function.Supplier;
public interface ProductSupplier extends Supplier<Product> {
}
XiaomiFactory.java:
package br.com.factory.xiaomi;
import br.com.factory.Product;
import br.com.factory.ProductFactory;
import br.com.factory.ProductSupplier;
public class XiaomiFactory implements ProductSupplier {
static {
ProductFactory.registerFactory("Xiaomi", "MI XX", XiamoMiXX::new);
}
private XiaomiFactory() {
}
#Override
public Product get() {
return new XiamoMiXX();
}
}
class XiamoMiXX implements Product {
#Override
public String getName() {
return "Xiaomi Mi XX";
}
}
ClassLoaderInitializer.java:
package br.com.factory;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
public class ClassLoaderInitializer {
private ClassLoaderInitializer() {
}
public static void initialize(Class<?> parentClass) {
try {
Enumeration<URL> resources = ClassLoaderInitializer.class.getClassLoader().getResources("");
while (resources.hasMoreElements()) {
URL nextElement = resources.nextElement();
try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { nextElement });) {
URL[] urLs = urlClassLoader.getURLs();
for (URL u : urLs) {
try {
File file = new File(u.toURI());
initializeClass(file, file, urlClassLoader, parentClass);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void initializeClass(File root, File file, ClassLoader loader, Class<?> parentClass) {
if (file.isDirectory()) {
File[] listFiles = file.listFiles();
for (File f : listFiles) {
initializeClass(root, f, loader, parentClass);
}
} else {
if (file.getName().toUpperCase().endsWith(".class".toUpperCase())) {
try {
String fileName = file.toString();
String className = fileName.substring(root.toString().length() + 1,
fileName.toUpperCase().lastIndexOf(".class".toUpperCase())).replace(File.separator, ".");
Class<?> clazz = Class.forName(className, false, loader);
if (clazz.isAssignableFrom(parentClass)) {
Class.forName(className, true, loader);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
}
The problem occurs in the initializeClass method,
more precisely in:
Class <?> Clazz = Class.forName (className, false, loader);
if (clazz.isAssignableFrom (parentClass)) {
Class.forName (className, true, loader);
}
the idea of the ClassLoaderInitializer class is to initialize the classes that inherit from a given class "Class <?> parentClass"
but when I call the method
Class.forName (className, true, loader);
for the second time, passing true in the second parameter, the class is not initialized.
if I call:
Class.forName (className, true, loader);
directly, the initializeClass method will initialize all classes, what I would not like to happen.
is there any outside of me explicitly initializing (forcing) the class specifies?
Use ServiceLoader
ServiceLoader allows you to register services using metadata in the META-INF/services/ folder. It allows you to use a Java API to register all your services at once. It's better than trying to do it yourself, especially since it's standardized and doesn't require "magic" to get registered. Magic which might be broken in Java 9 and later with the introduction of modules.
This is how you should use it:
ProductSupplier.java
public interface ProductSupplier extends Supplier<Product> {
// Add these methods
String getManufacturer();
String getModel();
}
ProductFactory.java
public class ProductFactory {
private static final Map<List<String>, ProductSupplier> SUPPLIERS;
static {
var suppliers = new HashMap<List<String>, ProductSupplier>();
for (var supplier : ServiceLoader.load(ProductSupplier.class)) { // Yes, it's as easy as this.
var key = List.of(supplier.getManufacturer(), supplier.getModel());
suppliers.put(key, supplier);
}
SUPPLIERS = Map.copyOf(suppliers);
}
public static Product createProduct(String manufacturer, String model) {
var key = List.of(manufacturer, model);
var supplier = suppliers.getOrDefault(key, () -> null);
return supplier.get();
}
}
XiaomiFactory.java
public class XiaomiFactory implements ProductSupplier {
#Override public String getManufacturer() { return "Xiaomi"; }
#Override public String getModel() { return "Mi XX"; }
#Override public Product get() { return new XiaomiMiXX(); }
}
In META-INF/services/com.br.factory.ProductSupplier:
com.br.factory.xiaomi.XiaomiFactory
com.br.factory.samsung.SamsungFactory # Need to create
com.br.factory.apple.AppleFactory # Need to create
I am currently making a Minecraft Mod Loader.
package spideyzac;
import java.util.concurrent.CopyOnWriteArrayList;
import org.lwjgl.opengl.Display;
import spideyzac.Module;
public class Client {
public static String name = "Pizza Mod Loader", version = "1";
public static CopyOnWriteArrayList<Module> modules = new CopyOnWriteArrayList<Module>();
public static void startup() {
Display.setTitle(name + " v" + version);
}
public static void onKey(int key) {
for (Module m : modules) {
if (m.keyCode == key) {
m.toggle();
}
}
}
public static void update() {
for (Module m : modules) {
if (m.isEnabled()) {
m.ifEnabled();
} else {
m.ifNotEnabled();
}
}
}
}
As you can see above I have a class called Client. Startup is called when the Minecraft game launches. Now I have a folder named Mods and when startup is called I need to load the mods from the mods folder into the ArrayList named modules. More in depth, each Mod will be have a Main class which inherits this Module class
package spideyzac;
import org.lwjgl.input.Keyboard;
import net.minecraft.client.Minecraft;
public class Module {
public static int keyCode;
public static String name;
public static boolean enabled;
public Minecraft mc = Minecraft.getMinecraft();
Module (int keyCode, String name) {
this.keyCode = keyCode;
this.name = name;
}
public void onEnable() {
}
public void onDisable() {
}
public void toggle() {
enabled = !enabled;
if (enabled) {
onEnable();
} else {
onDisable();
}
}
public void ifEnabled() {
}
public void ifNotEnabled() {
}
public static boolean isEnabled() {
return enabled;
}
public static int getKeyCode() {
return keyCode;
}
public static boolean checkKey(int key) {
return Keyboard.isKeyDown(key);
}
}
So when startup is called, I need to go through each Mod in the mods folder and add a new instance of the module's Main class to the ArrayList modules.
There is a class named URLClassLoader for this.
As the name says, it can load classes from URLs:
File[] files=new File("Mods").listFiles();
URL[] urls=new URL[files.length];
for(int i=0;i<files.length;i++){
urls[i]=files[i].toURI().toURL();
}
URLClassLoader modLoader=new URLClassLoader(urls);
In one line:
URLClassLoader modLoader=new URLClassLoader(Stream.of(new File("Mods").list()).map(file->file.toURI().toURL()).toArray(URL[]::new));
You can also create one ClassLoader per JAR.
File[] files=new File("Mods").listFiles();
for(int i=0;i<files.length;i++){
URL url=files[i].toURI().toURL();
URLClassLoader modLoader=new URLClassLoader(url);
}
You can then load classes if you know the package name:
Class<?> cl=modLoader.loadClass("fully.qualified.name");
Object o=cl.getDeclaredConstructor().newInstance();
if(o instanceof Module){
modules.add((Module)o);
}
If you don't know the class names, you can use a library called Reflections in order to get the Class objects.
I am programming a plugin system for my Java Application. My problem is if I tryto cast from the Child Class to my Parent Interface
I get the ClassCastException java.lang.ClassCastException: net.scrumplex.testplugin.Main cannot be cast to net.scrumplex.sprummlbot.plugins.SprummlPlugin
My Interface:
package net.scrumplex.sprummlbot.plugins;
import com.github.theholywaffle.teamspeak3.api.event.BaseEvent;
import com.github.theholywaffle.teamspeak3.api.event.TS3EventType;
public interface SprummlPlugin {
public boolean init(String version);
public void end();
public void handleEvent(TS3EventType type, BaseEvent event);
}
My TestPlugin:
package net.scrumplex.testplugin;
import com.github.theholywaffle.teamspeak3.api.event.BaseEvent;
import com.github.theholywaffle.teamspeak3.api.event.TS3EventType;
import net.scrumplex.sprummlbot.plugins.SprummlPlugin;
public class Main implements SprummlPlugin {
#Override
public void end() {
System.out.println("Stopped");
}
#Override
public void handleEvent(TS3EventType type, BaseEvent event) {
System.out.println(type.name());
}
#Override
public boolean init(String arg0) {
System.out.println("Test");
return true;
}
}
My PluginLoader:
public static void load(File jarFile) throws InstantiationException, IllegalAccessException, ClassNotFoundException,
IOException, PluginLoadException {
String path = "";
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry("plugin.ini");
InputStream input = jar.getInputStream(entry);
Ini ini = new Ini(input);
if (!ini.containsKey("Plugin")) {
jar.close();
throw new PluginLoadException("Ini file not compatible.");
}
Section sec = ini.get("Plugin");
if (sec.containsKey("main")) {
path = sec.get("main");
}
ClassLoader loader = URLClassLoader.newInstance(new URL[] { jarFile.toURI().toURL() });
SprummlPlugin plugin = (SprummlPlugin) loader.loadClass(path).newInstance();
pluginslist.add(plugin);
plugin.init(Vars.VERSION);
jar.close();
}
The class path for the TestPlugin is correct
How I fixed this:
I added this.getClass().getClassLoader()to my URLClassLoader.newInstance(...);
Assume:
A simple modular java library has only two module:
package com.mycorp.lib.logger;
public class LogcatLogger implements Logger {
#Override
public void out(LogcatLogger.LogMessage message){
Log.i("TAG", message.status);
}
...
}
package com.mycorp.lib.webservice;
public class JsonWebservice implements Webservice {
#Override
public void onErrorFound(String status) {
LogcatLogger.LogMessage message = new LogcatLogger.LogMessage();
message.status = status;
LogcatLogger.out(message);
}
...
}
Question:
How to make com.mycorp.lib.webservice and com.mycorp.lib.logger independent like this? ( client application can invoke methods too )
Notes:
I try Command Pattern before:
Result: api has many methods and many command class must create
Update1:
Added LogMessage as nested static class of LogcatLogger for more challenging situation
Update2:
Target Design image added.
Any help appreciated.
Use the Strategy Pattern. Basically, make LogcatLogger implement a custom interface, and reference that interface in the service.
package com.mycorp.lib.invoker;
public interface Logger {
void out(Loggable message);
}
package com.mycorp.lib.invoker;
public interface Loggable {
void setStatus(String status);
String getStatus();
}
package com.mycorp.lib.invoker;
public class LoggableFactory {
public static Loggable createDatabaseLogMessage() { return new DatabaseLogMessage(); }
public static Loggable createWebserviceLogMessage() { return new WebserviceLogMessage(); }
}
package com.mycorp.lib.logger;
public class WebserviceLogMessage implements Logger {
#Override public void out(Loggable message){
Log.i("TAG", message.getStatus());
}
...
}
package com.mycorp.lib.logger;
public class DatabaseLogMessage implements Logger {
#Override public void out(Loggable message) {
Log.i("JDBC",message.getStatus());
otherFancyStuff(); // whatever ? :)
}
}
package com.mycorp.lib.webservice;
public class JsonWebservice implements Webservice {
Logger out;
JsonWebservice(Logger out) { this.out = out; }
#Override
public void onErrorFound(String status) {
Loggable message = LoggableFactory.createWebserviceLogMessage();
message.setStatus(status);
out.out(message);
}
...
}