公開しました

「色揃えるやつ」を公開しました。
http://market.android.com/details?id=hm.orz.chaos114.android.irokae

パズルゲームです。
タップすると、その上下左右の色が変更されます。
色をすべて揃えると、ゲームクリアーです。

ゲーム画面でmenuボタンを押すと、難易度を変更できます。
それぞれ、以下のとおりです。
色数:2/3色
大きさ:2x2/3x3/4x4/5x5

3色では、3x3はクリアできました。
2色では、4x4までクリアできました。
それ以上は無理でした。出来た方はご連絡ください。

以下からダウンロード出来ます。


よろしければ、広告をタップしてください。
広告料が私に入ります。

メニュー表示時のみステータスバーを表示する

以下のように、オプションメニュー表示次のみ画面上部のステータスバーが表示される。

オプションメニューを閉じる(戻るボタンなど)と、ステータスバーが非表示になる。


以下、ソース。

package hm.orz.chaos114.android.example;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager;

public class ExampleActivity extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// タイトルバーを非表示
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		hideStatusBar();

		setContentView(R.layout.main);
	}

	/**
	 * ステータスバーを非表示状態にします。
	 */
	private void hideStatusBar() {
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
	}

	/**
	 * ステータスバーを表示状態にします。
	 */
	private void showStatusBar() {
		getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// XMLファイルより表示内容の設定
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.menu, menu);
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		showStatusBar();

		return super.onPrepareOptionsMenu(menu);
	}

	@Override
	public void onOptionsMenuClosed(Menu menu) {
		super.onOptionsMenuClosed(menu);

		hideStatusBar();
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.menu_stop:
			// 終了選択時
			finish();
			break;
		default:
			return false;
		}
		return true;
	}

}

まず、onCreateメソッドでタイトルバーを非表示に。
同時に、hideStatusBarを呼び出し、ステータスバーも非表示にします。

メニュー表示前にonPrepareOptionsMenuが呼ばれるので、その時にshowStatusBarを呼び出し、ステータスバーを表示状態に。

メニューが閉じられると(選択・戻るボタンなど)、onOptionsMenuClosedが呼ばれるので、hideStatusBarを呼び出し、ステータスバーを非表示状態に。

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を追加する」でした。
なんだか腑に落ちない。
まずもって、なんで部品のインターフェースオブジェクトが、部品を呼ぶ側に定義されてるんだろう・・・
まぁ、それを提案されて、却下出来なかった私も悪いんだけど・・・

再帰検索+テキストデータの連結

WSDLより、Javaソースを作成すると、web.xmlとかがWSDLファイル数分できました。
で、それらを連結しなくちゃいけないんだけど、ファイル数が20ぐらいあるので、プログラムを組んでみました。

ただ、web.xmlの宣言部やROOT要素は共通なので、不要。
よって、web.xmlの1,2,最終行は連結しない。
以下、ソース。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Concat {

    /** 指定フォルダ用定数 */
    private static final String FOLDER_PATH = "C:\\Users\\Public\\Desktop\\Test";

    /** 指定ファイル名用定数 */
    private static final String FILE_NAME = "web.xml";

    /**
     * メイン処理。
     * 
     * @param args 使用しません
     * @throws Exception 実行時例外
     */
    public static void main(String[] args) throws Exception {
        // 指定フォルダ
        File file = new File(FOLDER_PATH);

        // ファイルリストの取得
        List<File> list = getFile(file, FILE_NAME);

        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter(FILE_NAME));
            for (File f : list) {
                List<String> lines = getLineText(f);

                for (int i = 0; i < lines.size(); i++) {
                    if (i == 0 || i == 1 || i == lines.size() - 1) {
                        // 特定の行のみコピーしない
                        continue;
                    }
                    bw.write(lines.get(i));
                    bw.newLine();
                }
            }
        } finally {
            if (bw != null) {
                bw.close();
            }
        }
    }

    /**
     * 指定ファイル内のテキストを、行単位でリストにし返す。
     * 
     * @param file 指定ファイル
     * @return 行単位のテキストデータ
     * @throws IOException 入出力例外
     */
    private static List<String> getLineText(File file) throws IOException {
        // 返却値
        List<String> lines = new ArrayList<String>();

        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file));
            String line = null;
            // ファイルが存在する間ループ
            while ((line = br.readLine()) != null) {
                lines.add(line);
            }
        } finally {
            if (br != null) {
                br.close();
            }
        }

        return lines;
    }

    /**
     * ファイル名を指定し、指定フォルダ以下のファイルリストを再帰的に取得します。
     * 
     * @param folder 指定フォルダ
     * @param fileName ファイル名
     * @return ファイルリスト
     */
    private static List<File> getFile(File folder, String fileName) {
        // 返却値
        List<File> list = new ArrayList<File>();

        File[] files = folder.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                // 同一ファイル名を再帰検索
                list.addAll(getFile(file, fileName));
                continue;
            }

            if (file.getName().equals(fileName)) {
                // 同一ファイル名のパスを追加
                list.add(file);
            }
        }

        return list;
    }
}

