playコマンドがヒープ不足で失敗する時

ちょっと興味があって[www.playframework.com/:title=Playframework]を触っています。
自宅のmacと会社のWindowsPCにそれぞれPlay2.1の環境を作って共有しているんだけど、
WindowsPCでPlayプロジェクトでplayコマンドをうつと、以下のようにコンソールにで起動に失敗する。

Error occurred during initialization of VM
Could not reserve enough space for object heap
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

PCのスペックはWin7 32bit メモリ2G
ヒープ領域が確保できないようなので、playのスクリプトを見てみると、
/framework/build.bat
が本体のようでこのファイルの中に、

java -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M %DEBUG_PARAM% %JAVA_OPTS% -Dfile.encoding=UTF-8 -Dplay.version="%PLAY_VERSION%" -Dsbt.ivy.home="%~dp0..\repository" -Dplay.home="%~dp0." -Dsbt.boot.properties="%fp%sbt/sbt.boot.properties" -jar "%~dp0sbt\sbt-launch.jar" %*

とあるのでここを編集する。
Xms,Xmx,Xss,MaxPermSize全てのオプションを削除しても起動したのだけど、Xmxのでフォルトは64Mで心もとないので、Xmxを768Mに修正して無事起動した。
色々試した所、上記のPCのスペックだと、ヒープ領域とパーマネント領域の合計が1150Mを超えると失敗するようだった。
というかデフォルトでXmxが1Gは随分と富豪だなと感じる。

google driveのOCRを試すWebアプリを作ってみた

Ruby 認定資格Gold合格したので、何か作ろうかなと考えていた所、
Google DriveのOCRって使い物になるのかなと思い、
ファイルをアップロードしたらGoogle Driveに登録して、OCRのテキストを取得するような物を作ってみた。

Trying Google Drive OCR
利用するにあたって、GoogleのアカウントとDriveの利用、OAuth認証が必要です。
また、ここでアップロードされたファイルは、Driveに保存されます。

イメージはこちら

見て分かる通り、Driveに元画像とOCRの解析結果テキストが一つになった文書が作成されます。
テキストだけの取得は、色々調べたけどどうしてもページ内に埋め込めず断念。
というか、 OCRの精度は低い。あと日本語もまだ対応してないっぽい。


以下は作成時のメモ

Google Drive SDKのドキュメントはこちらにある。
Google Drive SDK — Google Developers
SDKAPIがあって、SDKChrome Web Storeに色々登録しろとか書いてあるけど、今回みたいなDriveへのファイルの登録とか取得だけだったらAPIだけでいい。SDKはDriveのUIに作成したアプリのメニューを追加したり連携したりする場合に使う。


APIを使う場合は、Google API ConsoleGoogle Accounts から、Drive APIを有効にし、Client IDとsecretを発行する。

今回作ったソースは https://github.com/kencharos/google-drive-oc-ruby に。
初めてGitをまともに使ったので、色々と変な事している気がする。
個人で実行する場合は上記のClient IDとsecretを、crient_secret.jsonに記載する。


アプリケーションは、heroku上で、sinatra,datamapper,haml などで作りました。
sinatraとてもいいです。


なお、herokuではファイルアップロードが一切できないと勘違い*1しており、最初はブラウザ側でFile APIを使用してbase64エンコードしてサーバーに送り、サーバー側でエンコードするような処理をしてた。
ファイル選択時にプレビュー出るのはその名残。


今回、初めてgit,github,ruby,herokuを使ってみました。
職場の、java, 占有ロックが基本の某バージョン管理ツール、起動するまで5分かかるサーバーという環境に比べると
色々と快適すぎて涙が出ます。


今後は、Rspec, Scala、html5の各API、ましなUI などもやってみる。


作成にあたり、以下のページを参考にさせていただきました。ありがとうございます。
Heroku上でSinatraアプリを動かすまでのまとめ - まちゅダイアリー(2011-10-02)
HerokuアプリをGitHubにもプッシュする - アインシュタインの電話番号
HerokuとGitHubの両方にプッシュする時の秘密にしたい値の扱い - アインシュタインの電話番号

*1:ファイルを書き込める領域が無いだけで、tmpfileは使える

Ruby始めました

