Parsing file .java for recognize String methods [duplicate] - java

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.

Related

Any way to programmatically determine which Java language features are available on current platform?

I was wondering if there is a Java API that could tell you whether a particular language feature (e.g. "diamond" operator) is available on the current platform.
(In other words, what I'm trying to do is analogous to "browser sniffing" in JavaScript.)
This would be really handy in meta-programming (writing a Java program that generates Java source code.
The best solution I've found so far is to parse System.getProperty("java.specification.version") and check whether it's ≥ the version that introduced this feature, but I'm not 100% sure that this property is available in all JVMs (or even whether it conforms to the same syntax in all JVMs). Another minor annoyance with this approach is that you have to take the extra step of looking up which version of Java introduced the language feature you're interested in. Not a big deal, since that info is pretty easy to google, but ideally it would be nice if there was an API that could readily provide the info, for example:
code.append("Map<Integer, String> map = ");
if (javax.meta.JavaVersion.getCurrentVersion().supportsDiamond()) {
code.append("new Map<>();");
} else {
code.append("new Map<Integer, String>();");
}
Obviously there's no package named javax.meta, but I was wondering if there might already be an existing solution for this problem that's cleaner than parsing the "java.specification.version" property.
Update: I just realized that Package#getSpecificationVersion() also provides the same value as System.getProperty("java.specification.version") but is probably more reliable, because System properties are mutable. In other words, the best way to get the Java spec version is probably to call Package#getSpecificationVersion() on any "built-in" package. For example: String.class.getPackage().getSpecificationVersion()
This will give you the version of java that the system is running.
System.getProperty("java.version")
If you're running Java 9 and greater you can use:
Runtime.Version version = Runtime.version();
Java Docs
Just a note the Java versioning naming standard changed at Java 9 as well.
Java Version: 1.7, 1.8, 9, 10, 11
I don't have a solution for you to check for specific features.
A feature could be: JDK 10
Method : Optional.orElseThrow()
API : API for Creating Unmodifiable Collections
System Property : Fore example, to Disable JRE Last Usage Tracking
GC enhancement (Full parallel)
Javadoc Support : (For Multiple Stylesheets)
It Could also be a REMOVAL of feature : also in Java 10
Removal of Support for Using Old LookAndFeel
Removal of Runtime.getLocalizedInputStream and getLocalizedOutputStream Methods
And so on..
So it is hard to check or discover programmatcally if a new feature exist or of it has been removed UNLESS you know what you are looking for, it needs to to be provided by Oracle itself as a documentation, feature name and description.
If we are going to create and API for that, we must get the list first from Oracle docs, and then do required checks for each feature to discover the current version or if it is supported.
Following is an example to programmaticaly check the compiler for a specific functionality.
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Arrays;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;
public class CompileSourceInMemory {
public static void main(String args[]) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
StringWriter writer = new StringWriter();
PrintWriter out = new PrintWriter(writer);
out.println("public class HelloWorld {");
out.println(" public static void main(String args[]) {");
out.println(" System.out.println(\"This is in another java file\");");
out.println(" }");
out.println("}");
out.close();
JavaFileObject file = new JavaSourceFromString("HelloWorld", writer.toString());
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits);
boolean success = task.call();
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
System.out.println(diagnostic.getCode());
System.out.println(diagnostic.getKind());
System.out.println(diagnostic.getPosition());
System.out.println(diagnostic.getStartPosition());
System.out.println(diagnostic.getEndPosition());
System.out.println(diagnostic.getSource());
System.out.println(diagnostic.getMessage(null));
}
System.out.println("Success: " + success);
if (success) {
try {
Class.forName("HelloWorld").getDeclaredMethod("main", new Class[] { String[].class })
.invoke(null, new Object[] { null });
} catch (ClassNotFoundException e) {
System.err.println("Class not found: " + e);
} catch (NoSuchMethodException e) {
System.err.println("No such method: " + e);
} catch (IllegalAccessException e) {
System.err.println("Illegal access: " + e);
} catch (InvocationTargetException e) {
System.err.println("Invocation target: " + e);
}
}
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),Kind.SOURCE);
this.code = code;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
See JDK 10 features
I just discovered another partial solution: javax.lang.model.SourceVersion. It still doesn't solve the problem entirely, but seems like a step in the right direction.
So instead of my old approach,
if (System.getProperty("java.specification.version").compareTo("1.7") >= 0)
// do something that requires Java 7
you could write:
if (SourceVersion.latestSupported().ordinal() >= SourceVersion.RELEASE_7.ordinal())
// do something that requires Java 7
(because SourceVersion has its enum constants declared in ascending order)
This class also provides the static methods isKeyword, isName, isIdentifier, which might be useful, but it was already reasonably easy to derive some of this info (e.g. Character.isJavaIdentifierPart(cp)).
Under the hood, SourceVersion also relies on reading the "java.specification.version" system property:
private static SourceVersion getLatestSupported() {
try {
String specVersion = System.getProperty("java.specification.version");
if ("1.8".equals(specVersion))
return RELEASE_8;
else if("1.7".equals(specVersion))
return RELEASE_7;
else if("1.6".equals(specVersion))
return RELEASE_6;
} catch (SecurityException se) {}
return RELEASE_5;
}
That's some smelly code right there! e.g. if someone calls System.setProperty("java.specification.version", "6.9"); then SourceVersion.latestSupported() will return RELEASE_5 ;)
I'm surprised that even Sun didn't provide a "magic" way to get the Java version directly from the VM (like the various internal "magic" operations exposed by the sun.misc package)

