Reputation: 11946
I'm working on an annotation which aims to it mandatory for a class to be immutable. Here the code of the processor:
@SupportedAnnotationTypes("archipel.immutability.IsImmutable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class IsImmutableProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (TypeElement type : annotations) {
processMustBeImmutable(roundEnv, type);
}
return true;
}
private void processMustBeImmutable(RoundEnvironment env, TypeElement type) {
for (Element element : env.getElementsAnnotatedWith(type)) {
processClass(element);
}
}
private void processClass(Element element) {
boolean isFinal=false;
for(Modifier modifier : element.getModifiers()) {
if (modifier.equals(Modifier.FINAL)) {
isFinal=true;
break;
}
}
if (!isFinal) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Class "+element+" is not immutable because it is not final");
} else {
for (Element subElement : element.getEnclosedElements()) {
if (subElement.getKind()==ElementKind.FIELD) {
isFinal=false;
for(Modifier modifier : subElement.getModifiers()) {
if (modifier.equals(Modifier.FINAL)) {
isFinal=true;
break;
}
}
if (!isFinal) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Field "+element+" is not immutable because it is not final");
} else {
Element superElement = subElement.getEnclosingElement();
// TODO
}
}
}
}
}
}
The annotation itself is trivial, of course:
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface IsImmutable {
}
And I compile it with a Ant script:
<project name="immutability" basedir="." default="main">
<property name="lib.dir" value="lib"/>
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="classes.dir" value="${build.dir}/classes"/>
<property name="meta.dir" value="${build.dir}/META-INF"/>
<property name="jar.dir" value="${build.dir}/jar"/>
<property name="processor-package"
value="archipel.immutability" />
<property name="processor" value="${processor-package}.IsImmutableProcessor"/>
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
<fileset dir="${classes.dir}"/>
</path>
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="compile" description="Compiles the code.">
<mkdir dir="${classes.dir}"/>
<javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath" />
</target>
<target name="jar" depends="compile">
<mkdir dir="${jar.dir}"/>
<jar destfile="${jar.dir}/${ant.project.name}.jar">
<fileset dir="${classes.dir}"/>
<service type="javax.annotation.processing.Processor" provider="archipel.immutability.IsImmutableProcessor"/>
</jar>
</target>
<target name="main" depends="clean,jar"/>
</project>
Problem is, something must be missing, because when I try to use the resulting annotation, provided by the resulting jar file, like the following, nothing happens:
@IsImmutable
public class Immut {
private int toto;
public int getToto() {
return toto;
}
public void setToto(int toto) {
this.toto = toto;
}
public final static void main(String args[]) {
Immut truc = new Immut();
truc.setToto(5);
truc.setToto(6);
}
}
Obviousy, this class is not final, and the class should signal an error in Eclipse. But it's not.
Any idea?
Edit: The jar file I built with my build.xml seems correct: It contains the class files, and also a META-INF/services/javax.annotation.processing.Processor
file, which contains archipel.immutability.IsImmutableProcessor
. I import this jar file in my test project, and when I use the annotation in my Immut class (which is only a rough test), nothing happens.
Upvotes: 6
Views: 11706
Reputation: 3170
probably it is worth to use cglib to make you object immutable. because imagine that you have a collection inside your class and collection field is final. You have also getter for this collection. does it mean that collection can't be modified outside teh class? not exactly. Only if this collection was wrapped with Collections.unmodifiedCollection method. Or if you are using guava immutable collections http://code.google.com/p/guava-libraries/wiki/ImmutableCollectionsExplained
The point is - don't try to hanle this only be checking whether fields are final - it will not handle all scenarios. use cglib proxy instead like spring and hibernate does
Upvotes: 0
Reputation: 1097
Annotation processing by default is disabled in eclipse. in order to enable it you need to open project properties and then 1. Java Compiler -> Annotation processing: enable annotation processing 2. Java Compiler -> Annotation processing -> factory path: add jar with factories
if you need some more information take a look at: getting started with AP in eclipse
Edited:
take a look at this tutorial. It explains in details how to set up eclipse to use custom annotation processors.
Important: when printing errors use this method: javax.annotation.processing.Messager.printMessage(Kind, CharSequence, Element)
instead of:
javax.annotation.processing.Messager.printMessage(Kind, CharSequence).
messages from the first one are visible in Problems view and most source-related views while messages from the second one are visible only in ErrorLog view.
Upvotes: 11