Prism Query Language
1. About this tutorial
In this tutorial, we first show how program sources are prepared and how the PQL runtime is started. We then intorduce the kinds of information PQL is designed to query using JHotdraw as an example. We conclude the tutorial by two crosscutting investigation examples.
2. Running PQL
The program sources need to be indexed before PQL is used. Currently, this indexing process is accomplished by a customized AspectJ compiler. This cutomization does not interfere with normal Java compilation. Here is the download of the compiler. Please issue: java -jar prismajc.jar, and follow the intruction of installing the ajc compiler. The indexing is started as the aspectj compiler is invoked normally to compile Java sources with two additional compiler flags as follows:
ajc –prismname [name of the index] –prismdirectory [where index is stored] @argfiles
As an example, to index the Jhotdraw source code, a file containing all files of Jhotdraw is created as "build.lst". We name our index file "jhotdraw.prism". This index file is generated as follows:
ajc -prismname jhotdraw -prismdirectory ../index @build.lst
Once the compilation completes, an index file should be generated in the indicated directory. The second step is then to load the index file into the PQL runtime. The PQL runtime is supplied as a jar file (download) and can be started as follows:
java -cp pql.jar pql.PQL [directory of index files]
PQL queries are entered as the console prompt:
pql> match type:"*.*.*(..)"; <enter>
CH.ifa.draw.util.PaletteIcon:659:668:java.awt.Image CH.ifa.draw.util.PaletteIcon.selected() CH.ifa.draw.util.PaletteLayout:2050:2082:void CH.ifa.draw.util.PaletteLayout.layoutContainer(java.awt.Container)
CH.ifa.draw.util.PaletteLayout:971:1007:java.awt.Dimension CH.ifa.draw.util.PaletteLayout.preferredLayoutSize(java.awt.Container) CH.ifa.draw.util.PaletteLayout:1085:1119:java.awt.Dimension CH.ifa.draw.util.PaletteLayout.minimumLayoutSize(java.awt.Container)
To view the matched element in the source code, PQL provides the "show" command if the "vi" command, aliasing GVIM, is available in the environment. PQL will fire vi with the row number and column number in a shell command. The "show" command can be used as follows:
pql> show CH.ifa.draw.util.PaletteLayout:1085:1119
3. Java Elements in PQL
The Prism Query Language (PQL) is a declarative language for retrieving information from large Java source code bases. It is designed to track two types of relationships among Java elements: type and scope.
Type relationships: Since Java is a strongly typed language, the source code of a program constitutes a type space partitioned by the Java elements of the program. These Java elements relate to each other via inheritance, composition, dependency, and realization. PQL uses the AspectJ name pattern language, with minor modifications, to express these relationships. The currently supported name patterns are explained via examples as follows. More examples later will further demostrate how these patterns can be used.
Pattern | Matches |
*..* | Any class type |
*..*TypeX* | Any class type (skipping package definitions) that has "TypeX" as part of its name |
*..TypeX+ (*..TypeX-) | TypeX and its subtypes including realization relationships (super types including interfaces) |
*..*.*(..) | Any method with any arguments |
*..TypeX.set*(..) | Any method defined in TypeX that begins with "set" in its name |
*..*.*(.., *..TypeX+,..) | Any method has either TypeX or its subtypes defined as one of its arguments |
*..*.*(*..TypeX+,..) | Same as above except TypeX appears as the first argument |
*..TypeX *..*.*(..) | Any method returns TypeX |
Usage relationship: In addition to the type relationship, Java elements are related to each other regarding how they are used to compose programs in a hierarchical organization of scopes. The most outer scope is the project itself. This scope covers a collection of class scopes which individually are collections of method scopes. The method scopes consist of control blocks, i.e., if, switch, or while blocks, or method invocations. A method invocation can also be a level of scope if it contains other types of scopes such as inner classes or method calls. The following table lists the PQL keywords for designating different kinds of scopes.
Keyword | Designates | Usage | Notes |
component | whole code space | component : " [regular expression] " | |
call | method invocations | call : "[ type pattern]" | |
branch | if , switch, while blocks | (branch id = if | while | switch) @ ( fieldref | call ) : " [type pattern]" | The type pattern captures the type usage in the condition expression of the control block |
type | class and method declarations | type: "[type pattern]" | |
fieldref | reference to field | fieldref: " [type pattern] [type pattern]" | The first type pattern captures the type of the field, the second captures the type where the field is declared |
field | field declarations | field: "[type pattern] [type pattern]" |
4. PQL Queries
Type patterns and scope designators introduced previously are central to PQL queries. PQL queries can be used to find either these scopes directly or find scopes matching a certain crieterion. We now go through details of PQL queries via a series of examples, using Jhotdraw as the target system.
4.1 Finding scopes directly:
Syntax: match [scope definition] ;
Query | PQL statement |
Any "Figure" type | match type:"*..*Figure*"; |
Children of type " Figure" | match type:"*..Figure+"; |
Method of any figure | match type:"*..*Figure*.*(..)"; |
Method have any figure as arguments | match type:"*..*.*(..,*Figure*,..) "; |
Method returning subtypes of figure | match type:"*..Figure+ *.*.*(..)"; |
Any field declarations of type "Figure" | match field:"*..Figure *..*.*"; |
Any if blocks making calls to "Vector" as part of its condition | match if@call:"*..Vector.*(..)"; |
4.2 Finding enclosing scopes or enclosed scopes:
Syntax: match [scope defintion] around | within [scope definition]
Query | PQL statement |
Types define "setter"s | match type:"*.*" around type:"*..*.set*(..) "; |
"Setters" call "getters" | match type:"*..*.set*(..) " around call:"*..*.get*(..)"; |
Any fields defined in subtypes of Figure | match field:"* *..*.*" within type:"*..Figure+"; |
"Getters" called by "setters" | match call:"*.*.get*(..)" within type:"*..*.set*(..)"; |
4.3 Finding sibling scopes
Syntax: match [scope defintion ] and | or | xor [scope definition]
Query | PQL statement |
Getters and setters defined in the same type | match type:"*..*.get*(..)" and type:"*..*.set*(..)"; |
Types define both "getters" and "setter" | match type:"*..*" around type:"*..*.get*(..)" and type:"*..*.set*(..)"; |
Methods make both calls to "Vector" and reference fields of type "Figure" | match type:"*..*.*(..)" around call:"*..Vector.*(..)" and fieldref:"*..Figure+ *..*.*"; |
Types define either "getters" or "setter" but not both | match type:"*..*" around type:"*..*.get*(..)" xor type:"*..*.set*(..)" |
4.4 Nesting scopes
Syntax: match [scope definition ] ( within | around match [scope definitoin] ) ?
Query | PQL statement |
Types define methods return subtypes of "Figure" and make "set" calls | match type:"*..*" around match type:"*..Figure+ *..*.*(..) " around match call:"*..*.set*(..)"; |
"Getter" calls made in "setter"s defined in subtypes of Listener | match call:"*..*.get*(..)" within match type:"*..*.set*(..)" within match type:"*..*Listener+"; |
"Getter" calls made in types containing field declarations of type Figure | match call:"*..*.get*(..)" within match type:"*..*" around match field:"*..Figure+ *..*.*"; |
4.5 Compliments of scope patterns
Syntax: match not [scope definition] or match [scope definition] not around | within [scope definition] | [query definition] ;
Query | PQL statement |
Setters made not in the subtypes of Figure | match call:"*..*.set*(..) " within not type:"*..Figure*+"; |
Find types do not define setters | match type:"*..*" not around call:"*..*.set*(..)"; |
4.6 Converted scopes
Synatx: scope: = [scope converters]( scope )
We first list the scope converters. The first column lists the current converter keywords, and the other columns list the scopes the converter converts to given the type of scope listed as the heading of that column.
Converters | for compoent | for class | for method | for field | for fieldref | for call |
tofield | field declarations of all types in the component | field declarations to the matched types | Exception | no conversion | no conversion | Exception |
tofeildref | field references to all types in the project | field references to the matched types | Exception | no conversion | no conversion | Exception |
tomethod | all methods of the matched project | all methods of the matched classes | no conversion | Exception | Exception | convert the call to its declaration, conversative polymorphism check |
totype | all classes types in the project | no conversion | Exception | Exception | Exception | Exception |
tocall | all calls to methods defined in matched projects | all calls to the methods defined in the matched types | calls to the matched methods | Exception | Exception | No conversion |
Next, we list some examples of how converters are useful.
Query | PQL statement |
Find the call sites of all the methods in classes having "java.util.Vector" as fields | match tocall(match type:"*..*" around field:"java.util.Vector *..*"); |
Find the declarations of the methods that are called by "setters" | match totype(match call:"*..*.*(..)" within type:"*..*.set*(..)"); |
For types declare Vector fields, find types declare them as fields | match type:"*.*" around tofield(match type:"*.*" around field:"*..Vector *..*.*"); |
Find all the setter calls made in setter methods which are called by setter methods | match call:"*..*.set*(..)" within tomethod(match call:"*..*.set*(..)" within type:"*..*.set*(..)"); |
Find methods containing calls to types that declare "Figure" as fields | match type:"*..*.*(..)" around tocall(match type:"*..*" around field:"*..Figure+ *..*.*"; |
4.7 Scope matching in results
Syntax: pick [scope definition] outof [query definition]
Sometimes, we are interested in more type information about the results than a query directly returns. For example, for a query returns a collection of methods, we might be interested in finding out all the class types that define these methods. Here are some additional examples:
Query | PQL statement |
Find method definitions of Figure in all accessors | pick type:"*..Figure+.*(..)" outof match type:"*..*.get*(..)"; |
Find Figure types define accessors | pick type:"*..Figure+" outof match call:"*..*.get*(..)"; |
4.8 Set operations of results
Syntax: [query definition] union | intersect | diff [query definition]
Query | PQL statement |
Find getters and setters | match type:"*..*.get*(..)" union match type:"*..*.set*(..)"; |
Find types define both getters and setters | match type:"*..*" around type:"*..*.get*(..)" intersect match type:"*..*" around type:"*..*.set*(..)"; |
Find types define either getters or setters | match type:"*..*" around type:"*..*.get*(..)" diff match type:"*..*" around type:"*..*.set*(..)"; |
4.9 Abstraction
All scope definitions and query definitions can be designated by variables, i.e., names. The boolean and hierarchical composition rules of queries and scopes can also be applied to these variables. To use variables, the scope definitions need to be defined in a slightly different form. For instance, instead of saying: type:"*.*", we can say: type all = "*.*". Instead of saying: match type:"*.*", we can now say: match all. We can alias the compositions of scope definitions as: type accessor = setter or getter. One advantage is that our query : match accessor does not need to change if we decide later that: type accessor = setter or getter or putter;
Similarly, for queries, instead of saying : match accessor within all, we can say: query findaccessor = match accessor within all; and findaccessor either becomes a command on the PQL or can be used in other queries like this: match all around tocall(findaccessor);
5. Feedbacks
Problems and feedbacks are welcome to send to czhang AT eecg DOT utoronto DOT ca.