Compiling and using Java from within another .jar file [duplicate]

This question already has answers here:
How to provide an interface to JavaCompiler when compiling a source file dynamically?
(3 answers)
Closed 5 years ago.
The community reviewed whether to reopen this question 4 months ago and left it closed:
Original close reason(s) were not resolved
(This question is similar to many questions I have seen but most are not specific enough for what I am doing)
Background:
The purpose of my program is to make it easy for people who use my program to make custom "plugins" so to speak, then compile and load them into the program for use (vs having an incomplete, slow parser implemented in my program). My program allows users to input code into a predefined class extending a compiled class packaged with my program. They input the code into text panes then my program copies the code into the methods being overridden. It then saves this as a .java file (nearly) ready for the compiler. The program runs javac (java compiler) with the saved .java file as its input.
My question is, how do I get it so that the client can (using my compiled program) save this java file (which extends my InterfaceExample) anywhere on their computer, have my program compile it (without saying "cannot find symbol: InterfaceExample") then load it and call the doSomething() method?
I keep seeing Q&A's using reflection or ClassLoader and one that almost described how to compile it, but none are detailed enough for me/I do not understand them completely.
Take a look at JavaCompiler
The following is based on the example given in the JavaDocs
This will save a File in the testcompile directory (based on the package name requirements) and the compile the File to a Java class...
package inlinecompiler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class InlineCompiler {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder(64);
sb.append("package testcompile;\n");
sb.append("public class HelloWorld implements inlinecompiler.InlineCompiler.DoStuff {\n");
sb.append(" public void doStuff() {\n");
sb.append(" System.out.println(\"Hello world\");\n");
sb.append(" }\n");
sb.append("}\n");
File helloWorldJava = new File("testcompile/HelloWorld.java");
if (helloWorldJava.getParentFile().exists() || helloWorldJava.getParentFile().mkdirs()) {
try {
Writer writer = null;
try {
writer = new FileWriter(helloWorldJava);
writer.write(sb.toString());
writer.flush();
} finally {
try {
writer.close();
} catch (Exception e) {
}
}
/** Compilation Requirements *********************************************************************************************/
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// This sets up the class path that the compiler will use.
// I've added the .jar file that contains the DoStuff interface within in it...
List<String> optionList = new ArrayList<String>();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + File.pathSeparator + "dist/InlineCompiler.jar");
Iterable<? extends JavaFileObject> compilationUnit
= fileManager.getJavaFileObjectsFromFiles(Arrays.asList(helloWorldJava));
JavaCompiler.CompilationTask task = compiler.getTask(
null,
fileManager,
diagnostics,
optionList,
null,
compilationUnit);
/********************************************************************************************* Compilation Requirements **/
if (task.call()) {
/** Load and execute *************************************************************************************************/
System.out.println("Yipe");
// Create a new custom class loader, pointing to the directory that contains the compiled
// classes, this should point to the top of the package structure!
URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});
// Load the class from the classloader by name....
Class<?> loadedClass = classLoader.loadClass("testcompile.HelloWorld");
// Create a new instance...
Object obj = loadedClass.newInstance();
// Santity check
if (obj instanceof DoStuff) {
// Cast to the DoStuff interface
DoStuff stuffToDo = (DoStuff)obj;
// Run it baby
stuffToDo.doStuff();
}
/************************************************************************************************* Load and execute **/
} else {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Error on line %d in %s%n",
diagnostic.getLineNumber(),
diagnostic.getSource().toUri());
}
}
fileManager.close();
} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exp) {
exp.printStackTrace();
}
}
}
public static interface DoStuff {
public void doStuff();
}
}
Now updated to include suppling a classpath for the compiler and loading and execution of the compiled class!
I suggest using the Java Runtime Compiler library. You can give it a String in memory and it will compile and load the class into the current class loader (or one of your choice) and return the Class loaded. Nested classes are also loaded. Note: this works entirely in memory by default.
e.g.
// dynamically you can call
String className = "mypackage.MyClass";
String javaCode = "package mypackage;\n" +
"public class MyClass implements Runnable {\n" +
" public void run() {\n" +
" System.out.println(\"Hello World\");\n" +
" }\n" +
"}\n";
Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode);
Runnable runner = (Runnable) aClass.newInstance();
runner.run();

