5

I've been goofing around with Kotlin of recent, and it's been Awesome!

I was working with Twitter4j library to tryout some stuff with the Twitter API. I wrote this code in Kotlin

object Demo {

    private val twitterStream = TwitterStreamFactory().instance
    @JvmStatic
    fun main(args: Array<String>) {

        val listener = object : StatusListener {

            override fun onStallWarning(warning: StallWarning?) {
                println("Got stall warning:" + warning)
            }

            override fun onScrubGeo(userId: Long, upToStatusId: Long) {
                println("Got scrub_geo event userId:$userId upToStatusId:$upToStatusId")
            }

            override fun onStatus(status: Status) {
                println("@" + status.user.screenName + " - " + status.text)
            }

            override fun onDeletionNotice(statusDeletionNotice: StatusDeletionNotice) {
                println("Got a status deletion notice id:" + statusDeletionNotice.statusId)
            }
            override fun onTrackLimitationNotice(numberOfLimitedStatuses: Int) {
                println("Got track limitation notice:" + numberOfLimitedStatuses)
            }
            override fun onException(ex: Exception) {
                ex.printStackTrace()
            }
        }

        twitterStream.addListener(listener)
        twitterStream.sample()

    }
}

but each time I ran it, I got exception in thread "main" java.lang.IllegalAccessError: tried to access class twitter4j.StreamListener from class co.enoobong.eno.twitter.bot.Demo at co.enoobong.eno.twitter.bot.Demo.main(Demo.kt:63)

Upon further investigation, Tools->Kotlin->Show Kotlin Bytecode. I decompiled to Java, only to discover that this is what was being generated.

 @JvmStatic
 public static final void main(@NotNull String[] args) {
  Intrinsics.checkParameterIsNotNull(args, "args");
  <undefinedtype> listener = new StatusListener() {
     public void onStallWarning(@Nullable StallWarning warning) {
        String var2 = "Got stall warning:" + warning;
        System.out.println(var2);
     }

     public void onScrubGeo(long userId, long upToStatusId) {
        String var5 = "Got scrub_geo event userId:" + userId + " upToStatusId:" + upToStatusId;
        System.out.println(var5);
     }

     public void onStatus(@NotNull Status status) {
        Intrinsics.checkParameterIsNotNull(status, "status");
        String var2 = "@" + status.getUser().getScreenName() + " - " + status.getText();
        System.out.println(var2);
     }

     public void onDeletionNotice(@NotNull StatusDeletionNotice statusDeletionNotice) {
        Intrinsics.checkParameterIsNotNull(statusDeletionNotice, "statusDeletionNotice");
        String var2 = "Got a status deletion notice id:" + statusDeletionNotice.getStatusId();
        System.out.println(var2);
     }

     public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
        String var2 = "Got track limitation notice:" + numberOfLimitedStatuses;
        System.out.println(var2);
     }

     public void onException(@NotNull Exception ex) {
        Intrinsics.checkParameterIsNotNull(ex, "ex");
        ex.printStackTrace();
     }
  };
  twitterStream.addListener((StreamListener)listener);
  twitterStream.sample();

}

Kotlin was trying to cast listener to StreamListener which is private in the Library (StatusListener extends it) hence the IllegalAccessError

Any ideas how to resolve please?

PS: Here's a working Java version of the code - https://github.com/yusuke/twitter4j/blob/master/twitter4j-examples/src/main/java/twitter4j/examples/stream/PrintSampleStream.java

PPS: I'm using Kotlin 1.1.4, on IntelliJ IDEA 2017.2.2

Enoobong
  • 1,594
  • 2
  • 15
  • 25

2 Answers2

2

Kotlin adds these casts to ensure the correct method is called when dealing with overloaded methods. The problem occurs because a public api addListener expects a package-private interface StreamListener. Trying to create such an api actually causes a warning exactly because of this problem and should be fixed on Twitter4j's side.

There is no direct way to fix this in Kotlin, you can work around the issue using a Java helper class and perhaps an extension method:

class Twitter4jFixer {
    public static void addListener(TwitterStream stream, StatusListener listener) {
        stream.addListener(listener);
    }
}


fun TwitterStream.addListenerFixed(listener: StatusListener) {
    Twitter4jFixer.addListener(this, listener)
}
Kiskae
  • 24,655
  • 2
  • 77
  • 74
  • Thanks for your response @Kiskae. I'd try this out. Actually it works perfectly in Java so no need for a fixer – Enoobong Aug 20 '17 at 12:28
0

Kiskae put a good point, Maybe author of twitter4j does not take interactions with other jvm languages into account.

I don't think the fix is necessary.

You can just put your class into package twitter4j if you must use the package private interface twitter4j.StreamListener because of the unwanted casting.

aristotll
  • 8,694
  • 6
  • 33
  • 53
  • Thanks for your response @aristottl! How do I put my class into `package twitter4j` it's an external library. Just added the dependency to my pom.xml – Enoobong Aug 20 '17 at 12:47
  • @Enoobong You just need to create a new package with name `twitter4j`. – aristotll Aug 20 '17 at 12:51
  • Twitter4j has a lot of weird cases like this, even in Java. For example if the Json object cache is enabled you can't clear the cache from outside of the `twitter4j` package, eventually causing OutOfMemory errors. – Kiskae Aug 20 '17 at 16:16