One of the problems I've encountered while using DTOs is that I often find myself shipping (accidentally) entities along with DTOs. To mitigate this problem, I created another Maven project with an annotation (#ValidDTO) and its processor that finds if a DTO annotated with #ValidDTO has #Entity annotated fields.
This is my annotation.
#Retention(RetentionPolicy.CLASS)
#Target(ElementType.TYPE)
public #interface ValidDTO {}
And, this is my processor.
#SupportedAnnotationTypes("com.aj.annotations.ValidDTO")
#SupportedSourceVersion(SourceVersion.RELEASE_11)
public class ValidDTOProcessor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> set,
RoundEnvironment roundEnv) {
List<Entity> entityFields = roundEnv.getElementsAnnotatedWith(ValidDTO.class)
.stream()
.filter(element -> element.getKind()==ElementKind.CLASS || element.getKind()==ElementKind.INTERFACE)
.map(Element::getEnclosedElements)
.flatMap(List::stream)
.filter(element -> element.getKind()==ElementKind.FIELD)
.map(element -> element.getAnnotation(Entity.class))
.collect(Collectors.toList());
if (!entityFields.isEmpty()) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Types annotated with ValidDTO " +
"cannot have member variables that are #Entity annotated");
}
return true;
}
}
This is how my POM.xml looks for the Maven project with the annotation and its processor
<groupId>com.aj</groupId>
<artifactId>aj-annotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
<generatedSourcesDirectory>${project.build.directory}/generated-sources/
</generatedSourcesDirectory>
<proc>none</proc>
<annotationProcessors>
<annotationProcessor>
com.aj.annotations.processors.ValidDTOProcessor
</annotationProcessor>
</annotationProcessors>
<debug>true</debug>
</configuration>
</plugin>
</plugins>
</build>
So, I installed this package as a dependency in another projected and annotated a DTO with it. I purposefully added couple of entities as member variables to see the error.
#ValidDTO
public class FacilityDTO {
private User user;
private List<User> users;
}
where,
#Entity
#Table("User")
public class User {}
is an entity.
Now, my custom annotation works perfectly good when I run mvn clean install or build project. I can see the expected "Types annotated with ValidDTO cannot have member variables that are #Entity annotated" in the terminal.
However, I do not see the error in the editor of the IDE. I've tried both Intellij and Eclipse and I do not see any red squiggly line underneath the annotation telling me that the DTO is invalid.
The closest expected desired behavior I can reference is a compile error when using #FunctionalInterface on an interface that has more than one abstract method.
I just need help configuring my IDE. Any help is appreciated!
In IntelliJ you can create custom inspections. These can be used to alert you to the presence of custom search patterns in your code (https://www.jetbrains.com/help/idea/creating-custom-inspections.html).
For your case:
Go to settings -> Editor -> Inspections. Activate "Structural search inspection" and add a "Search Template":
(UPDATE 06/2020: "Structural search" is not under "General" anymore but is now a separate topic)
Add following structural search:
You may change "Severity" to "Error" to get the red squiggly lines. :)
Related
I have the child class under test BoyTest.java. BoyTest extends a test class called GirlTest. I have injected a list using #Injectable(Jmockit) in GirlTest.
Now when I try to inject the list from GirlTest on #Tested instance of BoyTest. I see my test fail since the list is not injected. Test fail since, expected is empty list and actual is list containing a JSON.
Jmockit version under use is : v1.41 and JDK version 1.8.0_181
public class GirlTest {
#Tested
private Girl girl;
#Injectable
private List<someType> list;
}
public class BoyTest extends GirlTest {
#Tested
private Boy boy;
#Test
public void someTest() {
lis.add(json)
assertThat(boy.getJsons()).isEqualTo(Arrays.asList(json));
}
}
Test result: Expecting List element get added to list before assertion
statement is run and expecting test to pass Actual , test pass in
local, expecting test pass on Jenkins
maybe you has'nt configure maven-surefire-plugin
you can try to add these codes as follows in your pom.xml file
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.3</version>
<configuration>
<argLine>-javaagent:${settings.localRepository}/mockit/jmockit/0.998/jmockit-0.998.jar</argLine>
<useSystemClassLoader>true</useSystemClassLoader>
</configuration>
</plugin>
make sure that this line is configured.
<argLine>-javaagent:${settings.localRepository}/mockit/jmockit/0.998/jmockit-0.998.jar</argLine>
then you can use jmockit on jenkins
I want to null-check a List and assign it to a final class property:
private final ExcelDocument xls;
private final List<ValidationError> validationErrors;
public DefaultExaminationParser(ExcelDocument xls, List<ValidationError> validationErrors)
{
this.xls = xls;
this.validationErrors = validationErrors == null ? new ArrayList<>() : validationErrors;
}
The import is: import java.util.List; and import java.util.ArrayList;
The compiler in eclipse and maven is Java 1.8
My IDE (eclipse) does not show any problems with this line, but when I run a maven build it fails with following error:
DefaultExaminationParser.java:[84,51] error: incompatible types: List<? extends Object> cannot be converted to List<ValidationError>
The ValidationError is defined as:
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
public class ValidationError implements Serializable, DetailInfo {
[...]
}
I don't see any errors here. I can also run a unit test for this class without problems.
The problem was, that the compiler level for maven was set to 1.7 in one of the several parent pom files.
Used following snippet to set it explicitly to 1.8:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<encoding>utf8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
There are many internal Java annotations like SuppressWarning, FunctionalInterface, etc. which can limit the members of the class with the annotation, expand classes or even specify compiler options, but how can a normal programmer compose such annotations?
I searched on the annotation topics and all I could find is adding some meta values to the annotation like this, and how to use annotations, but nothing I can find that explains how to implement advanced annotations. Any directions would be helpful.
What you are looking for is compile-time annotation .
Basically, annotation processing can be done based on its RetentionPolicy. As per the Java docs, there are 3 type of RetentionPolicy -
CLASS Annotations are to be recorded in the class file by the compiler
but need not be retained by the VM at run time.
RUNTIME Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.
SOURCE Annotations are to be discarded by the compiler.
Compile-time annotation processing is related to RetentionPolicy.SOURCE because you want to process on source file at the time of compilation similar to other annotation like #Override.
Below is one example of a simple compile-time annotation processor -
Create Maven Project - Annotation_Compile_Time -
(A) Create a compile-time Annotation MyAnnotation in this project -
package xxx;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Documented
#Target(ElementType.TYPE)
#Inherited
#Retention(RetentionPolicy.SOURCE)
public #interface MyAnnotation {
}
(B) Create a annotation Processor MyAnnotationProcessor -
package xxx;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
#SupportedAnnotationTypes("xxx.MyAnnotation ")
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyAnnotationProcessor extends AbstractProcessor {
public MyAnnotationProcessor () {
super();
}
#Override
public boolean process(Set<? extends TypeElement> typeElementSet, RoundEnvironment roundEnv) {
for (Element e : roundEnv.getRootElements()) {
String className = e.toString();
String message = "Annotated class - " + className;
System.out.println(message);
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
}
return false;
}
}
(C) Create javax.annotation.processing.Processor file in directory - src/main/resources/META-INF/services with below content -
xxx.MyAnnotationProcessor
(D) Update pom.xml with build configuration -
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<verbose>true</verbose>
<fork>true</fork>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</build>
(E) Compile and install this project using mvn clean install.
Create Another Maven Project - Annotation_User - This project will use annotation defined in above project. Create 2 source files in this project annotated with this annotation
(A) AnnotationUser1 -
package xxx.consumer;
import xxx.MyAnnotation;
#MyAnnotation
public class AnnotationUser1 {
private String message;
public AnnotationUser1(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
(B) AnnotationUser2 -
package xxx.consumer;
import xxx.MyAnnotation;
#MyAnnotation
public class AnnotationUser2 {
private String message;
public AnnotationUser1(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
(C) Update pom.xml with Annotation project dependency -
<dependency>
<groupId>xxx</groupId>
<artifactId>Annotation_Compile_Time</artifactId>
<version>1.0</version>
</dependency>
Now, whenever, you will compile this project using mvn clean compile, your annotation processor defined in project 1 will get called. Currently, processor is just printing the name of the classes annotated with this annotation.
You can also refer to this page for details.
Next step comes is to analyse the source file and calculate no. of methods. Since, it is compile time processing, so you can not use Reflection API for getting no. of methods. One solution is to use Eclipse AST for parsing source file and calculating no. of methods or you can write your own logic.
Project lombok is mainly based on Compile-time annotation processing. If you want to do something useful, it would be better to study Project lombok source code
I think you need annotation processing:
API documentation - https://docs.oracle.com/javase/8/docs/api/javax/annotation/processing/Processor.html
Pretty good article - https://www.javacodegeeks.com/2015/09/java-annotation-processors.html
Also you can read sources of lombok (which is all about annotation processing): https://github.com/rzwitserloot/lombok
UPD:
For some cases you can write javaagent instead which is much better at least because it is at least has some tools like bytebuddy (which is great IMHO)
I am sharing how I made a custom annotation to resolve an issue in my app.
Problem:
I was using Spring AOP to do the logging in my app. To people new to AOP, what it did in simple words is instead of writing logger.log() in every method and class, you can tell AOP to do something (in my case logging) after/before/after-and-before each method. Now the problem is since every method is going to get logged, how do I prevent a certain method(like authentication) or parameter(like password) from getting logged.
So to do this, I created an annotation SkipLogging
#Target(value = { ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER })
#Retention(value = RetentionPolicy.RUNTIME)
public #interface SkipLogging {
}
and in my AOP class, I put in a condition that if any thing has this annotation, AOP should not do logging on that. Maybe following (partial) code will make more sense:
#Around("within(com.test..*)
public Object logAround(final ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// Log only if class/method is not annotated with SkipLogging
if (!(signature.getDeclaringType().isAnnotationPresent(SkipLogging.class)
|| method.isAnnotationPresent(SkipLogging.class))) {
try {
// Log before method entry
logger.info("");
Object result = joinPoint.proceed();
// Log after method exit
logger.info("");
return result;
} catch (Exception e) {
// Log after exception
logger.error("");
throw e;
}
} else {
return joinPoint.proceed();
}
}
Without going into much detail, look at the condition:
if (!(signature.getDeclaringType().isAnnotationPresent(SkipLogging.class)
|| method.isAnnotationPresent(SkipLogging.class
)))
which prevents classes and methods annotated with SkipLogging from getting logged. Similarly, I had written some code for putting this annotation on parameters and skipping them.
In the next steps, I had created annotations like #DebugLogging, #ErrorLogging etc. and put in a check in my AOP, to write debug log or error log based on the annotation present.
I'm working on project where I need to perform some action before running each JUnit test. This problem was solved using RunListener that could be added to the JUnit core. The project assembly is done using Maven, so I have this lines in my pom file:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<properties>
<property>
<name>listener</name>
<value>cc.redberry.core.GlobalRunListener</value>
</property>
</properties>
</configuration>
</plugin>
So, everything works using:
mvn clean test
But when tests are started using IntelliJ (using its internal test runner) the actions coded in our RunListener are not executed, so it is impossible to perform testing using IntelliJ infrastructure.
As I see, IntelliJ does not parse this configuration from pom file, so is there a way to explicitly tell IntelliJ to add RunListener to JUnit core? May be using some VM options in configuration?
It is much more convenient to use beautiful IntelliJ testing environment instead of reading maven output.
P.S. The action I need to perform is basically a reset of static environment (some static fields in my classes).
I didn't see a way to specify a RunListener in Intellij, but another solution would be to write your own customer Runner and annotate #RunWith() on your tests.
public class MyRunner extends BlockJUnit4ClassRunner {
public MyRunner(Class<?> klass) throws InitializationError {
super(klass);
}
#Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
// run your code here. example:
Runner.value = true;
super.runChild(method, notifier);
}
}
Sample static variable:
public class Runner {
public static boolean value = false;
}
Then run your tests like this:
#RunWith(MyRunner.class)
public class MyRunnerTest {
#Test
public void testRunChild() {
Assert.assertTrue(Runner.value);
}
}
This will allow you to do your static initialization without a RunListener.
Update for the impatient: it's simple, use package.- for sub-package scanning instead of package.*, as-per martoe's answer below!
I cannot seem to get onlyAnalyze working for my multi-module project: regardless of what package (or pattern) I set, maven-findbugs-plugin doesn't evaluate sub-packages as I'd expect from passing it packagename.*.
To prove either myself or the plugin at fault (though I always assume it's the former!), I setup a small Maven project with the following structure:
pom.xml
src/
main/java/acme/App.java
main/java/acme/moo/App.java
main/java/no_detect/App.java
which is very simple!
The POM has the following findbugs configuration:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.4.0</version>
<executions>
<execution>
<phase>verify</phase>
<goals><goal>findbugs</goal><goal>check</goal></goals>
</execution>
</executions>
<configuration>
<debug>true</debug>
<effort>Max</effort>
<threshold>Low</threshold>
<onlyAnalyze>acme.*</onlyAnalyze>
</configuration>
</plugin>
</plugins>
</build>
and every App.java has the following code with two obvious violations:
package acme;
import java.io.Serializable;
public class App implements Serializable
{
private static final class NotSer {
private String meh = "meh";
}
private static final NotSer ns = new NotSer();// Violation: not serializable field
public static void main( String[] args )
{
ns.meh = "hehehe";// Vilation: unused
System.out.println( "Hello World!" );
}
}
Note that no_detect.App has the same content as above, but my expectation is that it wouldn't be evaluated by findbugs because I have the "onlyAnalyze" option set to acme.* which I assume would evaluate acme.App and acme.moo.App and nothing else.
I now execute a mvn clean install to clean, build, test, run findbugs, package, install, which produces the following findbugs report (snipped for brevity) and results in a build failure which is expected because acme.App and acme.moo.App:
<BugInstance category='BAD_PRACTICE' type='SE_NO_SERIALVERSIONID' instanceOccurrenceMax='0'>
<ShortMessage>Class is Serializable, but doesn't define serialVersionUID</ShortMessage>
<LongMessage>acme.App is Serializable; consider declaring a serialVersionUID</LongMessage>
<Details>
<p> This field is never read. Consider removing it from the class.</p>
</Details>
<BugPattern category='BAD_PRACTICE' abbrev='SnVI' type='SE_NO_SERIALVERSIONID'><ShortDescription>Class is Serializable, but doesn't define serialVersionUID</ShortDescription><Details>
<BugCode abbrev='UrF'><Description>Unread field</Description></BugCode><BugCode abbrev='SnVI'><Description>Serializable class with no Version ID</Description></BugCode>
To summarise: only acme.App is analysed, acme.moo.App isn't (bad) and neither is no_detect.App (good).
I tried with two wildcards in the onlyAnalyze option but that produces a successful build but with a findbugs error (Dangling meta character '*' etc).
I tried with onlyAnalyze set to acme.*,acme.moo.* which analyzes all the expected classes (acme.App and acme.moo.App) which means it "works" but not as I expect; i.e. I have to explicitly declare all parent-packages for the classes I want to analyze: that could get large and difficult to maintain on a multi-module project!
Do I have to define every package I want analyzed, or can I declare a wildcard/regex pattern that will do what I want?
I'd rather not use the inclusion/exclusion XML because that requires far more setup and reasoning that I don't currently have time for...
To cite the Findbugs manual: "Replace .* with .- to also analyze all subpackages"