Dynamic Typecasting in Java

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.

Custom builtin in Jena

This is the code I have written but the new built-in does not seem to work. I get the error:
Exception in thread "main" com.hp.hpl.jena.reasoner.rulesys.impl.LPRuleSyntaxException: Syntax error in backward rule: matematica Unknown builtin operation mysum
Can anyone tell me where the error is? Here is my code:
package JenaRules;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
import org.semanticweb.owlapi.model.OWLOntologyCreationException;
import org.semanticweb.owlapi.model.OWLOntologyStorageException;
import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.ResultSetFormatter;
import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.reasoner.Reasoner;
import com.hp.hpl.jena.reasoner.rulesys.*;
import com.hp.hpl.jena.reasoner.rulesys.builtins.BaseBuiltin;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.vocabulary.RDFS;
import com.hp.hpl.jena.vocabulary.ReasonerVocabulary;
public class RulesOntology_MT {
public static void main(String[] args) throws OWLOntologyStorageException,
OWLOntologyCreationException, IOException {
BuiltinRegistry.theRegistry.register(new BaseBuiltin() {
#Override
public String getName() {
return "mysum";
}
#Override
public int getArgLength() {
return 2;
}
#Override
public boolean bodyCall(Node[] args, int length, RuleContext context) {
checkArgs(length, context);
BindingEnvironment env = context.getEnv();
Node n1 = getArg(0, args, context);
Node n2 = getArg(1, args, context);
if (n1.isLiteral() && n2.isLiteral()) {
Object v1 = n1.getLiteralValue();
Object v2 = n2.getLiteralValue();
Node sum = null;
if (v1 instanceof Number && v2 instanceof Number) {
Number nv1 = (Number)v1;
Number nv2 = (Number)v2;
int sumInt = nv1.intValue()+nv2.intValue();
sum = Util.makeIntNode(sumInt);
return env.bind(args[2], sum);
}
}
return false;
}
});
// NON SERVE
// final String exampleRuleString2 =
// "[mat1: equal(?s ?p )\n\t-> print(?s ?p ?o),\n\t (?s ?p ?o)\n]"+
// "";
final String exampleRuleString =
"[matematica:"+
"(?p http://www.semanticweb.org/prova_rules_M#totale_crediti ?x)"+
" -> " +
"(?p rdf:type http://www.semanticweb.org/prova_rules_M#:Persona)"+
"(?e rdf:type http://www.semanticweb.org/prova_rules_M#:Esame)"+
"(?p http://www.semanticweb.org/prova_rules_M#:haSostenutoEsameDi ?e)"+
"(?e http://www.semanticweb.org/prova_rules_M/persona#crediti_esame ?cr)"+
"mysum(?cr,2)"+
"]";
System.out.println(exampleRuleString);
/* I tend to use a fairly verbose syntax for parsing out my rules when I construct them
* from a string. You can read them from whatever other sources.
*/
final List<Rule> rules;
try( final BufferedReader src = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(exampleRuleString.getBytes()))) ) {
rules = Rule.parseRules(Rule.rulesParserFromReader(src));
}
/* Construct a reasoner and associate the rules with it */
// create an empty non-inferencing model
GenericRuleReasoner reasoner = (GenericRuleReasoner) GenericRuleReasonerFactory.theInstance().create(null);
reasoner.setRules(rules);
/* Create & Prepare the InfModel. If you don't call prepare, then
* rule firings and inference may be deferred until you query the
* model rather than happening at insertion. This can make you think
* that your Builtin is not working, when it is.
*/
InfModel infModel = ModelFactory.createInfModel(reasoner, ModelFactory.createDefaultModel());
infModel.prepare();
infModel.createResource(RDFS.Class);
//write down the result in RDFXML form
infModel.write(System.out);
}
}
Using the code that you provided, and Apache Jena 2.11.1, I cannot replicate the exception you are getting. Do note that when you call BuiltinRegistry.theRegistry.register(...), you are telling the reasoner that the builtin exists.
Solution
The exception that you are getting is likely because, in your actual code, you are not calling BuiltinRegistry.theRegistry.register(...) prior to calling Rule.parseRules(Rule.rulesParserFromReader(src));, so as far as the rule parser is concerned, you are using a Builtin which doesn't exist. To fix it, merely call register before parsing your rules. The toy example provided does not have this problem.
Using the example provided
I also noted that the provided code example did not include anything that would actually stimulate the rule to fire, so, in lieu of infModel.createResource(RDFS.Class);, I added the following lines:
final Resource s = infModel.createResource();
final Property p = infModel.createProperty("http://www.semanticweb.org/prova_rules_M#totale_crediti");
final Resource o = infModel.createResource();
infModel.add(s,p,o);
This stimulated the rule to fire, and led to the following exception trace:
com.hp.hpl.jena.reasoner.rulesys.BuiltinException: Error in clause of rule (matematica) mysum: builtin mysum not usable in rule heads
at com.hp.hpl.jena.reasoner.rulesys.builtins.BaseBuiltin.headAction(BaseBuiltin.java:86)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETEConflictSet.execute(RETEConflictSet.java:184)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETEConflictSet.add(RETEConflictSet.java:81)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETEEngine.requestRuleFiring(RETEEngine.java:249)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETETerminal.fire(RETETerminal.java:80)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETEClauseFilter.fire(RETEClauseFilter.java:227)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETEEngine.inject(RETEEngine.java:469)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETEEngine.runAll(RETEEngine.java:451)
at com.hp.hpl.jena.reasoner.rulesys.impl.RETEEngine.add(RETEEngine.java:174)
at com.hp.hpl.jena.reasoner.rulesys.FBRuleInfGraph.performAdd(FBRuleInfGraph.java:654)
at com.hp.hpl.jena.graph.impl.GraphBase.add(GraphBase.java:202)
at com.hp.hpl.jena.rdf.model.impl.ModelCom.add(ModelCom.java:1138)
at SO.test(SO.java:108)
As a note: my test class is SO.java and line 108 is where we call infModel.add(s,p,o).
The exception that I get is different than the exception you encountered, but it is worth explaining. The implementation that you provided implements Builtin#bodyCall(...), but not Builtin#headAction(...). We can see the exception is thrown from BaseBuiltin#headAction(...). This default behavior assumes that you didn't implement the method because your Builtin doesn't support it. In the toy problem, this is correct behavior because the example implementation cannot be used in rule heads.

