I'm writing a plugin for the Minecraft server implementation CraftBukkit, and I've come across a problem where I need to cast to a class that is found through reflection.
Here's the deal. The original code I wrote looked like this, with irrelevant parts removed:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import net.minecraft.server.v1_7_R3.EntityAnimal;
import net.minecraft.server.v1_7_R3.EntityHuman;
import org.bukkit.craftbukkit.v1_7_R3.entity.CraftAnimals;
import org.bukkit.craftbukkit.v1_7_R3.entity.CrafteEntity;
import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
public class Task extends BukkitRunnable {
private static final int MATING_DISTANCE = 14;
private final JavaPlugin plugin;
private final Random randomizer;
private boolean mateMode;
private double chance;
public Task(JavaPlugin plugin, double chance, boolean mateMode) {
this.plugin = plugin;
this.randomizer = new Random();
this.chance = chance;
this.mateMode = mateMode;
this.theTaskListener = listener;
}
public void run() {
List<World> worlds = plugin.getServer().getWorlds();
Iterator<World> worldIterator = worlds.iterator();
while (worldIterator.hasNext()) {
World world = worldIterator.next();
Collection<Animals> animals = world.getEntitiesByClass(Animals.class);
Iterator<Animals> animalIterator = animals.iterator();
while (animalIterator.hasNext()) {
Animals animal = (Animals) animalIterator.next();
EntityAnimal entity = (EntityAnimal) ((CraftEntity) ((CraftAnimals) animal)).getHandle();
EntityHuman feeder = null;
entity.f(feeder);
}
}
}
}
However, as you can see in the imports, this code imported the classes from only one version of the Minecraft server package - v1_7_R3. Now the problem is, I want to add support for more than that, and I want to be able to do that without creating separate versions of my plugin for each version of Minecraft. Despite the fact that most of the classes in the package are the same (at least ALL of those that I need) the package names are different, and therefore it can't be done with static imports (or at least I think so?)
So, I decided to use reflection in order to get the correct classes I need (this code is in another class):
private static final String[] requiredClasses = {
"net.minecraft.server.%s.EntityAnimal",
"net.minecraft.server.%s.EntityHuman",
"org.bukkit.craftbukkit.%s.entity.CraftAnimals",
"org.bukkit.craftbukkit.%s.entity.CraftEntity"
};
public static final String[] supportedVersions = {
"v1_7_R3",
"v1_7_R4"
};
public Class<?>[] initializeClasses() {
String correctVersion = null;
for (int i = 0; i < supportedVersions.length; i++) {
String version = supportedVersions[i];
boolean hadIssues = false;
for (int j = 0; j < requiredClasses.length; j++) {
String className = requiredClasses[j];
try {
Class.forName(String.format(className, version));
} catch (ClassNotFoundException e) {
getLogger().log(Level.INFO, String.format("The correct version isn't %s.", version));
hadIssues = true;
break;
}
}
if (!hadIssues) {
correctVersion = version;
break;
}
}
Class[] classes = new Class[requiredClasses.length];
if (correctVersion != null) {
getLogger().log(Level.INFO, String.format("The correct version is %s.", correctVersion));
for (int i = 0; i < requiredClasses.length; i++) {
String className = requiredClasses[i];
try {
classes[i] = Class.forName(String.format(className, correctVersion));
} catch (ClassNotFoundException e) {}
}
} else {
getLogger().log(Level.WARNING, "The version of Minecraft on this server is not supported.");
getLogger().log(Level.WARNING, "Due to this, the plugin will self-disable.");
getLogger().log(Level.WARNING, "To fix this issue, get build that supports your version.");
this.setEnabled(false);
}
return classes;
}
Now, this approach successfully retrieves the required classes in both versions currently supported. I passed these to the rewritten Task class using instance variables and an edited constructor, and I removed the version-specific imports:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
public class Task extends BukkitRunnable {
private static final int MATING_DISTANCE = 14;
private final JavaPlugin plugin;
private final Random randomizer;
private boolean mateMode;
private double chance;
private Class entityAnimal;
private Class entityHuman;
private Class craftAnimals;
private Class craftEntity;
public Task(JavaPlugin plugin, Class[] classes, double chance, boolean mateMode) {
this.plugin = plugin;
this.randomizer = new Random();
this.chance = chance;
this.mateMode = mateMode;
this.entityAnimal = classes[0];
this.entityHuman = classes[1];
this.craftAnimals = classes[2];
this.craftEntity = classes[3];
}
Now, how can I rewrite the Task.run() method so that it will use the reflection classes? There is a whole lot of typecasting involved and unfortunately it's all necessary due to the ridiculous amount of overloading in the Minecraft code. For example, the entity.f(EntityHuman human) method cannot simply be called by doing entity.f(null) because there are other overloading entity.f(Object object) methods.
I am open to all suggestions as I'm facing a dead-end here. If there is a better approach to the problem, I could change to that as well.
Thank you!
In an object oriented language, we have access to various design patterns that have been developed for exactly this purpose. We use two patterns in particular.
Adapter Pattern is used to provide the same interface to a number of different implementations. It is sometimes called a shim. You create one class per version of each server, importing libraries to each. The class implements an interface that they hold in common.
Factory Pattern is used to select among the adapter classes. You use whatever method you need to determine which server version you have, and it will create an object implementing the proper interface. The main code remains the same. It calls the factory to get an object that knows how to deal with the server.
The advantages of this approach are several. You don't pollute the name space by importing overlapping libraries. The main code is much less susceptible to change as new server versions are added; the only code that needs to be written is the new server shim and the factory that determines which adapter to produce.
Just a brainstorming idea. What if:
importing all supported versions
fully referencing the appropriate package's types
checking for the version that's targeted at a particular runtime (assumed it can be obtained somehow)
import net.minecraft.server.v1_7_R3.*;
import net.minecraft.server.v1_7_R4.*;
enum Version {
V1_7_R3,
V1_7_R4
}
Version currentVersion;
net.minecraft.server.v1_7_R3.EntityAnimal animal3;
net.minecraft.server.v1_7_R4.EntityAnimal animal4;
// obtain currentVersion
switch ( currentVersion ) {
case V1_7_R3:
animal3.method();
break;
case V1_7_R4:
animal4.method();
break;
}
This is somehow ugly, of course, but under the given circumstances it's the possibility that came into my mind first.
After reading Gerold Broser's response, I realized that I would have to somehow modify my approach in order to create some sort of a handler class that would carry out the version-specific operation - of course this would be an interface that would separately be implemented by a class per version.
However, this became a problem when I realized Maven wouldn't let me call two versions of the same groupid.artifactid object.
I quickly did some research and found mbaxter's Multiple Versions Tutorial as well as the AbstractionExamplePlugin implementation which perfectly demonstrates this approach.
The approach works perfectly and is what every Bukkit developer should use. Here's my finished plugin for further reference if necessary.
Related
Maybe what I am trying to do is not worthwhile, it sure feels that way after spending many days on it.
I have A Base Class shown here:
package jimmy.kilmer.com;
import java.awt.Color;
import jarPackageImports.AI;
import jarPackageImports.MovementAction;
import jarPackageImports.Info;
import jarPackageImports.PlayerAction;
public class GameAI extends AI {
public gameAI(Info info) {
super(info);
setJerseyNumber(32);
}
public Color getColor() {
return Color.RED;
}
public String getName() {
return "Usain Bolt";
}
public PlayerAction update() {
// TODO game movement actions
// all available methods not listed here...
info.getVelocity();
info.getX();
info.getY();
MovementAction steeringBehavior = null;
return steeringBehavior;
}
//basically used for testing setup
public int[][] populateAllPossibleNodes() {
int[][] allPossibleNodes = new int[screenWidth/20][screenHeight/20];
return allPossibleNodes;
}
}
I have been given a jar, that sets up the game environment. It uses reflection for the setup. I am not familiar with reflection, unfortunately, as I am more beginner level.
I have read a lot about TDD, and am convinced that can help me stay orderly, and code in a disciplined way. I have some say that TDD is not really useful for Game development, which the arguments may be true, in regard to making an "enjoyable game." But, from a purely coding standpoint, I remain steadfast in my believe that TDD is the way to go. But, that remains to be seen, since it is still theoretical. I would like to try it.
I have installed Junit 5, and have done many tutorials, but it's all pretty basic examples. My particular test case uses reflection, super classes, derived classes, dynamic data. My head is spinning.
My goal is just to get setup such that I can start doing some Test driven development.
Here is my Junit test class:
package jimmy.kilmer.com;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jarPackageImports.Info;
class GameAITest {
private GameAITest AIObject;
private jarPackageImports.Info info;
#BeforeEach
void setUp() throws Exception {
AIObject = new GameAITest(info);
#AfterEach
void tearDown() throws Exception {
}
#Test
void testPopulateAllPossibleNodes() {
// 1. given/arrange
int[][] array1 = new int[80][65];
// 2. when/act
int[][] array2 = AIObject.populateAllPossibleNodes();
// 3. then/assert
assertArrayEquals(array1, array2);
}
}
That is my best stab so far, but it still get a compile error. Specifically:
java.lang.NullPointerException:Cannot invoke "jarPackageImports.Info.getScene()" because "this.info" is null
In summation:
maybe everything I am trying is rubbish?
Do I need to use dynamic junit testing? I would have to read up on that.
Do I need to mock (use Mockito?) to instantiate an object to test? I would need to read up on that as well.
Is it possible to instantiate an object from GameAI? Do I need to/how would I use relection to do that? class.getConstructors()? And, I would have to read up on that.
thanks in advance.
I have a bunch of source files for java classes. I want to find those classes which are annotated by a given annotation class. The names of those classes should be written to a service provider list file.
Is there any machinery I could use to help me with this task? Or do I have to implement this myself from scratch?
If I had to do this myself, there are several approaches I can think of.
Write an Ant Task in Java. Have it create a ClassLoader using a suitable (probably configurable) class path. Use that loader to (attempt to) load the classes matching the input files, in order to inspect their annotations. Requires annotation retention at runtime, and full initialization of all involved classes and their dependencies.
Use javap to inspect the classes. Since I don't know of a programmatic interface to javap (do you?), this probably means iterating over the files and running a new process for each of them, then massaging the created output in a suitable way. Perhaps a <scriptdef>-ed task could be used for this. This would work with class-file annotation retention, and require no initialization.
Use an annotation processor to collect the information at compile-time. This should be able to work with sourcecode-only retention. But I have no experience writing or using annotation compilers, so I'm not sure this will work, and will need a lot of research to figure out some of the details. In particular how to activate the task for use by ant (Java 6 annotation processing configuration with Ant gives some pointers on this, as does What is the default annotation processors discovery process?) and when to create the output file (in each round, or only in the last round).
Which of these do you think has the greatest chances of success? Can you suggest code samples for one of these, which might be close to what I want and which I could adapt appropriately?
Encouraged by Thomas' comment, I gave approach 3 a try and got the following annotation processor working reasonably well:
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.tools.StandardLocation;
#SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationServiceProcessor extends AbstractProcessor {
// Map name of the annotation to name of the corresponding service interface
private static Map<String, String> annotationToServiceMap = new HashMap<>();
static {
// Adapt this to your use, or make it configurable somehow
annotationToServiceMap.put("Annotation1", "Service1");
annotationToServiceMap.put("Annotation2", "Service2");
}
#Override public Set<String> getSupportedAnnotationTypes() {
return annotationToServiceMap.keySet();
}
// Map name of the annotation to list of names
// of the classes which carry that annotation
private Map<String, List<String>> classLists;
#Override public void init(ProcessingEnvironment env) {
super.init(env);
classLists = new HashMap<>();
for (String ann: getSupportedAnnotationTypes())
classLists.put(ann, new ArrayList<String>());
}
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment env) {
for (TypeElement ann: annotations) {
List<String> classes =
classLists.get(ann.getQualifiedName().toString());
for (Element elt: env.getElementsAnnotatedWith(ann)) {
QualifiedNameable qn = (QualifiedNameable)elt;
classes.add(qn.getQualifiedName().toString());
}
}
if (env.processingOver()) { // Only write results at the end
for (String ann: getSupportedAnnotationTypes()) {
try {
write(ann, classLists.get(ann));
} catch (IOException e) {
throw new RuntimeException(e); // UGLY!
}
}
}
return true;
}
// Write the service file for each annotation we found
private void write(String ann, List<String> classes) throws IOException {
if (classes.isEmpty())
return;
String service = annotationToServiceMap.get(ann);
Writer w = processingEnv.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT,
"", "META-INF/services/" + service)
.openWriter();
classes.sort(null); // Make the processing order irrelevant
for (String cls: classes) {
w.write(cls);
w.write('\n');
}
w.close();
}
}
So far I've hooked this up to ant using <compilerarg>s from https://stackoverflow.com/a/3644624/1468366. I'll try something better and if I succeed will edit this post to include some ant snippet.
I wish to parse java source code files, and extract the methods source code.
I would need a method like this :
/** Returns a map with key = method name ; value = method source code */
Map<String,String> getMethods(File javaFile);
Is there a simple way to achieve this, a library to help me build my method, etc. ?
Download the java parser from https://javaparser.org/
You'll have to write some code. This code will invoke the parser... it will return you a CompilationUnit:
InputStream in = null;
CompilationUnit cu = null;
try
{
in = new SEDInputStream(filename);
cu = JavaParser.parse(in);
}
catch(ParseException x)
{
// handle parse exceptions here.
}
finally
{
in.close();
}
return cu;
Note: SEDInputStream is a subclass of input stream. You can use a FileInputStream if you want.
You'll have to create a visitor. Your visitor will be easy because you're only interested in methods:
public class MethodVisitor extends VoidVisitorAdapter
{
public void visit(MethodDeclaration n, Object arg)
{
// extract method information here.
// put in to hashmap
}
}
To invoke the visitor, do this:
MethodVisitor visitor = new MethodVisitor();
visitor.visit(cu, null);
I implemented lee's suggestion, there is no need of third party libraries to achieve that, the following example prints the names of the methods (tested with Java 17 but should work with Java 1.6 with minor changes):
import com.sun.source.util.JavacTask;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class Main {
public static void main(final String[] args) throws Exception {
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)) {
final Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(new File(args[0])));
final JavacTask javacTask = (JavacTask) compiler.getTask(null, fileManager, null, null, null, compilationUnits);
final Iterable<? extends CompilationUnitTree> compilationUnitTrees = javacTask.parse();
final ClassTree classTree = (ClassTree) compilationUnitTrees.iterator().next().getTypeDecls().get(0);
final List<? extends Tree> classMemberList = classTree.getMembers();
final List<MethodTree> classMethodMemberList = classMemberList.stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.collect(Collectors.toList());
// just prints the names of the methods
classMethodMemberList.stream().map(MethodTree::getName)
.forEachOrdered(System.out::println);
}
}
}
Note that other solutions except ANTLR don't support very latest versions of Java, javaparser doesn't fully support 19 currently (January 2023), JavaCC doesn't seem to support Java >= 9 according to its public documentation.
Federico Tomassetti wrote in 2016 that there was no parsing functionality as part of the JDK, I replied that he was wrong. I have nothing against third party libraries but providing false information to developers in order to promote her/his stuff is not honest and is not the kind of behavior I expect on StackOverflow. I use some classes and APIs available in Java since Java 1.6 released in December 2006.
I have a project where I defined a JNA wrapper to Windows kernel32 library, upon which I have made several helpers that are not critical for the project but increase the integration to the platform (namely: system debug logging with OutputDebugString + DebugView and Mailslot messaging features).
Here is my JNA defines:
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import com.sun.jna.Native;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIFunctionMapper;
import com.sun.jna.win32.W32APITypeMapper;
public interface JnaKernel32 extends StdCallLibrary {
//StdCall is needed for lib kernel32
#SuppressWarnings("unchecked")
Map ASCII_OPTIONS = new HashMap(){
{
put(OPTION_TYPE_MAPPER, W32APITypeMapper.ASCII);
put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.ASCII);
}
};
#SuppressWarnings("unchecked")
Map UNICODE_OPTIONS = new HashMap(){
{
put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
}
};
Map DEFAULT_OPTIONS = Boolean.getBoolean("w32.ascii") ? ASCII_OPTIONS : UNICODE_OPTIONS;
JnaKernel32 INSTANCE = (JnaKernel32) Native.loadLibrary("kernel32", JnaKernel32.class, DEFAULT_OPTIONS);
//some system defines
//...
}
And the Mailslot definition:
public class Mailslot {
static JnaKernel32 kernel32 = JnaKernel32.INSTANCE;
boolean localMailslot = false;
int lastError = 0;
private int hMailslot = JnaKernel32.INVALID_HANDLE_VALUE;
//...
}
And in some places I have also
static JnaKernel32 kernel32 = JnaKernel32.INSTANCE; //to call OutputDebugString
//...
kernel32.OutputDebugString("some debug message");
My concern is that project could be used also on GNU/Linux or MacOS X but obviously the Native.loadLibrary fails at runtime if executed on e.g. OSX.
I am considering about
either porting the native feature with other JNA binding
or simply disable the existing Windows kernel32 binding when running on another platform as it is just convenient but not mandatory helpers.
How can I isolate the platform-specific features and calls that are made? I was thinking about moving the JNA part to a runtime loaded plugin perhaps?
The answer is really a general software development strategy.
Identify the API that your client code needs
In this case, it might be MailsSlot.sendMessage(int destID, String msg)
Abstract the implementation details behind that API
public interface MailSlot {
void sendMessage(int destId, String msg);
}
Provide one or more concrete implementations to meet the API contract
public class Win32MailSlot implements MailSlot {
public void sendMessage(int destId, String msg) {
// Do stuff here that's windows-specific
}
}
public class OSXMailSlot implements MailSlot {
public void sendMessage(int destId, String msg) {
// Do stuff here that's windows-specific
}
}
Choose the appropriate implementation at runtime:
MailSlot mslot = Platform.IS_WINDOWS ? new Win32MailSlot() : new OSXMailSlot();
After a few implementations, you may find some duplicated code, which you might then refactor into an abstract base class shared among the platform-specific implementations.
See the JNA platform FileUtils class for an example of such an strategy.
I've been learning the Weka API on my own for the past month or so (I'm a student). What I am doing is writing a program that will filter a specific set of data and eventually build a bayes net for it, and a week ago I had finished my discretization class and attribute selection class. Just a few days ago I realized that I needed to change my discretization function to supervised and ended up using the default Fayyad & Irani method, after I did this I began to get this error in my attribute selection class:
Exception in thread "main" weka.core.WekaException:
weka.attributeSelection.CfsSubsetEval: Not enough training instances with class labels (required: 1, provided: 0)!
at weka.core.Capabilities.test(Capabilities.java:1138)
at weka.core.Capabilities.test(Capabilities.java:1023)
at weka.core.Capabilities.testWithFail(Capabilities.java:1302)
at weka.attributeSelection.CfsSubsetEval.buildEvaluator(CfsSubsetEval.java:331)
at weka.attributeSelection.AttributeSelection.SelectAttributes(AttributeSelection.java:597)
at weka.filters.supervised.attribute.AttributeSelection.batchFinished(AttributeSelection.java:456)
at weka.filters.Filter.useFilter(Filter.java:663)
at AttributeSelectionFilter.selectionFilter(AttributeSelectionFilter.java:29)
at Runner.main(Runner.java:70)
My attribute selection before the change worked just fine, so I think that I may have done something wrong in my discretize class. My other part of this question relates to that, because I also noticed that my discretize class does not appear to really be discretizing the data; it's just putting all the numeric data into ONE range, not binning it strategically like the Fayyad & Irani should.
Here is my discretize class:
import weka.core.Instances;
import weka.filters.Filter;
import weka.filters.supervised.attribute.Discretize;
import weka.filters.unsupervised.attribute.NumericToNominal;
public class DiscretizeFilter
{
private Instances data;
private boolean sensitiveOption;
private Filter filter = new Discretize();
public DiscretizeFilter(Instances data, boolean sensitiveOption)
{
this.data = data;
this.sensitiveOption = sensitiveOption;
}
public Instances discreteFilter() throws Exception
{
NumericToNominal nm = new NumericToNominal();
nm.setInputFormat(data);
Filter.useFilter(data, nm);
Instances nominalData = nm.getOutputFormat();
if(sensitiveOption)//if the user wants extra sensitivity
{
String options[] = new String[1];
options[0] = options[0];
options[2] = "-E";
((Discretize) filter).setOptions(options);
}
filter.setInputFormat(nominalData);
Filter.useFilter(nominalData,filter);
return filter.getOutputFormat();
}
}
Here is my attribute selection class:
import weka.attributeSelection.BestFirst;
import weka.attributeSelection.CfsSubsetEval;
import weka.core.Instances;
import weka.filters.supervised.attribute.AttributeSelection;
public class AttributeSelectionFilter
{
public Instances selectionFilter(Instances data) throws Exception
{
AttributeSelection filter = new AttributeSelection();
for(int i = 0; i < data.numInstances(); i++)
{
filter.input(data.instance(i));
}
CfsSubsetEval eval = new CfsSubsetEval();
BestFirst search = new BestFirst();
filter.setSearch(search);
filter.setEvaluator(eval);
filter.setInputFormat(data);
AttributeSelection.useFilter(data, filter);
return filter.getOutputFormat();
}
public int attributeCounter(Instances data)
{
return data.numAttributes();
}
}
Any help would be greatly appreciated!!!
Internally Weka stores attribute values as doubles. It appears that an exception was thrown because every single instance in your dataset (data) is "missing a class", i.e. was given an internal class attribute value NaN ("not a number") for whatever reason. I would recommend to double-check if data's class attribute was created/set correctly.
I figured it out, it was my mistake of misunderstanding the description of the method "outputFormat()" in the Discretize class. I instead got the filtered instances from the useFilter() and that solved my problems! I was just giving the attribute selection filter the wrong type of data.