Fork me on GitHub
#clojure
<
2020-11-30
>
stuartrexking02:11:24

How do a call a specific java method (which is overloaded by type, rather than number of params)?

andy.fingerhut02:11:59

Often giving a type hint on at least one of the parameters that has a distinct type from the other java method signatures should do it. Might require multiple type hints if one isn't enough to make the signature unique.

andy.fingerhut02:11:16

e.g. (.myMethod instance1 ^long x y z w)

andy.fingerhut02:11:00

that would be if the first parameter x is type long in the Java method signature. Can also type hint any or all of the other parameters.

stuartrexking02:11:00

Like this

(.setNonStrokingColor my-instance ^PDColor colour)

stuartrexking02:11:25

But that method is overloaded with a Color type arg as well.

andy.fingerhut02:11:06

Have you added (set! **warn-on-reflection** true) near the beginning of the source file where you make that call? e.g. after the ns form.

andy.fingerhut02:11:59

That can help detect when the Clojure compiler has not successfully resolved the method down to only one choice at Clojure compile time.

andy.fingerhut02:11:24

Adding it will not help the compiler pick one of the methods, but it will quickly tell you if it hasn't.

andy.fingerhut02:11:48

Any reflection warnings you see when that is present in a file imply that the Clojure compiler will perform run-time Java reflection on each call it warns about, which can be quite slow relative to when those warnings are not there. I wouldn't worry about such warnings in code that is run once or a few times, but if it is in a hot code path you want to be fast, best to eliminate it.

andy.fingerhut02:11:32

When you say "it doesn't work for me", what do you see happening in your call that isn't working?

stuartrexking02:11:34

And just to confirm, type hinting isn’t guaranteed to work?

stuartrexking02:11:41

I get a class cast exception.

andy.fingerhut02:11:25

Are you certain that the type of the argument is actually PDColor, e.g. you have done a debug print or something similar just before that method call to show the class of the argument?

andy.fingerhut02:11:25

maybe something like (if (not (instance? PDColor foo)) (println "(class foo)=" (class foo)))

stuartrexking02:11:37

I have. I’ll double check that.

andy.fingerhut02:11:58

It might be that 99.9% of the time it is the class you expect, but it only takes one that isn't ...

stuartrexking02:11:47

Here is what I know: 1. Class is definitely the right type 2. Type hinting makes no difference to which function is being called. I’ve even put a debug breakpoint in the function that should be called and it’s not being called.

stuartrexking02:11:01

I might have to use a different signature.

andy.fingerhut02:11:31

Do you know which one is being called?

stuartrexking02:11:18

This is the error

