ClojureでTwitter4jしてみた。(で、ちょっとハマったことなど・・・)

Twitter4j を試してみた。

参考はこのページ「コード例」

まずは、ステータスの更新

(import '(twitter4j Twitter))
(def id "hogehoge")
(def pw "piyopiyo")
(def twitter (new Twitter id pw))

(def status (. twitter updateStatus "test tweet (by twitter4j and Clojure)"))
(println (str "Successfully updated the status to [" (.getText status) "]."))

注意点:参考ページでは、

    Twitter twitter = new TwitterFactory().getInstance(twitterID,twitterPassword);

のように、TwitterFactoryを使っているが、これをそのまま移植するとうまくいかない。

user=> (import '(twitter4j TwitterFactory))
user=> (def twitter (. (new TwitterFactory) getInstance id pw))
java.lang.IllegalArgumentException: Can't call public method of non-public class: public java.lang.Object twitter4j.TwitterFactoryBase.getInstance(java.lang.String,java.lang.String) (NO_SOURCE_FILE:38)

ちなみに、Scalaで実験すると、Factory経由でも問題ない。

import twitter4j._

val twitterID = "hogehoge"
val twitterPW = "piyopiyo"
val twitter = new TwitterFactory.getInstance(twitterID,twitterPW)

val status = twitter.updateStatus("test tweet (by twitter4j)")
println("Successfully updated the status to [" + status.getText + "].")

Clojure からJavaのクラスを呼ぶときの何かが問題なのだろうが、
ソースを追う必要がありそうなので、今のところはここまで・・・(^^;


追記:

ソースを追ってみた・・・

上記の例外は、ClojureReflector.java85行目で投げられたもので、それはつまり103行目からの、「getAsMethodOfPublicBase」がnullを返したらからでした。

この関数はその名の通りクラス階層をたどりつつpublicなメソッドを探すものなのだが、TwitterFactory クラスの getInstance メソッドについて実行した時には、その親クラスをどこまでたどってもAbstractのままなので、このような結果になっていると言うわけ。

これは、仕様なのか、バグなのかは現時点では不明。

仕様として捉えた場合、現状ではAbstract Classを継承したClassで、オーバライドしない、親クラスの関数は呼べないってことなのだろうか・・・でも、それはいくらなんでもずいぶん前に表面化していそうだよなぁ・・・もしかすると、Abstract 且つ Generic という場合?・・・

とってもいい加減な推測なので、ツッコミ大募集!!(^^;


最後に、調査、実験に使ったコードをあげておきます。


Reflector.java

import java.lang.reflect.*;
import java.util.Arrays;

public class Reflector{
  public static Method getAsMethodOfPublicBase(Class c, Method m){
    for(Class iface : c.getInterfaces())
      {
      for(Method im : iface.getMethods())
        {
        if(im.getName().equals(m.getName())
           && Arrays.equals(m.getParameterTypes(), im.getParameterTypes()))
          {
          return im;
          }
        }
      }
    Class sc = c.getSuperclass();
    if(sc == null)
      return null;
  
    System.out.println(sc);
    for(Method scm : sc.getMethods())
      {
        if(scm.getName().equals(m.getName())) {
          System.out.println("Pass-1");
          System.out.println(scm);
          if(Arrays.equals(m.getParameterTypes(), scm.getParameterTypes())) {
            System.out.println("Pass-2");
            System.out.println(scm.getDeclaringClass().getModifiers());
            if(Modifier.isPublic(scm.getDeclaringClass().getModifiers()))
            {
              System.out.println("Pass-3");
              return scm;
            }
          }
        }
      }
    return getAsMethodOfPublicBase(sc, m);
  }
}

test.scala

import twitter4j._

val tf = new TwitterFactory
val klass = tf.getClass
val method = klass.getMethods.apply(6)

println(method)
println(method.getDeclaringClass.getModifiers)

println(Reflector.getAsMethodOfPublicBase(klass,method))


実行結果

$ javac Reflector.java
$ scala -cp twitter4j-core-2.1.1-SNAPSHOT.jar:. test.scala 
public java.lang.Object twitter4j.TwitterFactoryBase.getInstance(java.lang.String,java.lang.String)
1024
class twitter4j.TwitterFactoryOAuthSupportBase
Pass-1
public java.lang.Object twitter4j.TwitterFactoryBase.getInstance()
Pass-1
public java.lang.Object twitter4j.TwitterFactoryBase.getInstance(twitter4j.http.Authorization)
Pass-1
public java.lang.Object twitter4j.TwitterFactoryBase.getInstance(java.lang.String,java.lang.String)
Pass-2
1024
class twitter4j.TwitterFactoryBase
Pass-1
public java.lang.Object twitter4j.TwitterFactoryBase.getInstance()
Pass-1
public java.lang.Object twitter4j.TwitterFactoryBase.getInstance(twitter4j.http.Authorization)
Pass-1
public java.lang.Object twitter4j.TwitterFactoryBase.getInstance(java.lang.String,java.lang.String)
Pass-2
1024
class java.lang.Object
null