メソッド・ジェネレータとトリガ・ジェネレータの定義
メソッド・ジェネレータは、独自の実行時コードを生成する特殊なメソッドです。同様に、トリガ・ジェネレータは、独自の実行時コードを生成するトリガです。
ここでは、主にメソッド・ジェネレータについて説明しますが、トリガ・ジェネレータの場合も詳細は同様のものになります。
概要
InterSystems IRIS® データ・プラットフォームには、メソッド・ジェネレータを定義できるという強力な機能があります。メソッド・ジェネレータは、メソッドの実行時コードを生成する小さいプログラムであり、クラス・コンパイラによって呼び出されます。同様に、トリガ・ジェネレータもクラス・コンパイラによって呼び出され、トリガの実行時コードを生成します。
メソッド・ジェネレータは、InterSystems IRIS クラス・ライブラリ内で広範囲に使用されます。例えば、%PersistentOpens in a new tab クラスのメソッドの多くは、メソッド・ジェネレータとして実装されます。これにより、効率が少し下がる汎用コードの代わりに、カスタマイズされたストレージ・コードを各永続クラスに提供できます。多くの InterSystems IRIS データ型クラス・メソッドも、メソッド・ジェネレータとして実装されます。またこれにより、これらのクラスが使用される内容により、カスタム実装を行えるようになります。
メソッド・ジェネレータとトリガ・ジェネレータは、ユーザ独自のアプリケーション内で使用できます。メソッド・ジェネレータの一般的な使用方法は、1 つまたは複数の ユーティリティ・スーパークラスを定義し、使用するサブクラスの専用メソッドを提供することです。これらのユーティリティ・クラス内のメソッド・ジェネレータは、それらを使用するクラスの定義 (プロパティ、メソッド、パラメータ値など) に基づいて特別なコードを作成します。この技術の例としては、InterSystems IRIS ライブラリで提供されている %PopulateOpens in a new tab クラスと %XML.AdaptorOpens in a new tab クラスがあります。
基本
メソッド・ジェネレータは、InterSystems IRIS クラスのメソッドで、CodeMode キーワードに objectgenerator が設定されたものです。
Class MyApp.MyClass Extends %RegisteredObject
{
Method MyMethod() [ CodeMode = objectgenerator ]
{
Do %code.WriteLine(" Write """ _ %class.Name _ """")
Do %code.WriteLine(" Quit")
Quit $$$OK
}
}
クラス MyApp.MyClass がコンパイルされるとき、最終的には以下の実装を持った MyMethod メソッドが生成されます。
Write "MyApp.MyClass"
Quit
トリガ・ジェネレータを定義することもできます。これを行うには、トリガの定義内で CodeMode = objectgenerator を使用します。トリガ内で使用できる値は、メソッド・ジェネレータ内で使用できる値と多少異なります。
ジェネレータの機能
メソッド・ジェネレータはクラスをコンパイルするときに有効になります。メソッド・ジェネレータの操作は簡単です。クラス定義がコンパイルされるとき、クラス・コンパイラは以下のことを行います。
-
クラスの継承を解決します (継承されたすべてのメンバのリストを構築します)。
-
(各メソッドの CodeMode キーワードから) メソッド・ジェネレータとして指定されているすべてのメソッドのリストを作成します。
-
すべてのメソッド・ジェネレータからコードを集め、それを 1 つまたは複数の一時的なルーチンにコピーし、コンパイルします (これによってメソッド・ジェネレータ・コードを実行することができます)。
-
コンパイルされるクラスの定義を示す、一時的な一連のオブジェクトを作成します。これらのオブジェクトは、メソッド・ジェネレータ・コードで使用できます。
-
メソッド・ジェネレータのすべてのコードを実行します。
コンパイラは、各メソッドに GenerateAfter キーワードが存在すればその値を参照して、それぞれのメソッドを呼び出す順序を調整します。このキーワードによって、メソッド間にコンパイラのタイミングの依存性がある場合の制御を提供します。
-
各メソッド・ジェネレータの結果を (コード行と他のメソッド・キーワードに対するすべての変更)、コンパイルされたクラス構造にコピーします (クラスの実際のコードを生成するのに使用)。
元のメソッド・シグニチャ (引数と返りタイプ) は、すべてのメソッド・キーワード値と同様に、生成されたメソッドに使用されます。%IntegerOpens in a new tab の返りタイプを持たせるようにメソッド・ジェネレータを指定する場合、実際のメソッドは %IntegerOpens in a new tab の返りタイプを持ちます。
-
メソッド・ジェネレータによって生成されたコードと、メソッド・ジェネレータではないメソッドで生成されたコードを結合させることによって、実行可能なクラスのコードを生成します。
詳細は、トリガ・ジェネレータについても同じです。
メソッド・ジェネレータで使用できる値
メソッド・ジェネレータの実装には、メソッド・ジェネレータ・コードが実行されるコンテキストを理解することが重要です。前のセクションで説明したように、クラス・コンパイラはメソッド・ジェネレータ・コードを、クラス継承が解決された後、クラスのコードを生成する前の時点で呼び出します。メソッド・ジェネレータ・コードが呼び出されるときに、クラス・コンパイラは以下の変数をメソッド・ジェネレータ・コードで使用できるようにします。
変数 | 説明 |
---|---|
%code | %Stream.MethodGeneratorOpens in a new tab クラスのインスタンス。これは、メソッドのコードを出力するストリームです。 |
%class | %Dictionary.ClassDefinitionOpens in a new tab クラスのインスタンス。クラスの元の定義を含みます。 |
%method | %Dictionary.MethodDefinitionOpens in a new tab クラスのインスタンス。メソッドの元の定義を含みます。 |
%compiledclass | %Dictionary.CompiledClassOpens in a new tab クラスのインスタンス。コンパイルされるクラスの、コンパイルされた定義を含みます。つまり、継承が解決された後のクラスに関する情報を含むということです (スーパークラスから継承されたものを含む、すべてのプロパティやメソッドのリストなど)。 |
%compiledmethod または %objcompiledmethod | コンパイルされるメソッドの %Dictionary クラスのインスタンス (例 : %Dictionary.CompiledMethodOpens in a new tab、%Dictionary.CompiledPropertyMethodOpens in a new tab、%Dictionary.CompiledIndexMethodOpens in a new tab)。生成されるメソッドの、コンパイルされた定義を含みます。 |
%parameter | パラメータ名によってインデックスされたすべてのクラス・パラメータの値を含む配列。例えば、%parameter("MYPARAM") は、現在のクラスに対する MYPARAM クラス・パラメータの値を含みます。この変数は、%class オブジェクト経由で使用できる、パラメータ定義のリストを使用する簡単な方法として提供されています。 |
%kind または %membertype | メンバ・メソッドの場合、このメソッドに関連するクラス・メンバの種類 (例 : プロパティ・メソッドの場合は a、インデックス・メソッドの場合は i)。 |
%mode | メソッドのタイプ (例 : method、propertymethod、indexmethod)。 |
%pqname または %member | メンバ・メソッドの場合、このメソッドに関連するクラス・メンバの名前。 |
トリガ・ジェネレータで使用できる値
メソッドと同様に、トリガもジェネレータとして定義できます。つまり、トリガの定義内で CodeMode = objectgenerator を使用できます。トリガ・ジェネレータで使用できる変数を以下に示します。
変数 | 説明 |
---|---|
%code、%class、%compiledclass、および %parameter | 前のセクションを参照してください。 |
%trigger | %Dictionary.TriggerDefinitionOpens in a new tab クラスのインスタンス。トリガの元の定義を含みます。 |
%compiledtrigger または %objcompiledmethod | %Dictionary.CompiledTriggerOpens in a new tab クラスのインスタンス。生成されるトリガのコンパイル済みの定義を含みます。 |
%kind または %membertype | トリガの場合、これは値 t です。 |
%mode | トリガの場合、これは値 trigger です。 |
%pqname または %member | このトリガの名前。 |
メソッド・ジェネレータの定義
メソッド・ジェネレータを定義するには、以下の手順を実行します。
-
メソッドを定義し、その CodeMode キーワードを objectgenerator に設定します。
-
メソッドのボディで、クラスがコンパイルされるときに、実際のメソッド・コードを生成するコードを記述します。このコードは、コードを出力するために %code オブジェクトを使用します。これは、使用できる他のオブジェクトをインプットとし、どのようなコードを生成するかを決定します。
以下は、クラスが属するすべてのプロパティの名前をリストにするメソッドを生成する、メソッド・ジェネレータの例です。
ClassMethod ListProperties() [ CodeMode = objectgenerator ]
{
For i = 1:1:%compiledclass.Properties.Count() {
Set prop = %compiledclass.Properties.GetAt(i).Name
Do %code.WriteLine(" Write """ _ prop _ """,!")
}
Do %code.WriteLine(" Quit")
Quit $$$OK
}
このジェネレータは、以下に類似した実装を持つメソッドを生成します。
Write "Name",!
Write "SSN",!
Quit
メソッド・ジェネレータ・コードについて、以下のことに注意してください。
-
%code オブジェクトの WriteLine メソッドを使用して、メソッドの実際の実装を含むストリームにコード行を書き込みます。(Write メソッドを使用して、行末の文字なしでテキストを出力することもできます)。
-
生成されたコードの各行の文頭には、スペース文字があります。ObjectScript が行の最初のスペースにはコマンドを許可しないので、これは必須です。使用しているメソッド・ジェネレータが Basic、または Java コードを作成している場合は必要ありません。
-
作成されたコード行は文字列に表示されるので、引用符文字を二重にすることで ("") エスケープすることに注意する必要があります。
-
クラスのプロパティのリストを検索するには、%compiledclass オブジェクトを使用します。%class オブジェクトを使用することもできますが、その場合は継承されたプロパティはリストにせず、コンパイルされているクラス内で定義されたプロパティのみをリストにします。
-
$$$OK のステータス・コードを返します。これは、メソッド・ジェネレータが正常に実行したことを示します。この返り値は、メソッドの実際の実装には関係ありません。
メソッド・ジェネレータ内で CodeMode を指定する
既定で、メソッド・ジェネレータは code メソッドを作成します (つまり、生成されたメソッドに対する CodeMode キーワードが code に設定されるということ)。これは、%code オブジェクトの CodeMode プロパティを使用して変更できます。
例えば、以下のメソッド・ジェネレータは ObjectScript 式のメソッドを生成します。
Method Double(%val As %Integer) As %Integer [ CodeMode = objectgenerator ]
{
Set %code.CodeMode = "expression"
Do %code.WriteLine("%val * 2")
}
ジェネレータおよび INT コード
メソッド・ジェネレータとトリガ・ジェネレータでは、クラスのコンパイル後に、統合開発環境 (IDE) に対応する INT コードを表示すると非常に便利になる場合があります。InterSystems Studio を使用している場合は、[表示] メニューから [他のコードの表示] を選択します。Visual Studio Code に InterSystems ObjectScript 拡張機能を使用している場合は、エディタでクラス・ファイルを右クリックして、[他を表示] を選択します。
ジェネレータがカーネルで実装されるほど十分に単純である場合、.INT コードは生成されないことに注意してください。
サブクラスへの影響
ここでは、ジェネレータ・メソッドを定義したクラスのサブクラスに含まれるジェネレータ・メソッドに特有のトピックについて説明します。
当然のことですが、サブクラスはスーパークラスのコンパイル後にコンパイルする必要があります。
サブクラス内のメソッド生成
ジェネレータ・メソッドを定義するクラスをサブクラス化するとき、InterSystems IRIS は、前述したものと同じコンパイル・ルールを使用します。ただし、生成されるコードの外見がスーパークラス生成コードと同じになる場合、InterSystems IRIS はサブクラスのメソッドをリコンパイルしません。このロジックでは、両方のクラスのインクルード・ファイルが同じかどうかについては考慮しません。インクルード・ファイルで定義されたマクロをメソッドが使用する場合、別のインクルード・ファイルをサブクラスで使用しても、InterSystems IRIS はサブクラスのメソッドをリコンパイルしません。ただし、どのクラスのジェネレータ・メソッドも強制的にリコンパイルすることは可能です。そのためには、そのメソッドの ForceGenerate メソッド・キーワードを指定します。このキーワードは、別のシナリオでも必要になることがあります。
スーパークラスのメソッドの呼び出し
サブクラスでは、ローカルに生成されたメソッドではなく、スーパークラスのために生成されたメソッドを使用することが必要になる場合があります。その場合は、$$$OK を返すだけのジェネレータ・メソッドをサブクラスで定義します。以下に例を示します。
ClassMethod Demo1() [ CodeMode = objectgenerator ]
{
quit $$$OK
}
生成されたメソッドの削除
生成されたメソッドをサブクラスから削除して、そのクラスのメソッドの呼び出しを不可能にすることができます。そのためには、スーパークラスのジェネレータ・メソッドを定義するときに、現在のクラスの名前を検証して目的のシナリオでのみコードを生成するロジックを組み込みます。以下に例を示します。
ClassMethod Demo3() [ CodeMode = objectgenerator ]
{
if %class.Name="RemovingMethod.ClassA" {
Do %code.WriteLine(" Write !,""Hello from class: " _ %class.Name _ """")
}
quit $$$OK
}
このメソッドをサブクラスから呼び出そうとすると、<METHOD DOES NOT EXIST> エラーを受け取ることになります。
このロジックは、前のセクションで説明したものとはわずかに違う点に注意してください。特定のクラスにジェネレータ・メソッドが存在していても、メソッドが NULL 実装である場合は、スーパークラスのメソッドが代用されます (存在する場合)。ただし、特定のクラスのジェネレータ・メソッドが、特定のサブクラスにはコードを生成しない場合、そのサブクラスにメソッドは存在しなくなり、そのメソッドは呼び出しできなくなります。