仕事だとJavaしかやってないし、javaの仕事しかない業界だったりするので、
前から興味があったRubyの勉強始めました。
ついでなんで、Ruby技術者認定の取得も目指してます。
使ってるのはこの教科書

Ruby公式資格教科書 Ruby技術者認定試験 Silver/Gold対応 (EXPERT EXPASS)

Ruby公式資格教科書 Ruby技術者認定試験 Silver/Gold対応 (EXPERT EXPASS)

特異クラスおもしれーとかって思ってたらその辺はGold試験の範疇らしく、
Silver試験は組み込みクラスの挙動についてがメインのようで、暗記物が多くめんどい。
どのメソッドが破壊的メソッドなのかかどうかがわかりにくく*1、この辺を整理するのが吉かと思い、
とりあえず表にまとめてる。String,Array,Hashができれば多分対策としては十分と思われる。

Welcome to Google Docs

*1:Scalaだと型でimutable,mutableを切り替えられるので、そこは静的型付けの利点なのかな

総当たりのパターンを作る

検証用のコードなんかではたまに、複数個のパラメータの設定の組み合わせを全部試すようなことをやることがあります。
例えばとあるメソッドに5個パラメータがあるとしてそれらのパラメータに値を設定する or 設定しない だけでも
2の5乗で32の組み合わせがあります。

全部の組み合わせをどうにか作成したいと思い、二進数の各桁の値で設定有無を判定するようなコードを書きました。
以下のような感じです。

public class Test {

	public static void main(String[] args) {		
		final int SIZE = 5;
		for (int i = 0; i < Math.pow(2, SIZE); i++) {
			for (int j = 0; j< SIZE; j++) {
				int flg = (i >> j) % 2;
				System.out.print((flg == 0 ? "OFF" : "ON") + "\t");	
			}
			System.out.println();
		}
	}
}

値のon/offだけでなく、色々なバリエーションがある場合はこれでは難しいので、
ちょっと再帰を使ってみて、scalaで書いてみる。
要するに直積を求めるものですね。

  def product[A](list:List[A]*): List[List[A]] = list match {
    case Seq() => List(Nil)
    case _ => list.head.flatMap(x => product(list.tail: _*).map(y => x :: y))
  }
  product(List(1,2,3), List("A","B","C"), List(7,8,9)) foreach println

実はこんなことしなくても、scalaでは

for(a<-List(1,2,3);b<-List("A","B","C");c<-List(7,8,9)) yield {
  a::b::c::Nil
}

で同じ事ができるのですが、引数を可変にしたいということでこうなりました。

なお、今回のコードは末尾再帰ではないので引数が増えると簡単にOutOfMemoryErrorが起きます。
頑張って末尾再帰にしたり、Stream使ってみたりしましたが、
何だか旨くいかなかったので、何か方法を考える。。。
FPは奥が深い。

iTextで目次付きのPDFを作る

Java,C#でPDFを作成できるiTextというライブラリがあります。
国産の帳票製品だとレイアウトを作り込むようなものがほとんとで、論文のようなアウトラインがある文章を作るのが難しいので、そういうときに便利です。
以前はMPL等のライセンスでしたが、今はAGPLもしくは、お金払って専用ライセンス購入という形式になったようです。

アウトラインの内容で目次も作れたらなーと色々考えていたが、ページ数が動的に変わる場合もあるので、
一度本文を作ってから、別のPDFで目次を作るような流れになってしまった。
他にいいやり方あるのかな。。

できたPDFはこちらに。

ソースは一応Gist

ファイル内の全文検索の検討メモ

テキストファイル、wordやExcelなどのOfficeファイル、PDFなど様々なファイルを登録しておいて、
登録したファイルの中の文章から検索を行いたいというような話です。
今はApache Solrとかが流行ってますが、
アプリケーションの導入となると何かとハードルが高いので、
手持ちの材料で何かできないかというメモです。
雑です。

というわけで、まずはOracle Textについて調べてみた。
Oracle Textは全文検索の仕組みをOracleに載せたもの。
例えば、文章の中に"田中△一郎"、"田中・一郎"、"田中一郎" みたいに同じ単語でも間にスペースや記号があったりしても、"田中一郎"という検索ワードで一律ヒットさせたいというようなもの。
最近のバージョンであれば製品に最初から入っている(インストールの仕方によっては入ってない場合もあるが追加インストールできる)

