Once you have written a predicate (e.g. the System.out.println detector) you can query it interactively in the Prolog Console. However, that is not really nice since it means a lot of typing and the results are just text with lots of element IDs that are hard to interpret by a human.
This section tells you how to let JTransformer know that your predicate should be
- shown as an analysis in the 'Control Center'
- run at the click of a button or even automatically
and that its results should be
- indicated by markers in Java source code
- displayed in the result overview table.
The visual effects of these declarations are shown in the Control Center section.
The predicate analysis_api:analysis_definition(Name, Trigger, Severity, Category, Description) tells the JT Control Center about an analysis that it should display. Its six arguments define properties of the analysis:
- Name is the name of the analysis. It is used when providing results for this analysis. This can be any atom.
- Trigger specifies if this analysis is executed automatically or manually. There are two possible values:
- onSave: This analysis is executed after each update of the factbase caused by modification of any files contained in the factbase.
- manually: This analysis is only executed explicitly using the Control Center.
- Severity defines the kind of the marker created in Eclipse for each result. Possible values are info, warning and error.
- Category is the category of this analysis. Analysis are grouped by category in the Control Center.
- Description is the description of this analysis. It is shown in the Control Center.
The following listing shows the definition of the System.out.println detector from our this tutorial.
% The predicate analysis_definition/5 logically belongs to the % analysis_api module but clauses can be defined in many files: :- multifile(analysis_api:analysis_definition/5). % The clause defining the System.out.println detector: analysis_api:analysis_definition( 'sysout_call', % Name onSave, % Trigger warning, % Severity 'Logging', % Category 'Call to System.out.println' % Description ).
Implementing a clause for the multifile predicate analysis_api:analysis_result/3 makes the results of you analysis predicate accessible to the GUI of JT. The predicate to implement has three arguments:
- Name is the name of the analysis for which results should be provided. It must be the same name that was provided in the related
- Group is an arbitrary Prolog term that is used to identify analysis results that logically belong together. An analysis (e.g. the Singleton Pattern Detection) may find multiple results that are connected. In such a case we say that the connected results belong to the same group. This is indicated by the same identifying term. Other analyses (e.g. Depth of inheritance (DOI)) find only individual results (conceptually each group has only one element). In this case the Group argument must be a free variable (typically, the anonymous variable _).
- Result is a term describing a result. It is generated using the predicate make_result_term/3 and contains
- the ID of the result PEF,
- the role of this PEF (if the result is part of a group),
- a description to be displayed (a natural language explanation of the role) and
- the location in the Java source code that should be highlighted.
The first thing to do is to include the following declaration into your file:
:- multifile(analysis_api:analysis_result/3). % (Name, Group, Result)
Then you implement a clause for the predicate. Two examples are shown below. Our tutorial project contains several more examples.
The System.out.println detector implemented in the sysout_call/6 predicate finds individual results. This is indicated by the anonymous variable “_” in the second argument of the related clause for analysis_result/3:
1: analysis_api:analysis_result('sysout_call', _, Result) :- 2: % Call the System.out.println detector that you implemented: 3: sysout_call(CallId, _, _, _, _, _) , 4: 5: % Create a description 6: Description = 'Call to System.out.println', 7: 8: % Wrap everything into a result term for the GUI: 9: make_result_term(sysout_call(CallId), Description, Result).
Note also that the functor of the first argument in the call to make make_result_term/3 can be anything, it does not need to be the predicate name. In our example, the decision to use the predicate name (compare lines 3 and 9) was a matter of personal taste. See also → Generating a Result Term.
The Singleton Pattern Detection analysis from our Tutorial project 1) finds groups of related results. Each occurrence of a singleton pattern consists of three elements:
- a class,
- a getInstance() method in that class and
- a field of that class that stores the unique instance of the class.
Each element is represented by one result term. The terms are returned to the calling site (the GUI of JT) one by one, via backtracking.
To tell the GUI that all result that are related to the same class belong together the predicate uses the group identifier singleton(Class) in the second argument. So the GUI knows that is should present elements that share this identifier together, as parts of one result for the Singleton Pattern Detection analysis.
1: analysis_api:analysis_result('mine_singleton', singleton(Class), Result) :- 2: % Call the Singleton Pattern detector predicate: 3: classMethodReturnsOwnInstance(Class, Method, Field), 4: 5: % Wrap each element into a result term for the GUI, indicating which 6: % element plays which role in the detected pattern occurrence. 7: % The disjunction forces returning the results one by one, via backtracking: 8: ( make_result_term(class(Class), 9: 'This is a singleton', 10: Result) 11: ; make_result_term(getInstance(Method), 12: 'This is the getInstance method of a singleton', 13: Result) 14: ; make_result_term(instance_field(Field), 15: 'This is the instance field of a singleton', 16: Result) 17: ).
See also → Generating a Result Term.
Sometimes a generic description in your marker is not enough and you want to add dynamically computed values (e.g. names of referenced elements, results of metrics …). For the “Depth of Inheritance (DOI)” metric from our Tutorial project 2) it is helpful to add the computed value to the marker.
1: analysis_api:analysis_result('Stinky_DOI', _, Result) :- 2: % Call the DOI analysis predicate that you implemented: 3: stinky_doi(DOI, Id, _), 4: 5: % Create a description that refers to the dynamically computed DOI value: 6: format(atom(Description), 'The Depth of Inheritance of this class is ~w', [DOI]), 7: 8: % Wrap everything into a result term for the GUI: 9: make_result_term(stinky_doi(Id), Description, Result).
Note the call to format/3 in line 6 that creates the description dynamically, depending on the value of the DOI variable.
There is also a way to create the description in the make_result_term call. Just delete line 6 and replace line 9 by:
make_result_term(stinky_doi(Id), 'The Depth of Inheritance of this class is ~w'-[DOI], Result).
The predicate make_result_term/3 is used to generate a term describing a result. It has three arguments. The first two are required input, the third is bound to the generated term:
- RoleTerm is a term with the following properties:
- The functor is the name of the role played by this result.
- The first argument is the ID of a PEF returned as a result by your analysis predicate.
- It may also have further arguments.
- Description is a description of this result.
- Result is the term representing this result for the JT GUI.
make_result_term(RoleTerm, Description, Result) is predefined. To use it you have to import it:
:- use_module(jt_analysis(make_result_terms), [ make_result_term/3 % (RoleTerm, Description, Result) ]).