Skip to content

Compile(Reader) and Compile(String) #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 16, 2015
Merged

Compile(Reader) and Compile(String) #3

merged 9 commits into from
Mar 16, 2015

Conversation

Squareys
Copy link
Contributor

Hello @dscho, @ctrueden, hello all!

I am currently working on a scripting node for KNIME and very happy to be able to use scijava and scripting-java.
Current goal is to execute one Command Plugin for every row in an input table.
This requires the Plugins Java code (as String) to be compiled once and then be reused for every row.

Until now there was no way to do this (except use eval(String) for the first row and the load the class manually to run the command on the following rows). Since there is an compile(File, Writer) already, I suggest adding compile(String) and compile(Reader) aswell.
I replaced the code which does the compilation in eval(Reader) with a call to the new compile(Reader) method.

Open for discussion:

  • I set the return value of compile to Class<?>. Other possibilities could be the name of the main class or void, analog to compile(File, Writer)
  • Shouldn't compile(File, Writer) use getContext().getErrorWriter() ?
  • I moved the class loader modification code to compile aswell ("needed for sezpoz"?), please pay special attention to whether that makes sense when reviewing.

What are your thoughts?

Greetings,
Jonathan

@dscho
Copy link
Contributor

dscho commented Aug 28, 2014

I set the return value of compile to Class<?>. Other possibilities could be the name of the main class or void, analog to compile(File, Writer)

I think it makes most sense to do it that way.

Shouldn't compile(File, Writer) use getContext().getErrorWriter() ?

Yes!

I moved the class loader modification code to compile aswell ("needed for sezpoz"?), please pay special attention to whether that makes sense when reviewing.

"Needed for annotation processing" now... we don't use Sezpoz anymore. And actually, as far as scripting-java is concerned, we do not need to pay attention to annotation processing anyway, because the PluginService is already initialized at that point and we have to "discover" the plugin (if it is a plugin) in a different way anyhow.

@dscho
Copy link
Contributor

dscho commented Aug 28, 2014

Oh, and a very fundamental comment: the way to prevent frequent recompiling would be to remember intermediate compilations of the identical source code.

To this end, it might make sense to turn the logic around, from passing a StringReader to the compile() method that takes a Reader to passing a String to the other method. That way, we could first checksum the code and pick a name for the temporary directory based on that checksum. It could then detect whether the code had been compiled already and just reuse it. The responsible javaService could even have a weak hash map of class loaders associated with temporary directories...

@Squareys
Copy link
Contributor Author

Thank you for reviewing! I'll fix the issues you mentioned tomorrow, I don't know if I'll be able to do the recompilation checking though (timewise), I'll will try, though.

JavaEngine provides the possibility to evaluate Java code from a Reader,
sometimes one might not want to have that code immediately executed.

For a Java File, the compile(File,Writer) method was provided already,
this commit adds such a method for compiling from Reader.
A convenience method to compile from String, analog to eval(String).
@Squareys
Copy link
Contributor Author

@dscho I did not do the recompilation checking. I might do in the future, but in a separate pull request.
I went over the compile(File,Writer), which also returns the compiled Class now, to keep the compile methods a little more consistent.

@ctrueden
Copy link
Member

ctrueden commented Dec 3, 2014

@Squareys Still planning to work on this further? Do you understand what @dscho is asking you to do?

@Squareys
Copy link
Contributor Author

Squareys commented Dec 3, 2014

Not in too near future, but yes. This is related to a task I am doing for Knip/@dietzc , which is currently on hold for another, so I cannot promise that this will be done too soon.

Is there any specific reason you ask?

@ctrueden
Copy link
Member

ctrueden commented Dec 3, 2014

@Squareys I was just going back through all the pending PRs, trying to resolve as many as possible, and categorizing those that remain. In this case, I just wanted to make sure we were all on the same page that the ball is currently in your court. Let us know if you need any help with it!

@dietzc
Copy link

dietzc commented Jan 23, 2015

@Squareys @ctrueden maybe we get some minutes on saturday to discuss whats missing that we can merge this PR.

@Squareys
Copy link
Contributor Author

