Integerを"=="で比較してはいけない

追記:Javaの話です。

public class Main {
    public static void main(String args[]) throws Exception {
        Integer i1 = 1;
        Integer i2 = get1();
        if (i1 == i2) {
            System.out.println("equal");
        } else {
            System.out.println("not equal!!");
        }
    }
    public static Integer get1() {
        // DBとかから取ってきたつもり
        return new Integer(1);
    }
}

は、"not equal!!"と言われてしまいます。
(ただし、コンパイラや実行環境に依存するかもしれません。)

Integerはプリミティブ型ではないので、==では、同一のインスタンスかどうかを確認してしまっているんですね。(たぶん

普通、比較といえば、値の同一性を知りたいと思うので、

public class Main {
    public static void main(String args[]) throws Exception {
        Integer i1 = 1;
        Integer i2 = get1();
        if (i1.equals(i2)) { // ←ここを変えた
            System.out.println("equal!!");
        } else {
            System.out.println("not equal!!");
        }
    }
    public static Integer get1() {
        // DBとかから取ってきたつもり
        return new Integer(1);
    }
}

まぁ、こうしますよね。

ただ、上記の場合は i1がnullの場合はNullPointerExceptionが発生します。当たり前ですね。
なので、 i1 が必ずnullでは無い場合にしか使えないです。
nullの場合、一致とみなすか、不一致とみなすかは文脈によるかと思いますので、割愛します。
org.apache.commons.lang.ObjectUtils.equals を利用するのも手ですね。

8/10追記 java.util.Objects.equals ってのがJava7からあるみたいです。(コメントで教えていただきました。)

ちゃんと考えれば当たり前なんですが、誰かの書いたコードで、ぼんやり読んでるとハマりますね。(というか、ハマりました。

androidからGAEのサーバにデータを送信する

androidアプリと、GAE(Python)の連携です。
共円チェッカーでステージを送信するときのコードです。

String url = context.getString(R.string.upload_url);
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> nameValuePair = new ArrayList<NameValuePair>();
nameValuePair.add(new BasicNameValuePair("data", params[0]));
try {
    httpPost.setEntity(new UrlEncodedFormEntity(nameValuePair,
            HTTP.UTF_8));
    HttpResponse response = httpClient.execute(httpPost);
    String ret = EntityUtils.toString(response.getEntity());

    return ret;
} catch (IOException e) {
    throw new RuntimeException(e);
} finally {
    httpClient.getConnectionManager().shutdown();
}

URLはstrings.xmlに定義し、Context#getString(int)にて取得しています。
データの送信には、org.apache.http.client.HttpClientを利用しました。
また、GETではなく、POSTで送信するために、org.apache.http.client.methods.HttpPostを利用しています。
で、送信したいデータはparams[0]に設定してあるので、BasicNameValuePairに設定し、Listに追加しています。
HttpEntityEnclosingRequestBase#setEntity(HttpEntity)に設定する際にUTF-8エンコードしています。(たぶん、この設定のおかげで日本語が送信できています)
設定が完了したら、HttpClient#execute(HttpUriRequest)でデータを送信します。
EntityUtils#toString(HttpEntity)で、レスポンスを文字列として取得しています。
あとは、finally節にてHttpClientのクローズを行っています。


で、これを受け取るサーバ側

#!/usr/bin/env python
#-*- coding: utf-8 -*-
#
import logging
from google.appengine.ext import webapp, db
from google.appengine.ext.webapp.util import run_wsgi_app

# パズルのステージ情報
class KyouenPuzzle(db.Model):
    # ステージ番号
    stageNo = db.IntegerProperty(required=True)
    # サイズ
    size = db.IntegerProperty(required=True)
    # ステージ上の石の配置
    stage = db.StringProperty(required=True)
    # 作者
    creator = db.StringProperty()

# 中略

# 登録処理
class KyouenRegist(webapp.RequestHandler):
    # POSTリクエストを処理します。
    def post(self):
        # パラメータ名:dataを取得
        data = self.request.get('data').split(',')
        logging.debug("post data:" + str(data))

        # 入力データの登録    
        model = KyouenPuzzle(stageNo=self.getNextStageNo(),
                             size=int(data[0]),
                             stage=data[1],
                             creator=data[2].replace('\n', ''))
        model.put()

        # レスポンスの返却
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('success stageNo=' + str(model.stageNo))
        return

application = webapp.WSGIApplication([('/regist', KyouenRegist)
                                      ], debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

KyouenPuzzleクラスがDB登録用のクラスです。
4つのプロパティを持っています。で、それが登録されます。
KyouenRegistクラスは、javaで言う、javax.servlet.http.HttpServletを継承したクラス。
postメソッドを定義し、POSTリクエストを処理します。
リクエストデータをself.request.get('data')で取得できます。('data'はjavaで指定した文字列)
日本語としてちゃんと取得できました。
KyouenPuzzleのコンストラクタに対して、creatorに代入する際にreplace('\n', '')としているのは、エラー対策です。(creatorがmultiline=Trueとして定義されていないので、複数行のデータを登録するとエラーになります)


こんな感じでGAEにデプロイしておけば、android側で

<string name="upload_url">http://[設定したID].appspot.com/regist</string>

と設定しておけば、GAE上のDBにデータを登録できます。


Pythonは初めてだったので、ループの構文やら、文字列操作やらに手間取りましたが、簡潔に記述できて楽しかったです。
暇ができれば、ちゃんと本買って勉強しようと思います。

戻るボタンの制御について

色揃えるやつでは、パズル画面より戻った際に、リストに"CLEARED"と表示されます。
あと、クリア後に"Retry"ボタンが存在します。

このため、初回クリア後、"Retry"が押され、そのまま戻るボタンを押されると、リストの再描画のタイミングが無く、"CLEARED"が表示されませんでした。

そこで、戻るボタンを押下した際に、呼び出し元Activityに結果を返却することにしました。


まず、呼び出す際に、Activity#startActivityForResultにて呼び出すように変更。
で、呼び出し元ActivityにonActivityResultメソッドを実装。その中で、受け取ったクリア情報より内部保持しているリストを更新し、ArrayAdapter#notifyDataSetChangedを呼び出し。

あと、呼び出される側に下記の処理を追加。

@Override
public boolean dispatchKeyEvent(KeyEvent e) {
    if (e.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        if (e.getAction() == KeyEvent.ACTION_DOWN) {
            // 戻るボタンが押された場合
            
            // 結果を設定
            Intent intent = new Intent();
            intent.putExtra("item", item);
            setResult(RESULT_OK, intent);
            finish();
            return true;
        }
    }
    return super.dispatchKeyEvent(e);
}

戻るボタンを押された際には、現在のステージ情報を必ず返すようにしました。
戻り値についてですが、dispatchKeyEventメソッドのJavaDocには下記のように記載されています。

Return true if this event was consumed.

エキサイト翻訳によると、

この出来事が消費されたなら、本当に戻ってください。

と、いうことなので、この場合はtrueを返すのが正解っぽいです。



ちなみに、"KeyEvent.ACTION_DOWN"ではなく、"KeyEvent.ACTION_UP"としてしまうと、2画面戻るといった、よくわからない動きをしてしまいます。
0.5から、0.5.2へバージョンアップしたのは、これを修正しました。

通知領域に文字を表示する

今回作成したサンプルプログラムです。
http://dl.dropbox.com/u/8518065/android/notificationSample1/SampleAndroid.apk
画像は、notifyボタンを押した直後の状態

で、下記がソース。

まずは、main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <EditText
        android:id="@+id/title"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="sample_title"
        />
    <EditText
        android:id="@+id/message"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="sample_message"
        />
    <Button
        android:id="@+id/button1"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="notify" 
        />
</LinearLayout>

次に、メインとなるアクティビティクラス。

package hm.orz.chaos114.android.sample;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class SampleAndroidActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        init();
    }

    private void init() {
        // タイトル入力領域を取得
        final EditText titleTextView = (EditText) findViewById(R.id.title);
        // メッセージ入力領域を取得
        final EditText messageTextView = (EditText) findViewById(R.id.message);

        // 通知ボタンを取得
        Button button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CharSequence title = titleTextView.getText();
                CharSequence message = messageTextView.getText();
                NotificationUtil.notify(SampleAndroidActivity.this, title,
                        message);
            }
        });
    }
}

