JPL Guru needed: JPL throws when calling a method on an interface

I have been poking the JPL interface a bit, using Adopt OpenJKD 14 underneath.

A simple trial with a java.lang.String object (which is Prologified into an atom automatically) and a java.util.GregorianCalendar (which stays a Java Object referenced through a jref) work well and out of the box once you have set your environment variables.

So I wanted to do something slightly more interesting and connect to libreoffice, trasncribing the simplest exercise possible:

public class FirstUnoContact {

    public static void main(String[] args) {
        try {
            // get the remote office component context
            com.sun.star.uno.XComponentContext xContext =
                com.sun.star.comp.helper.Bootstrap.bootstrap();
            System.out.println("Connected to a running office ...");
            com.sun.star.lang.XMultiComponentFactory xMCF =
                xContext.getServiceManager();
            String available = (xMCF != null ? "available" : "not available");
            System.out.println( "remote ServiceManager is " + available );
            System.out.println();
        }
        catch (java.lang.Exception e){
            e.printStackTrace();
        }
        finally {
            System.exit(0);
        }
    }
}

So here it is (well, not yet fully complete):

first_uno_contact :-
    use_module(library(jpl)),  % SWI-Prolog: use_module/1 in predicate role, as the God of Logic Programming intended
    % XComponentContext is an interface!
    % Java: com.sun.star.uno.XComponentContext xContext = com.sun.star.comp.helper.Bootstrap.bootstrap();
    jpl_call('com.sun.star.comp.helper.Bootstrap','bootstrap',[],XContext),
    format("Connected to a running office with XContext = ~q\n",[XContext]),
    (jpl_is_object(XContext) -> format("XContext is an object\n") ; format("XContext is NOT an object\n")),
    write_class_inheritance_path_of_object(XContext),        
    % Java: com.sun.star.lang.XMultiComponentFactory xMCF = xContext.getServiceManager();
    jpl_call(XContext,'getServiceManager',[],_XMcf). % THIS THROWS

write_class_inheritance_path_of_object(JObj) :- 
    assertion(jpl_is_object(JObj)),
    jpl_object_to_class(JObj, JClass),
    format("Object is of Class ~q\n",[JClass]),
    write_class_inheritance_path(JClass).
    
write_class_inheritance_path(JX) :-     
    assertion(jpl_is_object(JX)), % it's an object
    assertion((jpl_object_to_class(JX,JXClass),jpl_class_to_classname(JXClass,'java.lang.Class'))), % of type java.lang.Class
    jpl_class_to_classname(JX, ClassName),
    format("Class ~q has name ~q\n",[JX,ClassName]),
    jpl_call(JX,'getSuperclass',[],JSuperClass),
    (jpl_null(JSuperClass)
     -> 
     true
     ;     
     (format("Class ~q has superclass ~q\n",[JX,JSuperClass]),
      write_class_inheritance_path(JSuperClass))).

When you run this, you get:

?- first_uno_contact.
Connected to a running office with XContext = <jref>(0x27c7330)
XContext is an object
Object is of Class <jref>(0x2602720)
Class <jref>(0x2602720) has name 'com.sun.proxy.$Proxy1'
Class <jref>(0x2602720) has superclass <jref>(0x27c7338)
Class <jref>(0x27c7338) has name 'java.lang.reflect.Proxy'
Class <jref>(0x27c7338) has superclass <jref>(0x2602710)
Class <jref>(0x2602710) has name 'java.lang.Object'
ERROR: Domain error: `object_or_class' expected, found `<jref>(0x27c7330)' (1st arg must be an object, classname, descriptor or type)

So, JPL says that

  • the reference to the object <jref>(0x27c7330)
  • which implements com.sun.star.uno.XComponentContext (which is a Java interface, see here),
  • and is of actual class com.sun.proxy.$Proxy1, which is an object constructed at runtime

cannot be recognized as an object? Maybe? Is there something trivial I am overlooking?

(These exercise sometimes get you entangled in a basket of concepts … obejct, class, runtime, not runtime … then you remember, its all just text and text transformations and text retrieval, syntactic machines, nothing so see here, and you go on!)

It’s not immensely informative, the message itself is already informative (more informative than most error messages one encounters in dark corners, especially the dreaded bugbear from the hell, “an unexpected error occurred”):

ERROR: Domain error: `object_or_class' expected, found `<jref>(0x2017d40)' (1st arg must be an object, classname, descriptor or type)
ERROR: In:
ERROR:   [12] throw(error(domain_error(object_or_class,<jref>(0x2017d40)),context(...,'1st arg must be an object, classname, descriptor or type')))
ERROR:   [11] jpl:jpl_call(<jref>(0x2017d40),getServiceManager,[],_34262) at /usr/local/logic/swipl/lib/swipl/library/jpl.pl:307
ERROR:    [9] <user>
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

But - good idea anyway! Let’s take a look at that JPL predicate starting at /usr/local/logic/swipl/lib/swipl/library/jpl.pl:307.

In fact, we can pull in the whole first part of that predicate, the “predicate entry check” into our program, call it and debug it.

We find that “the object has no type” (not sure what that means; I feel this might be a problem with JDK 14?)

New code below, without the inheritance tree printing, but with the JPL predicate entry check.

first_uno_contact :-
    use_module(library(jpl)),  % SWI-Prolog: use_module/1 in predicate role, as the God of Logic Programming intended
    % XComponentContext is an interface!
    % Java: com.sun.star.uno.XComponentContext xContext = com.sun.star.comp.helper.Bootstrap.bootstrap();
    jpl_call('com.sun.star.comp.helper.Bootstrap','bootstrap',[],XContext),
    format("Connected to a running office with XContext = ~q\n",[XContext]),
    (jpl_is_object(XContext) -> format("XContext is an object\n") ; format("XContext is NOT an object\n")),
    (jpl_object_to_type(XContext,Type) -> format("~q is of type ~q\n",[XContext,Type]) ; format("~q has no type!\n",[XContext])),
    jpl_call_entrycheck(XContext,'getServiceManager',[],_XMcf). % THIS THROWS


jpl_call_entrycheck(X, Mspec, Params, R) :-
    (   jpl_object_to_type(X, Type)         % the usual case (goal fails safely if X is var or rubbish)
    ->  Obj = X,
        Kind = instance
    ;   var(X)
    ->  throw(error(instantiation_error,context(jpl_call/4,'1st arg must be bound to an object, classname, descriptor or type')))
    ;   atom(X)
    ->  (   jpl_classname_to_type(X, Type)     % does this attempt to load the class?
        ->  (   jpl_type_to_class(Type, ClassObj)
            ->  Kind = static
            ;   throw(error(existence_error(class,X),context(jpl_call/4,'the named class cannot be found')))
            )
        ;   throw(error(type_error(class_name_or_descriptor,X),context(jpl_call/4,'1st arg must be an object, classname, descriptor or type')))
        )
    ;   X = class(_,_)
    ->  Type = X,
        jpl_type_to_class(Type, ClassObj),
        Kind = static
    ;   X = array(_)
    ->  throw(error(type_error(object_or_class,X),context(jpl_call/4,'cannot call a static method of an array type, as none exists')))
    ;   throw(error(domain_error(object_or_class,X),context(jpl_call/4,'1st arg must be an object, classname, descriptor or type')))
    ).

If we run this:

Connected to a running office with XContext = <jref>(0x1ce8e80)
XContext is an object
<jref>(0x1ce8e80) has no type!

If we debug this:

   Call: (12) jpl:jpl_object_to_type(<jref>(0x1420d00), _87946) ? creep
   Call: (15) jpl:jni_func(31, <jref>(0x1420d00), _87992) ? creep
   Exit: (15) jpl:jni_func(31, <jref>(0x1420d00), <jref>(0x14a1e48)) ? creep
   Call: (17) jpl:jni_func(33, <jref>(0x1420bf8), getName, '()Ljava/lang/String;', _88508) ? creep
   Exit: (17) jpl:jni_func(33, <jref>(0x1420bf8), getName, '()Ljava/lang/String;', jmethodID(1602284)) ? creep
   Call: (18) jpl:jni_ensure_jvm ? creep
   Exit: (18) jpl:jni_ensure_jvm ? creep
   Call: (18) jpl:jni_alloc_buffer(15, 0, _88684) ? creep
   Exit: (18) jpl:jni_alloc_buffer(15, 0, 327196) ? creep
   Call: (17) jpl:jni_func(36, <jref>(0x14a1e48), jmethodID(1602284), 327196, _88776) ? creep
   Exit: (17) jpl:jni_func(36, <jref>(0x14a1e48), jmethodID(1602284), 327196, 'com.sun.proxy.$Proxy1') ? creep
   Fail: (12) jpl:jpl_object_to_type(<jref>(0x1420d00), _88858) ? creep
   Redo: (11) first_uno_contact ? creep
^  Call: (12) format("~q has no type!\n", [<jref>(0x1420d00)]) ? creep
<jref>(0x1420d00) has no type!
^  Exit: (12) format("~q has no type!\n", [<jref>(0x1420d00)]) ? abort

Ok, next I will take a look at jpl:jpl_object_to_type, but not immediately.

Ok, I have looked at the Prolog side of the code in jpl.pl and it parses to classname wrongly (i.e. not according to spec). In fact, it assumes an “inner class” starts with a $ (this is wrong; the name of an “inner class” is just a class name which traditionally has contained a $ but this is not a necessity) and that a non-inner class does NOT start with a $ (also wrong, and we are hitting this problem here - the parsing of the class name silently fails - no type for you!).

The DCG is also a mess, looks far too complex and doesn’t use the proper Java names to identify name parts, according to

The Java® Language Specification

I can try to rewrite it and make a pull request (or at least try) but are there test cases to check that I haven’t messed up and/or will it be acceptable to restyle the code? (Plus I’m not yet 100% knowledgeable about DCGs, but one has to learn)

Here is the SVG of the DCG as far as I have traced it … failure occurs at the reddish $ which is not catered for.

see https://jpl7.org/PrologApiOverview#gotchas

Think of JPL as an interface to the JVM, not to Java :-/

1 Like

@anionic, should we probably upgrade the section “Naming static nested classes” (and possibly other related ones) from “Gotcha” to their own subsection in the doc so it is quickly seen by JPL users?

Issue 6 points out to lack of clarity or prominence of this issue in the doc, so maybe being in “Gotcha” is a bit hidden?

Yes indeed, and add internal links to it wherever classnames are mentioned in the Prolog API / Overview & Reference, or at least where new or bemused users are likely to read about using them…

NB my cute page architecture on jpl7.org has a position:fixed header and crude trickery for scrolling to subheadings, but may not work reliably when following a link to an in-page target e.g. …#gotchas :frowning:

Would also be good to start a “Cookbook” section of illustrative examples.

Thanks for pointing that. Yes it was moved to GitHub (though bitbucket should have re-directed). I have updated that text and link.

@anionic, done. I have basically put all the entries in the gotcha under level 2 with a heading in the navigation bar so people see it right away, they make sense there as the other entries.

Not sure what you meant with “classnames are mentioned in the Prolog API / Overview & Referenc”, which classnames?

Yes Cookbook would be good with significant examples. The doc itself has many examples (e.g., how to iterate through solutions), but I would be interested in HOWTO examples of far from trivial tricks. In this post, people are discussing non-trivial use of Java inside Prolog (the “easy” and more “natural” use is Prolog from Java I think). Would be great to know and document more about these usages and the “tricks” used.

Ok guys, I have amused myself to box out all the “throws messages” into a separate predicate (actually a separate file) to make the jpl.pl code more readable, as a prelude to revieweing the parsing of JVM/Java classnames.

Interested in a pull request?

The code is mostly in the hands of @ssardina and @anionic. Overall I’m typically happy with cleanup PRs if it is quite obvious they won’t cause new trouble.

1 Like

Thanks Jan.

I will prepare a pull request - we’ll see.