Instant Play Framework Starterを買いました

本格的にPlay触ろうと思ってチュートリアルを漁ったけど情報が古かったりして少し困っていた。そんな中、たまたまこの記事を見つけてInstan Play Framework Starterという本の存在を知り、自分のニーズに合いそうだと思いさっそく購入。


Instant Play Framework Starter

Instant Play Framework Starter


ページ数は少なく、環境構築とチュートリアルに絞って説明してある。実際にPlayを触り始めてる人には物足りなさそうだが、これからPlayを始めようとしている自分にはちょうど良い。それと、英語が苦手な僕でも読める程度にはやさしい英語で書いてあるのも嬉しい。仕事ではeclipseのプラグイン開発、家ではゲーム開発という具合だったのでWebアプリケーションの開発をほとんどやってこなかったからなぁ...。近いうちに何か作ってherokuにでも上げてみるか。

Scala IDEでPlay frameworkを使う(Webアプリの編集)

引き続きScala IDEのPlay frameworkプラグインを触る。今回はWebアプリを修正して実際にScala IDEを使用する。

モデルクラスを作成する

modelsパッケージの直下にQuoteという名前のクラスを作成する。Package Explorerで右クリックして、New->Scala Classを選択する。もしなければOthers...を選択して、ウィザードからScala Classを選択する。以下の項目を修正して、Finishボタンを押すとクラスが作成される。

  • Package: models
  • Name: Quote

作られたクラスを以下のように修正していく。


models/Quote.scala

package models

case class Quote(text: String, author: String)

views/index.scala.html

@(message: String, quote: models.Quote)
 
@main("Welcome to Play 2.1") {
 
    <p>@quote.text<em> - @quote.author</em></p>
 
}

controllers/Application.scala

package controllers

import play.api._
import play.api.mvc._
import models._;

object Application extends Controller {
  
  def index = Action {
    Ok(views.html.index("Test", Quote("Hoge", "Fuga")))
  }
}

これで再度http://localhost:9000にアクセスしたとき、以下のように表示される。

f:id:mi_kami:20130618223730p:plain

Scala IDEとPlay frameworkの連携

Playのテンプレート(views配下の*.scala.html)は、そのファイルを変更した時に自動的に再読み込みされ、コンパイルされる。尚、テンプレートファイルはすべてScalaコードに変換され、targetフォルダ配下に格納される。
Scala IDE側はeclipse 3.7から追加された"Refresh using native hooks or polling"機能を使ってリソースが変更されたかどうかをチェックして、変更されたらコンパイルを実行しているようだ。

Scala IDEでPlay frameworkを使う(プロジェクトを作る)

前回の続き。基本的にはこれに沿って進める。まずはScala IDEとPlay frameworkのプラグインがインストールできたのでプロジェクトを作る。

Playアプリケーションの起動

まずは作成したプロジェクト(test)に移動してplayコマンドを実行する。実行したらrunコマンドでアプリケーションが起動する。

$ cd test
$ play
 ...
[test] $ run

あとはhttp://localhost:9000/にアクセスすると以下のようなページが表示される。

f:id:mi_kami:20130617223328p:plain

それぞれのタイミングで何をやってるんだろう?最初にアクセスしたときに必要なファイルをコンパイルしてるように見える。

eclipseコマンドでPlayのeclipseプロジェクトを作る

Playのeclipseコマンドを使ってeclipseプロジェクトを作る。まずはctrl-dでrunモードから抜け、"eclipse"とタイプする。

[test] $ eclipse
[info] About to create Eclipse project files for your project(s).
[info] Successfully created Eclipse project files for project(s):
[info] test

そしてもう1度アプリケーションを実行する。

[test] $ run

--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0%0:9000

(Server started, use Ctrl+D to stop and go back to the console...)


ここでeclipseに戻り、Preferenceを開く。フィルタに"workspace"とタイプし、Workspaceの設定を開いて、"Refresh using native hooks or polling"にチェックを入れる。

f:id:mi_kami:20130617224948p:plain


次に、Project ExplorerかPackage Explorerで右クリックして、Importを選択する。"Existing Projects into Workspace"を選択してNextをクリック。

f:id:mi_kami:20130617225215p:plain


"Select or root directory"でplayのプロジェクトフォルダを選択する。フォルダ配下を検索して見つかったtestのeclipseプロジェクトを選択して、Finishボタンをクリックする。

f:id:mi_kami:20130617225419p:plain


エラーが出ていなければインポートは完了。明日は実際に使うところを書く。

Scala IDEでPlay frameworkを使う(環境構築)

