2. Dictionary API
The
dictionary API is a type-safe reflection API that can reflect on many
elements. The following code sample is a revision of the preceding
example that finds inventory classes by using the dictionary API. You
can’t use this API to get information about when an element was
modified. Instead, this example reflects a bit more on the class
information and lists only abstract classes.
static void findAbstractInventoryClasses(Args _args) { Dictionary dictionary = new Dictionary(); int i; DictClass dictClass;
for(i=1; i<=dictionary.classCnt(); i++) { dictClass = new DictClass(dictionary.classCnt2Id(i));
if (dictClass.isAbstract() && strStartsWith(dictClass.name(), 'Invent')) { info(strfmt("%1", dictClass.name())); } } }
|
The Dictionary class provides information about which elements exist. With this information, you can instantiate a DictClass
object that provides specific information about the class, such as
whether the class is abstract, final, or an interface; which class it
extends; whether it implements any interfaces; and what methods it
includes. Notice that the DictClass
class can also reflect on interfaces. Also notice how the class counter
is converted into a class ID; this conversion is required because the
IDs aren’t listed consecutively.
When you
run this job, you’ll notice that it’s much slower than the
implementation that uses the table data API—at least the first time you
run it! The job performs better after the information is cached.
Figure 1 shows the object model for the dictionary API. As you can see, some elements can’t be reflected upon by using this API.
The following example revises the FindStaticMethodsOnCustTable from the preceding code by using the dictionary API. It also reports the method parameters of the methods.
static void findStaticMethodsOnCustTable(Args _args) { DictTable dictTable = new DictTable(tableNum(CustTable)); DictMethod dictMethod; int i; int j; str parameters;
for (i=1; i<=dictTable.staticMethodCnt(); i++) { dictMethod = new DictMethod( UtilElementType::TableStaticMethod, dictTable.id(), dictTable.staticMethod(i));
parameters = ''; for (j=1; j<=dictMethod.parameterCnt(); j++) { parameters += strfmt("%1 %2", extendedTypeId2name(dictMethod.parameterId(j)), dictMethod.parameterName(j));
if (j<dictMethod.parameterCnt()) parameters += ', '; } info(strfmt("%1(%2)", dictMethod.name(), parameters)); } }
|
As mentioned earlier, reflection can also be used to invoke methods on objects. This example invokes the static Find method on the table CustTable.
static void invokeFindOnCustTable(Args _args) { DictTable dictTable = new DictTable(tableNum(CustTable)); CustTable customer; ; customer = dictTable.callStatic( tableStaticMethodStr(CustTable, Find), '1201');
print customer.Name; //Prints Sparrow Wholesales pause; }
|
Notice the use of the intrinsic function tableStaticMethodStr to make a reference to the Find method.
You
can also use this API to instantiate class and table objects. Suppose
you want to select all records in a table with a given table ID. The
following example shows you how.
void findRecords(TableId _tableId) { DictTable dictTable = new DictTable(_tableId); Common common = dictTable.makeRecord(); FieldId primaryKeyField = DictTable.primaryKeyField();
while select common { info(strfmt("%1", common.(primaryKeyField))); } }
|
First, notice the call to the makeRecord method that instantiates a table cursor object that points to the correct table. You can use the select
statement to select records from the table. If you wanted to, you could
also insert records by using the table cursor. Notice the syntax used
to get a field value out of the cursor object; this syntax allows any
field to be accessed by its field ID. This example simply prints the
content of the primary key field. You can use the makeObject method on the class DictClass to create an object instance of a class.
All
the classes in the dictionary API discussed so far are defined as
system APIs. On top of each of these is an application-defined class
that provides even more reflection capabilities. These classes are
named SysDict<Concept>, and each class extends its counterpart in the system API. For example, SysDictClass extends DictClass.
Consider the following example. Table fields have a property that specifies whether the field is mandatory. The DictField class returns the value of the mandatory property as a bit set in the return value of its flag
method. Testing of a bit set is somewhat cumbersome, and if the
implementation of the flag changes, the consuming applications breaks.
The SysDictField class encapsulates the bit-testing logic in a mandatory method. Here is how the method is used.
static void mandatoryFieldsOnCustTable(Args _args) { DictTable dictTable = new DictTable(tableNum(CustTable)); SysDictField sysDictField; int i;
for (i=1; i<=dictTable.fieldCnt(); i++) { sysDictField = new SysDictField( dictTable.id(), dictTable.fieldCnt2Id(i));
if (sysDictField.mandatory()) info(sysDictField.name()); } }
|
You might also want to browse the SysDict classes for static methods. Many of these provide additional reflection information and better interfaces. For example, the SysDictionary class provides a classes method that has a collection of SysDictClass instances. You could use this method to simplify the earlier findAbstractInventoryClasses example.
Notice how all the examples instantiate the dictionary classes by using their new
constructor. Some developers use an alternative way to instantiate the
dictionary classes, but you should avoid it. Recall the hierarchy of
the objects shown in Figure 1. A parent object can return an instance of a child object, as shown here.
DictTable dictTable = new DictTable(tableId); DictField firstField, nextField; firstField = dictTable.fieldObject(dictTable.fieldNext(0)); nextField = dictTable.fieldObject(dictTable.fieldNext(dictField.id()));
|
The primary reason to avoid this construct is that you can’t substitute Dict classes with SysDict classes. If you ever need reflection methods available only on the SysDict
classes, you must refactor the code. Writing the code so that it is
easy to substitute the class makes refactoring easier and lowers the
risk of introducing bugs in the refactoring process. Another reason
to avoid this construct is the lack of API consistency. The examples
used in this section that instantiate dictionary classes all follow the
same structure, which is consistent for all the classes in the
dictionary API.