Say I have an annotation #Annotate and a class A. How can I check whether #Annotate is used on class A and its subclasses only (probably at compile time)?
So, if we have:
/* Example 1 */
public class A {
}
public class B extends A {
}
public class C {
}
How do I check that class A and class B can be annotated by #Annotate but class C is not allowed (and might raise a compile error).
If we go with the decision that this would be checked at compile time:
/* Example 2 */
public class A {
}
#Annotate
public class B extends A {
}
Example 2 will not raise a compile time error because #Annotate is used on a subclass of A. Example 3, however, will raise an compile error because #Annotate is not used on a subclass of A.
/* Example 3 */
#Annotate
public class C {
}
However, this does not have to be checked at compile time in anyway. I just personally thought that it makes sense to do so.
You should write an annotation processor. An annotation processor can generate new Java files and, more relevantly for your case, issue compile-time warnings.
You will invoke it at compile time by running the java compiler like this: javac -processor MyProcessor MyFile.java. If regular javac or your annotation processor issues any warnings, then compilation fails.
Your annotation processor's logic is specific to your #Annotate annotation and is simple: permit the annotation only on classes that subclass A. There are various tutorials on the web about how to write an annetation processor, in addition to the Oracle documentation I linked above.
You can use my checker-framework.
Just Simply add annotation #CheckType on your annotation
#Target(TYPE)
#CheckType(value = A.class, type = Type.EXTEND_ALL)
public #interface OnlyOnA {
}
Now it can check if it is annotated on A or its subclass.
It will raise a compile error
eclipse snapshot
maven snapshot
Related
Consider the following scenario:
Let say I have a class A in "src" folder of my project.
class A {
void foo() {
B b = new B();
}
}
Class B is defined in another jar which is included as a dependency in build.gradle
class B extends C {
}
Now, Class C is defined in another jar which will be provided on runtime and not on compile time. Gradle is able to compile Class A without error.
But, when I import Class c in Class A then it gives "class not found".
import other.C; // this line gives error
class A {
void foo() {
B b = new B();
}
}
Is this the desired behavior of Java compiler to ignore the Class C if it not imported directly?
Also, what happened if use a function in class A using object of B which is in Class C but not overridden in class B.
The exact answer to your question depends on the Java compiler version and whether or not it requires access to C for doing its job.
All in all, I would say that such a setup is fragile and you should not do it. If your library that defines A requires B which effectively makes use of C in its public API as is the case for extends then C should be made visible to your library.
First of all we need to understand how java compilers work.
Whatever you reference by name or as a 'token' in your code, should be accessible to compiler during compilation phase.
The classes that you load using Class.forload or getClass method are not required to be available in the classpath.
What you are essentially referring by compiletime and runtime is about packaging.
What you say that a particular dependency say Class C will be provided at runtime, its an instruction to bundle tasks to ignore that dependency while building the jar. So if you compile and deploy your application as jar, class C will not be present in it. At the same time, the jar containing class B will be included in your deployment package.
If you provide exact gradle file, I might be able to answer more precise.
How to add the #Override annotation to a method while creating the class using javaassist?
ClassPool pool = ClassPool.getDefault();
CtClass ctClasz = pool.makeClass("test.ExampleImpl");
ctClasz.addInterface(pool.get(MyInterface.class.getName()));
CtMethod method = CtNewMethod.make ("#Override public void print() { System.out.println(\"Hello! \"); }", ctClasz);
ctClasz.addMethod(method);
System.out.println("Implementd: Interfaces:" + ctClasz.getInterfaces());
System.out.println("Methods: " + ctClasz.getMethods());
ctClasz.writeFile("D:");
This code is throwing exception as follows:
Exception in thread "main" javassist.CannotCompileException: [source error] syntax error
near "#Override p"
at javassist.CtNewMethod.make(CtNewMethod.java:78)
at javassist.CtNewMethod.make(CtNewMethod.java:44)
at javaassist.Demo.main(Demo.java:17)
Caused by: compile error: syntax error near "#Override p"
at javassist.compiler.Parser.parseClassType(Parser.java:983)
at javassist.compiler.Parser.parseFormalType(Parser.java:191)
at javassist.compiler.Parser.parseMember1(Parser.java:51)
at javassist.compiler.Javac.compile(Javac.java:89)
at javassist.CtNewMethod.make(CtNewMethod.java:73)
... 2 more
#Override isn't a runtime annotation so even if you could add it, it wouldn't make any difference whatsoever.
For annotations that do have a runtime effect (RetentionPolicy.RUNTIME), you can take a look at this question.
Short Version
It isn't interesting to add the annotation. Because as this has #java.lang.annotation.Retention(value=java.lang.annotation.RetentionPolicy.SOURCE), it will not make any difference. So, you don't need to care about this issue.
I would care with annotations that has #java.lang.annotation.Retention(value=java.lang.annotation.RetentionPolicy.RUNTIME) retention.
Long Version
This anntotation has #java.lang.annotation.Retention(value=java.lang.annotation.RetentionPolicy.SOURCE) retention meaning it will not change anything even if you add it with JAVASSIST when you are generating some classes to use during runtime.
The annotation doesn't has any influence on the code. When Java generates the sources this is already stripped out. As JAVASSIST generates code it makes no sense add it.
According documentation, retention can be configured as:
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.
In JAVASSIST it would be interesting to add the RUNTIME or CLASS annotations (but for CLASS it would not be that interesting, see here).
#Override is only useful to the compiler.
It tells the compiler to ensure that the annotated method either:
a. Overrides a method on the superclass
b. Implements an interface method.
This becomes of particular importance when interfaces or superclasses change. Your class may otherwise compile, but the method you think is defining functionality on the interface or the superclass may no longer be doing that.
So the #Override annotation lets the compiler bark at you in that case.
Edit
Example:
public interface Foo {
void bar();
}
public class FooImpl {
public void bar() { ... }
}
public class MyFooExtension extends FooImpl {
public void bar() { .... }
}
Now, let's say Foo and FooImpl change:
public interface Foo {
void bar(String input);
}
public class FooImpl {
public void bar(String input) { ... }
}
You're MyFooExtension class would still compile, yet the "bar()" method in that class would never be called. Thus your method is useless. If you add the #Override annotation, you'd get a compile error telling you that no method "void bar()" is being overriden, and you would have to fix your class in order to get it to compile.
For the following custom Java annotation
#CustomAnnotation(clazz=SomeClass.class)
public class MyApplicationCode
{
...
}
I basically want to be able to grab both the Class object for the MyApplicationCode and the clazz parameter at compile time to confirm some coding convention consistencies (another story). Basically I want to be able to access MyApplicationCode.class and Someclass.class code in the annotation processor. I'm almost there but I'm missing something. I have
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.SOURCE)
public #interface CustomAnnotation
{
public Class clazz();
}
Then I have for the processor:
public class CustomAnnotationProcessor extends AbstractProcessor
{
private ProcessingEnvironment processingEnvironment;
#Override
public synchronized void init(ProcessingEnvironment processingEnvironment)
{
this.processingEnvironment = processingEnvironment;
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment environment)
{
Set<? extends Element> elements = environment.getElementsAnnotatedWith(ActionCommand.class);
for(Element e : elements)
{
Annotation annotation = e.getAnnotation(CustomAnnotation.class);
Class clazz = ((CustomAnnotation)annotation).clazz();
// How do I get the actual CustomAnnotation clazz?
// When I try to do clazz.getName() I get the following ERROR:
// Attempt to access Class object for TypeMirror SomeClass
// Also, how do I get the Class object for the class that has the annotation within it?
// In other words, how do I get MyApplicationCode.class?
}
}
}
So what I'm trying to do in the process method is to grab SomeClass.class and MyApplication.class from the original code below to do some custom validation at compile time. I can't seem for the life of me figure out how to get those two values...
#CustomAnnotation(clazz=SomeClass.class)
public class MyApplicationCode
Update: The following post has a lot more details, and it's much closer. But the problem is that you still end up with a TypeMirror object from which to pull the class object from, which it doesn't explain: http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/
Update2: You can get MyApplication.class by doing
String classname = ((TypeElement)e).getQualifiedName().toString();
I was going to point you in the direction of the blog http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/, but it looks like you already found that one.
I see you figured out how to access the MyApplication Element, so I wont cover that....
The exception you see actually contains the type of the annotation property within it. So you can reference the annotation clazz value when you catch the exception:
public class CustomAnnotationProcessor extends AbstractProcessor
{
private ProcessingEnvironment processingEnvironment;
#Override
public synchronized void init(ProcessingEnvironment processingEnvironment)
{
this.processingEnvironment = processingEnvironment;
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment environment)
{
Set<? extends Element> elements = environment.getElementsAnnotatedWith(ActionCommand.class);
for(Element e : elements)
{
CustomAnnotation annotation = e.getAnnotation(CustomAnnotation.class);
TypeMirror clazzType = null;
try {
annotation.clazz();
} catch (MirroredTypeException mte) {
clazzType = mte.getTypeMirror();
}
System.out.println(clazzType); // should print out SomeClass
}
}
}
Yes, this is a total hack of a solution, and I'm not sure why the API developers decided to go this direction with the annotation processor feature. However, I have seen a number of people implement this (including myself), and the article mentioned describes this technique as well. This seems to be an acceptable solution at the moment.
In terms of "grabbing" the class values for MyApplicationCode and SomeClass, you will not be able to do so if they are classes being compiled. You can, however, use the Element and TypeMirror representations to perform some high level validation on your classes (Method, Field, Class names, annotations present, etc)
After reading this related SO question, I found this excellent page about the Java Annotation Processing Tool (APT). It's from 2005 so may not be the best way to do this these days.
APT [...] is an annotation processing tool for Java. More specificially, APT allows you to plug code in to handle annotations in a source file as the code compilation is occurring - and in that process, you can emit notes, warnings, and errors.
More information about APT in Java 6 from Oracle's docs.
Interesting blog post from someone at Oracle about APT.
Another example usage of APT -- this time from 2009.
This is only for Oracle's JDK.
It is compile time. I would think the compiler is not even finished up compiling the source code. You retrieve such information from AnnotatedElement instance which will give you relevant information of the type you have annotated, but not its runtime properties, thats not yet available since the relevant class files are not yet loaded by the virtual machine. And the compiler is not even guaranteed to be running under a virtual machine for java, so it is not mandated to be able to load class files. Its requirement is only to be able to produce bytecodes that any particular virtual machine can read.
So go check on the mirror Api, and for any relevant information on the class/method/field you have annotated, check on AnnotatedElement representing that instance.
And as aside note: this is information is just what i reasoned up, so it might not be the actual truth.
I have the following situation:
I have a Java class hierarchy like this:
package org.foo.some;
public class Model extends org.foo.some.GenericModel { // ... }
package org.bar;
public class MyModel extends org.foo.some.Model { // ... }
where org.foo.some.Model and org.foo.some.GenericModel are out of my reach (not my code). In Scala, also out of my reach, there is:
package org {
package foo {
package object some {
type Model = org.foo.some.ScalaModel
}
}
}
This leads to a funny behavior in Scala code, e.g.
val javaModel:MyModel = new org.bar.MyModel()
trait FooTrait[T <: org.foo.some.GenericModel] { // ... }
class FooClass extends FooTrait[MyModel] { //... }
does not compile and raises the following error:
type arguments [org.bar.MyModel] do not conform to trait FooTrait's type
parameter bounds [T <: org.foo.some.GenericModel]
Further, I can't invoke any method of org.foo.some.Model nor of org.foo.some.GenericModel on javaModel:
javaModel.doSomething()
raises
value create is not a member of org.bar.MyModel
I am under the impression that the package object is "hijacking" the visibility of the Java class hierarchy in Scala code. Indeed, ScalaModel does not extend org.foo.some.GenericModel.
Is there maybe a way to still access the hierarchy from within Scala code?
Edit: when re-compiling the code out of my reach and removing the type re-definition, everything works. So I think what I'm looking at is a way to "disable" an package-level type definition for a specific class.
Are you using a GUI (in particular Eclipse) to build your project?
This seems related to Scala trouble accessing Java methods (that has no answer but where the general consensus is that the problem is not with scala but with Eclipse).
If a class defined an annotation, is it somehow possible to force its subclass to define the same annotation?
For instance, we have a simple class/subclass pair that share the #Author #interface.
What I'd like to do is force each further subclass to define the same #Author annotation, preventing a RuntimeException somewhere down the road.
TestClass.java:
import java.lang.annotation.*;
#Retention(RetentionPolicy.RUNTIME)
#interface Author { String name(); }
#Author( name = "foo" )
public abstract class TestClass
{
public static String getInfo( Class<? extends TestClass> c )
{
return c.getAnnotation( Author.class ).name();
}
public static void main( String[] args )
{
System.out.println( "The test class was written by "
+ getInfo( TestClass.class ) );
System.out.println( "The test subclass was written by "
+ getInfo( TestSubClass.class ) );
}
}
TestSubClass.java:
#Author( name = "bar" )
public abstract class TestSubClass extends TestClass {}
I know I can enumerate all annotations at runtime and check for the missing #Author, but I'd really like to do this at compile time, if possible.
You can do that with JSR 269, at compile time.
See : http://today.java.net/pub/a/today/2006/06/29/validate-java-ee-annotations-with-annotation-processors.html#pluggable-annotation-processing-api
Edit 2020-09-20: Link is dead, archived version here : https://web.archive.org/web/20150516080739/http://today.java.net/pub/a/today/2006/06/29/validate-java-ee-annotations-with-annotation-processors.html
I am quite sure that this is impossible to do at compile time.
However, this is an obvious task for a "unit"-test. If you have conventions like this that you would like enforced, but which can be difficult or impossible to check with the compiler, "unit"-tests are a simple way to check them.
Another possibility is to implement a custom rule in a static analyzer. There are many options here, too.
(I put unit in scare-quotes, since this is really a test of conventions, rather than of a specific unit. But it should run together with your unit-tests).
You could make an Annotation (e.g. #EnforceAuthor) with #Inherited on the superclass and use compiler annotations (since Java 1.6) to catch up at compile time. Then you have a reference to the subclass and can check if another Annotation (e.g. #Author)) is missing. This would allow to cancel compiling with an error message.