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(...);
Related
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'm doing a WSDL client and want to know how I can set an XML element to be CDATA.
I'm using the wsimport to generate the source code, and the CDATA element is part of the request XML.
This is the XML class of the request:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = { "dataRequest" })
#XmlRootElement(name = "ProcessTransaction")
public class ProcessTransaction {
protected String dataRequest;
public String getDataRequest() {
return dataRequest;
}
public void setDataRequest(String value) {
this.dataRequest = value;
}
}
I've already tried the #XmlAdapter, but it changes nothing on the output...
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class AdaptorCDATA extends XmlAdapter<String, String> {
#Override
public String marshal(String arg0) throws Exception {
return "<![CDATA[" + arg0 + "]]>";
}
#Override
public String unmarshal(String arg0) throws Exception {
return arg0;
}
}
In the XML class:
#XmlJavaTypeAdapter(value=AdaptorCDATA.class)
protected String dataRequest;
I tried to debug, but it never steps on the AdaptorCDATA function.
The wsimport version is 2.2.9 and the jaxb-api version is 2.1.
So, as #user1516873 suggested, i moved the code to cxf, and with this, is working well. Now i'm using the "wsdl2java" to generate the code, and the jars from cxf on my project.
What is different in the code:
CdataInterceptor
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
public class CdataInterceptor extends AbstractPhaseInterceptor<Message> {
public CdataInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(Message message) {
message.put("disable.outputstream.optimization", Boolean.TRUE);
XMLStreamWriter writer = (XMLStreamWriter) message.getContent(XMLStreamWriter.class);
if (writer != null && !(writer instanceof CDataContentWriter)) {
message.setContent(XMLStreamWriter.class, new CDataContentWriter(writer));
}
}
public void handleFault(Message messageParam) {
System.out.println(messageParam);
}
}
CDataContentWriter
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.cxf.staxutils.DelegatingXMLStreamWriter;
public class CDataContentWriter extends DelegatingXMLStreamWriter {
public CDataContentWriter(XMLStreamWriter writer) {
super(writer);
}
public void writeCharacters(String text) throws XMLStreamException {
boolean useCData = text.contains("RequestGeneric");
if (useCData) {
super.writeCData(text);
} else {
super.writeCharacters(text);
}
}
// optional
public void writeStartElement(String prefix, String local, String uri) throws XMLStreamException {
super.writeStartElement(prefix, local, uri);
}
}
Using the Writer and the Interceptor:
MyService wcf = new MyService(url, qName);
IMyService a = wcf.getBasicHttpBinding();
Client cxfClient = ClientProxy.getClient(a);
CdataInterceptor myInterceptor = new CdataInterceptor();
cxfClient.getInInterceptors().add(myInterceptor);
cxfClient.getOutInterceptors().add(myInterceptor);
And it works perfect!
Can i run a method from am Plugin like onEnable(), my script doesnt work. :(
The extended Class:
package de.R3N3PDE.X.API;
import de.R3N3PDE;
public class JavaPlugin{
Server server;
public ServerManager getServer(){
return new ServerManager(server);
}
public ServerManager setServer(Server s){
server = s;
}
public PluginLogger getLogger(){
return new PluginLogger(server);
}
}
The Plugin:
package de.R3N3PDE.PluginExample;
import de.R3N3PDE.X.API.JavaPlugin;
public class PluginExample extends JavaPlugin{
public void onEnable(){
getLogger().info("Plugin Enabled!");
}
}
The PluginLoader:
package de.R3N3PDE.X.Plugin;
imports de.R3N3PDE.X.API.JavaPlugin;
public void initPlugins(){
try {
File loc = new File("plugins");
extendClasspath(loc);
ServiceLoader<JavaPlugin> sl = ServiceLoader.load(JavaPlugin.class);
Iterator<JavaPlugin> apit = sl.iterator();
while (apit.hasNext()){;
JavaPlugin met = apit.next();
//Set the Server
med.setServer(s);
//run void onEnable(); in the Plugin????
}
} catch (IOException e) {
e.printStackTrace();
}
}
====================
EDIT:
Thanks. I have a new Problem...
The URLClassLoader find Plugins but the ServiceLoader can´t load the Plugin.
I think the ServiceLoader cant fint the extended class.
The Plugin:
package de.R3N3PDE.PluginExampleName;
import de.CodingDev.X.Server.API.JavaPlugin;
public class PluginExampleName extends JavaPlugin{
#Override
public void onEnable() {
System.out.println("Test");
getLogger().info("Plugin Enabled!");
}
}
I have make a new Plugin Loader the old version dosen´t work:
public void onLoad(){
File loc = new File("plugins");
File[] flist = loc.listFiles(new FileFilter() {
public boolean accept(File file) {return file.getPath().toLowerCase().endsWith(".jar");}
});
URL[] urls = new URL[flist.length];
for (int i = 0; i < flist.length; i++)
urls[i] = flist[i].toURI().toURL();
URLClassLoader ucl = new URLClassLoader(urls);
for(URL url : ucl.getURLs()){
s.getLogger().info("PluginLoader", "Found Plugin: " + url.getFile());
}
ServiceLoader<JavaPlugin> sl = ServiceLoader.load(JavaPlugin.class, ucl);
Iterator<JavaPlugin> apit = sl.iterator();
while (apit.hasNext()){
s.getLogger().info("PluginLoader", "Loading Plugin");
apit.next().onEnable();
}
}
The Log:
[LOG] [02.11.2013 15:26:09] [PluginLoader] [INFO] Loading Plugins...
[LOG] [02.11.2013 15:26:09] [PluginLoader] [INFO] Found Plugin: /C:/Users/René/Desktop/Server/plugins/CommandLogger.jar
What about making your class abstract with abstract onEnable method?
public abstract class JavaPlugin{
Server server;
public ServerManager getServer(){
return new ServerManager(server);
}
public ServerManager setServer(Server s){
server = s;
}
public PluginLogger getLogger(){
return new PluginLogger(server);
}
public abstract void onEnable();
}
And then you have to implement onEnable method in your subclasses.
You could then call the onEnable method of any dynamically generated subclass that extends the JavaPlugin.
The second way could be implementing the onEnable method in Javaplugin and then overriding the method in an extended class.
public class JavaPlugin {
...
public void onEnable(){
getLogger().info("JavaPlugin onEnable default implementation!");
}
}
In your PluginExample file:
public class PluginExample extends JavaPlugin{
#Override
public void onEnable(){
getLogger().info("Plugin Enabled!");
}
}