class org.apache.pdfbox.pdmodel.graphics.color.PDColor cannot be cast to class [F (org.apache.pdfbox.pdmodel.graphics.color.PDColor is in unnamed module of loader 'app'; [F is in module java.base of loader 'bootstrap')

andy.fingerhut02:11:21

Or none of them are, because the first time you try you get an exception?

stuartrexking02:11:35

I don’t think any are being called.

andy.fingerhut02:11:10

Do you have a stack trace with that exception?

stuartrexking02:11:48

Can I generate one? Nothing coming out in the REPL

stuartrexking03:11:07

let me try catch it

andy.fingerhut03:11:34

A stack trace could tell you if the error is occurring in your code, at the call you are interested in now, or somewhere within the depths of the Java library

andy.fingerhut03:11:01

*e is the value of the last exception in a REPL session

andy.fingerhut03:11:18

You can show it by evaluating that form

andy.fingerhut03:11:51

The most recent calls appear first in the stack trace. The first few are inside of Clojure's implementation of run-time reflection it appears

andy.fingerhut03:11:24

The first stack frame that is not inside of Clojure is this one: [ventures.fierce.pdfbox.appearance_content_stream$non_stroking_color_GT_ invokeStatic "appearance_content_stream.clj" 39]

andy.fingerhut03:11:36

The 39 should be a line number in the file appearance_content_stream.clj

andy.fingerhut03:11:17

The exception message is this: "Cannot cast org.apache.pdfbox.pdmodel.graphics.color.PDColor to [F"

andy.fingerhut03:11:56

complaining that it has a value of type PDColor, and it cannot cast it to type [F, where [ denotes a Java array of the next thing, and F is a 1-letter abbreviation for one of the Java primitive types, I think float IIRC

andy.fingerhut03:11:37

An array of primitive floats is the type of the only parameter of one of the other 1-parameter signatures of that method setNonStrokingColor, I see.

andy.fingerhut03:11:46

If you added that set! statement to warn on reflection, do you get a warning on this line 39?

andy.fingerhut03:11:43

Out of curiosity, do you have the dependency you added to your Leiningen project.clj file or deps.edn file for this apache library?

andy.fingerhut03:11:01

I might try to create a REPL locally on my machine and make a call to that method to see if I get the same behavior.

stuartrexking03:11:25

I’m using deps.edn

stuartrexking03:11:53

I don’t get a warning when I have the type on the instance (not the param)

stuartrexking03:11:13

Would you like me to share the repo with you?

andy.fingerhut03:11:35

If it is public, or you otherwise don't mind.

stuartrexking03:11:24

If you want to trigger it run the code in the comment block at the end of the core ns.

stuartrexking03:11:43

I’m mid refactor so the output is likely to be broken or not output at all.

stuartrexking03:11:01

I’m trying to isolate all the icky java interop.

stuartrexking03:11:20

If you have any tips on how to improve the code please let me know.

stuartrexking03:11:24

Thanks for your help.

andy.fingerhut03:11:44

It may not matter, but what OS, JDK version (e.g. 8, 11, etc.), and Clojure version are you using?

stuartrexking03:11:03

Java

openjdk 11.0.2 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)

stuartrexking03:11:51

Clojure Version: 1.10.1.727

andy.fingerhut03:11:49

I am running macOS 10.14.6, AdoptOpenJDK 11 something, and Clojure 1.10.1, and am seeing a similar excpetion

andy.fingerhut03:11:05

This is still weird to me so far. Clojure is using its run-time reflection techniques for this call, which is slower, but usually succeeds in finding the correct method. In this case, it is only finding that the class has 1 non-static method with 1 parameter, but clearly there are more.

andy.fingerhut03:11:15

Or at least the documentation says there are more ...

stuartrexking03:11:44

If I grab the source of that version of the jar I can see the methods in the abstract parent class.

stuartrexking03:11:15

And if I use the IntelliJ decompiler on the compiled code I can see them as well.

andy.fingerhut03:11:45

The method you want has the 'bridge' and 'synthetic' flags set in that class. The one with the float[] type parameter has neither of those 'bridge' nor 'synthetic' flags.

andy.fingerhut03:11:13

The Clojure reflector looks like it might use methods without the 'bridge' flag in preference to those that do.

andy.fingerhut03:11:43

I don't know why it does that

andy.fingerhut03:11:08

It is also odd to me that the reflection warnings do not show up at all for the method call that is causing this exception.

andy.fingerhut03:11:41

That would suggest to me that the Clojure compiler should have found what it thinks is a matching method at compile time, and not try to look for one at run time.

andy.fingerhut03:11:13

But if the compile time looking for a method has this same behavior of preferring non-bridge methods over bridge methods, perhaps that is part of the cause, too.

andy.fingerhut03:11:00

• What abstract parent class? According to the Javadoc page you sent me a link to, the class PDAppearanceContentStream extends class Object directly (or I misunderstand that JavaDoc page, perhaps)

stuartrexking04:11:02

I might be confusing myself. Let me through it again…

stuartrexking04:11:29

Something not right with this lib.

stuartrexking04:11:54

Or that version of it’s javadoc.

stuartrexking04:11:35

I think that Javadoc isn’t correct.

andy.fingerhut04:11:26

I just sent a message to the #clojure-dev channel describing this situation generally.

andy.fingerhut04:11:45

I can try adding a link there to this discussion thead, in case someone sees it and is interested in looking further.

stuartrexking04:11:04

I appreciate your help

andy.fingerhut04:11:17

Sure, no problem. Not there yet, obviously, but some clues.

andy.fingerhut04:11:21

There may be a completely-in-Clojure way to handle this, but if so, I don't yet know what that is.

andy.fingerhut04:11:59

If you are willing to go the length of writing a little bit of Java code, you could write that method call in Java, and it will probably be able to find the right method.

andy.fingerhut04:11:16

Then call that Java method you write from Clojure via Java interop (as long as you don't write several methods with same name & # of parameters that leads to this same issue in your Java code, of course)

kenj07:11:55

is it common to write in spec.test checks into unit tests, or are they normally just run by hand from the REPL?

seancorfield16:11:05

@risinglight I wouldn't say it was "common" to write such check's into unit tests but I think people do it because they have no better guidance on how/when to run generative tests right now.

Heather17:11:20

do you have recommendations on where to get guidance on generative tests? We’re looking into them for our team.

seancorfield17:11:36

I really wish I did, for our team too.

seancorfield17:11:16

The practice we've adopted is to have a few generative tests in our "unit test" suite but with fairly low iterations so they don't take long (since unit tests are meant to be fast), and then to have additional generative tests in comment forms that we run manually from time to time. Not a great solution, to be honest.

seancorfield17:11:03

I have some "small" check's in unit tests but mostly I put them in RCFs and run them manually from time to time when the code has changed significantly.

colinkahn17:11:01

> RCFs What does this stand for

seancorfield17:11:47

Rich Comment Forms. Using comment to have blocks of dev/test code in your source files. Stu Halloway coined the name, because it's an approach that Rich Hickey uses quite a bit. I've been doing it for quite a while before I heard that from Stu, so it's kind of nice validation of the technique to know Rich (and others) do this too.

colinkahn17:11:31

Ah, got it, thanks!

seancorfield17:11:47

You could always use test metadata tagging to selectively run "unit" tests and "integration" tests separately (although I don't know how common that is either).

kenj17:11:14

Thanks, I’ve definitely been struggling a bit with the lack of guidance part. Same with when/how exactly to utilize instrument .

seancorfield17:11:52

@risinglight What I tend to do with instrument is to add it to the top of my test files, to instrument functions in the source namespace corresponding to the test namespace. You can see that in action in the tests for next.jdbc for example.

👍 1
seancorfield17:11:45

I'll also enable it manually sometimes during dev, especially when I'm exploring parts of our code base that I didn't write.

ordnungswidrig19:11:25

Hi! Who happens to have a link to a list of clojure(script) static website / blog generators? I have a octopress installation which is littly rusty now and I want to replace it with something I do understand.

borkdude19:11:48

@ordnungswidrig #cryogen seems to be a well supported Clojure static site generator. Some people have also used babashka + bootleg (e.g. https://github.com/lambdaisland/gaiwan_co#tech-stack) but that's a more do it yourself solution.

👍 1