getFileで再帰的にweb.xmlを検索。
mainのループの中でファイルのテキストデータを取得し、
その指定行数以外をReaderに出力。
って流れかな?

再帰って久々に使った・・・。

重複チェック

まずは、簡単なオブジェクトを定義

/**
 * ユーザクラス
 */
class User {
  /** ID */
  private String id;
  /** 名前 */
  private String name;
  /** メールアドレス */
  private String email;

  (Getter, Setterなどが定義されている)
}

で、単純にemailが重複していないかをチェックします。

class Test {
  public static void main(String[] args) {
    // メールアドレスset
    Set<String> set = new HashSet<String>();

    List<User> userList = (いろいろなUserクラスが入っているとする。)

    for (User user : userList) {
      String bufEmail = user.getEmail();
      if (set.contains(bufEmail)) {
        // 存在する場合
        System.out.println(bufEmail + "が重複しています。");
      } else {
        // 存在しない場合、setに追加
        set.add(bufEmail);
      }
    }
  }
}


次、どのidと重複しているかを表示する場合。

class Test2 {
  public static void main(String[] args) {
    // キー:email、値:idとなるmap
    Map<String, String> map = new HashMap<String, String>();

    List<User> userList = (いろいろなUserクラスが入っているとする。)

    for (User user : userList) {
      String bufEmail = user.getEmail();
      String tgtId = map.get(bufEmail);
      if (tgtId != null) {
        // 存在した場合
        System.out.println(bufEmail + "が" + tgtId + "と重複しています。");
      } else {
        // 存在しなかった場合、mapに追加
        map.put(bufEmail, user.getId());
      }
    }
  }
}


以上、変数名などテキトー。
また、Eclipseがやる気を起こしてくれないため、手書き+未コンパイル。
動作確認なし。
間違ってたら、そのうち直そうと思います。

SimpleDateFormatについて

文字列strがあり、

SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
sdf.parse(str);

とした場合、
"2010/03/18"を与えると正常に動作しますが、
"2010/03/1a"を与えた場合も例外が発生しません。
取得できるデータは2020年3月1日となります。


エラーチェックとしてparseしてみる場合も、上記のような場合はエラーとならないので、ご注意ください。

Google Web Toolkit覚書(GAEプラグイン)

流れ

Eclipseにて「Webアプリケーション・プロジェクト」を作成したら、基本的なファイルは作られる。
プロジェクトにて右クリック、実行、Webアプリケーションを選択すると、実行できる。


プロジェクト作成時に(プロジェクト名を"Sample"とする。)
war/Sample.html
が作成される。
tdタグなどにid属性をつけておく。(Javaよりそのid値を利用しアクセスする)


src/パッケージ/Sample.gwt.xml
が何者かによって呼び出される(はず)
entry-pointタグのclass属性クラスがロードされる。
そのクラスがEntryPointインターフェースを実装しており、onModuleLoadが呼び出される。

final TextBox nameField = new TextBox();
RootPanel.get("nameFieldContainer").add(nameField);

htmlのid値が"nameFieldContainer"となっているため、そのエレメント内部にTextBoxが追加される。

サーバとの通信

clientパッケージに、
GreetingServiceインターフェース
GreetingServiceAsyncインターフェース
が必要。(名前は*Service, *ServiceAsyncが妥当?)
命名規則や、シグネチャが間違っている場合は書いてる途中に、
「No asynchronous version of method メソッド名 exists in type GreetingServiceAsync」
とか言われるけど、インターフェースx2、実装クラスをちゃんと記述したらエラーは無くなる。


serverパッケージに
GreetingServiceImplクラス
が必要。(名前は*ServiceImplが妥当?)
client.GreetingServiceを実装する。(Asyncが無い方)


