How to read Javadoc comments by reflection? - java

I need to know how to read Javadoc comments at run-time (probably by reflection?)
Say I have the following function:
/**
* function that do some thing
*/
public void myFunc()
{
//...
}
At runtime, I can get much information about this function by reflection.. But cannot read the comments. So the question is, How to read this javadoc comments at runtime.

Doclet class:
public class ExtractCommentsDoclet {
public static boolean start(RootDoc root) throws IOException {
for (ClassDoc c : root.classes()) {
print(c.qualifiedName(), c.commentText());
for (FieldDoc f : c.fields(false)) {
print(f.qualifiedName(), f.commentText());
}
for (MethodDoc m : c.methods(false)) {
print(m.qualifiedName(), m.commentText());
if (m.commentText() != null && m.commentText().length() > 0) {
for (ParamTag p : m.paramTags())
print(m.qualifiedName() + "#" + p.parameterName(), p.parameterComment());
for (Tag t : m.tags("return")) {
if (t.text() != null && t.text().length() > 0)
print(m.qualifiedName() + "#return", t.text());
}
}
}
}
return true;
}
private static void print(String name, String comment) throws IOException {
if (comment != null && comment.length() > 0) {
new FileWriter(name + ".txt").append(comment).close();
}
}
}
And maven execution:
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>aggregate</goal>
</goals>
</execution>
</executions>
<configuration>
<doclet>ExtractCommentsDoclet</doclet>
<docletPath>${project.build.directory}/classes</docletPath>
<reportOutputDirectory>${project.build.outputDirectory}/META-INF</reportOutputDirectory>
<useStandardDocletOptions>false</useStandardDocletOptions>
</configuration>
</plugin>
Read docs from classpath:META-INF/apidocs

You can't. They're removed from the compiled code.

Consider to use annotations instead of Javadoc and write an annotation processor.

You can't do that, cause javadoc isn't compiled into final classes.

Related

AspectJ plugin builds fine but at runtime annotations don't work

I am using the AspectJ Maven plugin to build my project and use an AspectLibrary, which is a jar in which I have my aspects defined.
Here is the Aspect that I am trying to use
#Around("execution(* *(..))&&#annotation(com.cisco.commerce.pricing.lp.commons.util.annotations.TimeMe)")
public Object timeMeAroundAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {// NOSONAR
Timer timer = Timer.instance().start();
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
TimeMe timeMeAnnotation = method.getAnnotation(TimeMe.class);
String name = timeMeAnnotation.name();
boolean log = timeMeAnnotation.log();
boolean addToMetrics = timeMeAnnotation.addToMetrics();
Object response = null;
try {
response = proceedingJoinPoint.proceed();
} finally {
try {
Long timeTaken = timer.timeTaken();
if (log) {
LOGGER.info("MethodName: {} Time taken: {}", name, timeTaken);
}
if (addToMetrics) {
ExecutionDetailsUtil.addMethodExecutionTime(name, timeTaken);
}
} catch (Exception e) {
LOGGER.warn("Exception while trying to log time", e);
}
}
return response;
}
This code is in a jar file, which I am using as the aspectLibrary in my pom
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<encoding>UTF-8</encoding>
<source>${java.source-target.version}</source>
<target>${java.source-target.version}</target>
<Xlint>ignore</Xlint>
<aspectLibraries>
<aspectLibrary>
<groupId>it.cvc.ciscocommerce.lps.lp-commons</groupId>
<artifactId>lp-commons</artifactId>
</aspectLibrary>
</aspectLibraries>
<complianceLevel>${java.source-target.version}</complianceLevel>
</configuration>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
Below is my annotation defintion
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface TimeMe {
public String name();
public boolean log() default true;
public boolean addToMetrics() default true;
}
Here is the snippet where I am trying to use this annotation (in a different code base which uses the above jar as a dependency)
#TimeMe(name = "classifyLine")
private void classifyLine(PricingObject pricingObject,
PricingLineObject pricingLineObject, LineTypes lineTypes) {
//logic
}
My build runs fine and prints the following in the MAVEN Console
[INFO] Join point 'method-execution(void com.cisco.pricing.lps.main.ListPriceService.classifyLine(com.cisco.pricing.lps.bean.PricingObject, com.cisco.pricing.lps.bean.PricingLineObject, com.cisco.pricing.lps.dto.LineTypes))' in Type 'com.cisco.pricing.lps.main.ListPriceService' (ListPriceService.java:235) advised by around advice from 'com.cisco.commerce.pricing.lp.commons.util.logging.LoggingAspectDefiner' (lp-commons-2019.03.01-SNAPSHOT.jar!LoggingAspectDefiner.class(from LoggingAspectDefiner.java))
I exploded the war file and looked at the class files generated. I have the following AjcClosure1 class generated for the java file where I used the annotation.
public class ListPriceService$AjcClosure1 extends AroundClosure {
public Object run(Object[] paramArrayOfObject) {
Object[] arrayOfObject = this.state;
ListPriceService.classifyLine_aroundBody0((ListPriceService)
arrayOfObject[0],
(PricingObject)arrayOfObject[1],
(PricingLineObject)arrayOfObject[2], (LineTypes)arrayOfObject[3],
(JoinPoint)arrayOfObject[4]);return null;
}
public ListPriceService$AjcClosure1(Object[] paramArrayOfObject)
{
super(paramArrayOfObject);
}
}
And in the java class file, where I use the annotation, I see no changes to the classifyLine method.
However, when I run my application, the annotation is not working. It doesn't execute the Aspect I have defined in the jar.
I have no clue why. Is my pattern not matching? It matches and works fine in a Spring application but not in this non Spring application.