How do you dynamically compile and load external java classes? [duplicate]

This question already has answers here:
How to provide an interface to JavaCompiler when compiling a source file dynamically?
(3 answers)
Closed 5 years ago.
The community reviewed whether to reopen this question 4 months ago and left it closed:
Original close reason(s) were not resolved
(This question is similar to many questions I have seen but most are not specific enough for what I am doing)
Background:
The purpose of my program is to make it easy for people who use my program to make custom "plugins" so to speak, then compile and load them into the program for use (vs having an incomplete, slow parser implemented in my program). My program allows users to input code into a predefined class extending a compiled class packaged with my program. They input the code into text panes then my program copies the code into the methods being overridden. It then saves this as a .java file (nearly) ready for the compiler. The program runs javac (java compiler) with the saved .java file as its input.
My question is, how do I get it so that the client can (using my compiled program) save this java file (which extends my InterfaceExample) anywhere on their computer, have my program compile it (without saying "cannot find symbol: InterfaceExample") then load it and call the doSomething() method?
I keep seeing Q&A's using reflection or ClassLoader and one that almost described how to compile it, but none are detailed enough for me/I do not understand them completely.
Take a look at JavaCompiler
The following is based on the example given in the JavaDocs
This will save a File in the testcompile directory (based on the package name requirements) and the compile the File to a Java class...
package inlinecompiler;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
public class InlineCompiler {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder(64);
sb.append("package testcompile;\n");
sb.append("public class HelloWorld implements inlinecompiler.InlineCompiler.DoStuff {\n");
sb.append(" public void doStuff() {\n");
sb.append(" System.out.println(\"Hello world\");\n");
sb.append(" }\n");
sb.append("}\n");
File helloWorldJava = new File("testcompile/HelloWorld.java");
if (helloWorldJava.getParentFile().exists() || helloWorldJava.getParentFile().mkdirs()) {
try {
Writer writer = null;
try {
writer = new FileWriter(helloWorldJava);
writer.write(sb.toString());
writer.flush();
} finally {
try {
writer.close();
} catch (Exception e) {
}
}
/** Compilation Requirements *********************************************************************************************/
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// This sets up the class path that the compiler will use.
// I've added the .jar file that contains the DoStuff interface within in it...
List<String> optionList = new ArrayList<String>();
optionList.add("-classpath");
optionList.add(System.getProperty("java.class.path") + File.pathSeparator + "dist/InlineCompiler.jar");
Iterable<? extends JavaFileObject> compilationUnit
= fileManager.getJavaFileObjectsFromFiles(Arrays.asList(helloWorldJava));
JavaCompiler.CompilationTask task = compiler.getTask(
null,
fileManager,
diagnostics,
optionList,
null,
compilationUnit);
/********************************************************************************************* Compilation Requirements **/
if (task.call()) {
/** Load and execute *************************************************************************************************/
System.out.println("Yipe");
// Create a new custom class loader, pointing to the directory that contains the compiled
// classes, this should point to the top of the package structure!
URLClassLoader classLoader = new URLClassLoader(new URL[]{new File("./").toURI().toURL()});
// Load the class from the classloader by name....
Class<?> loadedClass = classLoader.loadClass("testcompile.HelloWorld");
// Create a new instance...
Object obj = loadedClass.newInstance();
// Santity check
if (obj instanceof DoStuff) {
// Cast to the DoStuff interface
DoStuff stuffToDo = (DoStuff)obj;
// Run it baby
stuffToDo.doStuff();
}
/************************************************************************************************* Load and execute **/
} else {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Error on line %d in %s%n",
diagnostic.getLineNumber(),
diagnostic.getSource().toUri());
}
}
fileManager.close();
} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exp) {
exp.printStackTrace();
}
}
}
public static interface DoStuff {
public void doStuff();
}
}
Now updated to include suppling a classpath for the compiler and loading and execution of the compiled class!
I suggest using the Java Runtime Compiler library. You can give it a String in memory and it will compile and load the class into the current class loader (or one of your choice) and return the Class loaded. Nested classes are also loaded. Note: this works entirely in memory by default.
e.g.
// dynamically you can call
String className = "mypackage.MyClass";
String javaCode = "package mypackage;\n" +
"public class MyClass implements Runnable {\n" +
" public void run() {\n" +
" System.out.println(\"Hello World\");\n" +
" }\n" +
"}\n";
Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode);
Runnable runner = (Runnable) aClass.newInstance();
runner.run();

Categories