サーバー側は通常のJavaが利用できる。(好きに書いて良い)
クライント側はjava.lang以下+java.util以下のパッケージ以外は、googleパッケージや自作クラスのみ利用可能(?)
sharedパッケージ以下にはサーバ・クライアント両方で利用するクラスを定義する。
POJOなBeanやバリデータなどを定義するためのパッケージ?
→ただし、
・バリデータでも基本以外のクラスも利用できないので、簡単なチェックのみを記述し、実際のチェックはサーバサイドで行うべき(?)
・サーバ・クライアント間のデータを、POJOなBeanに詰めてやりとりする事は可能。ただし、それをそのまま永続化(DBに登録)はできない。(アノテーションを付けると、コンパイル時に怒られる→org.datanucleus.metadata.InvalidMetaDataException: Class com.noboru.shared.SampleModel has application-identity and no objectid-class specified yet has 0 primary key fields. Unable to use SingleFieldIdentity.)


どのパッケージをクライアント側とする(JavaScriptに変換する?)かは、Sample.gwt.xmlのsourceタグによる?

永続化(JDOの場合)

package com.sample.server;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.sample.shared.TransModel;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class StoreModel {

	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Long id;
	@Persistent
	private Date date;

	public StoreModel() {
	}

	public StoreModel(Date date, Integer amount, String store,
			String note) {
		this.date = date;
	}

	public StoreKakeiboModel(TransModel model) {
		SimpleDateFormat sdf = new SimpleDateFormat();
		sdf.applyPattern("yyyy/MM/dd");
		try {
			this.date = sdf.parse(model.getDate());
		} catch (ParseException e) {
			e.printStackTrace();
			this.date = null;
		}
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}
}

上記のように、いくつかのアノテーションが必要。
@PersistenceCapable
@PrimaryKey
@Persistent
が必要っぽい。


また、お約束クラス(?)が必要。(無くても毎回書けば動く)

package com.sample.server;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public class PMF {

	private static final PersistenceManagerFactory pmfInstance = JDOHelper
			.getPersistenceManagerFactory("transactions-optional");

	private PMF() {
	}

	public static PersistenceManagerFactory get() {
		return pmfInstance;
	}
}

transactions-optionalは、META-INFのjdoconfig.xmlによる?
いろいろ設定できるみたいだけど、不明。
デフォルトのままで動くので放置。


インサート(登録)は以下の通り

package com.sample.server;

import javax.jdo.PersistenceManager;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.sample.client.GreetingService;
import com.sample.shared.KakeiboModel;

public class GreetingServiceImpl extends RemoteServiceServlet implements
		GreetingService {

	public void insertNew(TransModel model) {

		StoreModel newModel = new StoreModel(model);
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try {
			pm.makePersistent(newModel);
		} finally {
			pm.close();
		}
	}
}

PersistenceManager#makePersistentの引数に永続化対象クラスを渡すだけ。
更新も同様にできる。(らしい)


SELECT(照会)は以下の通り。

	@SuppressWarnings("unchecked")
	public List<TransModel> listAll() {
		List<TransModel> ret = new ArrayList<TransModel>();

		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery(StoreModel.class);
		query.setOrdering("date asc");

		List<StoreModel> list = (List<StoreModel>) query
				.execute();
		for (StoreModel storedData : list) {
			TransModel model = new TransModel();
			model.setId(Long.toString(storedData.getId()));
			model.setDate(Converter.dateToString(storedData.getDate()));

			ret.add(model);
		}
		return ret;
	}

ただ、サーバ側→クライアント側では、TransModelのコンストラクタにStoreModelを渡したりはできない。(serverパッケージをインポートできないため。)
サーバ側に、ファクトリークラスを作ったりしたらよい?

まとめ

GWTをやる気が薄れてきたので、文字として残しておく。
後で参考になるといいな。

日付をフォーマットする

DBより取得した値を、MM月dd日に変換する。
ただし、一桁の場合はスペース埋めを行う。
(ゼロ埋めなら簡単なのに・・・)

// DB取得値のつもり
String dateStr = "2010-02-19 23:44:40";
// Dateに変換
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
Date date = sdf.parse(dateStr);

// Calendarを取得
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

// フォーマット
int month = calendar.get(Calendar.MONTH) + 1;
int day = calendar.get(Calendar.DAY_OF_MONTH);
String formatDate = String.format("%2d月%2d日", month, day);

// 出力
System.out.println(formatDate);

出力結果
2月19日
↑スペースがちゃんと出力されています。
もっと簡単にできると思うんだけど・・・


ちなみに、ゼロ埋めで良ければ

// フォーマット
SimpleDateFormat sdf2 = new SimpleDateFormat("MM月dd日");
String formatDate2 = sdf2.format(date);

// 出力
System.out.println(formatDate2);

出力結果
02月19日

ゼロ埋めならこんなに簡単。
仕様変更してくれんかなー。(たぶん無理)