あと、そこから呼ばれているユーティリティクラス。

package hm.orz.chaos114.android.sample;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;

public class NotificationUtil {

    public static void notify(Context context, CharSequence title,
            CharSequence message) {

        // 通知をタップされたときのintentを作成
        Intent newIntent = new Intent(context, SampleAndroidActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
                newIntent, 0);
        
        // Notificationオブジェクトの作成
        Notification notification = new Notification(R.drawable.icon, title,
                System.currentTimeMillis());
        notification.setLatestEventInfo(context, title, message, contentIntent);
        notification.flags = Notification.FLAG_AUTO_CANCEL;
        
        // Managerオブジェクトの取得
        NotificationManager notificationManager = (NotificationManager) context
                .getSystemService(Context.NOTIFICATION_SERVICE);
        
        // このアプリの通知を一旦削除し、再通知
        notificationManager.cancelAll();
        notificationManager.notify(0, notification);
    }
}


あと、補足事項。

  • NotificationManager#notifyの第一引数が、今回固定値となっています。

アプリケーション内で同一のidが指定された場合、cancelAllをしてやらないと、再通知されませんでした。
複数の通知を表示したいは、表示した最後の番号をSharedPreferencesとかに入れといて、それにインクリメントして表示するとか?
さらに、あとでcancel(通知を削除)したい場合、通知内容とidを保存しないといけないかな?(あまり、そんな場面が思いつきませんが。)

  • 通知をクリックした際のintent