Scala + Play frameworkを試したくなったのでeclipseの環境を構築することにした。Scala IDEよりは評判の良いIntelliJを使う手もあったが、何か困った事があったときに中のコードもある程度読めるのでこちらを選択。

Play frameworkのインストール(Mac用)

Homebrewがインストールされていることを前提とする。以下のコマンドを入れるだけでインストール出来る。

$ brew install scala
$ brew install play

Scala-ideのインストール

Scala IDEを利用する環境を作るには以下の2つの方法がある。

  • 自前でeclipseをダウンロードし、その後でScala IDEのプラグインを自分で追加する
  • Scala IDEのサイトからSDK(プラグインが予め追加されたeclipse)をダウンロードする

2番の方法が一番手軽に出来るのでおすすめだ。Scala IDEのページからダウンロードできる。注意点として、Play frameworkを使う場合は"For Scala 2.10.x"を選択すること。そうしないとPlay framework用のプラグインが追加できない。(2.9系のupdate-siteにはPlay framework用のプラグインが無い)

Scala IDEにPlay frameworkプラグインをインストールする

  1. ダウンロードしたeclipseを起動して、Help -> Install New Softwareを選択する。
  2. "Install"ウィザードの"Add"ボタンをクリック
  3. Nameに"Scala IDE 2.1"、LocationにScala IDE 2.1のupdate-site(http://download.scala-ide.org/sdk/e37/scala210/stable/site)を指定して"OK"ボタンをクリック
  4. Work withドロップダウンが追加した項目になってることを確認して、下のリストから"Scala IDE plugins" -> "Play2 support in Scala IDE"を選択する
  5. ウィザードの"OK"ボタンを押して、ウィザードに沿って入力すればインストール完了

f:id:mi_kami:20130616171309p:plain

プロジェクトの作り方&使い方についてはまた明日書く。

何故eclipseではScalaもJavaもビルドできるのか

比較的新参の僕は知る由もないが、「その昔はシェルスクリプトでビルドをしていてだな・・・クラスパスを通すのが非常に大変じゃった」という旨の話を先日聞いた。それいつの時代なのだろうか・・・全く想像できない。
eclipseでボタンぽちぽちしてビルドする僕にはまるで分からない世界だが、そのまま放置するのも気持ち悪く、その辺りを調べてたのでまとめておく。

プロジェクト・ネーチャーの定義

eclipseJavaだけでなく、ScalaClojureC++等の好きな言語のプロジェクトを作って、その言語をコンパイル出来る。それはプロジェクトのネーチャーが定義されているからだ。ネーチャーとは、そのプロジェクトが何であるかを特徴付ける為の機能である。eclipseのプロジェクト配下にある.projectを開くと以下の記述を見つけることができる。

<projectDescription>
	...
	<natures>
		<nature>org.scala-ide.sdt.core.scalanature</nature>
		<nature>org.eclipse.jdt.core.javanature</nature>
	</natures>
</projectDescription>

ここを見ると、これがScalaプロジェクトであり、なおかつJavaプロジェクトであることが分かる。

ビルダーの定義

メニューからBuildをクリックしたとき、もしくは(Build Automaticallyにチェックを入れていれば)リソースが変更されたときにJavaScalaのコードのビルドを開始する。このとき、定義されたビルダーのビルドコマンドを順次実行している。.projectをもう1度見てみよう。

<projectDescription>
	...
	<buildSpec>
		<buildCommand>
			<name>org.eclipse.jdt.core.javabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
		<buildCommand>
			<name>org.scala-ide.sdt.core.scalabuilder</name>
			<arguments>
			</arguments>
		</buildCommand>
	</buildSpec>
	...
</projectDescription>

JavaのビルダーとScalaのビルダーが定義されている。この各ビルダーで定義されている処理が実際にビルド時に実行される。余談だが、JavaJavaBuilderクラスが、Scala(Scala IDE)はScalaBuilderクラスが定義されており、Incremental Build/Build All/Cleanしたときの処理がそれぞれ書かれている。

Javaのビルダーの挙動

Compilerクラスを読めば分かるはず。しかしまだ読み切れていない。どうやってJDKのバージョンが指定されているか、クラスパスの設定をどのように読み込むか、その辺りはまた別の機会に書く予定。

jarファイルとMANIFEST.MF

eclipseJavaの開発しているとボタンぽちぽちするだけで勝手にコンパイルして実行してくれたりするので、中で何が動いているのか全く分からない。しかし何故動いてるのか分からないまま使ってるのも非常に気持ち悪い。
以前jar関係で少しハマったので少しだけjarファイルについて調べてみたので結果を残しておく。

jarとは何か

jarファイルとは、classファイルなどの必要なリソースをzip圧縮したものだ。これを配布してアプリケーションやライブラリとして使ってもらうことができる。
jarコマンドを使うと指定したファイルやフォルダをzip圧縮し、jarファイルを作成できる。以下のようなTest.javaを作ってjarファイルを作ってみよう。

package test;

public class Test
{
    public void	hoge()
    {
        System.out.println("hoge");
    }
}

そして、以下のコマンドを実行する。testフォルダにTest.classを格納し、それをjarファイル化する。

$ javac Test.java
$ mkdir test
$ mv Test.class test
$ jar cvf test.jar test

jarの中身の情報を表すMANIFEST.MF

MANIFEST.MFというのはjarのバージョン情報やクラスパスの情報が格納されたファイルだ。jarコマンドで明示的にMANIFEST.MFを指定しなかった場合、自動的に追加される。unzipコマンドで解凍して確認してみよう。

$ unzip test.jar
Manifest-Version: 1.0
Created-By: 1.6.0_41 (Apple Inc.)

なお、jarコマンドでMANIFEST.MFを指定してアーカイブ化する場合、mオプションを使用する。

jar cvfm test.jar MANIFEST.MF Test.class

jar内のファイルの構造

jarファイル内部の構造もパッケージと同一にする必要がある。例えば、testパッケージに所属するTestというクラスがあった場合、/test/Test.classの構造をフォルダごとjarファイルに入れておく。おそらくこのjarの構造がそのままロードされる。

javaコマンド実行時のjarファイルのクラスパス指定方法

フォルダ配下がclassファイルであれば、クラスパスはフォルダを指定するだけで良い。testフォルダにTest.classが入っている場合は以下のコマンドで実行できる。

$ java -cp .:hoge Sample

しかしjarファイルを指定する場合は注意が必要だ。java6未満の場合はフォルダは指定出来ない為、"lib/test.jar"のように個別のjarを指定する。Java6以上であればワイルドカードが使える為、"lib/*"のように指定すれば良い。

$ java -cp .:lib/* Sample

eclipseプラグイン開発におけるパス表記再考

eclipseのプラグインを開発しているとパスの表記方法がいくつもあることに気が付く。意識していないと混乱するので適当にメモ。

eclipse上でのパス表記方法

最初に、eclipseには2つのパスの表記方法がある。1つはeclipseのworkspaceからのパス、もう1つはOS標準のパス表記だ。それに加えて、それぞれについて絶対パス相対パスを持つ。OS標準のダイアログを使ってファイルを読み込んだ場合、取得したパスはOS標準の絶対パスになっている。
eclipse上のパスは全てIPathを使って管理される。これはパスを表す為だけのインターフェースであり、これ自体にはOSのファイルシステムのパスとplatformのパスを変換するようなインターフェースは備わっていない。親フォルダのIPathを返したり、別のIPathからの相対パスを返す、といったインターフェースは持っている。

eclipse上でファイルを扱うには

IResourceはeclipseのworkspace上にあるリソースを扱う為のインターフェースである。これを継承したファイルを扱うためのIFile、フォルダを扱うためのIFolderも存在する。基本的にeclipseプラグイン作成時はjava.io.Fileは直接使わずにこれらのインターフェースを使用する。
なお、getLocation()はOSのファイルシステム絶対パスを返し、getFullPath()はplatformからのパスを返却する。
ひとつ注意したいのは、なんらかの関数からIFileをインスタンス化したオブジェクトを取得出来たとしても、実際にファイルがあるかどうかはIResource#existsがtrue/falseかを見ることでしか判断出来ない。IFileを予め取得しておいて、そこにファイルを新規に作成する、という使い方をすることもあるからだ。

選択された要素を取得して、パスを表示するだけの例を示す。

public void run(IAction action) {
	IStructuredSelection selection = (IStructuredSelection) window
			.getSelectionService().getSelection();
	IFile file = (IFile) selection.getFirstElement();

	// getFullPathを使った場合
	IPath fullPath = file.getFullPath();
	System.out.println(fullPath.toOSString());
	
	// getLocationを使った場合
	IPath locationPath = file.getLocation();
	System.out.println(locationPath.toOSString());

	// 最後のファイル名だけfuga.txtに置換する
	System.out.println(locationPath.uptoSegment(locationPath.segmentCount() - 1).append("fuga.txt"));
}

以下実行結果。

/hoge.hoge/hogehoge.txt
/Users/mkouhei0910/Documents/runtime-EclipseApplication/hoge.hoge/hogehoge.txt
/Users/mkouhei0910/Documents/runtime-EclipseApplication/hoge.hoge/fuga.txt