Walton Hoops
Walton Hoops

Reputation: 864

Access public method defined on abstract base class from Clojure

I'm attempting to use the DNSJava library from clojure. I attempt the following:

dmarced.dns> (def results (.run (Lookup. "google.com" Type/TXT)))
#'dmarced.dns/results
dmarced.dns> (def r (get results 0))
#'dmarced.dns/r
dmarced.dns> r
#object[org.xbill.DNS.TXTRecord 0x687a3556 "google.com.\t\t3599\tIN\tTXT\t\"v=spf1 include:_spf.google.com ~all\""]
dmarced.dns> (class r)
org.xbill.DNS.TXTRecord
dmarced.dns> (instance? TXTRecord r)
true

Great! I know from the docs that I should be able to use .getStrings to get the content of the record.

dmarced.dns> (.getStrings r)
Reflection warning, *cider-repl dmarced*:150:13 - reference to field getStrings can't be resolved.
IllegalArgumentException Can't call public method of non-public class: public java.util.List org.xbill.DNS.TXTBase.getStrings()  clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:88)

Ok quick google tells me this can be solved via type hints:

dmarced.dns> (.getStrings ^TXTRecord r)
Reflection warning, *cider-repl dmarced*:153:13 - call to method getStrings on org.xbill.DNS.TXTRecord can't be resolved (argument types: ).
IllegalArgumentException Can't call public method of non-public class: public java.util.List org.xbill.DNS.TXTBase.getStrings()  clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:88)

Huh. At I've search Google further, and read the full page on interop, but I'm not having any luck.

Looking at the source for TXTRecord I see that it extends TXTBase which is an abstract class where getStrings is implemented. Since TXTRecord extends it, I should have access to getStrings through that (and I went so far as to write a Java program to verify it.

Does anyone know how I can access this via Clojure?

EDIT Working java program

import org.xbill.DNS.*;

class Main {
    public static void main(String [] args) throws TextParseException {
        Lookup l = new Lookup("google.com", Type.TXT);
        Record [] rs = l.run();
        for(int i = 0; i < rs.length; i++) {
            TXTRecord tr = (TXTRecord)rs[i];
            for(int j = 0; j < tr.getStrings().size(); j ++) {
                System.out.println(tr.getStrings().get(j));
            }
        }

    }
}

Upvotes: 3

Views: 264

Answers (2)

Walton Hoops
Walton Hoops

Reputation: 864

Piotrek seems to be right about why it's occurring. The Clojure bug report suggests writing a Java class to get the information. It's not pretty but I managed to work around it with

dmarced.core> (def txt-strings-method (doto (.getDeclaredMethod TXTBase "getStrings" nil) (.setAccessible true)))
#'dmarced.core/txt-strings-method
dmarced.core> (defn get-txt-strings [r]
                (.invoke m r nil))
#'dmarced.core/get-txt-strings
dmarced.core> (get-txt-strings r)
["v=spf1 include:_spf.google.com ~all"]
dmarced.core> 

Upvotes: 1

Piotrek Bzdyl
Piotrek Bzdyl

Reputation: 13175

It looks that it is a known bug CLJ-1243. You can find more details and probably root cause by reading Netty issue description:

The public methods inherited from AbstractBootstrap cannot be invoked via the Java reflection API due to long-standing bugs such as JDK-4283544.

This is a serious problem for alternative JVM languages which use reflection to discover Java methods. For example, Clojure uses reflection in its compiler, and it cannot invoke these methods at all, as reported in CLJ-1243.

And part of the description from the JDK bug:

java.lang.reflect.Field (get* and set*) and Method (invoke) base their access check on the declaring class. This is contrary to the JLS, which defines accessibility in terms of the reference type.

Upvotes: 4

Related Questions