Software Engineering for Smart Data Analytics & Smart Data Analytics for Software Engineering
Once you have written an predicate (e.g. the Singleton Pattern 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 is an analysis and hence that it should be
and that its results should be
The visual effects of these declarations are shown in the Control Center section.
In the simplest case, a result is just a single program element (method, field, call, …). you just need to add two predicates to your analysis.
:- multifile jt_analysis_definition/6. % (Name, Trigger, Severity, Category, Description, QuickfixAvailable) :- multifile jt_analysis/2. % (Name, Id)
Let's take for example the Depth of inheritance metric from our Tutorial project 1). If you want to follow that tutorial step by step you should uncomment the jt_analysis predicates on the bottom of the file metrics/doi.pl.
In that file you find the implementation of the predicate
stinky_doi(DOI, Class, FQN)
If you want to add this analysis to the JT Control Center and make the result visible in the editor you have to add the following declaration:
jt_analysis_definition('stinky_doi', 'onSave', 'warning', 'Metrics', 'Depth of Inheritance is too big', false).
If you want to add a description to the category, you can do this with the following predicate.
:- multifile(jt_analysis_group/2). % CategoryName, Description jt_analysis_group('Metrics', 'Metrics for the java code').
This is optional, the category will also be displayed if you do not add this predicate.
The jt_analysis_definition predicate is just there to make this analysis visible in the JTCC.
The analysis itself will be executed when you add the following predicate that links the jt_analyis declaration to your existing predicate:
jt_analysis('stinky_doi', Id) :- stinky_doi(_DOI, Id, _FQN).
The jt_analysis predicate should return all results which you want to make visible in the editor. The first argument is the name of the analysis (as defined in the jt_analysis_definition predicate). The second argument is an id for which this analysis is true. In the case of our stinky_doi example this is the id of the class. In this simple case, the description will be taken from the jt_analysis_definition predicate and the source location of the marker will just be the source location of the fact with the specified id.
If everything worked fine, than a result of this analysis should look like this:
If you want to improve the appearance of the marker, you should take a look at the following “Advanced examples”.
If the description from the jt_analysis_definition predicate is not specific enough for your analysis, you can add an individual description for every result. Let's say, we want to add the found depth of inheritance to every marker (not only the information that the depth is too big). We have to extend the jt_analysis definition:
:- multifile jt_analysis/3. % (Name, Id, Description) jt_analysis('stinky_doi', Id, Description) :- stinky_doi(DOI, Id, _FQN), format(atom(Description), 'Depth of Inheritance of this class is ~w', [DOI]).
If we do this, the marker will display the description provided in the jt_analysis predicate. The description in jt_analysis_definition will just be used for the JTCC (because here we have no individual result).
If you added the previous example, you will see that the marker will point to the complete source location of the analysis' result id. For some analyses this is sufficient, but in our case we don't want to mark the complete class but only the name of the class. So we can add another predicate to set an individual source location.
:- multifile jt_analysis_source_location/5. % (Name, Id, File, Start, Length) jt_analysis_source_location('stinky_doi', Id, File, Start, Length) :- source_location(Id, File, _, _), sl_argT(Id, identifier(_), Start, Length).
Here we say that a marker for the stinky_doi analysis should be linked to the identifier of the specified element. Since we know that the result is always a class, we can be sure that such an identifier exists. See also our Source Location API.
There are analyses for which one hightlighted element in the source code is not enough. So we need to possibility to add multiple markers to different source code elements from only one analysis. An example is the desin pattern mining. If you have an analysis that finds singleton patterns in your source code, it would be nice to have more markers in your editor than just a “this is a singleton class” marker. In particular, you would also want to have markers for the 'getInstance method' or the 'instance field'.
The sample singleton mining in the tutorial project is an example with multiple markers. The definition predicate looks as usual:
jt_analysis_definition('mine_singleton', onSave, info, 'Design Patterns', 'Detector for Singleton patterns', false).
However, to display multiple result elements, we need a list of these elements (not only one ID as provided by the jt_analysis/2 predicate). This means, we have to use another predicate:
:- multifile jt_analysis_list/3. % (Name, ResultList, MainId)
ResultList has to be a list of terms with the following structure:
marker(Id, File, Start, Length, Description, ResultTerm)
As you see, every marker has it's own source location (including the file), description and result term. You don't need to create these terms manually, you can also use the make_marker_for/4 predicate as shown below:
jt_analysis_list('mine_singleton', MarkerList, MainId) :- mine_singleton(Type, Method, Field), % call to singleton mining MainId = Type, % MainId is the singleton class itself make_marker_for(Type, 'This is a singleton', mine_singleton_class, TypeMarker), make_marker_for(Method, 'This is a getInstance method', mine_singleton_getInstance, MethodMarker), make_marker_for(Field, 'This is the instance field of a singleton', mine_singleton_instance_field, FieldMarker), MarkerList = [ TypeMarker, MethodMarker, FieldMarker].
Here you see the complete example for the singleton mining. First we call the mine_singleton/3 predicate (the mining itself). The MainId is in this case the singleton class (which is important for grouping the markers). The make_marker_for predicate/4 creates the marker terms, with the correct source location (identifier for classes and methods, complete source location otherwise). This works for single IDs (like in our case) or for lists (in this case, the result would be a list of markers). The second argument is the description of the marker, the third argument is the result term, which is important for the transformation (see analysis mining).
The only thing that has to be done is to add the three marker terms to a list.
Here you can see the markers for the Singleton analysis. Every important element is highlighted with a seperate marker.
The JT Analysis API gives you the possibility to add transformation to your analysis predicates. So you can use the quickfix mechanism in eclipse 2) to start a transformation, based on your analysis.
The transformation is done with CTs. If you don't know how to use CTs, take a look at our CT Tutorial.
If you have some analysis and some CT which transforms the result of this analysis you have to add the information, that these to belong together. This is done by one of the following predicates.
:- multifile jt_fix/4. % (OptionList, ResultTerm, CtHead, Description) :- multifile jt_fix_default/3. % (ResultTerm, CtHead, Description)
The jt_fix_default is only a redirect to jt_fix with the options 'global' and 'preview' (see below for the meaning of these options).
For an example on how to use a quickfix for your analysis you can take a look in the singleton_constructor_not_private.pl in the smells folder in our tutorial project.
jt_fix([preview], singleton_constructor_not_private(Id), make_constructor_private(Id), 'Change visibilty of constructor to private').
This means: If we have a result with the result term 'singleton_constructor_not_private(Id)' we provide a quickfix with the label 'Change visibilty of constructor to private' and if the user selects this fix, we call the CT 'make_constructor_private(Id)'.
If you're adding a fix for your analysis, don't forget to update the jt_analysis_definition and set the QuickfixAvailable parameter to true. Your quickfix will also be visibile and work if you don't do it, but in the JTCC the information would be missing.
It is possible to add JTransformer specific annotations to your code. One example is the suppress annotation (which is similar to the @Suppress annotation from Java). If you add this to your code, the marker will not be visible. You can enable this transformation for your analysis with one of the following predicates.
:- multifile jt_fix_suppress/1. % (ResultTerm) :- multifile jt_fix_suppress/3. % (ResultTerm, ResultId, Name)
The jt_fix_suppress/1 predicate ist just a shortcut, where Name is the functor and ResultId the only parameter of ResultTerm
If you want to add the suppress warning possibility to our stinky_doi example you have to add this code:
jt_fix_suppress(stinky_doi(Id), Id, stinky_doi).
The second argument is the id, to which the marker should be added (this could also be the enclosing class for example). In this case (since this is the default implementation) you could also write:
jt_fix_suppress(stinky_doi(_Id)).
The result will look like this:
Another example for annotations is the jt_analysis_mining. We can use this with our singleton mining example. If you agree to the result of the analysis you maybe want to add an “accepted” annotation to your code, if you don't agree you want to add a “rejected” annotation. This enables you to check your own implementation (maybe an improved version of your mining predicate doesn't find the rejected results). It's also a good way to document your code, because it adds comments to the code which give information on which design pattern was used.
:- multifile jt_analysis_mining/1. % (ResultTerm) :- multifile jt_analysis_mining/3. % (ResultTerm, ResultId, Name)
Like in the suppress annotation, the jt_analysis_mining/1 is just a shortcut.
To support this mining annotations in our singleton analysis, you just have to add the following code:
jt_analysis_mining(mine_singleton_class(_Type)). jt_analysis_mining(mine_singleton_getInstance(_Method)). jt_analysis_mining(mine_singleton_instance_field(_Field)).
The result will look like this: