Is there a way to give the java compiler some kind of variable that is accessible to the running java code?
In C/C++ I can give the compile -DKEY=VALUE and that would cause the preprocessor to have a #define for KEY equals to VALUE. I can then check this value in compile time to effect what code is being compiled.
I found java's -D, but that puts values give the the java command line in System.getProperty(). I want an argument give in compile time, not invocation time.
javac has the
-Akey[=value]
commandline option to pass information to annotation processors.
With java annotations it is possible to generate additional code on the fly, which can be configured on command line. It allow to produce more source code, configuration files, xml files, ... The main limitation is that you are allowed only to (re)generate new source files, you cannot modify existing ones.
Below is a short tutorial on how to allow from javac command specify parameters which will be visible in Java code. How usefull is that? Ie. you could specify a boolean option which would disable some parts of code, I am preety sure this parts of code could be removed using tools like proguard - or even optimized out by javac. Other uses is to specify new version number. Those use cases are mostly what c++ marcros are used for.
So, you need :
a dummy annotation class which will allow processor to run. It should be specified only once in your application.
a processor class which will run for above dummy annotation, and generate options class. It will also read options from javac command line.
a dummy Main class for testing purposes.
You will have to also compile your processor file before compiling Main class. This of course is done only when processor class is modified. All the three files are at the bottom. Now the compilation looks as follows (I am on windows):
Compile processor:
javac .\com\example\ConfigWritterAnnotationProcessor.java
Then Main.java with additional parameters to processor:
javac -processor com.example.ConfigWritterAnnotationProcessor -AtextToPrint="Hello World!" -AenablePrint=true ./com/example/Main.java
And thats all, now you may run Main.class and it will use Options class generated during compilation with above parameters set. It will look as follows:
package com.example;
public class Options {
public static final String textToPrint = "Hello World!";
public static final boolean enablePrint = true;
}
ProcessorStarterAnnotation.java
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.TYPE})
public #interface ProcessorStarterAnnotation {
}
Main.java
package com.example;
#ProcessorStarterAnnotation
public class Main {
public static void main(String[] args) {
if ( com.example.Options.enablePrint ) {
System.out.println(com.example.Options.textToPrint);
}
else {
System.out.println("Print disabled");
}
}
}
ConfigWritterAnnotationProcessor.java
package com.example;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Map;
import java.util.Set;
#SupportedAnnotationTypes("com.example.ProcessorStarterAnnotation")
#SupportedSourceVersion(SourceVersion.RELEASE_6)
#SupportedOptions({"textToPrint", "enablePrint"})
public class ConfigWritterAnnotationProcessor extends AbstractProcessor {
private Map<String,String> options;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
options = processingEnv.getOptions();
}
#Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment currentRound) {
if (!currentRound.processingOver()) {
// This for-s are because processor is also run on newly created Options class.
for (TypeElement te : annotations) {
for (Element e : currentRound.getElementsAnnotatedWith(te)) {
try {
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating com.example.Options");
JavaFileObject javaFile = processingEnv.getFiler().createSourceFile("com.example.Options");
Writer w = javaFile.openWriter();
try {
PrintWriter pw = new PrintWriter(w);
pw.println("package com.example;");
pw.println("public class Options {");
pw.println(" public static final String textToPrint = \"" + options.get("textToPrint") + "\";");
pw.println(" public static final boolean enablePrint = " + options.get("enablePrint") + ";");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
}
}
return false;
}
}
There is nothing like this in Java. Compile time constants must be declared in source code, and as far as I know, there is no pre-processor.
BTW, you could use flags given to java command line (-D args) to initialize java constants at runtime, that would mimic what you are looking for.
Ex:
class Foo {
private static final String BAR;
static {
String foobar = System.getProperty("foo.bar");
if(foobar != null && foobar.length()>0) {
BAR = foobar;
} else {
BAR = "somedefaultvalue";
}
}
}
Invoke with java Xxx -Dfoo.bar=foobar
As there is no notion of preprocessing in Java, a solution is to design your own.
One may think of using a standard C preprocessor or a custom-made one and compile the preprocessed output, but this has the disadvantage of duplicating the files, so that the project will become more complex, and the support from the development environment will degrade (like ability to jump to a syntax error).
My suggestion is to use annotations via comments that will guide a custom preprocessor and let it do substitutions before compiling.
For example,
public static void main(String[] args) {
int nDisks = 3;
doTowers(nDisks, 'A', 'B', 'C');
}
would become
public static void main(String[] args) {
int nDisks = /*#NDISKS*/ 3 /**/;
doTowers(nDisks, 'A', 'B', 'C');
}
Then your preprocessor would have a definition file such as
NDISKS 5
turning the code in
public static void main(String[] args) {
int nDisks = /*#NDISKS*/ 5 /**/;
doTowers(nDisks, 'A', 'B', 'C');
}
Similarly, you can emulate conditional code compilation with
doTowers(topN - 1, from, to, inter);
/*!PRINT*/
System.out.println("Disk "
+ topN + " from " + from + " to " + to);
/**/
doTowers(topN - 1, inter, from, to);
which could be turned by the preprocessor (with a definition like PRINT OFF) into
doTowers(topN - 1, from, to, inter);
/*!PRINT
System.out.println("Disk "
+ topN + " from " + from + " to " + to);
*/
doTowers(topN - 1, inter, from, to);
You can use alternative syntaxes, but the main ideas are
that the annotated code remains compilable,
that the preprocessor substitutions are reversible.
It would be against the design of the language to have it that way. What -DKEY=VALUE does is that it actually replaces KEY with VALUE in the source during preprocessor in C/C++.
Java does not have a preprocessor so that's mechanism is not available. If you want something "equivalent" you have to question what you mean by that. By not preprocessing the source it wouldn't be really equivalent.
If you on the other hand would like it to mean to set the value of the symbol KEY to the value VALUE you'd run into the problem that you would need to declare the symbol KEY anyway to determine it's type. In this case it would only be yet another constant/variable with the constraints that implies.
This means that even with such a feature it would not actually alter the generated code and you would hardly be better of than defining the value att launch time. That's why supplying the parameter via java would be the way to go.
Related
Is there a command line tool that can automatically fix non formatting but still seemingly simple CheckStyle issues in Java source code like:
Avoid inline conditionals
Make "xxx" a static method
I know there are various tools to fix formatting and some IDEs have fairly advanced quick fixers but so far I could not find anything that can recursively run on a source code folder or be integrated in a commit hook.
Sounds like a nice challenge, but I was also unable to find an automatic tool that can do this. As you already described, there are plenty of options to change code formatting. For other small issues, you could perhaps run Checkstyle from the command-line and filter out fixable warnings. A library for parsing and changing Java source code could help to actually make the changes, like for example JavaParser. Perhaps you could write a custom tool in a relatively small amount of time using a Java source code manipulation tool like JavaParser.
(There are other tools like ANTLR that could be used; see for more ideas this question on Stack Overflow: Java: parse java source code, extract methods. Some libraries like Roaster and JavaPoet do not parse the body of methods, which makes them less suitable in this situation.)
As a very simple example, assume we have a small Java class for which Checkstyle generates two messages (with a minimalistic checkstyle-checks.xml Checkstyle configuration file that only checks FinalParameters and FinalLocalVariable):
// Example.java:
package q45326752;
public class Example {
public static void main(String[] arguments) {
System.out.println("Hello Checkstyle...");
int perfectNumber = 1 + 2 + 3;
System.out.println("Perfect number: " + perfectNumber);
}
}
Checkstyle warnings:
java -jar checkstyle-8.0-all.jar -c checkstyle-checks.xml Example.java
[ERROR] Example.java:4:29: Parameter arguments should be final. [FinalParameters]
[ERROR] Example.java:7:13: Variable 'perfectNumber' should be declared final. [FinalLocalVariable]
Using JavaParser, these two warnings could be fixed automatically like this (the code tries to demonstrate the idea; some parts have been ignored for now):
// AutomaticCheckstyleFix.java:
package q45326752;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.*;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.*;
import java.io.File;
import java.io.FileNotFoundException;
public class AutomaticCheckstyleFix {
private MethodDeclaration bestMatchMethod;
private int bestMatchMethodLineNumber;
private Statement statementByLineNumber;
public static void main(final String[] arguments) {
final String filePath = "q45326752\\input\\Example.java";
try {
new AutomaticCheckstyleFix().fixSimpleCheckstyleIssues(new File(filePath));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private void fixSimpleCheckstyleIssues(File file) throws FileNotFoundException {
CompilationUnit javaClass = JavaParser.parse(file);
System.out.println("Original Java class:\n\n" + javaClass);
System.out.println();
System.out.println();
// Example.java:4:29: Parameter arguments should be final. [FinalParameters]
MethodDeclaration methodIssue1 = getMethodByLineNumber(javaClass, 4);
if (methodIssue1 != null) {
methodIssue1.getParameterByName("arguments")
.ifPresent(parameter -> parameter.setModifier(Modifier.FINAL, true));
}
// Example.java:7:13: Variable 'perfectNumber' should be declared final.
// [FinalLocalVariable]
Statement statementIssue2 = getStatementByLineNumber(javaClass, 7);
if (statementIssue2 instanceof ExpressionStmt) {
Expression expression = ((ExpressionStmt) statementIssue2).getExpression();
if (expression instanceof VariableDeclarationExpr) {
((VariableDeclarationExpr) expression).addModifier(Modifier.FINAL);
}
}
System.out.println("Modified Java class:\n\n" + javaClass);
}
private MethodDeclaration getMethodByLineNumber(CompilationUnit javaClass,
int issueLineNumber) {
bestMatchMethod = null;
javaClass.getTypes().forEach(type -> type.getMembers().stream()
.filter(declaration -> declaration instanceof MethodDeclaration)
.forEach(method -> {
if (method.getTokenRange().isPresent()) {
int methodLineNumber = method.getTokenRange().get()
.getBegin().getRange().begin.line;
if (bestMatchMethod == null
|| (methodLineNumber < issueLineNumber
&& methodLineNumber > bestMatchMethodLineNumber)) {
bestMatchMethod = (MethodDeclaration) method;
bestMatchMethodLineNumber = methodLineNumber;
}
}
})
);
return bestMatchMethod;
}
private Statement getStatementByLineNumber(CompilationUnit javaClass,
int issueLineNumber) {
statementByLineNumber = null;
MethodDeclaration method = getMethodByLineNumber(javaClass, issueLineNumber);
if (method != null) {
method.getBody().ifPresent(blockStmt
-> blockStmt.getStatements().forEach(statement
-> statement.getTokenRange().ifPresent(tokenRange -> {
if (tokenRange.getBegin().getRange().begin.line == issueLineNumber) {
statementByLineNumber = statement;
}
})));
}
return statementByLineNumber;
}
}
Another approach could be to create new Checkstyle plugins based on the ones you are trying to create an automatic fix for. Perhaps you have enough information available to not only give a warning but to also generate a modified version with these issues fixed.
Personally I would hesitate to have issues fixed automatically upon commit. When there are many simple fixes to be made, automation is welcome, but I would like to check these changes before committing them. Running a tool like this and checking the changes could be a very fast way to fix a lot of simple issues.
Some checks that I think could be fixed automatically:
adding static
fixing inline conditionals
FinalParameters and FinalLocalVariable: adding final
ModifierOrder: reordering modifiers (example: final static private)
NeedBraces: adding braces
How is it possible, to improve your logging mechanism, by not having the overhead of string concatenations?
Consider the following example:
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get logger
Logger log = Logger.getLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, but will evaluate the getValue method
log.fine("Trace value: " + getValue());
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
The log method log.fine(...) will not log anything, because the log level is set to INFO. The problem is, that the method getValue will be evaluated anyway.
And this is a big performance issue in big applications with a lot of debug statements.
So, how to solve this problem?
Since Java8 it is possible to use the new introduced lambda expressions for this scenario.
Here is a modified example of the logging:
LoggerTest.class
import java.util.logging.Level;
import java.util.logging.Logger;
public class LoggerTest {
public static void main(String[] args) {
// get own lambda logger
LambdaLogger log = new LambdaLogger(LoggerTest.class.getName());
// set log level to INFO (so fine will not be logged)
log.setLevel(Level.INFO);
// this line won't log anything, and will also not evaluate the getValue method!
log.fine(()-> "Trace value: " + getValue()); // changed to lambda expression
}
// example method to get a value with a lot of string concatenation
private static String getValue() {
String val = "";
for (int i = 0; i < 1000; i++) {
val += "foo";
}
return val;
}
}
LambdaLogger.class
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
public class LambdaLogger extends Logger {
public LambdaLogger(String name) {
super(name, null);
}
public void fine(Callable<String> message) {
// log only, if it's loggable
if (isLoggable(Level.FINE)) {
try {
// evaluate here the callable method
super.fine(message.call());
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
With this modification you can improve the performance of your applications a lot, if you have many log statements, which are only for debugging purposes.
Of course you can use any Logger you want. This is only an example of the java.util.Logger.
#bobbel has explained how to do it.
I'd like to add that while this represents a performance improvement over your original code, the classic way of dealing with this is still faster:
if (log.isLoggable(Level.FINE)) {
log.fine("Trace value: " + getValue());
}
and only marginally more verbose / wordy.
The reason it is faster is that the lambda version has the additional runtime overheads of creating the callable instance (capture cost), and an extra level of method calls.
And finally, there is the issue of creating the LambdaLogger instances. #bobbel's code shows this being done using a constructor, but in reality java.util.logging.Logger objects need to be created by a factory method to avoid proliferation of objects. That implies a bunch of extra infrastructure (and code changes) to get this to work with a custom subclass of Logger.
Apparently Log4j 2.4 includes support for lambda expressions which are exactly useful for your case (and which other answers have replicated manually):
From https://garygregory.wordpress.com/2015/09/16/a-gentle-introduction-to-the-log4j-api-and-lambda-basics/
// Uses Java 8 lambdas to build arguments on demand
logger.debug("I am logging that {} happened.", () -> compute());
Just create wrapper methods for your current logger as:
public static void info(Logger logger, Supplier<String> message) {
if (logger.isLoggable(Level.INFO))
logger.info(message.get());
}
and use it:
info(log, () -> "x: " + x + ", y: " + y);
Reference: JAVA SE 8 for the Really Impatient eBook, pages 48-49.
use a format String, and an array of Supplier<String>. this way no toString methods are called unless the the log record is actually publishable. this way you dont have to bother with ugly if statements about logging in application code.
I need to programmatically find out which JRE classes can be referenced in a compilation unit without being imported (for static code analysis). We can disregard package-local classes. According to the JLS, classes from the package java.lang are implicitly imported. The output should be a list of binary class names. The solution should work with plain Java 5 and up (no Guava, Reflections, etc.), and be vendor agnostic.
Any reliable Java-based solution is welcome.
Here are some notes on what I've tried so far:
At first glance, it seems that the question boils down to "How to load all classes from a package?", which is of course practically impossible, although several workarounds exist (e.g. this and this, plus the blog posts linked there). But my case is much simpler, because the multiple classloaders issue does not exist. java.lang stuff can always be loaded by the system/bootstrap classloader, and you cannot create your own classes in that package. Problem is, the system classloader does not divulge its class path, which the linked appoaches rely on.
So far, I haven't managed to get access to the system classloader's class path, because on the HotSpot VM I'm using, Object.class.getClassLoader() returns null, and Thread.currentThread().getContextClassLoader() can load java.lang.Object by delegation, but does not itself include the classpath. So solutions like this one don't work for me. Also, the list of guaranteed system properties does not include properties with this kind of classpath info (such as sun.boot.class.path).
It would be nice if I didn't have to assume that there is an rt.jar at all, and rather scan the list of resources used by the system classloader. Such an approach would be safer with respect to vendor specific JRE implementations.
Compiled classes appear to contain readable java/lang text. So I wrote a little bit of code to see if these imports can be extracted. It's a hack, so not reliable, but assuming you can extract/list all classes in a jar-file, this could be a starting point.
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
public class Q21102294 {
public static final String EXTERNAL_JAR = "resources/appboot-1.1.1.jar";
public static final String SAMPLE_CLASS_NAME = "com/descartes/appboot/AppBoot.class";
public static HashSet<String> usedLangClasses = new HashSet<String>();
public static void main(String[] args) {
try {
Path f = Paths.get(EXTERNAL_JAR);
if (!Files.exists(f)) {
throw new RuntimeException("Could not find file " + f);
}
URLClassLoader loader = new URLClassLoader(new URL[] { f.toUri().toURL() }, null);
findLangClasses(loader, SAMPLE_CLASS_NAME);
ArrayList<String> sortedClasses = new ArrayList<String>();
sortedClasses.addAll(usedLangClasses);
Collections.sort(sortedClasses);
System.out.println("Loaded classes: ");
for (String s : sortedClasses) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void findLangClasses(URLClassLoader loader, String classResource) throws Exception {
URL curl = loader.getResource(classResource);
if (curl != null) {
System.out.println("Got class as resource.");
} else {
throw new RuntimeException("Can't open resource.");
}
ByteArrayOutputStream bout = new ByteArrayOutputStream();
InputStream in = curl.openStream();
try {
byte[] buf = new byte[8192];
int l = 0;
while ((l = in.read(buf)) > -1) {
bout.write(buf, 0, l);
}
} finally {
in.close();
}
String ctext = new String(bout.toByteArray(), StandardCharsets.UTF_8);
int offSet = -1;
while ((offSet = ctext.indexOf("java/lang/", offSet)) > -1) {
int beginIndex = offSet;
offSet += "java/lang/".length();
char cnext = ctext.charAt(offSet);
while (cnext != ';' && (cnext == '/' || Character.isAlphabetic(cnext))) {
offSet += 1;
cnext = ctext.charAt(offSet);
}
String langClass = ctext.substring(beginIndex, offSet);
//System.out.println("adding class " + langClass);
usedLangClasses.add(langClass);
}
}
}
Gives the following output:
Got class as resource.
Loaded classes:
java/lang/Class
java/lang/ClassLoader
java/lang/Exception
java/lang/Object
java/lang/RuntimeException
java/lang/String
java/lang/StringBuilder
java/lang/System
java/lang/Thread
java/lang/Throwable
java/lang/reflect/Method
Source code of the used compiled class is available here.
OK, I misread the question. Checking the JLS, all I see is:
"Every compilation unit implicitly imports every public type name declared in the predefined package java.lang, as if the declaration import java.lang.*; appeared at the beginning of each compilation unit immediately after any package statement. As a result, the names of all those types are available as simple names in every compilation unit."
(http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html)
If you want to know which types that includes, it's going to vary from version to version of Java...
I have a question regarding structuring of code.
I have let us say three types of packages A,B and C.
Now, classes in package A contains classes which contain the main() function. These classes
need some command line arguments to run.
In package B, there are classes which contains some public variables, which need to be configured, at different times. For example before calling function A, the variable should be set or reset, the output differs according to this variable.
In package C, uses the classes in package B to perform some tasks. They do configure their variables as said before. Not only when the object is created, but also at intermediate stage.
Package A also has classes which in turn use classes from package B and package C. In order to configure the variables in classes of B and C, class in package A containing the main() function, reads command line arguments and passes the correct values to respective class.
Now, given this scenario, I want to use Apache Commons CLI parser.
I am unable to understand how exactly I should write my code to be structured in an elegant way. What is a good design practice for such scenario.
Initially I wrote a class without Apache to parse the command line arguments.
Since I want a suggestion on design issue, I will give an excerpt of code rather than complete code.
public class ProcessArgs
{
private String optionA= "default";
private String optionB= "default";
private String optionC= "default";
public void printHelp ()
{
System.out.println ("FLAG : DESCRIPTION : DEFAULT VALUE");
System.out.println ("-A <Option A> : Enable Option A : " + optionA);
System.out.println ("-B <Option B> : Enable Option B : " + optionB);
System.out.println ("-C <Option C> : Enable Option C : " + optionC);
}
public void printConfig()
{
System.out.println ("Option A " + optionA);
System.out.println ("Option B " + optionB);
System.out.println ("Option C " + optionC);
}
public void parseArgs (String[] args)
{
for (int i=0;i<args.length;i++)
{
if (args[i].equalsIgnoreCase ("-A"))
optionA = args[++i];
else if (args[i].equalsIgnoreCase ("-B"))
optionB = args[++i];
else if (args[i].equalsIgnoreCase ("-C"))
optionC = args[++i];
else
throw new RuntimeException ("Wrong Argument : " + args[i] + " :: -h for Help.");
}
}
}
Points to note -
I already have 50+ command line options and they are all in one place.
Every class uses only a group of command line options.
I tried to write an interface, somehow but I am unsuccessful. I am not sure if this is a good way to do it or not. I need some design guidelines.
Here is the code which I wrote -
public interface ClassOptions
{
Options getClassOptions();
void setClassOptions(Options options);
}
public class Aclass implements ClassOptions
{
private String optionA="defaultA";
private String optionB="defaultB";
public Options getClassOptions()
{
Options options = new Options();
options.addOption("A", true, "Enable Option A");
options.addOption("B", true, "Enable Option B");
return options;
}
public void setClassOptions(Options options, String args[])
{
CommandLineParser parser = new BasicParser();
CommandLine cmd=null;
try
{
cmd = parser.parse( options, args);
} catch (ParseException e)
{
// TODO Auto-generated catch block
// e.printStackTrace();
System.out.println("ignored option");
}
if(cmd.hasOption("A"))
optionA = "enabled";
if(cmd.hasOption("B"))
optionB = "enabled";
}
}
I think the problems in such writing of code are -
There are different types of arguments like int, double, string, boolean. How to handle them all.
getClassOption() and setClassOption() both contain the arguments "A", "B" for example. This code is prone to errors made while writing code, which I would like to eliminate.
I think the code is getting repetitive here, which could be encapsulated somehow in another class.
Not all the arguments are required, but can be ignored.
Thank You !
I would recommend to you JCommander.
I think it's a really good Argument Parser for Java.
You define all the Argument stuff within annotations and just call JCommander to parse it.
On top of that it also (based on your annotations) can print out the corresponding help page.
You don't have to take care about anything.
I believe you will love it! :)
Take a look at it: http://jcommander.org/
There are a lot of examples and such!
Good Luck! :)
simple example for command line argument
class CMDLineArgument
{
public static void main(String args[])
{
int length=args.length();
String array[]=new String[length];
for(int i=0;i<length;i++)
{
array[i]=args[i];
}
for(int i=0;i<length;i++)
{
System.out.println(array[i]);
}
I'm trying to do something similar to Question 4617364 but for Python - load a class from python script file, where said class implements a Java interface and hand it over to some Java code that can use its methods - but calls to the object method return invalid values and printing from the initializer doesn't seem to do anything.
My implementation looks like this:
Interface:
package some.package;
import java.util.List;
public interface ScriptDemoIf {
int fibonacci(int d);
List<String> filterLength(List<String> source, int maxlen);
}
Python Implementation:
from some.package import ScriptDemoIf
class ScriptDemo(ScriptDemoIf):
""" Class ScriptDemo implementing ScriptDemoIf """
def __init__(self):
print "Script Demo init"
def fibonacci(self, d):
if d < 2:
return d
else:
return self.fibonacci(d-1) + self.fibonacci(d-2)
def filterLength(self, source, maxlen):
return [ str for str in source if len(str) <= maxlen ]
Class loader:
public ScriptDemoIf load(String filename) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("jython");
FileReader script = new FileReader(filename);
try {
engine.eval(new FileReader(script));
} catch (FileNotFoundException e) {
throw new ScriptException("Failed to load " + filename);
}
return (ScriptDemoIf) engine.eval("ScriptDemo()");
}
public void run() {
ScriptDemoIf test = load("ScriptDemo.py");
System.out.println(test.fibonacci(30));
}
(Obviously the loader is a bit more generic in real life - it doesn't assume that the implementation class name is "ScriptDemo" - this is just for simplicity).
When the code is being ran, I don't see the print from the Python's __init__ (though if I put a print in the body of the script then I do see that), but the test variable in run() look like a valid jython "proxy object" and I get no casting errors. When I try to run the fibonacci() method I always get 0 (even if I change the method to always return a fixed number) and the filterLength() method always returns null (probably something to do with defaults according to the Java interface).
what am I doing wrong?
What version of jython are you using? You might have run into the JSR223 Jython bug : http://bugs.jython.org/issue1681
From the bug description:
Calling methods from an embedded Jython script does nothing when
using JSR-223 and Jython 2.5.2rc2, while Jython 2.2.1 just works fine.