@dietzc what's missing is discussed in previous comments:
the way to prevent frequent recompiling would be to remember intermediate compilations of the identical source code (#3 (comment))

@dscho
Copy link
Contributor

dscho commented Jan 24, 2015

@Squareys an example for checksumming a string:

private final static Charset utf8 = Charset.fromName("UTF-8");
private final static MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
private static String hash(final String code) {
    final InputStream input = new ByteArrayInputStream(code.getBytes(utf8));
    synchronized (sha1) {
        sha1.reset();
        final InputStream digestStream = new DigestInputStream(input, sha1);
        while (digestStream.read(buffer) >= 0); /* do nothing */
        digestStream.close();
        final byte[] bytes = sha1.digest();
        return String.format("%0" + (bytes.length << 1) + "x", new BigInteger(1, bytes));
    }
}

(This is not even compile-tested; I trust that you can get it to work, though.)

@ctrueden
Copy link
Member

@Squareys See also org.scijava.util.DigestUtils, which will save you the effort of @dscho's example above.

@dscho
Copy link
Contributor

dscho commented Jan 24, 2015

D'oh. Thanks, @ctrueden, for setting my head straight.

@Squareys
Copy link
Contributor Author

Squareys commented Mar 2, 2015

So, I need to compute the hash for the code input and then check, whether a entry for this hash already exists in the weak hash map.
The compile method accepts Reader, though. I could read through the Reader to extract a String of which I could compute the hash, but that doesn't seem like a particularly clean way to do this.
Any suggestions?

@dscho
Copy link
Contributor

dscho commented Mar 2, 2015

The compile method accepts Reader, though. I could read through the Reader to extract a String of which I could compute the hash, but that doesn't seem like a particularly clean way to do this.

That's what I referred to when I said this:

To this end, it might make sense to turn the logic around, from passing a StringReader to the compile() method that takes a Reader to passing a String to the other method.

@ctrueden
Copy link
Member

ctrueden commented Mar 2, 2015

@Squareys I wouldn't bother "prematurely optimizing" here. For the intended use cases, the String of source code will never be large enough to cause issues. If you go with Dscho's suggestion, it will make things easier.

@Squareys
Copy link
Contributor Author

Squareys commented Mar 4, 2015

While I'm at it: Is there a Eclipse code formatter .xml somewhere I can use?

@dscho
Copy link
Contributor

dscho commented Mar 4, 2015

@ctrueden
Copy link
Member

ctrueden commented Mar 5, 2015

@Squareys Please import the whole .epf at that link, since it contains settings beyond just Clean Up and Format. Specifically, it also has the order of import packages defined. Without doing that, we may get into "Clean-Up Wars."

@Squareys
Copy link
Contributor Author

Squareys commented Mar 5, 2015

Thank you for mentioning that, I wouldn't have done so otherwise.

Needed in future recompilation avoidance for hash calculation.
TODO: Move to a utility class?

Signed-off-by: Squareys <Squareys@googlemail.com>
@Squareys
Copy link
Contributor Author

Squareys commented Mar 9, 2015

Regarding avoiding recompilation; after trying to cram this in for a few hours, my oppinion on how to go about this changed:

I do not think, eval(..) nor compile(..) should support extra measures for avoiding recompilation. When compiling from a String or a Reader, a temporary maven project is created which is dependent on engine scope bindings (values assigned to "verbose" and "debug" keys). Therefore storing only the temporary directory in the hash map wont do it.

So, what if you still want to avoid recompilation? After having my problems understanding the compile(File, Writer) method, I realized, that this method merely compiles an existing file without doing all the other fancy stuff the compile(String/Reader) methods do. For this method to be useful, you will need to get the main class or whatever yourself. This also means the project files of the existing project probably won't change and minimaven can handle avoiding recompilation for multiple compile(File) calls (I am kinda assuming that for minimaven. Is this actually true?). My suggestion would be to improve the "workflow" around compile(File), maybe expose Builder after some more refactoring to provide simpler ways of getting the main class/running the project via JavaRunner etc, in another issue (This issue never meant to be about avoiding recompilation).

@ctrueden
Copy link
Member

ctrueden commented Mar 9, 2015

@Squareys Feel free to file issues along the lines of your thinking. FYI, realistically, no one at LOCI will be able to work on the scripting-java project any time soon. But you are more than welcome to make whatever improvements you need!

@ctrueden
Copy link
Member

ctrueden commented Mar 9, 2015

Regarding your questions, I am not an expert on the code. But personally, I would like to move away from any MiniMaven dependencies across the board. It is one more wrinkly component that we do not really have time to maintain. Rather, we should lean on the JRE's built-in infrastructure as much as possible, even if it means end users have to install a JDK. Maybe we could consolidate Java compilation logic into the scripting-java component itself, which can be reused in other places as needed? Unfortunately, I don't really have time to investigate and think through the issues thoroughly.

@Squareys
Copy link
Contributor Author

Squareys commented Mar 9, 2015

Alright, I will merge my changes into master of my fork tomorrow, they will not contain recompilation avoidance (for wich I will not create a separate issue since I came to the conclusion that it is not necessary), but contains a few improvements to documentation, readability and consistency.

I will notify you after, when I believe this PR could be ready for merge.

This allows us to compute a hash on the script as a String even if it was
input via the gererating Reader. We need to compute the hash for future
recompilation avoidance.

Signed-off-by: Squareys <Squareys@googlemail.com>
This order is much more efficient with the recently swapped execution order
of compile(String) and compile(Reader). See e663151.

Signed-off-by: Squareys <Squareys@googlemail.com>
compile(File, Writer) now uses the context error writer if the specified writer
is null. compile(file) calls compile(File, null), therefore compiles a given
file and writes errors to the context error writer. This maintains the
consistency througout the compile(..) and eval(..) methods.
There are now two constructors which clearly differentiate between creating
a project for a File and creating a project for a Reader.
This makes the calling code easier to read.

To avoid code duplication, two small private methods were added to Builder.
When a valid filename is specified by the ScriptEngine.FILENAME key in the
engine scope bindings, the given script will be ignored and the file
described by the filename will be compiled (and run) instead.

Signed-off-by: Squareys <Squareys@googlemail.com>
@Squareys
Copy link
Contributor Author

Alright. There is one last thing, which may or not be an issue: I switched up the order of execution from compile(String) -> compile(Reader) to compile(Reader) -> compile(String) which means reading the entire contents of the Reader into a String. Later, at the possible creation of the temp project, the String has been passed as StringReader and is read again to write out onto disk. (This was in preparation of the now cancelled/postponed compilation avoidance feature.)

The issue one could possibly see here is that this causes additional overhead when executing scripts from Readers. I don't believe this overhead is really significant though, I can't imagine reading from a StringReader to be too costly.

@Squareys
Copy link
Contributor Author

@ctrueden if this is an issue for you, I will see what I can do, otherwise I declare this PR ready for merge from my side.

@Squareys
Copy link
Contributor Author

@ctrueden I'm guessing time is sparse at the moment, just wanted to make sure you got the message that this is pull request is ready to be merged.

@ctrueden
Copy link
Member

Thanks @Squareys. FWIW, 4913afd has failing tests:

testEvalReader(org.scijava.plugins.scripting.java.JavaEngineTest)  Time elapsed: 0.239 sec  <<< FAILURE!
java.lang.AssertionError: null
    at org.junit.Assert.fail(Assert.java:86)
    at org.junit.Assert.assertTrue(Assert.java:41)
    at org.junit.Assert.assertTrue(Assert.java:52)
    at org.scijava.plugins.scripting.java.JavaEngineTest.testEvalReader(JavaEngineTest.java:161)

And 040383e has unrelated formatting changes.

But really, those are nitpicks. I'm gonna merge it in the interest of time.

ctrueden added a commit that referenced this pull request Mar 16, 2015
Compile(Reader) and Compile(String)
@ctrueden ctrueden merged commit ed91fc3 into scijava:master Mar 16, 2015
ctrueden added a commit to scijava/pom-scijava that referenced this pull request Mar 16, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants