Converter Java API

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Converter Java API

Robert.Panzer
I am currently playing around with Java converters.

One thing that came up is that asciidoctor Ruby directly calls the registered converter.
Therefore it passes a JRuby object that implements AbstractBlock (or AbstractNode) to the convert method and not an implementation provided by asciidoctorJ, e.g. a BlockImpl.

public interface Converter {
    Object convert(AbstractBlock node);
    Object convert(AbstractBlock node, String transform); // Always gets a org.jruby.gen.InterfaceImpl on initial invocation
}

I'd like to propose adding new methods to AbstractConverter that always get the correct Java counterpart of the node and AbstractConverter.convert() is implemented to transform the JRuby node and invoke this new method:

package org.asciidoctor.converter;

public abstract class AbstractConverter implements Converter {

    public Object convert(AbstractNode node) {
        AbstractNode javaNode = ...;// share impl from AbstractBlockImpl.overrideRubyObjectToJavaObject()
        return convertNode(javaBlock);
    }
    public Object convert(AbstractNode node, String transform) {
        AbstractNode javaNode = ...;// share impl from AbstractBlockImpl.overrideRubyObjectToJavaObject()
        return convertNode(javaBlock, transform);
    }

    public abstract Object convertNode(AbstractNode node);

    public abstract Object convertNode(AbstractNode node, transform);
}

An alternative would be decorate the converter.
But that would require to generate these converters unless we only support instance based converters. (That is Converter instances are registered instead of their classes.)

What do you think?

Kind regards,
Robert
Reply | Threaded
Open this post in threaded view
|

Re: Converter Java API

mojavelinux
Administrator
Indeed. It would be ideal of the method you implement to convert a node in a Java-based converter receives AsciidoctorJ's AbstractBlock instance instead of the native Ruby object. In essence, we want to preserve the abstraction.

I don't have a preference for how this is done. My recommendation is to implement the most elegant approach you can come up with from the perspective of the Java developer.

If this works, a possible approach is to keep the interface as is, but add methods in the abstract class that explicitly accept a JRuby object. Those methods can delegate to the interface methods. That way, it remains transparent to the Java developer.

Something like:

```java

public abstract class AbstractConverter implements Converter {

    public Object convert(RubyObject node) {
        AbstractNode javaNode = ...;// share impl from AbstractBlockImpl.overrideRubyObjectToJavaObject()
        return convert(javaBlock);
    }
    public Object convert(RubyObject node, String transform) {
        AbstractNode javaNode = ...;// share impl from AbstractBlockImpl.overrideRubyObjectToJavaObject()
        return convert(javaBlock, transform);
    }
}

```

Would that work? If so, that would be idea.

Cheers,

-Dan

On Tue, Dec 23, 2014 at 1:50 AM, Robert.Panzer [via Asciidoctor :: Discussion] <[hidden email]> wrote:
I am currently playing around with Java converters.

One thing that came up is that asciidoctor Ruby directly calls the registered converter.
Therefore it passes a JRuby object that implements AbstractBlock (or AbstractNode) to the convert method and not an implementation provided by asciidoctorJ, e.g. a BlockImpl.

public interface Converter {
    Object convert(AbstractBlock node);
    Object convert(AbstractBlock node, String transform); // Always gets a org.jruby.gen.InterfaceImpl on initial invocation
}

I'd like to propose adding new methods to AbstractConverter that always get the correct Java counterpart of the node and AbstractConverter.convert() is implemented to transform the JRuby node and invoke this new method:

package org.asciidoctor.converter;

public abstract class AbstractConverter implements Converter {

    public Object convert(AbstractNode node) {
        AbstractNode javaNode = ...;// share impl from AbstractBlockImpl.overrideRubyObjectToJavaObject()
        return convertNode(javaBlock);
    }
    public Object convert(AbstractNode node, String transform) {
        AbstractNode javaNode = ...;// share impl from AbstractBlockImpl.overrideRubyObjectToJavaObject()
        return convertNode(javaBlock, transform);
    }

    public abstract Object convertNode(AbstractNode node);

    public abstract Object convertNode(AbstractNode node, transform);
}

An alternative would be decorate the converter.
But that would require to generate these converters unless we only support instance based converters. (That is Converter instances are registered instead of their classes.)

What do you think?

Kind regards,
Robert



If you reply to this email, your message will be added to the discussion below:
http://discuss.asciidoctor.org/Converter-Java-API-tp2605.html
To start a new topic under Asciidoctor :: Discussion, email [hidden email]
To unsubscribe from Asciidoctor :: Discussion, click here.
NAML



--
Reply | Threaded
Open this post in threaded view
|

Re: Converter Java API

asotobu
In reply to this post by Robert.Panzer
Currently I am implementing the abstractnode counterpart in Java, and I think that tomorrow I will be able to push the code. So yes intead of using abstractblock we will be able to use abstractnode.

The other part of the discussion, sorry but I can't get the problem, probably because today I have headache that I can't think clearly :P
Reply | Threaded
Open this post in threaded view
|

Re: Converter Java API

Robert.Panzer
In reply to this post by mojavelinux
Just tried your proposal:

I have two simple convert methods in the Converter interface that should be called by asciidoctor Ruby:

public interface Converter {
    Object convert(IRubyObject node);
    Object convert(IRubyObject node, String transform);
}

And the AbstractConverter adds these methods so that a Java developer can stick with the well-known convert() methods:

public abstract class AbstractConverter implements Converter {

    @Override
    public final Object convert(IRubyObject node) {
        AbstractBlock javaBlock = overrideRubyObjectToJavaObject(node);
        return convert(javaBlock);
    }

    @Override
    public final Object convert(IRubyObject node, String transform) {
        AbstractBlock javaBlock = overrideRubyObjectToJavaObject(node);
        return convert(javaBlock, transform);
    }

    protected abstract Object convert(AbstractBlock node);

    protected abstract Object convert(AbstractBlock node, String transform);
...
}

It works... but unfortunately I get this warning in the console which makes me think that it is rather luck that the correct methods were called:

C:/work/git/asciidoctorj/asciidoctorj-core/build/resources/main/gems/asciidoctor-1.5.2/lib/asciidoctor/abstract_block.rb:71 warning: ambiguous Java methods found, using convert(org.jruby.runtime.builtin.IRubyObject)

I also tried playing with adding
@JRubyMethod(name="__convert_java__")
 on the java counterparts, but  it does not have any effect. (I must admit that I did not really understand that, sth I have to read about over the holidays. :) )
Reply | Threaded
Open this post in threaded view
|

Re: Converter Java API

mojavelinux
Administrator
Excellent!

We might need to ping the JRuby guys about the correct way to overload. It may be that we can name the Java method something different, then map it back to the Ruby method explicitly. I'm sure there's a way, we just need to find it.

Great work!

-Dan

On Tue, Dec 23, 2014 at 2:45 AM, Robert.Panzer [via Asciidoctor :: Discussion] <[hidden email]> wrote:
Just tried your proposal:

I have two simple convert methods in the Converter interface that should be called by asciidoctor Ruby:

public interface Converter {
    Object convert(IRubyObject node);
    Object convert(IRubyObject node, String transform);
}

And the AbstractConverter adds these methods so that a Java developer can stick with the well-known convert() methods:

public abstract class AbstractConverter implements Converter {

    @Override
    public final Object convert(IRubyObject node) {
        AbstractBlock javaBlock = overrideRubyObjectToJavaObject(node);
        return convert(javaBlock);
    }

    @Override
    public final Object convert(IRubyObject node, String transform) {
        AbstractBlock javaBlock = overrideRubyObjectToJavaObject(node);
        return convert(javaBlock, transform);
    }

    protected abstract Object convert(AbstractBlock node);

    protected abstract Object convert(AbstractBlock node, String transform);
...
}

It works... but unfortunately I get this warning in the console which makes me think that it is rather luck that the correct methods were called:

C:/work/git/asciidoctorj/asciidoctorj-core/build/resources/main/gems/asciidoctor-1.5.2/lib/asciidoctor/abstract_block.rb:71 warning: ambiguous Java methods found, using convert(org.jruby.runtime.builtin.IRubyObject)

I also tried playing with adding
@JRubyMethod(name="__convert_java__")
 on the java counterparts, but  it does not have any effect. (I must admit that I did not really understand that, sth I have to read about over the holidays. :) )


If you reply to this email, your message will be added to the discussion below:
http://discuss.asciidoctor.org/Converter-Java-API-tp2605p2608.html
To start a new topic under Asciidoctor :: Discussion, email [hidden email]
To unsubscribe from Asciidoctor :: Discussion, click here.
NAML



--
Reply | Threaded
Open this post in threaded view
|

Re: Converter Java API

asotobu
In reply to this post by Robert.Panzer
But currently the interface receives two AbstractBlocks https://github.com/asciidoctor/asciidoctorj/blob/converters/asciidoctorj-core/src/main/java/org/asciidoctor/converter/Converter.java

Currently I cannot see where is the problem, I mean for exapmlein this test https://github.com/asciidoctor/asciidoctorj/blob/converters/asciidoctorj-core/src/test/java/org/asciidoctor/converter/TextConverter.java it works.

Sorry for not understanding you hehehe
Reply | Threaded
Open this post in threaded view
|

Re: Converter Java API

Robert.Panzer
In reply to this post by mojavelinux
Some new strange findings:

I only get this warning if I call AbstractBlock.content() in my converter.

I don't get the warning if I call AbstractBlock.blocks() first and then AbstractBlock.content() afterwards.

Probably because AbstractBlockImpl.blocks() replaces the objects in the block list of the document with the Java counterparts?

@Alex: The problem that I am talking about originally is that the document that is initially passed to the converter is a JRuby object, not the AsciidoctorJ counterpart. Maybe this is not a problem, but I am quite unsure if it's a good idea to pass objects of completely different packages to a converter implementation.
For instance you cannot ask the objects returned by AbstractBlock.blocks() if they are an instance of org.asciidoctor.ast.Section because they are JRuby objects.
So without an initial conversion this code always has the output below:

Object convert(AbstractBlock abstractBlock, String s) {
    if (s == 'document') {
        abstractBlock.blocks().each {
                println ">>> ${it}"
                println ">>> ${it.getClass()}"
                println ">>> ${it instanceof Section}"
        }
    } else {
        null
    }
}

will produce an output like this:

>>> #<Asciidoctor::Section@2004 {level: 1, title: "And again", blocks: 1}>
>>> class org.jruby.RubyObject
>>> false
>>> #<Asciidoctor::Section@2006 {level: 1, title: "Bye", blocks: 1}>
>>> class org.jruby.RubyObject
>>> false

Reply | Threaded
Open this post in threaded view
|

Re: Converter Java API

asotobu
Right, now I understand. Yes this is a problem that I am not sure how to face it. Tomorrow let me merge the new API and abstractnode class to master, and then let's rework the whole part.