とりあえず試してみるのに、参考にしたのはこの辺りの資料

Oracle Textの全文検索OracleらしくSQLのwhere条件にCONTAINS('列名','検索ワード')という条件を書く事で行う。そのためには、

  • 特定のテーブルの列(不定形の文章を入れるような列)にOracleText用のIndexを作る。
  • そのIndexには、構文解析の設定が必要(例, N-GRAM方式のJAPANESE_VGRAM_LEXERや、形態素解析のJAPANESE__LEXERなど)
  • ただしテーブルの挿入や更新と同時にそのIndexは更新されないため、定期的にIndexの同期をプロシージャで行う必要がある。(普通のIndexと比べると処理が重いためか)

などを行う必要がある。

とはいえ、これはテーブルの列について全文検索する設定なので、ファイルの場合はどうするの?
というと、上記のIndexの設定で、特定のディレクトリやURLなどを設定することで
その場所にあるファイルからテキストを自動で抽出してIndexを作成するようである。
それはそれで面倒なので、テーブルにBLOBの列を作成して、そこに全文検索のIndexを作って、
ファイルのバイナリを登録したらどうなるのかを試してみたら、
あっさりとバイナリからファイルのテキストを抽出してIndexを作ってくれました。
全部は試していませんが、ファイルの形式(ワードとかエクセルとか)を識別しますので、
それなりに使えるかと思います。
ただし、Officeのテキストボックスの中の文章は読み込めないようでした。
(Indexの定期的な同期など、考えないといけない事は色々ありますが)

補足として、検索条件にヒットした文章のどこがヒットしたのかを明示したいこともあるかと思います。
そういう場合は、別途プロシージャを使う必要があります。
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/text.102/B19214-01/cdocpkg.htm
にある、CTX_DOC.プロシージャに色々と種類があり、例えば、
CTX_DOC.MARKUP というプロシージャで、検索条件にヒットした文章の中の、
ヒットした箇所を、<<< と >>> で囲んで表示できます。
CTX_DOC.MARKUP には、全文検索のIndex名や、レコードのユニークキー、検索条件などを引数として渡す必要があるので、
検索してヒットしたレコード1件ずつに対して、MARKUPを行っていくという手順になります。


非常に雑だと自分でも思いますが、とりあえず使ってみた結果はこんな感じでした。
Oracleが手元にあるなら、とりあえずやってみてもいいかもしれませんが、
Indexの再作成や、ちょっと手の込んだことをするとなるとプロシージャを多用したりするので、そこは割と面倒だと感じました。
ファイルの抽出でOfficeのテキストボックを抽出できないのはちょっと残念なので、
別のライブラリも試す予定。

Selenium2.xのちょっとした小技

最近仕事でSeleniumに手を出していて、Selenium2.0以降のAPIを使用している。
ところが、2.0以降についての情報が少なく、こんなことするにはどうすれば?みたいなことを調べると大抵旧バージョンだったりするので、2.xでのコードのサンプルのようなものをメモとして書いてみる。


また職場のブラウザがIE6だったりするので、Selenium IDEはほとんど使ってません。
言語はJavaメインなので、他の言語やブラウザでは使えないかもしれません。
また、旧バージョンのSelenium1.xに触ったことはありません。

  • 導入

ここからダウンロードする。
1.xでは、Selenium-RCと呼ばれていた、各言語のAPIは、WebDriverという名称に変わっているので、
各言語向けのWebDriverをダウンロードして、展開してクラスパスを通せば完了。
ただし、どのWebDriverを使うにしてもPCにはJREの1.5以上がインストールされている必要があります。
IE6は正式には2.xではサポートされていませんが、WinXp + IE6の組み合わせで正常に動きました。

  • 基本的な操作