AspectJ Compile time weaving fails for streams

I am getting below error when aspectj compiler runs.
[ERROR] Type mismatch: cannot convert from List<Object> to List<Tag>
My code is,
final List<Tag> customTags =
pathVariables.entrySet().stream().filter(entry -> {
return tagList.contains(entry.getKey());
}).map(tag -> {
return new Tag() {
#Override
public String getValue() {
logger.info("Key for the attached tag is: {}",
tag.getKey());
return tag.getKey();
}
#Override
public String getKey() {
logger.info("Value for the attached tag is: {}", (String)tag.getValue());
return (String) tag.getValue();
}
};
}).collect(Collectors.toList());
pom.xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.7</version>
</dependency>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.8</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<complianceLevel>1.8</complianceLevel>
<encoding>UTF-8</encoding>
<verbose>true</verbose>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal> <!-- use this goal to weave all your main classes -->
<goal>test-compile</goal> <!-- use this goal to weave all your test classes -->
</goals>
</execution>
</executions>
</plugin>
Things that I have tried,
1. Adding properties so as to tell maven compiler plugin to comply with java 8
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
Changing AspectJ version to 1.8.13
In both of the cases I got same error. If I use,
final List customTags = ...
with the same code I do not get this error. Am I missing anything?
Tried running dummy code of similar structure,
Map<String, Object> HOSTING1 = new HashMap<>();
HOSTING1.put("1", "linode.com");
HOSTING1.put("2", "heroku.com");
HOSTING1.put("3", "digitalocean.com");
HOSTING1.put("4", "aws.amazon.com");
List<String> tagList = Arrays.asList("1", "2", "3");
final List<Tag> customTags = HOSTING1.entrySet().stream().filter(entry -
> {
return tagList.contains(entry.getKey());
}).map(tag -> {
return new Tag() {
#Override
public String getValue() {
System.out.println("Value for the attached tag is: {}" + tag.getValue());
return (String) tag.getValue();
}
#Override
public String getKey() {
System.out.println("Key for the attached tag is: {}" + tag.getKey());
return (String) tag.getKey();
}
};
}).collect(Collectors.toList());
Explicitly specifying .map(tag -> {... helped! Without that AspectJ compiler was causing the issue. Code is,
final List<Tag> customTags =
pathVariables.entrySet().stream().filter(entry -> {
return tagList.contains(entry.getKey());
}).<Tag>map(tag -> {
return new Tag() {
#Override
public String getValue() {
logger.info("Key for the attached tag is: {}",
tag.getKey());
return tag.getKey();
}
#Override
public String getKey() {
logger.info("Value for the attached tag is: {}", (String)tag.getValue());
return (String) tag.getValue();
}
};
}).collect(Collectors.toList());

Get annotations when exec-maven-plugin runs Main does not work

I would like to run a Main class with exec-maven-plugin and from my dependencies generate documentation like a swagger file.
The annotation that I care is javax.ws.rs.Path which has #Retention(RetentionPolicy.RUNTIME)
My Java code
public class ContextClassReader extends ClassReader {
private static final ExtensibleClassLoader CLASS_LOADER = new ExtensibleClassLoader();
public ContextClassReader(final String className) throws IOException {
super(CLASS_LOADER.getResourceAsStream(className.replace('.', '/') + ".class"));
final URL resource = CLASS_LOADER.getResource(className.replace('.', '/') + ".class");
}
public static ClassLoader getClassLoader() {
return CLASS_LOADER;
}
public static void addClassPath(final URL url) {
CLASS_LOADER.addURL(url);
}
private static class ExtensibleClassLoader extends URLClassLoader {
ExtensibleClassLoader() {
super(new URL[]{});
}
#Override
public void addURL(final URL url) {
super.addURL(url);
}
}
Here is the loading of class and testing it for annotations.
final Class<?> clazz = ContextClassReader.getClassLoader().loadClass(className);
isAnnotationPresent(clazz);
public static boolean isAnnotationPresent(final AnnotatedElement annotatedElement) {
... annotatedElement.getAnnotations().length --> 0
... clazz.getMethods().length() ---> works !
}
My pom.xml
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>java</executable>
<workingDirectory>XXXXXXXXXXXXXXXXXXX</workingDirectory>
<addResourcesToClasspath>true</addResourcesToClasspath>
<additionalClasspathElements>true</additionalClasspathElements>
<includeProjectDependencies>true</includeProjectDependencies>
<includePluginDependencies>true</includePluginDependencies>
<includeProjectDependencies>true</includeProjectDependencies>
<mainClass>XXX.Main</mainClass>
</configuration>
</plugin>

Exception Handling/Mapping for a particular class

I have resource class which itself's talks with a internal service. This resource acts a rest API for the service. The service layer can throw unexpected exceptions, thus the resource should handle those handled unexpected exceptions and log it. I am using dropwizard framework which in turns use jersey. It goes like this.
#PATH(/user)
#GET
public Response getUser(#QueryParam("id") String userId) {
assertNotNull(userId);
try {
User user = service.getUser(userId);
return Response.ok(user).build();
}
catch (MyOwnException moe) { //basically 400's
return Response.status(400).entity(moe.getMsg()).build();
}
catch (Exception e) { //unexpected exceptions
logger.debug(e.getMessage);
return Response.status(500).entity(moe.getMsg()).build();
}
}
The problem here is that i have to do this exact same exception handling for each REST api endpoint. Can i do some kind of exception mapping for this particular resource so that i can put all the handling logic and logging there?
I know i can build a mapper for an particular exception in jersey, but that is for the whole module not a single class.
Afaig you can't register an ExceptionMapper to a resource method. I've tried this by implementing a DynamicFeature which was looking for a custom Annotation and then tried to register a custom ExceptionMapper with the FeatureContext.
The result was disillusioning:
WARNING: The given contract (interface javax.ws.rs.ext.ExceptionMapper) of class path.to.CustomExceptionMapper provider cannot be bound to a resource method.
Might not work:
But...
For a resource class this is in fact easy. Just register your ExceptionMapper for your resource class within your ResourceConfig. For me it looks like:
#ApplicationPath("/")
public class ApplicationResourceConfig extends ResourceConfig {
public ApplicationResourceConfig() {
// [...]
register(YourExceptionMapper.class, YourResource.class);
// [...]
}
}
So if you are okay with having this on resource class level, do it like this.
Otherwise you might need to use Aspects (but I don't see any reasons to do so). Example:
Aspect
#Aspect
public class ResourceAspect {
Logger logger = [...]
private static final String RESOURCE = "execution(public !static javax.ws.rs.core.Response path.to.resources..*(..)) && #annotation(path.to.HandleMyOwnException)";
#Around(RESOURCE)
public Object translateRuntimeException(ProceedingJoinPoint p) throws Throwable {
try {
return p.proceed();
} catch (MyOwnException moe) {
return Response.status(400).entity(moe.getMsg()).build();
} catch (Exception e) { //unexpected exceptions
logger.debug(e.getMessage);
return Response.status(500).entity(e.getMessage()).build();
}
}
}
Please notice, the RESOURCE config. Here it works for none static methods under path.to.resources which returning Response and are anntotated with the HandleMyOwnException annotation.
HandleMyOwnException
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface HandleMyOwnException {}
ResourceMethod
#GET
#PATH("/user")
#HandleMyOwnException
public Response getUser(#QueryParam("id") String userId) {
assertNotNull(userId);
return Response.ok(service.getUser(userId)).build();
}
pom.xml
<!-- deps -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.2</version> <!-- or newer version -->
</dependency>
<!-- build plugins -->
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<showWeaveInfo>true</showWeaveInfo>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<versionRange>[1.7,)</versionRange>
<goals>
<goal>compile</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
<plugins>
<pluginManagement>
Have a nice day!
EDITED
~ Added more complete pom.xml config
~ Corrected missing path for Annotation in ResourceAspect
Why not just factor out the exception handling into a private method?
#PATH(/user)
#GET
public Response getUser(#QueryParam("id") String userId) {
assertNotNull(userId);
return handleExceptions(() -> {
User user = service.getUser(userId);
return Response.ok(user).build();
});
}
private Response handleExceptions(Callable<Response> callable) {
try {
return callable.call();
}
catch (MyOwnException moe) { //basically 400's
return Response.status(400).entity(moe.getMsg()).build();
}
catch (Exception e) { //unexpected exceptions
logger.debug(e.getMessage);
return Response.status(500).entity(e.getMessage()).build();
}
}

Load own icon font in Vaadin

I created an icon font, using IcoMoon and tried following the instructions from Vaadin to use an own font
My files are structured like this:
|VAADIN
+---themes
+---my-theme
+---fonts
+---my-font-icons.ttf
|---...
|---addons.scss
|---my-theme.scss
|---styles.scss
In my styles.scss I have written following:
#import "addons.scss";
#import "my-theme.scss";
#include v-font(MyFontIcons, './fonts/my-font-icons');
.monitoring-theme {
#include addons;
#include my-theme;
}
But I'm not able to getting the font to work in Java.
public enum MyIconFont implements FontIcon {
BASKET(0xe905),
BOOKMARK(0x6e);
private static final String fontFamily = "MyFontIcons";
private int codepoint;
MyIconFont(int codepoint) {
this.codepoint = codepoint;
}
#Override
public String getMIMEType() {
throw new UnsupportedOperationException(FontIcon.class.getSimpleName() + " should not be used where a MIME type is needed.");
}
#Override
public String getFontFamily() {
return fontFamily;
}
#Override
public int getCodepoint() {
return codepoint;
}
#Override
public String getHtml() {
return "<span class=\"v-icon MyFontIcons\">&#x" + Integer.toHexString(codepoint) + ";</span>";
}
EDIT: Using the developer console in Chrome it looks like the font files aren't even loaded.
The .css file is created through maven, using:
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>
com.my.PluginThemeCompiler
</mainClass>
<arguments>
<argument>my-theme</argument>
</arguments>
</configuration>
</execution>
</executions>
The created .css file is located in the my-theme folder, but the theme is not included inside my vaadin application.
Is there any way to maybe load the font as a ClassResource and use the icons from there?
I think you will find that the v-font function serves files relative to the valo theme's font directory.
It should look something like:
#include v-font(MyFontIcons, '../../../../../my-theme/fonts/my-font-icons');

Categories

Resources