Weird Java Interop issues

My first time trying to use the Java Interop features and I’m hitting some issues using the Java Sound API

It’s just the beginning part of a little GUI I’m trying to make that will take audio input and display the waveform.

The problem code is a bit mixed up with the GUI code, but it’s here: https://github.com/geokon-gh/cameba/blob/master/src/cameba/audio.clj

basically the issue I’m having is I keep getting errors like:

No matching method getLine found taking 1 args for class com.sun.media.sound.PortMixer

but when i open up the matching java files there is a “public Line getLine(Line.Info info)”
ex: http://www.docjar.com/html/api/com/sun/media/sound/PortMixer.java.html#127

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 :slight_smile:
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.

1 Like

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

1 Like

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 :slight_smile:
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.

1 Like

Actually, are you sure you are calling getLine with a Line.Info ? It looks like maybe you’re just passing a string?

getLine takes this https://docs.oracle.com/javase/9/docs/api/javax/sound/sampled/Line.Info.html

1 Like

These are good ideas :smirk:, but I’m pretty confident I’m passing in a LineInfo

So I guess you figured out that the line that’s blowing up is in get-current-line

(.getLine (get-mixer (:mixer-info mixer-info)) (:line-info line-info))))

And if you look at the state map, the lines have maps that look like

{:name "Mic Boost target port",
:line-info #object[com.sun.media.sound.PortMixer$PortInfo 0x7a924bd "Mic Boost target port"]}

I had added the extra get-current-line-info function to double check exactly that :slight_smile:

I also tried to add a type hint and changed the line to look like:

(.getLine (get-mixer (:mixer-info mixer-info)) ^com.sun.media.sound.PortMixer$PortInfo (:line-info line-info))))` 

But the error remains the same

(not sure where to find the OpenJDK-11 version of this function… need to keep looking)

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

(get-current-line @cameba.core/*state)
;; => #object[com.sun.media.sound.PortMixer$PortMixerPort 0x254e5553 "com.sun.media.sound.PortMixer$PortMixerPort@254e5553"]

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.

2 Likes

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 :slight_smile:

@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

(let [mixer (get-mixer (:mixer-info (get-current-mixer-info @cameba.core/*state)))
      [method] (clojure.lang.Reflector/getMethods
                (class mixer)
                1
                "getLine"
                false)]
  {:method method
   :mixer-class (class mixer)
   :canAccess (.canAccess method mixer)})
;;=>
;; {:method
;;  #object[java.lang.reflect.Method 0x5f1d6566 "public javax.sound.sampled.Line com.sun.media.sound.PortMixer.getLine(javax.sound.sampled.Line$Info) throws javax.sound.sampled.LineUnavailableException"],
;;  :mixer-class com.sun.media.sound.PortMixer,
;;  :canAccess false}
  • 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.

1 Like

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.

2 Likes

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

2 Likes

Great detective work!

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?)

something like this:

Example use:

(ns foo.bar.mixer
  (:require [... :refer [defwrapper]])

(defwrapper javax.sound.sampled.Mixer)
(require '[foo.bar.mixer :as mixer])

(mixer/get-line my-mixer line-info)

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.