ブラウザの操作は、WebDriverクラスで行う。
FORM要素なんかは、WebDriverのfindElementメソッドでWebElement要素を見つけて、値を設定していく。
以下に、だらだらとサンプルコードをのせてみる。

    	// ブラウザに応じて生成するdriverのインスタンスを変える。
        WebDriver driver = new InternetExplorerDriver();
        driver.get("http://xxxxxxxx");
        // text,password,textareaなどをname属性で取得して、入力
        driver.findElement(By.name("hoge")).sendKeys("value");
        // checkbox,radioなどはid属性があれば簡単だが、
        // そうでない場合xpathでname属性、value属性を指定して、クリックさせる。
        driver.findElement(By.xpath("//input[@name='hoge' and @value='on']")).click();
        // selectのoption指定は、上記のようなxpathで頑張ることもできるが、Selectクラスで柔軟に指定できる。
        Select select = new Select(driver.findElement(By.id("hoge")));
        select.selectByIndex(1); //Optionタグのインデックスで選択。
        select.selectByValue("01"); //Option要素のvalue属性で選択。
        select.selectByVisibleText("表示文字列"); //Optionの表示文字列で選択。
        // ファイル選択もIE6では有効なファイルを絶対パスで指定すれば選択できる。
        driver.findElement(By.name("FILE_UPLOAD")).sendKeys("c:/temp/hoge.txt");
        // アンカーリンクは、アンカーの表示文字列でも検索可能。
        driver.findElement(By.linkText("リンク")).click();
        // フォームのサブミットは適当なWebElementからでも、要素を囲むFormを探してサブミットできる。
        driver.findElement(By.name("hoge")).submit();
        // onclick属性の実行などを行う場合は、ボタンを選択してクリックする。
        driver.findElement(By.id("hoge_button")).click();
        
        // ブラウザを閉じる。
        driver.close();
  • Javascriptのalertやconfirmなんかのダイアログ

1.xだとできないって記事が相当あるけど、2.xから操作できる。
WebDriverクラスの.switchTo().alert()で、ブラウザ上でポップアップしているJavascriptのダイアログが操作できる。
onloadイベントで実行したalert何かもちゃんと処理できる。

        WebDriver driver = new InternetExplorerDriver();
        Alert alert = driver.switchTo().alert();
        alert.getText(); //ダイアログのメッセージ
        alert.accept(); //alertやconfirmのOKを押す。
        alert.dismiss();// confirmのキャンセルを押す。
  • Windowの切り替え。

window.open()何かで新しく開いたウインドウとかに操作対象を切り替えるには、 WebDriverクラスの、switchTo().window("window_id")を使えばよく、 window_id には、window.open()の第二引数のウインドウIDを渡すみたいに公式ドキュメントに書いてあるのだが、IE6だからか旨くいかない。
解決策として、WebDriverクラスのgetWindowHandlesメソッドから取得できるIDを指定するのだが、
正直言って非常に面倒臭いコードになった。もう少し簡単にできないだろうか。

        // 現在操作しているWindow IDを控える。
        String currentWindowId = driver.getWindowHandle();
        // javascriptで、window.open()が行われるボタンをクリック。
        driver.findElement(By.id("button")).click();
        // ウィンドウ表示までに時間がかかると、seleniumが先走ることがあるのでウィンドウが増えるまで待機。
        (new WebDriverWait(driver, 10)).until(new ExpectedCondition<Boolean>() {
            public Boolean apply(WebDriver d) {
                return d.getWindowHandles().size() > 1;
            }
        });
        // 増えたウィンドウIDを取得する。
        String newWindowId = null;
        for (String id : driver.getWindowHandles()) {
        	if (!id.equals(currentWindowId)) {
        		newWindowId = id;
        	}
        }
        // ウィンドウ切り替え
        WebDriver newWindowDriver = driver.switchTo().window(newWindowId);
  • スナップショットを取る

WebDriverではできず、以下のようなコードで取得できた。
IEでスナップショットを取るには、SnapsIEというActiveX コンポーネントを入れる必要があるという記事もあったが、2.xでは必要ないようだ。

        // Selenium-RC互換のAPIを取得。
        Selenium selenium = new WebDriverBackedSelenium(driver, "http://xxxx");
        // base64でエンコードされたPNGのスクリーンショットを取得
        String base64png = selenium.captureScreenshotToString();
        // base64デコードして、ファイル等に書き込む。割愛

ちなみに、他にもcaptureScreenShotうんぬんというメソッドはあるのだが、実装されていないため、実行時エラーとなってしまった。

  • できないこと

pdfファイルなんかをダウンロードしたりすると、ファイルの保存なんかを行うダイアログが出てきますが、 このダイアログを操作することは2.xでもできない模様。。。


というわけで、(使ったことはないけど)1.xに比べて色々と便利になったような感じがしました。
今後も、小技的なものがあればまとめていく予定。