Again, I wasn’t feeling too well, was kept busy with other things, and therefore couldn’t work as much on my Concutest project as much as I wanted to. Earlier I had written that there were problems with arrays. This was only true for arrays of annotations; arrays of other values, i.e. primitive data, enumerations, Strings
and Class
constants, worked correctly before, I just had a few doubts after I observed the behavior of arrays of annotations.
Arrays of annotations were the only type of array that caused a problem because all other arrays are actually passed as arrays; arrays of annotations are not: The classes that represent annotations are only interfaces, i.e. there is no built-in way to construct these values, which makes passing an array of them more difficult. I could have created my own classes that implement these annotation interfaces, then created an array of them, then created instances of the new classes to fill the array, and then pass the array, but that seemed more difficult than sticking with my original approach, “flattening out” all all parameters.
The problem with flattening all parameters is that the predicate method for the same annotation may have to have a differing number of parameters, if the annotation contains an array somewhere and two uses of the annotation have different numbers of elements in the array. For now, I have decided that I will just create several distinct predicate methods, one for each combination of array sizes. A last problem that I didn’t see coming until very late was that I had to distinguish between arrays nested in different elements of another array, so for arrays, the index is encoded in the method name as well.
Let’s consider the following annotations, predicates and annotated methods:
class PrintPrimitive {
public static boolean checkString(Object thisO, String value) {
System.out.println("thisO = "+thisO+", value:String = "+value);
return false;
}
}
@PredicateLink(value=PrintPrimitive.class, method="checkString")
@interface PrintStringAnnotation {
String value();
}
This is the basic @PredicateLink
-type annotation, containing a String
as value, and its user-written predicate method.
Below are the @Combine
-type annotations that combine annotations using Boolean operations: @AndPrintStrings
has two arrays of @PrintStringAnnotation
annotations, and all elements get combined using AND
. @AndPrintStrings
therefore is a @Combine
-style annotation that combines @PredicateLink
-style annotations.
The annotation below that, @AndCombines
, is another @Combine
-style annotation, but it contains two arrays of @AndPrintStrings
annotation, so @AndCombines
combines several @Combine
-style annotations into one.
@Combine(Combine.Mode.AND)
@interface AndPrintStrings {
PrintStringAnnotation[] arr1();
PrintStringAnnotation[] arr2();
}
@Combine(Combine.Mode.AND)
@interface AndCombines {
AndPrintStrings[] arr3();
AndPrintStrings[] arr4();
}
When the @AndPrintStrings
or @AndCombines
annotations are used, then predicate methods must be generated. Consider the following uses:
@AndCombines(arr3={}, arr4={})
public void testAnnArrayxxxx() {}
@AndCombines(
arr3={},
arr4={@AndPrintStrings(arr1={},arr2={})})
public void testAnnArrayxx00() {}
@AndCombines(
arr3={},
arr4={@AndPrintStrings(
arr1={},
arr2={@PrintStringAnnotation("xx02a"),
@PrintStringAnnotation("xx02b")})})
public void testAnnArrayxx02() {}
@AndCombines(
arr3={@AndPrintStrings(
arr1={@PrintStringAnnotation("1a101")},
arr2={@PrintStringAnnotation("11a01")})},
arr4={@AndPrintStrings(
arr1={@PrintStringAnnotation("12a01"),
@PrintStringAnnotation("12b01")},
arr2={@PrintStringAnnotation("1022a"),
@PrintStringAnnotation("1022b")})})
public void testAnnArray1122() {}
@AndCombines(
arr3={@AndPrintStrings(
arr1={@PrintStringAnnotation("1a11001")},
arr2={@PrintStringAnnotation("11a1001")}),
@AndPrintStrings(
arr1={@PrintStringAnnotation("111a001")},
arr2={})},
arr4={@AndPrintStrings(
arr1={@PrintStringAnnotation("12a1001"),
@PrintStringAnnotation("12b1001")},
arr2={@PrintStringAnnotation("101022a"),
@PrintStringAnnotation("101022b")})})
public void testAnnArray111022() {}
All of these methods use the same annotation, @AndCombines
, so they should all invoke the same predicate method. However, if we look at the data stored inside these annotations, we realize that the number of data items differs:
testAnnArrayxxxx
contains two empty arraystestAnnArrayxx00
contains an empty array and an array with one@AndPrintStrings
, but that annotation contains two empty arrays.testAnnArrayxx02
contains an empty array and an array with one@AndPrintStrings
, and that annotation contains an empty array and an array with two@PrintStringAnnotation
.testAnnArray1122
contains two arrays with one@AndPrintStrings
annotation each. These nested annotations contain arrays with one or two@PrintStringAnnotation
, respectively.testAnnArray111022
contains two arrays, the first with two and the second with one@AndPrintStrings
annotation, respectively. These nested annotations contain two arrays with one@PrintStringAnnotation
each; one array with one@PrintStringAnnotation
and an empty array; and two arrays with two@PrintStringAnnotation
each, respectively.
It is easy to see that the first two uses of the annotation contain no actual data, the third two, the fourth six, and the last seven data elements. My program now creates five different predicate methods whose names contain name-size pairs for all arrays. The following predicate methods are created for the five annotation uses above (all of them begin with public static boolean
):
check$arr3$$$0$arr4$$$0
(java.lang.Object a)check$arr3$$$0$arr4$$$1$arr4$$0$arr2$$$0$arr4$$0$arr1$$$0
(java.lang.Object a)check$arr3$$$0$arr4$$$1$arr4$$0$arr2$$$2$arr4$$0$arr1$$$0
(java.lang.Object a, java.lang.String b, java.lang.String c)check$arr3$$$1$arr3$$0$arr2$$$1$arr3$$0$arr1$$$1
$arr4$$$1$arr4$$0$arr2$$$2$arr4$$0$arr1$$$2
(java.lang.Object a, java.lang.String b, java.lang.String c,
java.lang.String d, java.lang.String e, java.lang.String f,
java.lang.String g)check$arr3$$$2$arr3$$0$arr2$$$0$arr3$$0$arr1$$$1
$arr3$$1$arr2$$$1$arr3$$1$arr1$$$1$arr4$$$1$arr4$$0$arr2$$$2
$arr4$$0$arr1$$$2
(java.lang.Object a, java.lang.String b, java.lang.String c,
java.lang.String d, java.lang.String e, java.lang.String f,
java.lang.String g, java.lang.String h)
The methods begin with check
, followed by name-size pairs separated by $
. The name consists of the annotation members that lead to the array being described, where the individual members are again separated by $
. If an annotation is an element in an array, then the index in the array is indicated after the member’s name by $$
followed by an integer. The end of the name is described by $$$
, which is followed by an integer describing the size. In the third example, $arr4$$0$arr2$$$2
means that the array arr2
contained in element 0 of the arr4
array has size 2. Again, like so many times before, I have chosen the $
character because the JVM allows it in identifiers but the Java language does not; this avoids all possible clashes with regular Java identifiers.
Now my @Combine
-type annotations work the way I want them to work. So what is left to do? I need to convert the tests that I have so far into unit tests; right now, they are more “eyeball tests”: I eyeball whether the results are correct. Unfortunately, writing unit tests that involve compilation, instrumentation and execution of other Java source are rather painful to write. But I’ll do it — eventually.
I also want to provide an alternative way of using the thread checker’s instrumentation strategy, allowing the user to bypass the highly flexible but somewhat complicated FileInstrumentor
and FileInstrumentorLauncher
.
I also have to write up a concise, comprehensive guide with many examples to demonstrate all the ways the thread checker annotations can be used. For now, I’m happy, though, and I have other things to do. I need to grade, and somehow Kooprey, my object-oriented parser generator, got infected with code rot. Dr. Wong wants to present it to his COMP 202 class tomorrow (=today, it’s 5:12 AM), so I need to figure out what’s going on there.