@katzchang.contexts

日常の思い付きをまとめる。君達の熱い通りすがりコメントなどを待つ。

2011-12-11

Play!の黒魔術を読み解こうとしてみる(未完

この記事は、Play! framework Advent Calendar 2011 jp #play_ja : ATNDの11日目の記事です。

さて、軽めに行きましょう!(ということにさせてください…

僕とPlay!

とあるWEBサービス受託開発案件があり、自分を含めてメンバー的にJavaプログラマが多かったので、Javaが必然。で、環境面も含めてモロモロがフルスタックでサポートされてるフレームワークはないかなーということで、Play! Frameworkを使うことになりました。

2ヶ月ばかり携わったあとで私はその仕事を離れることになったのですが、無事サービスは開始されたようです。めでたしめでたし。

初めのPlayの印象は「黒魔術」の印象でしたが、それはControllerとviewの結合部分が黒いくらいだけで、それ以外の部分は案外素直な作りではあるので、学習にかかるコストは恐ろしく低く済みました。

で、せっかくなので、その黒魔術の部分について少し読んでみようとして、結局読みきれなかったんですが、書いていきます。

バージョンは1.2.3。ソースコードはすべて https://github.com/playframework/play から引用しています。

Controller.render(Object…args)

viewを描画するときに呼ぶのがControllerのrenderメソッドなわけで、引数として渡す変数名をview側の識別子として使えるわけで、なんとまぁ書きやすいけど不思議な部分なわけです。さて、行きましょう。

    protected static void render(Object... args) {
        String templateName = null;
        if (args.length > 0 && args[0] instanceof String && LVEnhancerRuntime.getParamNames().mergeParamsAndVarargs()[0] == null) {
            templateName = args[0].toString();
        } else {
            templateName = template();
        }
        renderTemplate(templateName, args);
    }

引数の確認をして、配列の先頭がテンプレート名かどうか判定しつつ、renderTemplate(templateName, args); に渡しています。次。

    protected static void renderTemplate(String templateName, Object... args) {
        // Template datas
        Map<String, Object> templateBinding = new HashMap<String, Object>(16);
        String[] names = LVEnhancerRuntime.getParamNames().varargs;
        if(args != null && args.length > 0 && names == null)
            throw new UnexpectedException("no varargs names while args.length > 0 !");
        for(int i = 0; i < args.length; i++) {
            templateBinding.put(names[i], args[i]);
        }
        renderTemplate(templateName, templateBinding);
    }

引数argsからMap templateBindingを作って、renderTemplate(temlateName, templateBinding); に渡しています。雰囲気的に、String[] names = LVEnhancerRuntime.getParamNames().varargs; が怪しいようです。

        public static ParamsNames getParamNames() {
            Stack<MethodExecution> stack = getCurrentMethodParams();
            if(stack.size() > 0) {
                MethodExecution me = getCurrentMethodExecution();
                return new ParamsNames(me.subject, me.paramsNames, me.varargsNames);
            }
            throw new UnexpectedException("empty methodParams!");
        }

スタックの内容があれば、MethodExecution meの内容から、ParamsNamesインスタンスを作って返しているようです。ParamsNamesはデータを扱う程度のクラスで、そのなかのvarargsが、viewに渡る識別子になる雰囲気です。

ということで、meに代入しているgetCurrentMethodExecution()。

        protected static LVEnhancer.MethodExecution getCurrentMethodExecution() {
            Stack<MethodExecution> stack = getCurrentMethodParams();
            if(stack.size() > 0)
                return stack.get(stack.size() - 1).currentNestedMethodCall;
            throw new UnexpectedException("empty methodParams!");
        }

さっきのgetParamNames()と同じように、getCurrentMethodParamsスタックを扱っているようです。で、そのスタックの最後のもの(Stackクラスならpeekと同義???)の、currentNestedMethodCallメンバを返しています。うむー。

で、そのメンバをセットしているのは1箇所で、LVEnhancer.LVEnhancerRuntime.initMethodCall。

        public static void initMethodCall(String method, int nbParams, String subject, String[] paramNames) {
            getCurrentMethodParams().peek().currentNestedMethodCall = new MethodExecution(subject, paramNames, nbParams);
            Logger.trace("initMethodCall for '" + method + "' with " + Arrays.toString(paramNames));
        }

先ほどから見るgetCurrentMethodParamsスタックをpeekして、currentNestedMethodCallを設定しているようですが、これ以上はEclipse上では動作を追うことができません。

ということで「initMethodCall」でgrepしてみると、LVEnhancerクラスのenhanceThisClassメソッドに、以下の記述が見つかりました。

                        stmt.append("play.classloading.enhancers.LVEnhancer.LVEnhancerRuntime.initMethodCall(\"" + dmio.getName() + "\", " + dmio.getNbParameters() + ", " + (methodParams.subject != null ? ("\"" + methodParams.subject + "\"") : "null") + ", $$paramNames);");
                        stmt.append("}");

                        insert(stmt.toString(), ctClass, behavior, codeAttribute, iterator, frame, false);

メソッド呼び出しのようなものが、文字列で書かれています。メソッドの先頭ではjavassist.CtClassを使っているので、Javassist*1を使ってバイトコードの変換をしているようです。

雰囲気的に(こればっかだな)、initMethodCallメソッドの最終引数paramNamesに渡すために埋め込んでいる、"$$paramNames"が怪しそうです。

                        DecodedMethodInvocationOp dmio = (DecodedMethodInvocationOp) frame.decodedOp;
                        StringBuffer stmt = new StringBuffer("{");
                        MethodParams methodParams = DecodedMethodInvocationOp.resolveParameters(frame);
                        stmt.append("String[] $$paramNames = new String[").append(methodParams.params.length + (methodParams.varargs != null ? methodParams.varargs.length : 0)).append("];");

DecodedMethodInvocationOp.resolveParameters(frame)から取ったものをゴリゴリして、String配列を定義するコードにしています。

まとめのようなもの

…とまぁ、追えたのはここまででした。この先は、JavassistのFrameからTrackableArrayを取り出してLocalVariableクラスをごにょごにょしてるらしいです。雰囲気的に。

LVEnhancer#enhanceThisClassはEnhancerクラスの抽象メソッドの実装で、抽象メソッド定義部分のコメントを見てみると、

    /**
     * The magic happen here...
     */
    public abstract void enhanceThisClass(ApplicationClass applicationClass) throws Exception;

とあるので、やはりここが臭いますよね〜という程度で、やんわりと〆ることにします。

次は @ です。よろしく!

2011-11-13

豚スペアリブの素朴なシチューは簡単で美味しいから覚えておくべき

f:id:katzchang:20111113212704j:image

じゃがいも、にんじん、玉ねぎ、パセリ、豚スペアリブ。調味は粒胡椒と塩。これだけ。

あとは、保温性の高い鍋。今回はホーロー鍋を用意したが、小さかったので、後で鉄鍋に入れ替えている。最も向いているのは土鍋だけど、うちにあるのはなぜかバカでかいので、今回は登場しない。

じゃがいもは皮をむく。で、鍋に入れておく。

にんじんは皮をむいて、適当に切っておく。玉ねぎもまた皮をむいて、これも適当に切っておく。で、これも鍋に入れておく。

パセリは葉の部分をちぎっては投げ、ちぎっては投げ、鍋に入れる。茎の部分も入れるが、茎は食べないことにする。

豚スペアリブは塩をまぶして、これも鍋に入れる。塩は多めでよい。

粒胡椒をそのまま入れて、塩を足す。鍋に入れる順番はこの限りではないので、準備ができたものから投入すればよい。

かぶるくらいに水を入れて、火にかける。沸騰したらそのままグツグツしておくとアクが集まるので、2回くらいとっておく。

ここで味見をして、塩を調整しておく。自信がなければ少なめにしておいたほうが無難っちゃ無難だが、できればここで塩を決めておいたほうが仕上がりの一体感がでる。ここは経験ですね。

で、蓋をして、火を止め、遊びに行く。

夕方帰ったら、火を再び点火し、沸騰したら出来上がり。もし、鍋の蓋を開けて脂の層が浮いていれば、固まっているうちに取っておく。脂はパセリの風味があって美味しいので、冷蔵し、炒め油などに利用すればよい。

f:id:katzchang:20111113013922j:image:w360

f:id:katzchang:20111113015035j:image:w360

f:id:katzchang:20111113015330j:image:w360

で、鍋をそのまま食卓に供する。好みで胡椒やオリーブオイル、バター、サワークリーム、マスタードなどを添えてもよい。驚くほど美味しい。粒胡椒まで柔らかい。

出来上がりまでの時間は長いが、調理にかかるのは長くて30分程度。写真の材料は4皿分程度だが、10皿分でも手間はそれほどかからない(むくじゃがいもの量が増える程度、ピーラーが大活躍)なので、例えば、友人兄弟家族が子供を連れて遊びに来て〜なんて場面ではちょうど良い。小さい子どもにも人気がある。朝のうち、もしくは前夜に仕込んでおくだけで、帰ってからの仕事が激減する。

肉は豚スペアリブを使ったが、牛肉でも美味しい。スネ、モモ、肩ロース…。鶏肉ならモモぶつ切りとか、手羽先とかを混ぜてもよさそう。

豚バラは避けたほうがよさそう。柔らかくなるまでに時間がかかる。これは経験でしかないんだけど、放置する調理法は赤身を柔らかくするが、脂の塊はあまり溶かさない。多分、脂部分は90度以上とかの高温で煮込まないとダメなんじゃあるまいか?ただしその温度だと、今度は赤身の水分が抜けやすくなるので、温度管理と加熱時間の調整が必要になる。肉部分がパッサパサなダメ豚角煮を食べて、ガッカリしたことがあるだろ?

まぁ、つまり、豚スペアリブは無難なわけですわ。

他、野菜としてセロリを入れても美味しい。プチトマトを入れると具になるし、トマト缶を入れるとトマトシチューになる。他に合いそうなのは、キャベツとか、大根やカブ、下仁田ネギなんかも良さげですね。ローリエなどハーブを足してもよい……が、ついつい「いつも使っています!」という人は、一度入れずに作ってみることをおすすめする。強くおすすめする。料理は、何を足すかよりも何を引くかなのだ。

あと、重要なのは鍋。保温性の高いものを使う。土鍋が安くて手に入りやすいので、なければ一つ買っておくとよいですよ。

おまけ

愛用している、いわゆるI型ピーラー。にんじん等の長物は向こう側に押し出して、じゃがいもや梨などの丸物はナイフのように使う。oxoのは安価で、刃渡りが長くて使いやすいですよ。

OXO タテ型ピーラー 20081

OXO タテ型ピーラー 20081

2011-10-11

大学イモは、水あめで作ると失敗しない

さつまいもって、意外とレシピに困るのは俺だけでしょうか?

サツマイモの皮を適当にむき、乱切りにして5分くらい水にさらす。水気を切って、中温の油で揚げる。

水飴適量を鍋で温めて柔らかくして、揚げたサツマイモと黒ごまを入れ、からめる。油を塗ったバット…なんて面倒なので、テフロン加工のフライパンに広げて、熱がとれたら皿に盛る。

f:id:katzchang:20111010100047j:image

f:id:katzchang:20111010100832j:image

f:id:katzchang:20111010101635j:image

f:id:katzchang:20111010101732j:image

f:id:katzchang:20111010102842j:image

f:id:katzchang:20111010102956j:image

f:id:katzchang:20111010103630j:image

カリッと仕上げたい場合は、二度揚げするといいかもしれない。揚げ物は程よい水分があるほうが良い感じに揚がるので、揚げたサツマイモを冷凍する、一度揚げ後に水に浸す、霧吹き、揚げる前に茹でてしまうなど、色々やってみるとよい。自分はやらない。面倒だし。

水飴はあめの俵屋のじろ飴を使用。カタログでは500gで2,700円と高価だが、容器持ち込みの量り売りで700gを1,700円で買うことができた。らしい。砂糖を溶かしても良いが、加減が難しいんだよ。

2011-09-12

->や=>を検索するとき

  • ->: (single) arrow operator
  • =>: double arrow operator