今回、SampleAndroidActivityを起動しています。
この場合、SampleAndroidActivityを起動したまま通知をタップすると、2枚同じアクティビティが起動してしまいます。(=戻るを押下しても、もう1個のSampleAndroidActivityが表示される)
これを解消する場合、newIntentを生成後に下記のように設定。

newIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

ただし、複数のアクティビティが定義されていたりした場合、意図しない動きをするかもしれません。

  • PendingIntent

通知がタップされた際に発行するIntentを保持するIntentのようです。
あまりよくわかっていません。

javaによる2次元座標の計算など

とりあえず、下記に共円チェッカーで利用しているクラスの全文を記載します。

2011/07/18 22:30 Lineのコンストラクタ内の数式が誤っていたため、修正。また、参考サイトを記載。

package hm.orz.chaos114.android.kyouenchecker;

import java.util.ArrayList;
import java.util.List;

public class GameModel {

    class Point {
        double x;
        double y;

        Point(double x, double y) {
            this.x = x;
            this.y = y;
        }

        double getAbs() {
            return Math.sqrt(x * x + y * y);
        }

        Point difference(Point p2) {
            return new Point(x - p2.x, y - p2.y);
        }

        Point sum(Point p2) {
            return new Point(x + p2.x, y + p2.y);
        }

        @Override
        public String toString() {
            return "Point [x=" + x + ", y=" + y + "]";
        }
    }

    /**
     * Ax+By+C=0を表現するクラス。
     */
    class Line {
        Point p1;
        Point p2;
        double a;
        double b;
        double c;

        Line(Point p1, Point p2) {
            this.p1 = p1;
            this.p2 = p2;

            a = p1.y - p2.y;
            b = p2.x - p1.x;
            c = p1.x * p2.y - p2.x * p1.y;
        }

        double getY(double x) {
            double y = -1 * (a * x + c) / b;

            return y;
        }

        @Override
        public String toString() {
            return "Line [p1=" + p1 + ", p2=" + p2 + ", a=" + a + ", b=" + b
                    + ", c=" + c + "]";
        }
    }

    /**
     * 共円情報を表現するクラス。
     * 
     * @author noboru
     */
    class KyouenData {
        Point p1;
        Point p2;
        Point p3;
        Point p4;

        public KyouenData(Point p1, Point p2, Point p3, Point p4) {
            this.p1 = p1;
            this.p2 = p2;
            this.p3 = p3;
            this.p4 = p4;
        }
    }

    List<Point> points = new ArrayList<Point>();

    public void add(int x, int y) {
        Point p = new Point(x, y);

        points.add(p);
    }

    public KyouenData isKyouen() {
        if (points.size() < 4) {
            return null;
        }

        Point p1 = points.get(points.size() - 1);

        for (int i = 0; i < points.size() - 1; i++) {
            Point p2 = points.get(i);
            for (int j = i + 1; j < points.size() - 1; j++) {
                Point p3 = points.get(j);
                for (int k = j + 1; k < points.size() - 1; k++) {
                    Point p4 = points.get(k);
                    boolean kyouen = isKyouen(p1, p2, p3, p4);
                    if (kyouen) {
                        return new KyouenData(p1, p2, p3, p4);
                    }
                }
            }
        }

        return null;
    }

    public boolean isKyouen(Point p1, Point p2, Point p3, Point p4) {
        // p1,p2の垂直二等分線を求める
        Line l12 = getMidperpendicular(p1, p2);
        // p2,p3の垂直二等分線を求める
        Line l23 = getMidperpendicular(p2, p3);

        // 交点を求める
        Point intersection123 = getIntersection(l12, l23);
        if (intersection123 == null) {
            // p1,p2,p3が直線上に存在する場合
            Line l34 = getMidperpendicular(p3, p4);
            Point intersection234 = getIntersection(l23, l34);
            if (intersection234 == null) {
                // p2,p3,p4が直線状に存在する場合
                return true;
            }
        } else {
            double dist1 = getDistance(p1, intersection123);
            double dist2 = getDistance(p4, intersection123);
            if (Math.abs(dist1 - dist2) < 0.0000001) {
                return true;
            }
        }
        return false;
    }

    /**
     * 2点間の距離を求める。
     * 
     * @param p1 座標1
     * @param p2 座標2
     * @return 距離
     */
    public double getDistance(Point p1, Point p2) {
        Point dist = p1.difference(p2);

        return dist.getAbs();
    }

    /**
     * 2直線の交点を求める。
     * 
     * @param l1 直線1
     * @param l2 直線2
     * @return 交点座標(交点が存在しない場合、null)
     */
    public Point getIntersection(Line l1, Line l2) {
        double f1 = l1.p2.x - l1.p1.x;
        double g1 = l1.p2.y - l1.p1.y;
        double f2 = l2.p2.x - l2.p1.x;
        double g2 = l2.p2.y - l2.p1.y;

        double det = f2 * g1 - f1 * g2;
        if (det == 0) {
            return null;
        }

        double dx = l2.p1.x - l1.p1.x;
        double dy = l2.p1.y - l1.p1.y;
        double t1 = (f2 * dy - g2 * dx) / det;

        return new Point(l1.p1.x + f1 * t1, l1.p1.y + g1 * t1);
    }

    /**
     * 2点の垂直二等分線を求める。
     * 
     * @param p1 座標1
     * @param p2 座標2
     * @return 垂直二等分線
     */
    public Line getMidperpendicular(Point p1, Point p2) {
        Point midpoint = getMidpoint(p1, p2);
        Point dif = p1.difference(p2);
        Point gradient = new Point(dif.y, -1 * dif.x);

        Line midperpendicular = new Line(midpoint, midpoint.sum(gradient));
        return midperpendicular;
    }

    /**
     * 中点を求める。
     * 
     * @param p1 座標1
     * @param p2 座標2
     * @return 中点座標
     */
    public Point getMidpoint(Point p1, Point p2) {

        Point midpoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
        return midpoint;
    }
}


で、説明など。

Pointクラス

2次元座標(x座標,y座標)を表現するためのクラスです。
ベクトル(x方向成分,y方向成分)も表します。

getAbsメソッド

座標の原点からの距離を返します。
ベクトルの大きさを表します。

differenceメソッド
sumメソッド

それぞれ、保持している座標と引数の座標の和・差を返します。

Lineクラス

Ax+By+C=0を表現するクラスです。

コンストラクタ

引数として、2つの座標を受け取ります。
保持する情報は、その2点を通過する直線の式となります。
下記サイトに書いてある通りにしました。
http://bobobobo.wordpress.com/2008/01/07/solving-linear-equations-ax-by-c-0/

getYメソッド

引数としてx座標を受け、保持している直線上のy座標を返します。

KyouenDataクラス

呼び出し元に共円となった4点を返したいがために定義したクラスです。
Listでもよかった気がします。

isKyouenメソッド

