公開しました
「色揃えるやつ」を公開しました。
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日
ゼロ埋めならこんなに簡単。
仕様変更してくれんかなー。(たぶん無理)