I barely know Java, so maybe I’m missing something or my guess is I’m calling the interop wrong somehow. However at face value the error just looks incorrect to me b/c there clearly seems to be a method called getLine and it’s taking one argument.
I’ve been stuck on this for a couple of days, maybe someone has some insight here
It’s probably something obvious I’m missing
Thanks!
I would recommend making a minimal example of the error. You should be able to get it down to a handful of isolated lines. This is a good general debugging tactic but one specific reason I recommend it is that the only sexp calling getLine relies (via line-info and mixer-info) on a fn get-current-mixer that does not seem to exist. It’s probably just a rename of get-current-mixer-info, but when mistakes like that crop up in an example it’s usually a good idea to take a step back and buckle down on your process.
Refactoring this code to a minimal example will probably also help by removing the layers of indirection in the arguments you’re passing to getLine. As it stands the values being passed to that method are hidden behind enough lets and wrapper functions (both of which seem unnecessary to me) to make it unclear what’s actually causing the error.
Is it possible that you’re using a newer version of Java (9+)? According to StackOverflow,
[t]hese classes are proprietary and internal, they are not intended for public use. They are also a subject to change in the future. Close your eyes, don’t look at them and never use them.
My guess is that these (internal) classes changed. On a related note, they may be hidden altogether eventually: http://openjdk.java.net/jeps/260
Oh yikes. I pushed a bug… That’s really embarrassing
The REPL state drifted away from the file state :S I need to remember to rerun the program from scratch before pushing.
It’s fixed now
The reason I don’t have a small working example is because the mixer and line needs to be chosen at run time by the user and are machine dependent. So if I hardcode some fixed values that work for me, it may not work on your machine.
You’re right that the code is a bit more convoluted than it should be… but I haven’t figured out a good way to clean it up yet (maybe with memoizing some values instead of storing them in a map)
This sounds likely. You’re right that they are hidden classes, but they’re being returned by non-hidden methods - so it’s all a bit strange. I’m pretty much following the logic flow presented in the official tutorial : https://docs.oracle.com/javase/tutorial/sound/sampled-overview.html
You get a list of mixers-infos from the audio subsystem, choose one and get it’s corresponding mixer. Then you get the mixer’s line-infos, choose one and get it’s corresponding line. Finally you open the line and get the data.
But the weird part is that I can’t get the line b/c the mixer I’m getting is this hidden class type
I’ll keep digging around and if I discover anything new I’ll drop a note here
Thanks for the help and pointers. Really appreciate it
I don’t know about Java 9+, but in general, when I get a no matching method of arity exception, it is because there’s some ambiguity and Clojure can’t reflectively figure out what to call, so you have to type hint.
Turns out that the code is correct in the sense that you were calling .getLine with the right types of arguments, but Clojure had a hard time finding the right method implementation for some reason. Once you add type hints it works.
Basically what I did was first add
(set! *warn-on-reflection* true)
Then go function to function, evaluating them, and adding type hints until the warnings go away.
Once you’ve done that calling get-current-line works fine
Now I’m not 100% sure why it didn’t find it through reflection. Maybe there is another .getLine method with a different signature in some superclass. In any case when doing a lot of specific interop like this adding the type hints is a good idea. It aids performance, it catches bugs, it’s extra documentation, it fixes Reflector issues like the one you had here, and it makes your code Graal-compatible.
My assumption was always that adding type hints is a performance optimization and should never affect behavior. As the docs say
Clojure supports the use of type hints to assist the compiler in avoiding reflection in performance-critical areas of code. Normally, one should avoid the use of type hints until there is a known performance bottleneck.
That raises a few questions:
What are the specific conditions in which reflection can’t identify the correct method to call? (It might help to use clojure.reflect/reflect to take a look a the methods exposed)
If against expectations adding the type hint does affect behavior in this case, is that a bug that should be reported - or should the docs be updated?
Is there a way to get the code to run without adding the type hint (perhaps using an explicit cast)?
Wow, thank you so much for the patch @plexus. I seem to have a knack for choosing not very beginner friendly projects. Not sure I would have been able to dissect this problem without community help
@pesterhazy those are some good questions, I’m also no expert on these things, but I do know clojure.lang.Reflector has gotten a lot more complex and strict in recent versions (1.10? 1.9?) to accomodate the Java 9 module system. Before that reflection sometimes allowed you to bypass some Java visibility rules, whereas now it will also enforce these.
That still leaves me puzzled as to what’s going on here
We’re calling .getLine on an instance of com.sun.media.sound.PortMixer
The Reflector is able to find that method, you can see the signature:
public javax.sound.sampled.Line com.sun.media.sound.PortMixer.getLine(javax.sound.sampled.Line$Info)
but it won’t call it, because the Java reflection API (Method.canAccess) says that this class may not access this method
So I don’t think it’s a Clojure bug, it’s merely doing what Java tells it to do, but why .canAccess returns false for what is simply calling a public method directly on the object I have no idea.
I believe the issue is that you have to type hint the Mixer to its interface. The interface is in the javax namespace, which is module accessible. But the concrete class is inside com.sum, which is no longer module accessible in Java9+.
In this case, the reflector can’t tell there’s an accessible interface for it I’m guessing.
I would guess the mandatory hint is the one in the return of get-mixer to the ^javax.sound.sampled.Mixer interface.
I didn’t know that. But if this hypothesis is correct, it seems in Java9+ you might need to type hint for accessibility as well.
If there are method overloads of the same arity, but different types, you need to type hint to disambiguate. Same for return value overload of same arity.
Basically, any case where there is ambiguity, you have to type hint to disambiguate.
You beat me to it @didibus, I asked for help on Clojurians in #clojure-dev, and Ghadi Shayban who’s done a lot of work on this stuff replied and he said basically the same thing
com.sun.media.sound.PortMixer is not a public class
there are two levels of “public”-ness in Java 9+
to call something you need 1) a public class
2) if that class comes from a Java module (I think this one does) the module needs to mark the package as exported
you have 1 but not 2, I suspect
there is likely a public interface that you should be typehinting to
not the concrete class
if you see com.sun it’s almost always an impl of a public interface
In general I don’t think this can be true, because how does Clojure tell if an object exposes add(int i) vs add(Long i) which one to call?
Personally, I prefer wrapping Java interop code into functions, so I don’t have to litter the rest of the codebase with hints, and then hint everything, so there are no surprises.
I agree, I also tend to wrap methods in functions with appropriate tags, so that I can then just call those and have the type information flow from there.
Which made me wonder if no-one has come up with a macro to generate these wrapper functions, (maybe someone has?)