4点が登録されていない場合、判定を行いません。
最後に登録した点を基準とします。
それ以外の既存の3点を順に選んでゆき、それら4点が共円かどうかを判定します。
実際の判定処理は次のisKyouen(Point, Point, Point, Point)で行います。

isKyouen(Point, Point, Point, Point)メソッド

実際の共円判定を行います。
引数の4点が円周上に存在する場合、trueを返します。
以下、手順です。

  • 点1、点2の垂直二等分線を求めます。以降、線12とします。
  • 同じく、点2、点3の垂直二等分線を求めます。以降、線23とします。
  • 線12、線23の交点を求めます。(=点1、2、3を通る円の中心)
    • 線12、線23が平行な場合、nullが返却されます。(=点1,2,3が直線上に存在する)
      • 点3、点4の垂直二等分線を求めます。以降、線34とします。
      • 線23、線34の交点を求め、取得できない(=平行)な場合、点1,2,3,4が直線上に並んでいることになるため、trueを返します。
    • 線12、線23が平行でない場合、交点が返却されます。以降、中点123とします。
      • 点1と、中点123の距離を距離1とします。
      • 点4と、中点123の距離を距離2とします。
      • 距離1、距離2の差の絶対値が、10^-7以下の場合は同一とみなし、trueを返します。(10^-7なのは、特に理由はありません)

getDistanceメソッド

引数の2ベクトルの差を求めます。
そのベクトルの大きさを返します。

getIntersectionメソッド

引数の2直線の交点を求めます。
下記のURLの計算を実行しました。
http://www.h4.dion.ne.jp/~zero1341/t/03.htm
あまり、意味がわかってないです。

getMidperpendicularメソッド

引数の点1、点2の中点を求めます。中点12とします。
点1、点2のベクトルの差を求めます。ベクトル12とします。
ベクトル12のy成分に-1を掛け、x,yを逆にしたベクトルをベクトル21とします。
中点12を通り、(中点12にベクトル21を足した点)を通る線を返します。

getMidpointメソッド

引数の2点のx,y座標の、それぞれの和を2で割った値を設定したPointを返します。



と、いった形でチェックしています。
計算が誤っている場合など、コメント下さい。

共円チェッカーをAndroidマーケットに公開しました。

共円チェッカー」なるアプリを公開しました。

http://market.android.com/details?id=hm.orz.chaos114.android.kyouenchecker

昨日の夜、「たけしのコマ大数学科」にて紹介されていた、「共円」を判定するアプリです。


共円については、以下のページに解説があります。
灘校数学研究部のホームページ

判定アルゴリズムは

3点を通る円の中心を(2本の垂直二等分線の交点として)求め、
その中心から残りの1点までの距離が
他の3点までの距離と等しいかどうかを調べます。
等しければ4点は共円であり、異なれば4点は共円ではありません。

を利用しています。
久々に数学してみたため、間違いなどあるかもしれません。


番組内で、アプリにする話が出てましたが、
先に(たぶん)作ってみました。
ただし、インターフェース(見た目)はひんそーです。


免責事項的なこと。(なんか違うような気もするけど)
noboru@「共円チェッカー」の作者(=suzaku114@このブログを書いている者)は、灘校数学研究部様とは関係がありません。
アプリに対するご意見・ご感想は灘校数学研究部ではなく、noboru@アプリの作者にお願いします。

HTML表示でハマった

基本的には、下記のページにあるとおりでできます。

http://d.hatena.ne.jp/androidprogram/20100518/1274184600


で、その取得してきたCharSequenceに対して、文字列を追加したくなり、下記のように記述しました。

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="hoge">画像を &lt;img src=\"image_%1$d\"> 表示。</string>
</resources>


javaのソース

TextView textView = (TextView) findViewById(R.id.fuga);

String text = String.format(getResources().getText(R.string.hoge)
		.toString(), 0);

CharSequence html = Html.fromHtml(text, new Html.ImageGetter() {
	@Override
	public Drawable getDrawable(String source) {
		// 画像のリソースIDを取得
		int id = getResources().getIdentifier(source, "drawable",
				getPackageName());

		// リソースIDから Drawable のインスタンスを取得
		Drawable d = getResources().getDrawable(id);
		d.setBounds(0, 0, 24, 38);
		return d;
	}
}, null);

textView.setText(html + "追加");

