■
こんな感じで、@whenが動く時に、プロセッサのフィールドに、
要望。 - 設計と実装の狭間で。
処理対象リソースの@whenで指定したパスとは違うパスのノードを格納しておいて欲しいデス。
仕込みを入れれば対応できそうな予感。
糖衣構文による@bind指示子の解釈
/* * @bind public in methods.modifiers */ public List<CtMethod> methods;
これは、下のシンタックスシュガーでいいかなと。
/* */ public List<CtMethod<?>> methods = new ArrayList<CtMethod<?>>(); /** * Synthesized (from {@code methods}). * @when public in methods.modifiers */ public void hack__methods(CtMethod<?> methods) { this.methods.add(methods); }
より一般的に書くと、
/* * G<T> は java.util.Collection<T> を起動可能 * @bind (xに関係するクエリ) */ public G<T> x;
↓
/* * G<T> は java.util.Collection<T> を起動可能 */ public G<T> x = new (G<T>の実装)(); /** * @when (xに関係するクエリ) */ public void hack__x(T x) { this.x.add(x); }
という変換を行うHackを上記に対して適用する、という手もありますが、このためにラウンディングするのもアレかなと。
Hackの記述単位の規定
現在、Hackは「Hackを実現するメソッドごと」に「コンパイル対象の各クラスに対して」適用されます。
これだと複雑なことが何もできないので、いろいろ考えてます。
実際にクエリを保持するのはメソッドなので、最小単位が「クエリ@Javadoc+メソッド」であることは変えない予定ですが、一つなぎのHackを連続して適用することによってのみ実現可能な機能というのは需要があるようです。
そこで、Hackの記述単位というものを考えてみると、
- 各クエリ付きのメソッドをHackと呼んでいたが、そのようなメソッドを含むクラス全体をHackとする
- クエリ付きのメソッドはHackではなく、一つなぎのHackの部分機能を有するという意味でHackUnit(?)とする
名前変えただけじゃねーか、という話もありますが「Hackとは何か」を明確にするという利点があります。
HackはHackUnitの集まりなので、各HackUnitがどのような順序で適用されるべきか、ということを規定できるようになります。
現在の構想では、クエリ付きのメソッドに注釈で「@HackOrder(n)」みたいなイメージかなと。
Hackの適用範囲
先ほどの名前一覧に、「Hackをインスタンス化したものをHackObjectとする」という規定を追加します。
このHackObjectのライフサイクル(生存スコープ)を規定してやることで、要望のあったビルド時の横断的な情報などを保持しておくことができるはず。現在の構想では、次のライフサイクルを想定しています。
- LifeCycle.BUILD - ビルド全体にまたがったHack。HackObjectはビルドイベントに対してシングルトン
- LifeCycle.SOURCE - CompilationUnitにまたがったHack。HackObjectはCompilationUnitごとに生成される
- LifeCycle.TYPE - 型宣言にまたがったHack。HackObjectは型の宣言ごとに生成される
- LifeCycle.APPLY - HackObjectはHackUnitの起動のたびに生成される
この規定だけだとHackObjectがステートフルになっただけなので、もう一歩進めてみます。
ご要望にあった機能を実現するには、「ビルド横断的にデータを集めて、最後に処理」というものがありましたので、JUnitの@Before, @Afterみたいなものも欲しくなってきます。
- @BeforeHack : HackObjectが生成されて、同Hack内のHackUnitが起動される前に一度だけ呼び出される
- @AfterHack : HackObjectがスコープから外れたのち、次の任意のイベントが実行される前に一度だけ呼び出される
あまり膨らませすぎても大変なのでこのへんでしょうか。ちなみに要検討項目で、まだ確定してません。