
The powerful Quality Assurance Tool for your Java application.
This document describes the concepts of jQAssistant and usage information.
Overview
jQAssistant is a QA tool which allows the definition and validation of project specific rules on a structural level. It is built upon the graph database Neo4j and can easily be plugged into the build process to automate detection of constraint violations and generate reports about user defined concepts and metrics.
Example use cases:
-
Enforce naming conventions, e.g. EJBs, JPA entities, test classes, packages, maven modules etc.
-
Validate dependencies between modules of your project
-
Separate API and implementation packages
-
Detect common problems like cyclic dependencies or tests without assertions
The rules are expressed in Cypher - the easy-to-learn query language of Neo4j:
MATCH
(t:Test:Method)
WHERE NOT
(t)-[:INVOKES]->(:Assert:Method)
RETURN
t AS TestWithoutAssertion
License
jQAssistant is contributed under GNU General Public License, v3.
Quickstart
Command Line
Installation
-
Download and unpack the distribution, a directory ${project.artifactId}-${project.version} will be created.
Scan
bin/jqassistant.cmd scan -f lib
bin/jqassistant.sh scan -f lib
-
The JAR files contained in the lib/ folder will be scanned.
Explore
bin/jqassistant.cmd server
bin/jqassistant.sh server
-
Open a browser and navigate to http://localhost:7474
-
Enter the follwoing query in the top level area and hit Ctrl-Enter:
MATCH
(t:Type)-[:DECLARES]->(m:Method)
RETURN
t.fqn AS Type, count(t) AS DeclaredMethods
ORDER BY
DeclaredMethods DESC
LIMIT 20
Maven
Add the plugin
Add the following lines to the parent pom.xml file of your project:
<build>
<plugins>
<plugin>
<groupId>com.buschmais.jqassistant.scm</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<executions>
<execution>
<id>scan</id>
<goals>
<goal>scan</goal>
</goals>
</execution>
<execution>
<id>analyze</id>
<goals>
<goal>analyze</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<build>
<reporting>
<plugins>
<plugin>
<groupId>com.buschmais.jqassistant.scm</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<reportSets>
<reportSet>
<reports>
<report>report</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
Add a rule
Within your parent module create a directory "jqassistant" and a rules file in it:
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">
<group id="default">
<includeConstraint refId="my-rules:TestClassName" />
</group>
<constraint id="my-rules:TestClassName">
<requiresConcept refId="junit4:TestClass" />
<description>All JUnit test classes must have a name with suffix "Test".</description>
<cypher><![CDATA[
MATCH
(t:Junit4:Test:Class)
WHERE NOT
t.name =~ ".*Test"
RETURN
t AS InvalidTestClass
]]></cypher>
</constraint>
</jqa:jqassistant-rules>
Run the build
Execute the following command from your parent module:
mvn install
The build will fail with the message specified by your rule if it is violated. If everything is fine you can also create a report as part of your Maven site:
mvn site
Explore your application
jQAssistant comes with an integrated Neo4j server, you can run it using
mvn jqassistant:server
-
Open a browser and navigate to http://localhost:7474
-
Enter the follwoing query in the top level area and hit Ctrl-Enter:
MATCH
(t:Type)-[:DECLARES]->(m:Method)
RETURN
t.fqn AS Type, count(t) AS DeclaredMethods
ORDER BY
DeclaredMethods DESC
LIMIT 20
Introduction
This chapter provides an introduction to the concepts of jQAssistant.
How it works
The basic idea behind jQAssistant is to integrate the following steps into the build process of a software system:
-
Scan the generated artifacts and store structural information about them into a database
-
Analyze the structures using rules which are represented by queries
-
Report violations
jQAssistant itself is a plugin based framework. It comes with a pre-defined set of plugins containing scanners, rules and reports but can be easily extended by custom rules or implementations.
As database an embedded instance of Neo4j Community Edition is managed and used by jQAssistant. This means that no setup or configuration of a dedicated server is required. Neo4j has been chosen because:
-
it is a mature open source graph database
-
it allows easy modelling of structural elements of a software and their relations
-
it comes with a very expressive and easy to learn query language (Cypher)
Rules
Rules are expressed as Cypher queries and are specified in XML-files:
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">
<group id="default">
<includeConstraint refId="my-rules:MyConstraint"/>
</group>
<constraint id="my-rules:MyConcept">
<description>A human readable description of the concept.</description>
<cypher><![CDATA[
MATCH
...
WHERE
...
SET
...
CREATE
...
RETURN
...
]]></cypher>
</constraint>
<constraint id="my-rules:MyConstraint">
<requiresConcept refId="my-rules:MyConcept" />
<description>A human readable description of the constraint.</description>
<cypher><![CDATA[
MATCH
...
WHERE
...
RETURN
...
]]></cypher>
</constraint>
</jqa:jqassistant-rules>
Each rule comes with an unique id (e.g. "my-rules:MyConstraint") which can be referenced by other rules. jQAssistant will take care about executing the rules in the correct order. Furthermore a human readable description shall help developers to understand the rationale behind them.
A rule can optionally define the severity level. jQAssistant allows to break the build if there are violations in the configured severity level (or higher). For example, if the severity is set to critical, and if there are violated constraints with blocker and/or critical severity; the build will break. This feature allows projects to pay down their technical debt in an iterative manner.
Following severity levels are supported:
-
info
-
minor
-
major
-
critical
-
blocker
Groups
A group is a set of rules that shall be executed together. This allows to adjust analysis depth for different types of builds, e.g. a Continuous Integration build (CI) can be configured to only execute rules with low costs (i.e. execution times) whereas a report build is allowed to run for a longer time with more expensive checks.
Concepts
The information created by the scanner represents the structure of a software project on a raw level. Concept rules allow enriching the database with higher level information to ease the process of writing queries that check for violations (i.e. constraints) . This typically means adding labels, properties or relations.
jQAssistant comes with language and framework plugins which include general technical concepts, e.g.
-
"java:Entity" provided by the JPA2 plugin adds a label "Entity" to a node if it represents a class which is annotated by "@javax.persistence.Entity".
-
"java:MethodOverrides" provided by the Java plugin adds a relation "OVERRIDES" between a method of a sub class to the super class methods it overrides.
It is recommended to use concepts to enrich the database with information which is specific for the concrete project, e.g. labels can be added to
-
package nodes representing modules of the application ("Module")
-
package nodes that represent technical layers ("UI", "EJB")
-
class nodes representing elements with a specific role ("Controller", "Model")
NOTE Even if the primary intention of a concept is to enrich data it still must provide a return clause. If a concept returns an empty result a warning will be generated by jQAssistant. The rationale is that in such case the concept does not match the structure of the application and other rules which depend on it will probably not work as expected. The return clause of the concept shall preferably return a node/relation itself instead of an attribute of it. Similarly, return clauses with only count of matching nodes shall be avoided. With this, XML and HTML reports can provide additional information about the concept.
Constraints
A Constraint is a query which detects violations, e.g.
-
classes with specific roles (e.g. entity, controller, etc.) that are either located in the wrong packages or have names that do not fit defined conventions
-
invocations of methods which are deprecated and/or forbidden (e.g. constructors of java.util.Date)
-
dependencies to other modules which are not allowed
A constraint can depend on one or more concepts and usually is referenced by one or more groups.
NOTE If a constraint returns a result jQAssistant will report an error including the provided description and information about the returned elements. This information shall help the developer to understand and fix the problem.
Command Line
Shell scripts are provided for executing jQAssistant from the command line of Microsoft Windows® or Unix compatible systems. They are located in the bin/ directory of the distribution:
-
jqassistant.cmd
-
jqassistant.sh
The command line accepts tasks and their specific options:
jqassistant.sh <task1> <task2> <task3> -<option1> -<option2>
The following example will scan the content of the directories classes/ and test-classes/:
jqassistant.sh scan -f classes,test-classe
Tasks
scan
Description
Scans files or directories and stores the gathered information in database.
Options
-
-f <file1>,<file2> or --files <file1>,<file2>
-
specifies a list of files or directories to scan
-
-
-u <url1>,<url2> or --urls <url1>,<url2>
-
specifies a list of URLs to scan
-
-
-reset
-
reset the database before scanning
-
analyze
Description
Executes an analysis.
Options
-
-severity <severity>
-
specifies the severity of a problem for the analysis to fail. The program will return with exit code 2.
-
values: 'info', 'minor', 'major', 'critical' or 'blocker'
-
default: 'minor'
-
effective-rules
Description
List the rules which would be executed for an analysis and the given concepts, constraints or groups.
Common options
-s, --storeDirectory <directory>
-
specifies the location of the database to use
-
default: './jqassistant/store'
-concepts <concept1>,<concept2>
-
specifies the ids of the concepts to be applied
-constraints <constraint1>,<constraint2>
-
specifies the ids of the constraints to be validated
-groups <group1>,<group2>
-
specifies the ids of the groups to be executed
-
default: 'default'
-r, --ruleDirectory <directory>
-
specifies the directory where reports (XML, HTML) will be stored
-
default: './jqassistant/rules'
-reportDirectory
-
specifies the directory where reports (XML, HTML) will be stored
-
default: './jqassistant/report'
Maven Plugin
jQAssistant provides a plugin for Apache Maven which can be used to provide either fully automated scanning and analysis during the build process or manual execution from a command line.
Setup
Project Scope
Software projects often consist of several modules which are assembled to executable or deployable artifacts. In a Maven project these modules are usually organized hierarchically with a common parent module which is referenced directly or indirectly by all sub-modules. For each project jQAssistant uses a separate database with its own set of rules. Thus if a goal is executed within a Maven structure jQAssistant first determines the project scope, i.e. the root module, by searching within the tree starting from the current module following the parent relation until either a module is found where a directory "jqassistant/" exists or no parent is defined. The determined root module defines the location of
-
the set of rules to apply (from the directory "jqassistant/")
-
the database, default "{project.build.directory}/jqassistant/store"
-
the generated native report, default: "{project.build.directory}/jqassistant/jqassistant-report.xml")
-
and the generated HTML report, default "{project.build.directory}/site/jqassistant.html")
The following examples demonstrate different scenarios, the root modules as detected by jQAssistant are marked using asterisks.
root*
|-pom.xml
|
|-jqassistant
| |-rules.xml
|
|-module1
| |-pom.xml
|
|-module2
|-pom.xml
root
|-pom.xml
|
|-project1*
| |-jqassistant
| | |-rules1.xml
| |
| |-pom.xml
| |-module1
| | |-pom.xml
| |
| |-module2
| |-pom.xml
|
|-project2*
|-jqassistant
| |-rules2.xml
|-pom.xml
|-module1
| |-pom.xml
|
|-module2
|-pom.xml
Plugin Configuration
The jQAssistant Maven plugin must be configured in the pom.xml of the root module, it should not be overwritten by sub-modules.
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>com.buschmais.jqassistant.scm</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<version>1.0.0-M4</version>
<executions>
<execution>
<id>scan</id>
<goals>
<goal>scan</goal>
</goals>
<!--
<configuration>
<scanIncludes>
<scanInclude>
<path>config</path>
</scanInclude>
</scanIncludes>
<scanProperties>
<customScanner.property>value</customScanner.property>
</scanProperties>
</configuration>
-->
</execution>
<execution>
<id>analyze</id>
<goals>
<goal>analyze</goal>
</goals>
<!--
<configuration>
<concepts>
<concept>junit4:TestClass</concept>
</concepts>
<constraints>
<constraint>junit4:TestMethodWithoutAssertion</constraints>
</constraints>
<groups>
<group>default</group>
</groups>
<failOnViolations>true</failOnViolations>
<severity>critical</severity>
</configuration>
-->
</execution>
</executions>
<!--
<configuration>
<skip>false</skip>
<storeLifecycle>REACTOR</storeLifecycle>
<rulesDirectory>jqassistant</rulesDirectory>
<rulesDirectories>
<rulesDirectory>target/generated-rules</rulesDirectory>
</rulesDirectories>
<xmlReportFile>target/jqassistant/jqassistant-report.xml</xmlReportFile>
</configuration>
<dependencies>
<dependency>
<groupId>com.buschmais.jqassistant.plugin</groupId>
<artifactId>jqassistant.plugin.jpa2</artifactId>
<version>1.0.0-M4</version>
</dependency>
</dependencies>
-->
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.buschmais.jqassistant.scm</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<version>1.0.0-M4</version>
<reportSets>
<reportSet>
<reports>
<report>report</report>
</reports>
</reportSet>
</reportSets>
<!--
<configuration>
<reportProperties>
<customReport.fileName>target/customReport.txt</customReport.fileName>
</reportProperties>
</configuration>
-->
</plugin>
</plugins>
</reporting>
...
</project>
Command Line
Goals may also be executed from the command line:
mvn com.buschmais.jqassistant.scm:jqassistant-maven-plugin:available-rules
Adding the following lines to the file settings.xml (usually located in the $HOME/.m2) eases execution of jQAssistant goals from the command line:
<pluginGroups>
<pluginGroup>com.buschmais.jqassistant.scm</pluginGroup>
</pluginGroups>
The same goal can now be executed using the following command line statement:
mvn jqassistant:available-rules
Goals
jqassistant:scan
Description
Scans the directories of compiled classes and test classes and stores the gathered information in database.
Configuration
-
scanIncludes
-
add directories or files to be included while scanning
-
wildcards are not supported
-
-
scanProperties
-
allows passing properties to scanner plugins.
-
jqassistant:reset
Description
Resets the database by deleting all nodes and relationships.
jqassistant:server
Description
Starts the integrated Neo4j web server (http://localhost:7474).
jqassistant:analyze
Description
Executes an analysis.
Configuration
-
failOnViolations
-
determines if jQAssistant shall break the build if violations are detected (see also severity)
-
default: 'false'
-
-
severity
-
determines if jQAssistant shall break the build when constraint with higher severity level are violated and failOnViolations is set to true
-
default: 'info'
-
jqassistant:effective-rules
Description
List the rules which would be executed for an analysis and the given concepts, constraints or groups.
jqassistant:available-rules
Description
List all available rules.
jqassistant:report
Description
Transforms the XML report into HTML (i.e. for generating a Maven site).
Configuration
Common Configuration Properties
Execution
skip
-
skip execution of the plugin
-
default: 'false'
Store
storeDirectory (-Djqassistant.store.directory)
-
specifies the location of the database
-
default: '{rootModule}/target/jqassistant/store'
storeLifecycle (-Djqassistant.store.lifecycle)
-
specifies the lifecycle of the data store
-
'REACTOR': cache the store for the execution time of the reactor for fast operations
-
'MODULE': open and close the store for each module
-
-
default: 'REACTOR'
Analysis And Report
concepts (-Djqassistant.concepts)
-
specifies the ids of the concepts to be applied
constraints (-Djqassistant.constraints)
-
specifies the ids of the constraints to be validated
groups (-Djqassistant.groups)
-
specifies the ids of the groups to be executed
-
default: 'default'
rulesDirectory (-Djqassistant.rules.directory)
-
specifies the name of the directory which contains rules
-
this directory is also used to identify the root module of a project, see Project Scope
-
default: 'jqassistant'
rulesDirectories (-Djqassistant.rules.directories)
-
specifies a list of directory names relative to the root module containing additional rules
xmlReportFile (-Djqassistant.report.xml)
-
specifes the target file for writing the XML report
-
default: '{rootModule}/target/jqassistant/jqassistant-report.xml'
Examples
Rules
Forbidden Method
Demonstrates a constraint to define methods which must not be invoked by the application. The example denies usage of all constructors of java.util.Date.
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">
<constraint id="example:ConstructorOfDateMustNotBeUsed">
<description>Constructors of java.util.Date must not be used.</description>
<cypher><![CDATA[
MATCH
(dateType:Type)-[:DECLARES]->(forbiddenMethod:Method),
(type:Type)-[:DECLARES]->(method:Method)-[:INVOKES]->(forbiddenMethod)
WHERE
dateType.fqn = 'java.util.Date'
AND forbiddenMethod:Constructor
RETURN
method AS Method
]]></cypher>
</constraint>
<group id="default">
<includeConstraint refId="example:ConstructorOfDateMustNotBeUsed"/>
</group>
</jqa:jqassistant-rules>
Naming
The following rules demonstrate concepts and constraints to enforce names and locations of classes with dedicated roles. These are identified either by an implemented interface or an annotation.
Interface
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">
<concept id="controller:ControllerClass">
<description>Labels all classes implementing the Controller interface as "Controller".</description>
<cypher><![CDATA[
MATCH
(controllerClass:Type:Class)-[:IMPLEMENTS*]->(controllerType:Type)
WHERE
controllerType.fqn = "com.buschmais.jqassistant.examples.rules.naming.Controller"
SET
controllerClass:Controller
RETURN
controllerClass
]]></cypher>
</concept>
<constraint id="controller:ClassNameMustHaveControllerSuffix">
<requiresConcept refId="controller:ControllerClass"/>
<description>All controller implementations must have a name suffix "Controller".</description>
<cypher><![CDATA[
MATCH
(controllerClass:Class:Controller)
WHERE
NOT controllerClass.name =~ ".*Controller"
RETURN
controllerClass AS ControllerClass
]]></cypher>
</constraint>
<constraint id="controller:ClassesMustBeLocatedInControllerPackage">
<requiresConcept refId="controller:ControllerClass"/>
<description>All controller implementations must be located in the package "model".</description>
<cypher><![CDATA[
MATCH
(package:PACKAGE)-[:CONTAINS]->(controllerClass:Class:Controller)
WHERE
NOT package.name = "controller"
RETURN
controllerClass AS ControllerClass, package AS InvalidPackage
]]></cypher>
</constraint>
</jqa:jqassistant-rules>
Annotation
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">
<concept id="model:ModelClass">
<description>Labels all classes annotated with @Model as "Model".</description>
<cypher><![CDATA[
MATCH
(modelClass:Type:Class)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(modelAnnotationType:Type)
WHERE
modelAnnotationType.fqn = "com.buschmais.jqassistant.examples.rules.naming.Model"
SET
modelClass:Model
RETURN
modelClass AS ModelClass
]]></cypher>
</concept>
<constraint id="model:ClassNameMustHaveModelSuffix">
<requiresConcept refId="model:ModelClass"/>
<description>All model classes must have a name suffix "Model".</description>
<cypher><![CDATA[
MATCH
(modelClass:Class:Model)
WHERE
NOT modelClass.name =~ ".*Model"
RETURN
modelClass AS ModelClass
]]></cypher>
</constraint>
<constraint id="model:ClassesMustBeLocatedInModelPackage">
<requiresConcept refId="model:ModelClass"/>
<description>All model classes must be located in the package "model".</description>
<cypher><![CDATA[
MATCH
(package:PACKAGE)-[:CONTAINS]->(modelClass:Class:Model)
WHERE
NOT package.name = "model"
RETURN
modelClass AS ModelClass, package AS InvalidPackage
]]></cypher>
</constraint>
</jqa:jqassistant-rules>
Plugins
Custom Scanner Plugin
A custom scanner plugin allows extending the functionality of jQAssistant to read arbitrary structures (e.g. from files) and create the corresponding information in the database. The following example demonstrates this for CSV files.
The plugin is an artifact which contains the scanner implementation, model classes and a plugin descriptor. The project needs to declare the following dependencies:
<dependency>
<groupId>com.buschmais.jqassistant.core</groupId>
<artifactId>jqassistant.core.scanner</artifactId>
<version>1.0.0-M4</version>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.plugin</groupId>
<artifactId>jqassistant.plugin.common</artifactId>
<version>1.0.0-M4</version>
</dependency>
<dependency>
<groupId>au.com.bytecode</groupId>
<artifactId>opencsv</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
The artifact 'jqassistant.core.scanner' provides the scanner plugin API, 'jqassistant.plugin.common' contains common functionality shared by plugins, the library 'opencsv' is used to read CSV files.
The model to be stored in the database is defined using the approach provided by eXtended Objects. It is based on annotated interfaces declaring methods representing properties and relations.
First a label "CSV" is defined which is going to be used for all nodes created by the scanner:
@Label("CSV")
public interface CSVDescriptor extends Descriptor {
}
A CSV file is represented by CSVFileDescriptor which inherits from CSVDescriptor and FileDescriptor (part of the scanner API). Thus a node of this type will carry the labels "CSV" and "File". Furthermore a list of "HAS_ROW" relations is defined by the property "rows".
public interface CSVFileDescriptor extends CSVDescriptor, FileDescriptor {
@Relation("HAS_ROW")
List<CSVRowDescriptor> getRows();
}
A single row is a node defined by the type CSVRowDescriptor which inherits the label "CSV" from CSVDescriptor and provides its own label "Row", a property "lineNumber" and a list of "HAS_COLUMN" relations.
@Label("Row")
public interface CSVRowDescriptor extends CSVDescriptor {
int getLineNumber();
void setLineNumber(int lineNumber);
@Relation("HAS_COLUMN")
List<CSVColumnDescriptor> getColumns();
}
CSVColumnDescriptor finally defines a column of a row following the principles explained above:
@Label("Column")
public interface CSVColumnDescriptor extends CSVDescriptor {
String getValue();
void setValue(String value);
int getIndex();
void setIndex(int index);
}
The implementation of the plugin itself inherits from AbstractScannerPlugin which requires generic type parameters for the item type it handles and the descriptor type it creates. In the example FileResource is used which represents a file contained in a directory or archive. This allows plugins to be independent of the source where files or directories are picked up by the scanner.
The method accepts is called by the scanner to determine if the plugin can handle the given item. The example matches the value of the parameter path against the file extension ".csv". The scope parameter may be used to further restrict executions of the plugin, e.g. by checking equality against JavaScope.CLASSPATH.
The scan method actually reads the CSV file and stores the gathered data into the database using the interface Store provided by the scanner context.
public class CSVFileScannerPlugin extends AbstractScannerPlugin<FileResource, CSVFileDescriptor> {
@Override
public boolean accepts(FileResource item, String path, Scope scope) throws IOException {
return path.toLowerCase().endsWith(".csv");
}
@Override
public CSVFileDescriptor scan(FileResource item, String path, Scope scope, Scanner scanner) throws IOException {
// Open the input stream for reading the file.
try (InputStream stream = item.createStream()) {
// Create the node for a CSV file.
final Store store = scanner.getContext().getStore();
final CSVFileDescriptor fileDescriptor = store.create(CSVFileDescriptor.class);
// Parse the stream using OpenCSV.
CSV csv = CSV.create();
csv.read(stream, new CSVReadProc() {
@Override
public void procRow(int rowIndex, String... values) {
// Create the node for a row
CSVRowDescriptor rowDescriptor = store.create(CSVRowDescriptor.class);
fileDescriptor.getRows().add(rowDescriptor);
rowDescriptor.setLineNumber(rowIndex);
for (int i = 0; i < values.length; i++) {
// Create the node for a column
CSVColumnDescriptor columnDescriptor = store.create(CSVColumnDescriptor.class);
rowDescriptor.getColumns().add(columnDescriptor);
columnDescriptor.setIndex(i);
columnDescriptor.setValue(values[i]);
}
}
});
return fileDescriptor;
}
}
}
Finally the model and the plugin implementation must be declared in the jQAssistant plugin descriptor:
<jqa-plugin:jqassistant-plugin
xmlns:jqa-plugin="http://www.buschmais.com/jqassistant/core/plugin/schema/v1.0">
<description>Provides a scanner for CSV file.</description>
<model>
<class>com.buschmais.jqassistant.examples.plugins.scanner.model.CSVFileDescriptor</class>
<class>com.buschmais.jqassistant.examples.plugins.scanner.model.CSVRowDescriptor</class>
<class>com.buschmais.jqassistant.examples.plugins.scanner.model.CSVColumnDescriptor</class>
</model>
<scanner>
<class>com.buschmais.jqassistant.examples.plugins.scanner.CSVFileScannerPlugin</class>
</scanner>
</jqa-plugin:jqassistant-plugin>
The plugin is automatically loaded by the scanner if it can be found on the classpath, e.g. by adding it as dependency to the maven plugin.
Custom Rule Plugin
Rules (i.e. concepts, constraints or groups) can be shared between projects by packaging their rule descriptors in JAR artifacts as a classpath resources under /META-INF/jqassistant-rules/, e.g.
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">
<constraint id="common:TopLevelPackage">
<description>All types defined in a project must use "com.buschmais" as top level package.</description>
<cypher><![CDATA[
MATCH
(:Artifact)-[:CONTAINS]->(type:Type)
WHERE
NOT type.fqn =~ "com\\.buschmais\\..*"
RETURN
type AS TypeInWrongPackage
]]></cypher>
</constraint>
</jqa:jqassistant-rules>
Furthermore these resources must be declared in the jQAssistant plugin descriptor /META-INF/jqassistant-plugin.xml:
<jqa-plugin:jqassistant-plugin xmlns:jqa-plugin="http://www.buschmais.com/jqassistant/core/plugin/schema/v1.0">
<description>Example plugin defining a set of custom rules.</description>
<rules>
<resource>common.xml</resource>
</rules>
</jqa-plugin:jqassistant-plugin>
The plugin and its rules are automatically loaded by the analyzer if it can be found on the classpath, e.g. by adding it as dependency to the maven plugin.
Custom Report Plugin
A custom report plugin may be implemented to create custom representations of analysis results. Such a plugin must be provided as an artifact which contains a Java class and a plugin descriptor. Therefore a project is required which declares the required dependencies:
<dependency>
<groupId>com.buschmais.jqassistant.core</groupId>
<artifactId>jqassistant.core.report</artifactId>
<version>1.0.0-M4</version>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.plugin</groupId>
<artifactId>jqassistant.plugin.java</artifactId>
<version>1.0.0-M4</version>
</dependency>
</dependencies>
The artifact 'jqassistant.core.report' provides the report plugin API, 'jqassistant.plugin.java' provides interfaces representing Java types which might be used by the plugin implementation:
import com.buschmais.jqassistant.core.report.api.ReportPlugin;
import com.buschmais.jqassistant.plugin.java.api.model.TypeDescriptor;
public class CustomReportPlugin implements ReportPlugin {
private static final String PROPERTY_FILENAME = "customReport.fileName";
private String fileName;
@Override
public void initialize(Map<String, Object> properties) throws ReportException {
this.fileName = (String) properties.get(PROPERTY_FILENAME);
if (this.fileName == null) {
throw new ReportException("Property " + PROPERTY_FILENAME + " is not specified.");
}
}
@Override
public void begin() throws ReportException {
}
@Override
public void end() throws ReportException {
}
@Override
public void beginConcept(Concept concept) throws ReportException {
}
@Override
public void endConcept() throws ReportException {
}
@Override
public void beginGroup(Group group) throws ReportException {
}
@Override
public void endGroup() throws ReportException {
}
@Override
public void beginConstraint(Constraint constraint) throws ReportException {
}
@Override
public void endConstraint() throws ReportException {
}
@Override
public void setResult(Result<? extends Rule> result) throws ReportException {
Rule rule = result.getRule();
if (rule instanceof Concept && "example:MethodsPerType".equals(rule.getId())) {
try {
PrintWriter writer = new PrintWriter(new FileWriter(fileName));
writer.println("Methods per Type");
writer.println("================");
for (Map<String, Object> row : result.getRows()) {
TypeDescriptor type = (TypeDescriptor) row.get("Type");
Long methodCount = (Long) row.get("MethodCount");
writer.println(type.getFullQualifiedName() + ":" + methodCount);
}
writer.close();
} catch (IOException e) {
throw new ReportException("Cannot write custom report.", e);
}
}
}
}
The plugin implementation must be declared in the jQAssistant plugin descriptor:
<jqa-plugin:jqassistant-plugin xmlns:jqa-plugin="http://www.buschmais.com/jqassistant/core/plugin/schema/v1.0">
<description>Example plugin defining a custom report.</description>
<report>
<class>com.buschmais.jqassistant.examples.plugins.report.CustomReportPlugin</class>
</report>
</jqa-plugin:jqassistant-plugin>
The plugin is automatically loaded by the analyzer if it can be found on the classpath, e.g. by adding it as dependency to the maven plugin.
Maven Project Using Custom Plugins
Custom plugins containing rules, scanners or reporters must be specified as dependency of the Maven plugin. It is also possible to specify plugin specific properties:
<plugins>
<plugin>
<groupId>com.buschmais.jqassistant.scm</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<version>1.0.0-M4</version>
<configuration>
<reportProperties>
<customReport.fileName>target/customReport.txt</customReport.fileName>
</reportProperties>
</configuration>
<dependencies>
<dependency>
<groupId>com.buschmais.jqassistant.examples</groupId>
<artifactId>jqassistant.examples.plugins.scanner</artifactId>
<version>1.0.0-M4</version>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.examples</groupId>
<artifactId>jqassistant.examples.plugins.rule</artifactId>
<version>1.0.0-M4</version>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.examples</groupId>
<artifactId>jqassistant.examples.plugins.report</artifactId>
<version>1.0.0-M4</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
SonarQube
jQAssistant Plugin
Projects may retrieve jQAssistant rules from a SonarQube instance using a permalink. Furthermore a sensor is provided which will create issues on violated constraints.
The plugin is available from the standalone distribution in the folder "sonar/" and must be copied to the directory "extensions/plugins" of the SonarQube server. After startup a repository 'jQAssistant' is available in the view 'Coding rules' providing all rules which are distributed with jQAssistant.
New rules can be added or created in the following ways:
-
by creating new concepts and constraints from the pre-defined templates (i.e. "Concept Template" and "Constraint Template")
-
by deploying a custom rule extension (demonstrated by the example "Custom Rule Extension")
Next the following settings must be applied to the jQAssistant Maven plugin in the pom.xml of the project to be analyzed:
<plugins>
<plugin>
<groupId>com.buschmais.jqassistant.scm</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
<executions>
<execution>
<id>scan</id>
<goals>
<goal>scan</goal>
</goals>
</execution>
<execution>
<id>analyze</id>
<goals>
<goal>analyze</goal>
</goals>
<configuration>
<rulesUrl>http://localhost:9000/profiles/export?format=jqassistant&language=java&name=MyQualityProfile</rulesUrl>
<groups>
<group>MyQualityProfile</group>
</groups>
<failOnViolations>true</failOnViolations>
<severity>critical</severity>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
-
Rules are retrieved from the SonarQube server using the URL specified in 'rulesUrl'. The value is the jQAssistant permalink available within the quality profile from the SonarQube UI. Note that special characters must be replaced by XML entities.
-
The specified 'group' must be the name of the quality profile used to analyze the project.
-
The build shall break if a violation is detected, therefore 'failOnViolations' is set to 'true'.
-
The build shall break only if violation is detected with severity equal to or higher than critical, therefore severity is set to critical
Custom Rule Extension
A custom rule extension can be implemented to make user defined jQAssistant rules available for SonarQube. It is a Maven project which declares packaging 'sonar-plugin':
<project ...>
...
<properties>
<org.codehaus.sonar_version>3.7.4</org.codehaus.sonar_version>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.buschmais.jqassistant.scm</groupId>
<artifactId>jqassistant-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-packaging-maven-plugin</artifactId>
<version>1.9</version>
<extensions>true</extensions>
<configuration>
<basePlugin>jqassistant</basePlugin>
<pluginClass>com.buschmais.jqassistant.examples.sonar.ruleextension.MyJQAssistantExtension</pluginClass>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>native2ascii-maven-plugin</artifactId>
<version>1.0-beta-1</version>
<executions>
<execution>
<goals>
<goal>native2ascii</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.buschmais.jqassistant.sonar</groupId>
<artifactId>jqassistant.sonar.plugin</artifactId>
<version>1.0.0-M4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-plugin-api</artifactId>
<version>3.7.4</version>
</dependency>
</dependencies>
...
</project>
Notes on the sonar plugin configuration:
-
'basePlugin' must be set to 'jQAssistant' to indicate which plugin shall be extended.
-
The implementation of 'pluginClass' (i.e. 'MyJQAssistantExtension') returns an empty list of extensions. It must be specified as this is required by SonarQube.
package com.buschmais.jqassistant.examples.sonar.ruleextension;
import java.util.Collections;
import java.util.List;
import org.sonar.api.SonarPlugin;
/**
* Defines a sonar extension.
*/
public class MyJQAssistantExtension extends SonarPlugin {
@SuppressWarnings("rawtypes")
@Override
public List getExtensions() {
return Collections.emptyList();
}
}
The rules itself are packaged according to the following structure:
<v1:jqassistant-plugin xmlns:v1="http://www.buschmais.com/jqassistant/core/plugin/schema/v1.0">
<description>An example plugin.</description>
<rules>
<resource>example.xml</resource>
</rules>
</v1:jqassistant-plugin>
-
example.xml specifies the path to a jQAssistant rule file relative to /META-INF/jqassistant-rules
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/analysis/rules/schema/v1.0">
<!-- Don't take these constraints serious - they are just here to illustrate integration with SonarQube -->
<constraint id="example:AllClassesMustBeNamedFoo">
<description>All classes must be named 'Foo'.</description>
<cypher><![CDATA[
MATCH
(type:Class)
WHERE NOT
type.name = 'Foo'
RETURN
type AS InvalidClass
]]></cypher>
</constraint>
<constraint id="example:FieldsMustBeReadOnly" severity="critical">
<description>All declared fields must be read-only (i.e. not be written).</description>
<cypher><![CDATA[
MATCH
(m:Method)-[w:WRITES]->(f:Field)
RETURN
w AS WriteAccess
]]></cypher>
</constraint>
</jqa:jqassistant-rules>
The created JAR archive and the jQAssistant must be copied to the folder 'extensions/plugins' of the SonarQube installation. The defined rules will be visible in the repository "jQAssistant".
Plugins
This section provides detailed descriptions of all distributed plugins.
jqassistant.plugin.cdi
Provides rules for CDI.
Scanner for beans.xml files
Imports beans descriptors from META-INF/beans.xml or WEB-INF/beans.xml files.
:File:Cdi:Beans
Represents a beans.xml file.
Name | Description |
---|---|
fileName |
The file name |
version |
The version of the CDI specification this descriptor represents, e.g. '1.0' |
beanDiscoveryMode |
The bean discovery mode, i.e. "all", "annotated" or "none" |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
HAS_INTERCEPTOR |
0..n |
References an interceptor type which is activated |
|
HAS_DECORATOR |
0..n |
References a decorator type which is activated |
|
HAS_ALTERNATIVE |
0..n |
References an alternative type (class or stereotype annotation) which is activated |
cdi.xml
Concepts
cdi:Alternative
Labels all types annotated by @javax.enterprise.inject.Alternative with "Cdi" and "Alternative".
MATCH
(alternative:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(alternativeType:Type)
WHERE
alternativeType.fqn="javax.enterprise.inject.Alternative"
SET
alternative:Cdi:Alternative
RETURN
alternative AS Alternative
cdi:Any
Labels all elements annotated by "javax.enterprise.inject.Any" with "Cdi" and "Any".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(anyType:Type)
WHERE
anyType.fqn = "javax.enterprise.inject.Any"
SET
e:Cdi:Any
RETURN
e AS Any
cdi:ApplicationScoped
Labels all beans, fields or methods annotated by @javax.enterprise.context.ApplicationScoped with "Cdi" and "ApplicationScoped".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(scopeType:Type)
WHERE
(e:Type or e:Method or e:Field)
and scopeType.fqn="javax.enterprise.context.ApplicationScoped"
SET
e:Cdi:ApplicationScoped
RETURN
e AS ApplicationScopedElement
cdi:ConversationScoped
Labels all beans, fields or methods annotated by @javax.enterprise.context.ConversationScoped with "Cdi" and "ConversationScoped".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(scopeType:Type)
WHERE
(e:Type or e:Method or e:Field)
and scopeType.fqn="javax.enterprise.context.ConversationScoped"
SET
e:Cdi:ConversationScoped
RETURN
e AS ConversationScopedElement
cdi:Decorator
Requires concepts:
Labels all types annotated by @javax.decorator.Decorator with "Cdi" and "Decorator".
MATCH
(decorator:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(decoratorType:Type)
WHERE
decoratorType.fqn="javax.decorator.Decorator"
SET
decorator:Cdi:Decorator
RETURN
decorator AS Decorator
cdi:Default
Labels all elements annotated by "javax.enterprise.inject.Default" with "Cdi" and "Default".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(defaultType:Type)
WHERE
defaultType.fqn = "javax.enterprise.inject.Default"
SET
e:Cdi:Default
RETURN
e AS Default
cdi:Delegate
Labels all fields annotated annotated by @javax.decorator.Delegate with "Cdi" and "Delegate".
MATCH
(delegate:Field)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(delegateType:Type)
WHERE
delegateType.fqn="javax.decorator.Delegate"
SET
delegate:Cdi:Delegate
RETURN
delegate AS Delegate
cdi:Dependent
Labels all beans, fields or methods annotated by @javax.enterprise.context.Dependent with "Cdi" and "Dependent".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(scopeType:Type)
WHERE
(e:Type or e:Method or e:Field)
and scopeType.fqn="javax.enterprise.context.Dependent"
SET
e:Cdi:Dependent
RETURN
e AS DependentElement
cdi:Disposes
Creates a relation DISPOSES between a parameter and its type if the parameter is annotated by @javax.enterprise.inject.Disposes.
MATCH
(:Type)-[:DECLARES]->(disposeMethod:Method)-[:HAS]->(parameter:Parameter),
(parameter)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(disposesType:Type),
(parameter)-[:OF_TYPE]->(type)
WHERE
disposesType.fqn="javax.enterprise.inject.Disposes"
CREATE UNIQUE
(parameter)-[:DISPOSES]->(type)
RETURN
disposeMethod AS DisposeMethod
cdi:EventConsumer
Requires concepts:
Labels all beans declaring method that has parameter of type "javax.enterprise.event.Observes" with "Cdi" and "EventConsumer".
MATCH
(a:Type)-[:DECLARES]->(member:Method)-[:HAS]->(param:Parameter),
(param)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(injectType:Type)
WHERE
injectType.fqn = "javax.enterprise.event.Observes"
SET
a:Cdi:EventConsumer
RETURN
DISTINCT a.fqn AS cdiEventConsumer
cdi:EventProducer
Requires concepts:
Labels all beans declaring "InjectionPoint" of type "javax.enterprise.event.Event" with "Cdi" and "EventProducer".
MATCH
(a:Type)-[:DECLARES]->(member:Field:Cdi:InjectionPoint),
(member)-[:OF_TYPE]->(injectType:Type)
WHERE
injectType.fqn = "javax.enterprise.event.Event"
SET
a:Cdi:EventProducer
RETURN
DISTINCT a.fqn AS cdiEventProducers
cdi:InjectionPoint
Labels all fields or methods annotated by @javax.inject.Inject with "Cdi" and "InjectionPoint".
MATCH
(:Type)-[:DECLARES]->(member),
(member)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(injectType:Type)
WHERE
(member:Field or member:Method)
and injectType.fqn="javax.inject.Inject"
SET
member:Cdi:InjectionPoint
RETURN
member AS InjectionPoint
cdi:Named
Labels all types or methods annotated by "javax.inject.Named" with "Cdi" and "Named".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(namedType:Type)
WHERE
namedType.fqn = "javax.inject.Named"
SET
e:Cdi:Named
RETURN
e AS Named
cdi:New
Labels all elements annotated by "javax.enterprise.inject.New" with "Cdi" and "New".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(newType:Type)
WHERE
newType.fqn = "javax.enterprise.inject.New"
SET
e:Cdi:New
RETURN
e AS New
cdi:Produces
Creates a relation PRODUCES between a field and its type or a method and its return type if the parameter is annotated by @javax.enterprise.inject.Disposes.
MATCH
(:Type)-[:DECLARES]->(member),
(member)-[:OF_TYPE|RETURNS]->(type),
(member)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(producesType:Type)
WHERE
(member:Field or member:Method)
and producesType.fqn="javax.enterprise.inject.Produces"
CREATE UNIQUE
(member)-[:PRODUCES]->(type)
RETURN
member AS Producer
cdi:Qualifier
Labels all annotation types annotated by @javax.inject.Qualifier with "Cdi" and "Qualifier" and adds the labels "Cdi" and "Nonbinding" to all non-binding annotation values (i.e. which are annotated by @javax.enterprise.util.Nonbinding).
MATCH
(qualifier:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(qualifierType:Type)
WHERE
qualifierType.fqn = "javax.inject.Qualifier"
SET
qualifier:Cdi:Qualifier
WITH
qualifier
MATCH
(qualifier)-[:DECLARES]->(attribute:Method),
(attribute)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(nonbindingType:Type)
WHERE
nonbindingType.fqn = "javax.enterprise.util.Nonbinding"
SET
attribute:Cdi:Nonbinding
RETURN
distinct qualifier AS Qualifier
cdi:RequestScoped
Labels all beans, fields or methods annotated by @javax.enterprise.context.RequestScoped with "Cdi" and "RequestScoped".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(scopeType:Type)
WHERE
(e:Type or e:Method or e:Field)
and scopeType.fqn="javax.enterprise.context.RequestScoped"
SET
e:Cdi:RequestScoped
RETURN
e AS RequestScopedElement
cdi:SessionScoped
Labels all beans, fields or methods annotated by @javax.enterprise.context.SessionScoped with "Cdi" and "SessionScoped".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(scopeType:Type)
WHERE
(e:Type or e:Method or e:Field)
and scopeType.fqn="javax.enterprise.context.SessionScoped"
SET
e:Cdi:SessionScoped
RETURN
e AS SessionScopedElement
cdi:SingletonScoped
Labels all beans annotated by @javax.inject.Singleton with "Cdi" and "SingletonScoped".
MATCH
(t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn = "javax.inject.Singleton"
SET
t:Cdi:SingletonScoped
RETURN
t AS cdiSingleton
cdi:Specializes
Labels all types and methods annotated by @javax.enterprise.inject.Specializes with "Cdi" and "Specializes".
MATCH
(specializes)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(specializesType:Type)
WHERE
(specializes:Type or specializes:Method)
and specializesType.fqn="javax.enterprise.inject.Specializes"
SET
specializes:Cdi:Specializes
RETURN
specializes AS Specialization
cdi:Stereotype
Labels all annotation types annotated by @javax.enterprise.inject.Stereotype with "Cdi" and "Stereotype".
MATCH
(stereotype:Type:Annotation)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(stereotypeType:Type)
WHERE
stereotypeType.fqn="javax.enterprise.inject.Stereotype"
SET
stereotype:Cdi:Stereotype
RETURN
stereotype AS Stereotype
Constraints
cdi:BeansMustNotUseFieldInjection
Requires concepts:
CDI beans shall not use field injection (constructor and setter injections are fine.).
MATCH
(a:Type)-[:DECLARES]->(member:Field:Cdi:InjectionPoint)
RETURN
DISTINCT a.fqn AS invalidBean
cdi:BeansMustUseConstructorInjection
Requires concepts:
All CDI beans must use constructor injection.
MATCH
(a:Type)-[:DECLARES]->(member:Cdi:InjectionPoint)
WHERE
NOT member:Constructor
RETURN
DISTINCT a.fqn AS invalidBean
decorator.xml
Concepts
decorator:Decorator
Requires concepts:
Labels all types annotated by @javax.decorator.Decorator with "Decorator".
MATCH
(decorator:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(decoratorType:Type)
WHERE
decoratorType.fqn="javax.decorator.Decorator"
SET
decorator:Decorator
RETURN
decorator AS Decorator
decorator:Delegate
Labels all fields annotated annotated by @javax.decorator.Delegate with "Decorator" and "Delegate".
MATCH
(delegate:Field)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(delegateType:Type)
WHERE
delegateType.fqn="javax.decorator.Delegate"
SET
delegate:Decorator:Delegate
RETURN
delegate AS Delegate
interceptor.xml
Concepts
interceptor:Binding
Labels all annotations annotated by "javax.interceptor.InterceptorBinding" with "Interceptor" and "Binding".
MATCH
(binding:Type:Annotation)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(bindingType:Type)
WHERE
bindingType.fqn = "javax.interceptor.InterceptorBinding"
SET
binding:Interceptor:Binding
RETURN
binding AS InterceptorBinding
interceptor:Interceptor
Labels all classes annotated by "javax.interceptor.Interceptor" with "Interceptor".
MATCH
(interceptor:Type:Class)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(interceptorType:Type)
WHERE
interceptorType.fqn = "javax.interceptor.Interceptor"
SET
interceptor:Interceptor
RETURN
interceptor AS Interceptor
jqassistant.plugin.ejb3
Provides rules for EJB3.
ejb3.xml
Concepts
ejb3:Local
Labels all types annotated with @javax.ejb.Local with "Ejb" and "Local".
MATCH (t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE a.fqn="javax.ejb.Local"
SET t:Ejb:Local
RETURN t AS LocalBean
ejb3:MessageDrivenBean
Labels all types annotated with @javax.ejb.MessageDriven with "Ejb" and "MessageDriven".
MATCH (t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE a.fqn="javax.ejb.MessageDriven"
SET t:Ejb:MessageDriven
RETURN t AS MessageDrivenBean
ejb3:Remote
Labels all types annotated with @javax.ejb.Remote with "Ejb" and "Remote".
MATCH (t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE a.fqn="javax.ejb.Remote"
SET t:Ejb:Remote
RETURN t AS RemoteBean
ejb3:SingletonBean
Labels all classes annotated with @javax.ejb.Singleton with "Ejb" and "Singleton".
MATCH (t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE a.fqn="javax.ejb.Singleton"
SET t:Ejb:Singleton
RETURN t AS SingletonEjb
ejb3:StatefulSessionBean
Labels all types annotated with @javax.ejb.Stateful with "Ejb" and "Stateful".
MATCH (t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE a.fqn="javax.ejb.Stateful"
SET t:Ejb:Stateful
RETURN t AS StatefulEjb
ejb3:StatelessSessionBean
Labels all types annotated with @javax.ejb.Stateless with "Ejb" and "Stateless".
MATCH (t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE a.fqn="javax.ejb.Stateless"
SET t:Ejb:Stateless
RETURN t AS StatelessEjb
jqassistant.plugin.jaxrs
Provides rules for JAX-RS.
jaxrs-resource.xml
Concepts
jaxrs:DeleteResourceMethod
Requires concepts:
Finds resource methods annotated with "@javax.ws.rs.DELETE" and labels them with 'DeleteResourceMethod'.
MATCH
(m:JaxRS:ResourceMethod)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:JaxRS:RequestMethodDesignator)
WHERE
a.fqn = "javax.ws.rs.DELETE"
SET
m:DeleteResourceMethod
RETURN
m AS DeleteMethod
jaxrs:GetResourceMethod
Requires concepts:
Finds resource methods annotated with "@javax.ws.rs.GET" and labels them with 'GetResourceMethod'.
MATCH
(m:JaxRS:ResourceMethod)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:JaxRS:RequestMethodDesignator)
WHERE
a.fqn = "javax.ws.rs.GET"
SET
m:GetResourceMethod
RETURN
m AS GetMethod
jaxrs:HeadResourceMethod
Requires concepts:
Finds resource methods annotated with "@javax.ws.rs.HEAD" and labels them with 'HeadResourceMethod'.
MATCH
(m:JaxRS:ResourceMethod)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:JaxRS:RequestMethodDesignator)
WHERE
a.fqn = "javax.ws.rs.HEAD"
SET
m:HeadResourceMethod
RETURN
m AS HeadMethod
jaxrs:OptionsResourceMethod
Requires concepts:
Finds resource methods annotated with "@javax.ws.rs.OPTIONS" and labels them with 'OptionsResourceMethod'.
MATCH
(m:JaxRS:ResourceMethod)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:JaxRS:RequestMethodDesignator)
WHERE
a.fqn = "javax.ws.rs.OPTIONS"
SET
m:OptionsResourceMethod
RETURN
m AS OptionsMethod
jaxrs:PostResourceMethod
Requires concepts:
Finds resource methods annotated with "@javax.ws.rs.POST" and labels them with 'PostResourceMethod'.
MATCH
(m:JaxRS:ResourceMethod)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:JaxRS:RequestMethodDesignator)
WHERE
a.fqn = "javax.ws.rs.POST"
SET
m:PostResourceMethod
RETURN
m AS PostMethod
jaxrs:PutResourceMethod
Requires concepts:
Finds resource methods annotated with "@javax.ws.rs.PUT" and labels them with 'PutResourceMethod'.
MATCH
(m:JaxRS:ResourceMethod)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:JaxRS:RequestMethodDesignator)
WHERE
a.fqn = "javax.ws.rs.PUT"
SET
m:PutResourceMethod
RETURN
m AS PutMethod
jaxrs:RequestMethodDesignator
Finds request method designator annotations (i.e. @GET, @POST, @PUT, @DELETE, @HEAD, @OPTIONS) and labels them with 'JaxRS' and 'RequestMethodDesignator'
MATCH
(a:Type)
WHERE
a.fqn IN ["javax.ws.rs.GET", "javax.ws.rs.POST", "javax.ws.rs.PUT", "javax.ws.rs.DELETE", "javax.ws.rs.HEAD", "javax.ws.rs.OPTIONS"]
SET
a:JaxRS:RequestMethodDesignator
RETURN
a AS RequestMethodDesignator
jaxrs:Resource
Requires concepts:
Finds classes or interfaces with atleast one method annotated with "@javax.ws.rs.Path" or with request method designator (i.e. @GET, @POST, @PUT, @DELETE, @HEAD, @OPTIONS) and labels them with 'JaxRS' and 'Resource'.
MATCH
(t:Type)-[:DECLARES]->(m:Method)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:Type)
WHERE
(a:JaxRS:RequestMethodDesignator OR a.fqn="javax.ws.rs.Path")
AND
(t:Class OR t:Interface)
SET
t:JaxRS:Resource
RETURN
DISTINCT t AS RestResource
jaxrs:ResourceMethod
Requires concepts:
Finds methods that belong to resource class or interface and annotated with request method designator (i.e. @GET, @POST, @PUT, @DELETE, @HEAD, @OPTIONS) and labels them with 'JaxRS' and 'ResourceMethod'.
MATCH
(c:JaxRS:Resource)-[:DECLARES]->(m:Method)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:JaxRS:RequestMethodDesignator)
SET
m:JaxRS:ResourceMethod
RETURN
m AS ResourceMethod
jaxrs:SubResourceLocator
Requires concepts:
Finds classes or interfaces with method annotated with "@javax.ws.rs.Path" but no request method designator and no entity parameters and labels the methods with 'JaxRS' and 'SubResourceLocator'.
MATCH
(t:Type)-[:DECLARES]->(m:Method)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn = "javax.ws.rs.Path"
AND
(t:Class OR t:Interface)
AND
NOT (m)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(:JaxRS:RequestMethodDesignator)
AND
(m)-[:HAS]->(:Parameter)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(:Type)
SET
m:JaxRS:SubResourceLocator
RETURN
DISTINCT m AS RestSubResourceLocator
jaxrs-provider.xml
Concepts
jaxrs:ContextProvider
Finds classes that implement "javax.ws.rs.ext.ContextResolver" and labels them with 'JaxRS' and 'ContextProvider'
MATCH
(c:Type:Class)-[:IMPLEMENTS]->(a:Type)
WHERE
a.fqn = "javax.ws.rs.ext.ContextResolver"
SET
c:JaxRS:ContextProvider
RETURN
DISTINCT c AS ContextProvider
jaxrs:EntityProvider
Finds classes that implement "javax.ws.rs.ext.MessageBodyReader" or "javax.ws.rs.ext.MessageBodyWriter" and labels them with 'JaxRS' and 'EntityProvider'
MATCH
(c:Type:Class)-[:IMPLEMENTS]->(a:Type)
WHERE
a.fqn IN ["javax.ws.rs.ext.MessageBodyReader", "javax.ws.rs.ext.MessageBodyWriter"]
SET
c:JaxRS:EntityProvider
RETURN
DISTINCT c AS EntityProvider
jaxrs:ExceptionMappingProvider
Finds classes that implement "javax.ws.rs.ext.ExceptionMapper" and labels them with 'JaxRS' and 'ExceptionMappingProvider'
MATCH
(c:Type:Class)-[:IMPLEMENTS]->(a:Type)
WHERE
a.fqn = "javax.ws.rs.ext.ExceptionMapper"
SET
c:JaxRS:ExceptionMappingProvider
RETURN
DISTINCT c AS ExceptionMappingProvider
jqassistant.plugin.java
Provides scanner for Java elements (e.g. packages, classes, manifest and property files) and rules for common language concepts (e.g. Deprecation), dependencies and metrics.
Package Scanner Class Scanner Manifest File Scanner Property File Scanner Service Loader File Scanner
Package Scanner
Imports Java packages.
:Package
A Java package, i.e. a directory containing '.class' files or other directories.
Name | Description |
---|---|
fqn |
Fully qualified name, e.g. 'java.lang' |
name |
The local name, e.g. 'lang' |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
CONTAINS |
0..n |
References a type located in the package |
|
CONTAINS |
0..n |
References a package located in the package |
Class Scanner
Imports Java classes, i.e. all scanned files having a '.class' suffix. Nodes with following labels will be created:
NOTE Some of these labels may be further qualified with other labels, see the description below.
NOTE The full set of information is only available for class files which have actually been scanned. Types which are only referenced (i.e. from external libraries not included in the scan) are represented by ':TYPE' nodes with a property 'fqn' and 'DECLARES' relations to their members. These are ':FIELD' or ':METHOD' labeled nodes which only provide the property 'signature'.
:Type
A Java type. Can be qualified by either ':Class', ':Interface', ':Enum' or ':Annotation'
Name | Description |
---|---|
fqn |
Fully qualified name, e.g. 'java.lang.Object' |
name |
The local name, e.g. 'Object' |
visibility |
optional, the visibility of the type, can be either 'public', 'protected', 'default' or 'private' |
abstract |
optional, 'true' indicates that the type is abstract, e.g. 'public abstract class …' |
static |
optional, 'true' indicates that the type has the static modifier, e.g. 'private static class …' |
final |
optional, 'true' indicates that the type is final, e.g. 'public final class…' |
synthetic |
optional, 'true' indicates that the type is synthetic, i.e. it has been generated |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
DECLARES |
0..n |
Declares an inner type of the type |
|
DECLARES |
0..n |
Declares a method of the type |
|
DECLARES |
0..n |
Declares a field of the type |
|
EXTENDS |
0..1 |
References a type this type extends from |
|
IMPLEMENTS |
0..1 |
References an "Interface" type this type implements |
|
ANNOTATED_BY |
0..n |
References an annotation which is present on the type |
|
DEPENDS_ON |
0..n |
References a type which this type depends on according to its signature (e.g. due to generic type parameters) |
NOTE Types which are referenced by scanned classes but have not been scanned themselves will only provide the property "fqn" and the relation "DECLARES".
NOTE Inheritance between interfaces (i.e. "public interface A extends B { … }") is represented using IMPLEMENTS relations, i.e. queries must use (a:Type:Interface)-[:IMPLEMENTS]→(b:Type:Interface) for pattern matching.
:Type:Class
Qualifies a Java type as class.
:Type:Interface
Qualifies a Java type node as interface.
:Type:Enum
Qualifies a Java type as enumeration.
:Type:Annotation
Qualifies a Java type as annotation.
:Field
A field declared in a Java type.
Name | Description |
---|---|
name |
The field name, e.g. 'id' |
signature |
The raw signature of the field, e.g. 'int id', 'java.lang.String toString()' |
visibility |
optional, The visibility of the field, can be either 'public', 'protected', 'default' or 'private' |
static |
optional, 'true' indicates that the field has the static modifier, e.g. 'static int id;' |
final |
optional, 'true' indicates that the field is final, e.g. 'final int id;' |
transient |
optional, 'true' indicates that the field is transient, e.g. 'transient int id;' |
volatile |
optional, 'true' indicates that the field is volatile, e.g. 'volatile int id;' |
synthetic |
optional, 'true' indicates that the field is synthetic, i.e. it has been generated |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
OF_TYPE |
1 |
References the type of the field |
|
ANNOTATED_BY |
0..n |
References an annotation which is present on the field |
|
DEPENDS_ON |
0..n |
References a type which this field depends on according to its signature (e.g. generic type parameters) |
NOTE Fields which are referenced by scanned classes but have not been scanned themselves will only provide the property "signature".
:Method
A method declared in a Java type.
Name | Description |
---|---|
name |
The method name, e.g. 'getId' |
signature |
The raw signature of the method, e.g. 'int getId()', 'java.lang.String concat(java.lang.String,java.lang.String)' |
visibility |
optional, The visibility of the method, can be either 'public', 'protected', 'default' or 'private' |
static |
optional, 'true' indicates that the method has the static modifier, e.g. 'static int getId();' |
final |
optional, 'true' indicates that the method is final, e.g. 'final int getId();' |
native |
optional, 'true' indicates that the method is native, e.g. 'native int getId();' |
synthetic |
optional, 'true' indicates that the method is synthetic, i.e. it has been generated |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
HAS |
0..n |
References a parameter of the method |
|
THROWS |
0..n |
References the exception type thrown by the method |
|
RETURNS |
0..n |
References the return type of the method |
|
ANNOTATED_BY |
0..n |
References an annotation which is present on the method declaration |
|
READS |
0..n |
References a field which is read by the method |
|
WRITES |
0..n |
References a field which is written by the method |
|
INVOKES |
0..n |
References a method which is invoked by the method |
|
DEPENDS_ON |
0..n |
References a type which this method depends on (e.g. generic type parameters, dependencies from the method body) |
NOTE Methods which are referenced by scanned classes but have not been scanned themselves will only provide the property "signature".
:Method:Constructor
Qualifies a method as constructor.
:Parameter
A method parameter.
Name | Description |
---|---|
index |
The index of the parameter according to the method signature (starting with 0) |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
OF_TYPE |
1 |
References the type of the parameter |
|
ANNOTATED_BY |
0..n |
References an annotation which is present on the parameter |
|
DEPENDS_ON |
0..n |
References a type which this parameter depends on according to its signature (e.g. generic type parameters) |
:Value
A value, can be qualified by either ':Primitive', ':Annotation', ':Class', ':Enum' or ':Array'.
Name | Description |
---|---|
name |
The method name, e.g. 'value' |
:Value:Primitive
A primitive value.
Name | Description |
---|---|
value |
The value |
:Value:Annotation
Represents a annotation on a Java element, e.g. '@Entity public class …'
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
OF_TYPE |
1 |
References the type of the annotation |
|
HAS |
0..n |
References an attribute of the annotation, e.g. '@Entity(name="MyEntity")' |
:Value:Class
Represents a class instance, e.g. as specified by annotation attribute.
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
IS |
1 |
References the type |
:Value:Enum
Represents an enum value.
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
IS |
1 |
References the field representing the enumeration value |
:Value:Array
Represents an array value, i.e. a node referencing value nodes.
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
CONTAINS |
0..n |
References a value contained in the array |
Manifest File Scanner
Imports manifest descriptors from META-INF/MANIFEST.MF files.
:File:Manifest
A MANIFEST.MF file containing sections.
Name | Description |
---|---|
fileName |
The file name |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
DECLARES |
0..n |
References a manifest section |
:ManifestSection
A manifest section.
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
HAS |
0..n |
References a manifest entry in the section |
:Value:ManifestEntry
A manifest entry.
Name | Description |
---|---|
name |
The name of the entry, e.g. 'Main-Class' |
value |
The value of the entry, e.g. 'com.buschmais.jqassistant.scm.cli.Main' |
Property File Scanner
Imports property files, i.e. all files having a suffix '.properties'.
:File:Properties
A property file containing key/value pairs.
Name | Description |
---|---|
fileName |
The file name |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
HAS |
0..n |
References a property value |
:Value:Property
A key value/pair.
Name | Description |
---|---|
name |
The name of the property |
value |
The value of the property |
Service Loader File Scanner
Imports service loader descriptors from "META-INF/services" directories.
:File:ServiceLoader
A file containing the implementation class names for a service interface
Name | Description |
---|---|
fileName |
The file name |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
OF_TYPE |
1 |
The type representing the service interface |
|
CONTAINS |
0..n |
References a type which implements the service interface |
dependency.xml
Concepts
dependency:Annotation
Creates a DEPENDS_ON relationship between an annotated element and the types of the annotation and its values.
MATCH
(e)-[:ANNOTATED_BY]->(a:Annotation)-[:OF_TYPE]->(at:Type)
WHERE
e:Type
or e:Method
or e:Parameter
or e:Field
CREATE UNIQUE
(e)-[:DEPENDS_ON]->(at)
WITH
e, a
MATCH
(a)-[:HAS|IS|CONTAINS|OF_TYPE*0..]->(vt:Type)
CREATE UNIQUE
(e)-[:DEPENDS_ON]->(vt)
RETURN
e AS AnnotatedType, COUNT(DISTINCT vt) as Elements
dependency:Artifact
Requires concepts:
Creates a DEPENDS_ON relationship between artifacts if there are type dependencies between them.
MATCH
(a1:Artifact)-[:CONTAINS]->(t1:Type)-[:DEPENDS_ON]->(t2:Type)<-[:CONTAINS]-(a2:Artifact)
WHERE
a1<>a2
CREATE UNIQUE
(a1)-[:DEPENDS_ON]->(a2)
RETURN
a1 AS Artifact, COLLECT(DISTINCT a2.name) AS Dependencies
dependency:FieldAccess
Creates a DEPENDS_ON relationship between a method and all fields types it reads and writes.
MATCH
(m:Method)-[:READS|WRITES]->(target:Field),
(dt:Type)-[:DECLARES]->(target)
CREATE UNIQUE
(m)-[:DEPENDS_ON]->(dt)
WITH
m, target
MATCH
(target)-[:OF_TYPE|DEPENDS_ON]->(ft)
CREATE UNIQUE
(m)-[:DEPENDS_ON]->(ft)
RETURN
m AS method, COUNT(DISTINCT ft) AS FieldAccesseCount
dependency:MethodInvocation
Requires concepts:
Creates a DEPENDS_ON relationship between a method and the dependencies of the method signature it invokes.
MATCH
(m:Method)-[:INVOKES]->(target:Method),
(dt:Type)-[:DECLARES]->(target)
CREATE UNIQUE
(m)-[:DEPENDS_ON]->(dt)
WITH
m, target
MATCH
(target)-[:HAS]->(:Parameter)-[:DEPENDS_ON]->(pt)
CREATE UNIQUE
(m)-[:DEPENDS_ON]->(pt)
WITH
m, target
MATCH
(target)-[:RETURNS]->(rt)
CREATE UNIQUE
(m)-[:DEPENDS_ON]->(rt)
RETURN
m AS Method, COUNT(DISTINCT rt) AS MethodInvocations
dependency:MethodParameter
Requires concepts:
Creates a DEPENDS_ON relationship between a method and the types of its parameters.
MATCH
(m:Method)-[:HAS]->(p:Parameter)-[:OF_TYPE|DEPENDS_ON]->(t:Type)
CREATE UNIQUE
(m)-[:DEPENDS_ON]->(t)
RETURN
m AS Method, COUNT(DISTINCT t) as Parameters
dependency:Package
Requires concepts:
Creates a DEPENDS_ON relationship between a packages if there are type dependencies between them.
MATCH
(p1:Package)-[:CONTAINS]->(t1:Type)-[:DEPENDS_ON]->(t2:Type)<-[:CONTAINS]-(p2:Package)
WHERE
p1<>p2
CREATE UNIQUE
(p1)-[:DEPENDS_ON]->(p2)
RETURN
p1 AS package, COUNT(DISTINCT p2) AS PackageDependencyCount
dependency:Type
Requires concepts:
Creates a DEPENDS_ON relationship between a type and all types its signature and body depends on.
MATCH
(t1:Type)-[:EXTENDS|IMPLEMENTS]->(t2:Type)
WHERE
t1<>t2
CREATE UNIQUE
(t1)-[:DEPENDS_ON]->(t2)
RETURN
t1 AS Type, COUNT(DISTINCT t2) AS DependencyCount
dependency:TypeBody
Requires concepts:
Creates a DEPENDS_ON relationship between a type and all types its body (i.e. methods and fields) depends on.
MATCH
(t1:Type)-[:DECLARES*0..1]->(fieldOrMethod)-[:OF_TYPE|RETURNS|DEPENDS_ON|THROWS]->(t2:Type)
WHERE
t1<>t2
and (fieldOrMethod:Field OR fieldOrMethod:Method)
CREATE UNIQUE
(t1)-[:DEPENDS_ON]->(t2)
RETURN
t1 AS Type, COUNT(DISTINCT t2) AS DependencyCount
Constraints
dependency:ArtifactCycles
Requires concepts:
There must be no cyclic artifact dependencies.
MATCH
(a1:Artifact)-[:DEPENDS_ON]->(a2:Artifact),
path=shortestPath((a2)-[:DEPENDS_ON*]->(a1))
WHERE
a1<>a2
RETURN
a1 AS Artifact, EXTRACT(a IN nodes(path) | a.fqn) AS Cycle
ORDER BY
Artifact.fqn
dependency:PackageCycles
Requires concepts:
There must be no cyclic package dependencies.
MATCH
(p1:Package)-[:DEPENDS_ON]->(p2:Package),
path=shortestPath((p2)-[:DEPENDS_ON*]->(p1))
WHERE
p1<>p2
RETURN
p1 AS Package, EXTRACT(p IN nodes(path) | p.fqn) AS Cycle
ORDER BY
Package.fqn
dependency:TypeCycles
Requires concepts:
There must be no cyclic type dependencies.
MATCH
(p1:Package)-[:DEPENDS_ON*0..1]->(p2:Package),
shortestPath((p2)-[:DEPENDS_ON*]->(p1))
WITH
p1, p2
MATCH
(p1)-[:CONTAINS]->(t1:Type),
(p2)-[:CONTAINS]->(t2:Type),
(t1)-[:DEPENDS_ON]->(t2),
path=shortestPath((t2)-[:DEPENDS_ON*]->(t1))
WHERE
NOT (
t1 = t2 OR (t1)-[:DECLARES]-(t2)
)
RETURN
LENGTH(path) AS Length, EXTRACT(p IN nodes (path) | p.fqn) AS Cycle
java.xml
Concepts
java:AnonymousInnerType
Requires concepts:
Sets a label "Anonymous" on anonymous inner types, i.e. types without a name.
MATCH
(innerType:Inner:Type)
WHERE
innerType.name =~ ".*\\$[0-9]*"
SET
innerType:Anonymous
RETURN
innerType AS AnonymousInnerType
java:Deprecated
Labels all nodes representing deprecated elements (types, fields, methods, packages or parameters) with "Deprecated".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(dt:Type)
WHERE
dt.fqn='java.lang.Deprecated'
SET
e:Deprecated
RETURN
e AS DeprecatedElement
java:Error
Labels types deriving from java.lang.Error as "Error".
MATCH
(throwable)-[:EXTENDS*]->(t:Type)
WHERE
t.fqn = 'java.lang.Error'
SET
throwable:Error
RETURN
throwable AS Error
java:Exception
Labels types deriving from java.lang.Exception as "Exception".
MATCH
(exception)-[:EXTENDS*]->(t:Type)
WHERE
t.fqn = 'java.lang.Exception'
SET
exception:Exception
RETURN
exception AS Exception
java:InnerType
Sets a label "Inner" on inner types.
MATCH
(:Type)-[:DECLARES]->(innerType:Type)
SET
innerType:Inner
RETURN
innerType AS InnerType
java:InvokesOverriddenMethod
Requires concepts:
Propagates INVOKES relationships to methods which implement or override the invoked method.
MATCH
(method:Method)-[:INVOKES]->(invokedMethod:Method),
(overridingMethod:Method)-[:OVERRIDES]->(invokedMethod)
CREATE UNIQUE
(method)-[r:INVOKES]->(overridingMethod)
RETURN r AS OverridingInvocation
java:MethodOverloads
Creates a relationship OVERLOADS between two "Method" labeled nodes if one method overloads another one from the same type (i.e. the methods have the same name but not the same signature).
MATCH
(type:Type)-[:DECLARES]->(method:Method),
(type)-[:DECLARES]->(otherMethod:Method)
WHERE
method <> otherMethod
AND method.name = otherMethod.name
AND method.signature <> otherMethod.signature
CREATE UNIQUE
(method)-[:OVERLOADS]->(otherMethod)
RETURN method AS OverloadedMethod, type AS DeclaringType
java:MethodOverrides
Requires concepts:
Creates a relationship OVERRIDES between two "Method" labeled nodes if one method overrides another one from a super type (i.e. the methods have the same signature).
MATCH
(type:Type)-[:DECLARES]->(method:Method),
(superType:Type)-[:DECLARES]->(otherMethod:Method),
(superType)-[:ASSIGNABLE_FROM]->(type)
WHERE
method.name = otherMethod.name
AND method.signature = otherMethod.signature
AND method.visibility <> 'private'
CREATE UNIQUE
(method)-[:OVERRIDES]->(otherMethod)
RETURN method AS OverriddenMethod, type AS DeclaringType, superType AS SuperType
java:RuntimeException
Labels types deriving from java.lang.RuntimeException as "RuntimeException".
MATCH
(runtimeException)-[:EXTENDS*]->(t:Type)
WHERE
t.fqn = 'java.lang.RuntimeException'
SET
runtimeException:RuntimeException
RETURN
runtimeException AS RuntimeException
java:Throwable
Labels types deriving from java.lang.Throwable as "Throwable".
MATCH
(throwable)-[:EXTENDS*]->(t:Type)
WHERE
t.fqn = 'java.lang.Throwable'
SET
throwable:Throwable
RETURN
throwable AS Throwable
java:TypeAssignableFrom
Creates a relationship ASSIGNABLE_FROM between two "Type" labeled nodes if one type is assignable from the other (i.e. a super class or interface).
MATCH
(type:Type)
CREATE UNIQUE
(type)-[:ASSIGNABLE_FROM]->(type)
WITH
type
MATCH
(type)-[:IMPLEMENTS|EXTENDS*]->(superType:Type)
CREATE UNIQUE
(superType)-[:ASSIGNABLE_FROM]->(type)
RETURN type AS AssignableType, superType AS AssignableFrom
metric.xml
Concepts
metric:Top10FieldsPerType
Requires concepts:
Returns the top 10 types regarding to the number of declared fields.
MATCH
(t:Type:File)-[:DECLARES]->(f:Field)
RETURN
t.fqn as Type, COUNT(f) as FieldCount
ORDER BY
FieldCount DESC
LIMIT 10
metric:Top10MethodsPerType
Requires concepts:
Returns the top 10 types regarding to the number of declared methods (including constructors).
MATCH
(t:Type:File)-[:DECLARES]->(m:Method)
RETURN
t.fqn as Type, COUNT(m) as MethodCount
ORDER BY
MethodCount DESC
LIMIT 10
metric:Top10TypeFanIn
Requires concepts:
Returns the top 10 types regarding the number of other types depending on them.
MATCH
(t:Type:File)<-[:DEPENDS_ON]-(dependent:Type)
RETURN
t.fqn as Type, COUNT(dependent) as Dependents
ORDER BY
Dependents DESC
LIMIT 10
metric:Top10TypeFanOut
Requires concepts:
Returns the top 10 types regarding the number of other types they depend on.
MATCH
(t:Type:File)-[:DEPENDS_ON]->(dependency:Type)
RETURN
t.fqn as Type, COUNT(dependency) as Dependencies
ORDER BY
Dependencies DESC
LIMIT 10
metric:Top10TypesPerArtifact
Returns the top 10 artifacts regarding the number of contained types.
MATCH
(a:Artifact:File)-[:CONTAINS]->(t:Type:File)
RETURN
a.fqn as Artifact, COUNT(t) as Types
ORDER BY
Types DESC
LIMIT 10
metric:Top10TypesPerPackage
Returns the top 10 packages regarding the number of contained types.
MATCH
(p:Package:File)-[:CONTAINS]->(t:Type:File)
RETURN
p.fqn as Package, COUNT(t) as Types
ORDER BY
Types DESC
LIMIT 10
jqassistant.plugin.java8
Provides Java 8 specific rules, e.g. concepts for functional interfaces and default methods.
java8.xml
Concepts
java8:DefaultMethod
Labels default methods of interfaces with "Default".
MATCH
(type:Type:Interface)-[:DECLARES]->(defaultMethod:Method)
WHERE NOT
has(defaultMethod.abstract)
SET
defaultMethod:Default
RETURN
defaultMethod AS DefaultMethod, type AS Interface
java8:FunctionalInterface
Labels functional interfaces (i.e. to be used as lambda expressions) with "FunctionalInterface".
MATCH
(functionalInterface:Interface)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(functionalInterfaceType)
WHERE
functionalInterfaceType.fqn = 'java.lang.FunctionalInterface'
SET
functionalInterface:FunctionalInterface
RETURN
functionalInterface AS FunctionalInterface
jqassistant.plugin.jpa2
Provides a scanner for persistence.xml descriptors and rules for JPA2 specific elements (e.g. entities).
Scanner for persistence.xml files
Imports persistence descriptors from META-INF/persistence.xml or WEB-INF/persistence.xml files.
:File:Jpa:Persistence
A persistence.xml file containing persistence units.
Name | Description |
---|---|
fileName |
The file name |
version |
The version of the JPA specification this descriptor represents, e.g. '2.0' |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
CONTAINS |
0..n |
References a contained persistence unit |
:Jpa:PersistenceUnit
A persistence unit.
Name | Description |
---|---|
description |
The description of the persistence unit. |
transactionType |
The transaction type, can be 'RESOURCE_LOCAL' or 'JTA' |
provider |
The class name of the JPA provider. |
jtaDatasource |
The JNDI name of the transactional datasource |
nonJtaDatasource |
The JNDI name of the non-transactional datasource |
validationMode |
The validation mode, can be 'NONE', 'CALLBACK' or 'AUTO' |
sharedCacheMode |
The shared cache mode, e.g. 'NONE' |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
CONTAINS |
0..n |
References a persistent type (entity) contained in the persistence unit |
|
HAS |
0..n |
References a property of the persistence unit |
jpa2.xml
Concepts
jpa2:Embeddable
Labels all types annotated with @javax.persistence.Embeddable with "Jpa" and "Embeddable".
MATCH
(t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="javax.persistence.Embeddable"
SET
t:Jpa:Embeddable
RETURN
t AS JpaEmbeddable
jpa2:Embedded
Labels all fields or methods annotated with @javax.persistence.Embedded with "Jpa" and "Embedded".
MATCH
(t:Type)-[:DECLARES]->(member),
(member)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
(member:Field or member:Method)
and a.fqn="javax.persistence.Embedded"
SET
member:Jpa:Embedded
RETURN
member AS JpaEmbedded
jpa2:EmbeddedId
Labels all fields or methods annotated with @javax.persistence.EmbeddedId with "Jpa" and "Embedded".
MATCH
(t:Type)-[:DECLARES]->(member),
(member)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
(member:Field or member:Method)
and a.fqn="javax.persistence.EmbeddedId"
SET
member:Jpa:EmbeddedId
RETURN
member AS EmbeddedId
jpa2:Entity
Labels all types annotated with @javax.persistence.Entity with "Jpa" and "Entity".
MATCH
(t:Type)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="javax.persistence.Entity"
SET
t:Jpa:Entity
RETURN
t AS JpaEntity
jpa2:NamedQuery
Requires concepts:
Creates a node labeled with "Jpa" and "NamedQuery" for each @NamedQuery defined for an entity. Furthermore a relation DEFINES is created between the entity and the named query.
MATCH
(entity:Jpa:Entity)-[:ANNOTATED_BY]->(namedQueries)-[:OF_TYPE]->(namedQueriesType:Type),
(namedQueries)-[:HAS]->(:Value:Array)-[:CONTAINS]->(namedQuery:Value:Annotation)-[:OF_TYPE]->(namedQueryType:Type),
(namedQuery)-[:HAS]->(nameAttribute:Value),
(namedQuery)-[:HAS]->(queryAttribute:Value)
WHERE
namedQueriesType.fqn = 'javax.persistence.NamedQueries'
and namedQueryType.fqn = 'javax.persistence.NamedQuery'
and nameAttribute.name = 'name'
and queryAttribute.name = 'query'
CREATE UNIQUE
(entity)-[:DEFINES]->(n:Jpa:NamedQuery {name:nameAttribute.value, query:queryAttribute.value})
RETURN
entity AS Entity, n.name AS Name, n.query AS Query
Constraints
jpa2:ValidationModeMustBeExplicitlySpecified
The validation mode of all persistence units must be explicitly specified and either set to CALLBACK or NONE.
MATCH
(pu:PersistenceUnit)
WHERE
not has(pu.validationMode)
OR NOT (
pu.validationMode='CALLBACK'
OR pu.validationMode='NONE'
)
RETURN
pu AS PersistenceUnit
jqassistant.plugin.junit4
Provides scanner for JUnit test reports and rules (e.g. for test classes/methods and ignored tests).
Scanner for JUnit4 test suite results
Imports results from JUnit4 test suites matching the file name 'TEST-.xml'.
:File:TestSuite
A file containing results of a JUnit4 test suite.
Name | Description |
---|---|
fileName |
The file name |
name |
The name of the test suite, e.g. the name of the class defining the suite |
tests |
The number of executed tests |
failures |
The number of failed tests |
errors |
The number of tests in error |
skipped |
The number of skipped tests |
time |
The time it took to run the suite |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
CONTAINS |
0..n |
References a test case |
:TestCase
A test case.
Name | Description |
---|---|
name |
The name of the test, e.g. the name of the test method |
className |
The name of the class containing the test |
time |
The time it took to run the test |
result |
The result of the test, either 'SUCCESS', 'FAILURE', 'ERROR' or 'SKIPPED' |
junit3.xml
Concepts
junit3:SetUpMethod
Requires concepts:
Labels all setUp methods declared by a test class with "SetUp" and "Junit3".
MATCH
(c:Class:Test:Junit3)-[:DECLARES]->(m:Method)
WHERE
m.signature = "void setUp()"
SET
m:SetUp:Junit3
RETURN
m AS SetUpMethod, c AS TestClass
junit3:TearDownMethod
Requires concepts:
Labels all tearDown methods declared by a test class with "TearDown" and "Junit3".
MATCH
(c:Class:Test:Junit3)-[:DECLARES]->(m:Method)
WHERE
m.signature = "void tearDown()"
SET
m:TearDown:Junit3
RETURN
m AS TearDownMethod, c AS TestClass
junit3:TestClass
Labels all non-abstract classes extending from junit.framework.TestCase "Test" and "Junit3".
MATCH
(c:Type:Class)-[:EXTENDS*]->(testCaseType:Type)
WHERE
testCaseType.fqn = "junit.framework.TestCase"
SET
c:Test:Junit3
RETURN
c AS TestClass
junit3:TestMethod
Requires concepts:
Labels all test methods declared by a test class with "Test" and "Junit3".
MATCH
(c:Class:Test:Junit3)-[:DECLARES]->(m:Method)
WHERE
m.signature =~ "void test.*\\(.*\\)"
SET
m:Test:Junit3
RETURN
m AS Test, c AS TestClass
junit4.xml
Concepts
junit4:AfterClassMethod
Labels all methods annotated by "@org.junit.AfterClass" with "Junit4" and "AfterClass".
MATCH
(c:Type:Class)-[:DECLARES]->(m:Method),
(m:Method)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="org.junit.AfterClass"
SET
m:Junit4:AfterClass
RETURN
m AS AfterClassMethod, c AS TestClass
junit4:AfterMethod
Labels all methods annotated by "@org.junit.After" with "Junit4" and "After".
MATCH
(c:Type:Class)-[:DECLARES]->(m:Method),
(m:Method)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="org.junit.After"
SET
m:Junit4:After
RETURN
m AS AfterMethod, c AS TestClass
junit4:AssertMethod
Labels all assertion methods declared by org.junit.Assert with "Junit4" and "Assert".
MATCH
(assertType:Type)-[:DECLARES]->(assertMethod)
WHERE
assertType.fqn = 'org.junit.Assert'
and assertMethod.signature =~ 'void assert.*'
SET
assertMethod:Junit4:Assert
RETURN
assertMethod
junit4:BeforeClassMethod
Labels all methods annotated by "@org.junit.BeforeClass" with "Junit4" and "BeforeClass".
MATCH
(c:Type:Class)-[:DECLARES]->(m:Method),
(m:Method)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="org.junit.BeforeClass"
SET
m:Junit4:BeforeClass
RETURN
m AS BeforeClassMethod, c AS TestClass
junit4:BeforeMethod
Labels all methods annotated by "@org.junit.Before" with "Junit4" and "Before".
MATCH
(c:Type:Class)-[:DECLARES]->(m:Method),
(m:Method)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="org.junit.Before"
SET
m:Junit4:Before
RETURN
m AS BeforeMethod, c AS TestClass
junit4:IgnoreTestClassOrMethod
Labels all classes or methods annotated with "@org.junit.Ignore" with "Junit4" and "Ignore".
MATCH
(e)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="org.junit.Ignore"
SET
e:Junit4:Ignore
RETURN
e AS IgnoredElement
junit4:TestCaseImplementedByMethod
Creates a relation IMPLEMENTED_BY between all test cases from test reports and their implementing methods.
MATCH
(testcase:TestCase)
WITH
testcase
MATCH
(testclass:Type)-[:DECLARES]->(testmethod:Method)
WHERE
testclass.fqn = testcase.className
and testmethod.name = testcase.name
CREATE UNIQUE
(testcase)-[:IMPLEMENTED_BY]->(testmethod)
RETURN
testcase AS TestCase
junit4:TestClass
Requires concepts:
Labels all classes containing test methods with "Test" and "Junit4".
MATCH
(c:Type:Class)-[:DECLARES]->(m:Method:Junit4:Test)
SET
c:Test:Junit4
RETURN
c AS TestClass, COLLECT(m) AS TestMethods
junit4:TestClassOrMethod
_The rule is deprecated: This concept has been replaced by "junit4:TestMethod" and "junit4:TestClass"._
Requires concepts:
Finds test methods (i.e. annotated with "@org.junit.Test") and labels them and their containing classes with "Test" and "Junit4".
MATCH
(c:Type:Class)-[:DECLARES]->(m:Method:Junit4:Test)
RETURN
c AS TestClass, COLLECT(m) AS TestMethods
junit4:TestMethod
Finds all test methods (i.e. annotated with "@org.junit.Test") and labels them with "Test" and "Junit4".
MATCH
(m:Method)-[:ANNOTATED_BY]-()-[:OF_TYPE]->(a:Type)
WHERE
a.fqn="org.junit.Test"
SET
m:Test:Junit4
RETURN
m AS Test
Constraints
junit4:AssertionMustProvideMessage
Requires concepts:
All assertions must provide a message.
MATCH
(testType:Type)-[:DECLARES]->(testMethod:Method),
(testMethod)-[invocation:INVOKES]->(assertMethod:Assert:Method)
WHERE
NOT assertMethod.signature =~ 'void assert.*\\(java.lang.String,.*\\)'
RETURN
invocation AS Invocation,
testType AS DeclaringType,
testMethod AS Method
junit4:IgnoreWithoutMessage
All @Ignore annotations must provide a message.
MATCH
(e)-[:ANNOTATED_BY]->(ignore:Annotation)-[:OF_TYPE]->(ignoreType:Type)
WHERE
ignoreType.fqn= "org.junit.Ignore"
AND NOT (ignore)-[:HAS]->(:Value{name:"value"})
RETURN
e AS IgnoreWithoutMessage
junit4:TestMethodWithoutAssertion
Requires concepts:
All test methods must perform assertions (within a call hierarchy of max. 3 steps).
MATCH
(testType:Type)-[:DECLARES]->(testMethod:Test:Method)
WHERE
NOT (testMethod)-[:INVOKES*..3]->(:Method:Assert)
RETURN
testType AS DeclaringType,
testMethod AS Method
jqassistant.plugin.maven3
Provides a scanner for Maven 3 modules.
Scanner for Maven projects
Imports information from Maven projects.
:Maven:Project:File
A pom.xml file describing a single Maven project.
Name | Description |
---|---|
fileName |
The directory of the project. |
name |
The name |
groupId |
The group id |
artifactId |
The artifact id |
packaging |
The packaging type, e.g. "jar" |
version |
The version |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
CREATES |
0..n |
References an artifact created by the project |
|
HAS_PARENT |
0..1 |
References the parent project (optional) |
|
HAS_MODULE |
0..n |
References modules of this project (optional) |
:Artifact
Represents an artifact, e.g. a JAR-File.
Name | Description |
---|---|
groupId |
The group id |
artifactId |
The artifact id |
type |
The type, e.g. "jar" |
classifier |
The classifiert |
version |
The version |
Name | Target label(s) | Cardinality | Description |
---|---|---|---|
CONTAINS |
:File |
0..n |
References a file contained in the artifact |
DEPENDS_ON |
0..n |
References a artifact which is a declared dependency |
Name | Description |
---|---|
scope |
The declared scope, e.g. "compile" |
optional |
"true" indicates that this dependency is optional. |
maven3.xml
Constraints
maven3:HierarchicalParentModuleRelation
If a parent Maven project declares a module then the parent project must also be declared as the parent of the module (i.e. to keep the project hierarchy consistent).
match
(parent:Maven:Project)-[:HAS_MODULE]->(module:Maven:Project)
where
not (module)-[:HAS_PARENT]->(parent)
return
module as InvalidModule
jqassistant.plugin.osgi
Provides rules for OSGi bundles (e.g. Bundles, Activator).
osgi-bundle.xml
Concepts
osgi-bundle:Activator
Requires concepts:
Creates an ACTIVATES relation between a class and the bundle artifact if the class is declared as "Bundle-Activator" in the bundle manifest.
match
(bundle:Osgi:Bundle)-[:CONTAINS]->(manifest:File:Manifest),
(manifest)-[:DECLARES]->(section:ManifestSection),
(section)-[:HAS]->(entry:ManifestEntry),
(bundle)-[:CONTAINS]->(activator:Class)
where
entry.name = "Bundle-Activator"
and entry.value = activator.fqn
create unique
(activator)-[:ACTIVATES]->(bundle)
return
activator as Activator, bundle as Bundle
osgi-bundle:Bundle
Labels all artifacts with a manifest declaring a bundle name as "Osgi" and "Bundle" and adds the properties "bundleSymbolicName" and "bundleVersion".
MATCH
(bundle:Artifact)-[:CONTAINS]->(manifest:Manifest:File),
(manifest)-[DECLARES]->(section:ManifestSection),
(section)-[:HAS]->(nameEntry:ManifestEntry),
(section)-[:HAS]->(versionEntry:ManifestEntry)
WHERE
nameEntry.name = "Bundle-SymbolicName"
AND versionEntry.name = "Bundle-Version"
SET
bundle:Osgi:Bundle,
bundle.bundleSymbolicName = nameEntry.value,
bundle.bundleVersion = versionEntry.value
RETURN
bundle as Bundle, bundle.bundleSymbolicName as BundleSymbolicName, bundle.bundleVersion as BundleVersion
osgi-bundle:ExportPackage
Requires concepts:
Creates an EXPORTS relation from a bundle artifact to all packages which are declared as "Export-Package" in the bundle manifest.
match
(bundle:Osgi:Bundle)-[:CONTAINS]->(package:Package)
with
bundle, package, "(^|.*,)\\s*"+ replace(package.fqn, ".", "\\.")+"\\s*((;|,).*|$)" as pattern
match
(bundle)-[:CONTAINS]->(manifest:File:Manifest),
(manifest)-[:DECLARES]->(section:ManifestSection),
(section)-[:HAS]->(entry:ManifestEntry)
where
entry.name = "Export-Package"
AND entry.value=~ pattern
create unique
(bundle)-[:EXPORTS]->(package)
return
bundle as Bundle, collect(package) as ExportedPackages
osgi-bundle:ImportPackage
Requires concepts:
Creates an IMPORTS relation from a bundle artifact to all packages which are declared as "Import-Package" in the bundle manifest.
match
(package:Package)
with
package, "(^|.*,)\\s*"+ replace(package.fqn, ".", "\\.")+"\\s*((;|,).*|$)" as pattern
match
(bundle:Osgi:Bundle)-[:CONTAINS]->(manifest:File:Manifest),
(manifest)-[:DECLARES]->(section:ManifestSection),
(section)-[:HAS]->(entry:ManifestEntry)
where
entry.name = "Import-Package"
and entry.value =~ pattern
create unique
(bundle)-[:IMPORTS]->(package)
return
bundle as Bundle, collect(package) as ImportedPackages
osgi-bundle:InternalType
Requires concepts:
Labels all internal types (i.e. which are not located in an exported package) as "Internal".
match
(bundle:Bundle:Osgi)-[:CONTAINS]->(internalPackage:Package),
(bundle)-[:CONTAINS]->(internalType:Type),
(internalPackage)-[:CONTAINS]->(internalType:Type)
where not
(bundle)-[:EXPORTS]->(internalPackage)
set
internalType:Internal
return bundle as Bundle, collect(internalType) as InternalTypes
Constraints
osgi-bundle:InternalTypeMustNotBePublic
Requires concepts:
Internal types must not be public if no depending types exist in other packages of the bundle.
match
(bundle:Osgi:Bundle)-[:CONTAINS]->(internalType:Type:Internal),
(internalPackage:Package)-[:CONTAINS]->(internalType)
optional match
(bundle)-[:CONTAINS]->(otherPackage:Package),
(otherPackage)-[:CONTAINS]->()-[:DEPENDS_ON]->(internalType)
where
internalType.visibility='public'
with
bundle, internalPackage, internalType, collect(otherPackage) as otherPackages
where
all(otherPackage in otherPackages where internalPackage = otherPackage)
and not
(internalType)-[:ACTIVATES]->(bundle)
return
bundle as Bundle, internalType as InternalType
osgi-bundle:UnusedInternalType
Requires concepts:
A bundle must not contain internal types which are not used by other types in the bundle.
match
(bundle:Osgi:Bundle)-[:CONTAINS]->(internalType:Type:Internal)
where not (
(internalType)-[:ACTIVATES]->(bundle)
or
(bundle)-[:CONTAINS]->(:Type)-[:DEPENDS_ON]->(internalType)
)
return
bundle as Bundle, internalType as InternalType
jqassistant.plugin.tycho
Provides a scanner for tycho modules.
Scanner for Tycho projects
Adds Tycho specific resources to be scanned to the file system scanner.