で、出てきたのが、画像部分が点線で囲まれた中に「OBJ」と書かれているアイコン。
ちなみに、drawableフォルダには「image_0.png」っていうファイルが、ちゃんとあります。

実際、htmlのクラスは「android.text.SpannableStringBuilder」なのですが、
「html + "追加"」のクラスは「java.lang.String」となってしまう。
そのへんが原因?

まぁ、当たり前ですが、ハマったのでメモ。

Amazon Product Adveristing APIにて、403エラー

以下のサンプルコードを元に、実装してみた。
http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?AuthJavaSampleSig2.html


結果、はIOException。
表示されたメッセージは以下のような感じ。
Server returned HTTP response code: 403


いろいろ検索してみた結果、下記のサイトにて対処法を発見。
http://d.hatena.ne.jp/tanamon/20091212/1260640371

Base64 encoder = new Base64();

↑を、↓に変更。

Base64 encoder = new Base64(0);


ちなみに、Base64は「commons-codec-1.4.jar」に入っていました。
別のバージョンだったら、また違うのかもしれません。

BigDecimalとdoubleの変換について

doubleは浮動小数点型なので(?)、小数を正確に表現できないことは知ってました。
で、BigDecimalに変換する際に失敗しました。


以下、やっちゃった間違い。

double d = 0.1;
BigDecimal b = new BigDecimal(d);

System.out.println(b);

上記の出力結果
0.1000000000000000055511151231257827021181583404541015625


で、0.1を保持するBigDecimalオブジェクトを作成したい場合、
"0.1"という文字列よりBigDecimalを作成する必要があるらしい。
それを簡単にやってくれるのが、BigDecimal#valueOfメソッドだそうで、

double d = 0.1;
BigDecimal b = BigDecimal.valueOf(d);

System.out.println(b);

上記の出力結果
0.1

と、なりました。

大規模開発における、NoSuchMethodException

NoSuchMethodErrorだったかも。

今日(昨日?)あったこと。


現在、外部結合試験(?)中のソースを修正することになりました。
修正内容としては、JavaBeans?(ValueObjectとかDTOとか呼ばれる、Getter,Setterを持つクラス)に、
インスタンスフィールドを追加し、Getter,Setterも追加する。
んで、EJB内でDB読んで、Setterで値を入れて、条件によっては値を変更して、Getterで読みにいって、DBに再度書く。


フツーに、修正して、単体テストして、内部結合環境で疎通してもらって、外部結合環境にリリースしてもらいました。
で、その環境でテストしてもらったところ、NoSuchMethodExceptionが発生。
Setterが見つからないらしい。
でも、コンパイルは通ったらしい。


・・・?
意味不明。で、リリースが正しく行われているかとか、それ以前にちゃんと修正したものを提出しているかとか、いろいろ調べたけど、ちゃんとリリースされているらしい。


諦めきれずに、実際に環境にデプロイされているEARファイルを頂いてきて、解凍。
んで、出てきたJARを全部Eclipseに入れて、ビルドパスに通しました。
コンパイラが既に入っていたので、修正したクラスを逆コンパイルしても、ちゃんと修正された物が入ってる。
ちゃんとリリースされていることが確認できたわけなんだけど、例外の原因が不明。


で、感のいい人ならここまでしなくても分かったのかもしれないけど、ここでようやく気づく。
Ctrl+Shift+Tで、クラス検索。
修正したBeanを検索したところ、2つある。
片方は自分が修正したもので、追加したGetter,Setterがちゃんと存在している。
で、もう片方は、追加する前の状態。


ナニコレ?

で、思い出したのが、このクラス、外部の部品とのインターフェースになっておりました。
そのため、その部品をコンパイルするためにそのBeanが必要。
だからコピーされてたらしい。(ってか、知らされてないし
で、クラスロードの際に、その部品内のBeanの方が優先され、コンパイルは通るけど、メソッドが見つからないという状態。
無事原因がわかってめでたしめでたし。


※ただし、対応方法が、「外部の部品の方にも同様のGetter,Setterを追加する」でした。
なんだか腑に落ちない。
まずもって、なんで部品のインターフェースオブジェクトが、部品を呼ぶ側に定義されてるんだろう・・・
まぁ、それを提案されて、却下出来なかった私も悪いんだけど・・・