This discussion is archived
10 Replies Latest reply: Mar 6, 2010 10:56 AM by 843793 RSS

Determine removed annotations and/or deleted classes

843793 Newbie
Currently Being Moderated
For those, who did not read my previous thread, first some background information: I'm developing an annotation processor based on Java 6, that should store it's state in a file. The processor creates a single class based on annotations in several user classes. Thus it needs to know all these classes, even if only one class changes. In order to achieve this, it stores the already gathered information in a file and reloads this file everytime the processor get's initialized.

My question is: Is there a way, to determine, when an annotation gets removed or when an annotated class gets deleted?

When the code is built incrementally, I do not know how to distinguish between a class that is not compiled because it is deleted and one that is still there and did not change. I determine the annotated classes with
RoundEnvironment.getElementsAnnotatedWith(...)
This returns only those classes that changed and thus get compiled. I tried to check for existence of the classes that my processor already knows with
class.forName(...)
but this does not work - probably because the compiled classes are not on the classpath when the processor runs. Any hints? Is it possible at all?
  • 1. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    >
    My question is: Is there a way, to determine, when an annotation gets removed or when an annotated class gets deleted?
    There are no direct hooks for a processor to be informed of that event, but there is infrastructure to support such operations in the annotation processing framework.

    For example, the originating-elements parameters on the Filer's createFoo methods are intended to register dependencies to allow the system to re-run processors to update output elements if their dependencies have changed, including if a file has been deleted.
    When the code is built incrementally, I do not know how to distinguish between a class that is not compiled because it is deleted and one that is still there and did not change. I determine the annotated classes with
    RoundEnvironment.getElementsAnnotatedWith(...)
    This returns only those classes that changed and thus get compiled. I tried to check for existence of the classes that my processor already knows with
    class.forName(...)
    but this does not work - probably because the compiled classes are not on the classpath when the processor runs. Any hints? Is it possible at all?
    The proper way to check for a type existing in the annotation processing environment is to call javax.lang.model.util.Elements.getTypeElement.
  • 2. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    Hello j.d.darcy,

    thank you for your help.
    j.d.darcy wrote:
    The proper way to check for a type existing in the annotation processing environment is to call javax.lang.model.util.Elements.getTypeElement.
    This sounds good. But my first attempt failed. It seems as if I can only access changed or new files. This is what I did:
    - Add class A
    - Compile
    - processor runs
    - Add class B
    - Compile
    - processor runs. Class B can be found. Class A cannot be found.

    I probably need some time to check this out. I will come back at the end of the week.

    Greetings

    yawah
  • 3. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    yawah wrote:
    Hello j.d.darcy,

    thank you for your help.
    j.d.darcy wrote:
    The proper way to check for a type existing in the annotation processing environment is to call javax.lang.model.util.Elements.getTypeElement.
    This sounds good. But my first attempt failed. It seems as if I can only access changed or new files. This is what I did:
    - Add class A
    - Compile
    - processor runs
    - Add class B
    - Compile
    - processor runs. Class B can be found. Class A cannot be found.

    I probably need some time to check this out. I will come back at the end of the week.
    I recommend checking that your sourepath and classpath settings are configured properly.
  • 4. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    j.d.darcy wrote:

    I recommend checking that your sourepath and classpath settings are configured properly.
    I am using hickory for testing purpose and thought, this would manage sourcepath and classpath for me. At least I did not find a way to change these settings for the Compilation class instance. I will add some logging to my processor, so that I can test if there are differences, when using the processor in a "real" project...

    Here is my code:
         Compilation vCompilation = new Compilation();
         InputStream vSourceStream = ClassLoader.getSystemResourceAsStream("testsrcfiles/SimpleClass.testsrc");
         HickoryUtil.addSource(vCompilation, vSourceStream, "testsrcfiles.SimpleClass");
    
         vCompilation.doCompile(new PrintWriter(System.out));
    
         System.out.println("Actions-Klasse:");
         System.out.println(vCompilation.getGeneratedSource("com/actionap/Actions.java"));
         
         Compilation v2ndCompilation = new Compilation(vCompilation);
         vSourceStream = ClassLoader.getSystemResourceAsStream("testsrcfiles/AnotherSimpleClass.testsrc");
         HickoryUtil.addSource(v2ndCompilation, vSourceStream, "testsrcfiles.AnotherSimpleClass");
    
         v2ndCompilation.doCompile(new PrintWriter(System.out));
    The HickoryUtil.addSource method looks like this:
        public static void addSource(Compilation aCompilation, InputStream aSourceStream, String aClassName)
             throws FileNotFoundException, IOException {
         MemSourceFileObject vNewClass = aCompilation.addSource(aClassName);
         Writer vClassWriter = vNewClass.openWriter();
    
         IOUtils.copy(aSourceStream, vClassWriter);
    
         vClassWriter.close();
        }
    In the init method of my processor I check for existence of the classes "testsrcfiles.SimpleClass" and "testsrcfiles.AnotherSimpleClass". During the second compilation the first one returns null and the second one is found. This is how I do the checking:
    element1 = elementUtil.getTypeElement("testsrcfiles.SimpleClass");
    element2 = elementUtil.getTypeElement("testsrcfiles.AnotherSimpleClass");
    elementUtil is initialized by calling getElementUtils() on the processing enironment that is passed to the init method of the processor.
  • 5. Re: Determine removed annotations and/or deleted classes
    608410 Newbie
    Currently Being Moderated
    This works for me
        public void testGeneratedElementsVisibleInNextCompile() {
            Compilation compilation = new Compilation();
            compilation.addSource("com.example.Foo")
                    .addLine("package com.example;")
                    .addLine(" public class Foo {}");
            ElementFinderProcessor processor = new ElementFinderProcessor();
            compilation.useProcessor(processor);
            compilation.doCompile(null);
            assertTrue(processor.processDone);
            assertTrue(processor.elements.get("com.example.Foo"));
            assertFalse(processor.elements.get("com.example.Bar"));
    
            // now second compile "com.example.Bar" and see if com.example.Foo is visible
            compilation = new Compilation(compilation);
            compilation.addSource("com.example.Bar")
                    .addLine("package com.example;")
                    .addLine("public class Bar {}");
            processor = new ElementFinderProcessor();
            compilation.useProcessor(processor);
            compilation.doCompile(null);
            assertTrue(processor.processDone);
            assertTrue(processor.elements.get("com.example.Foo"));
            assertTrue(processor.elements.get("com.example.Bar"));
        }
    
        @SupportedAnnotationTypes("*")
        static class ElementFinderProcessor extends AbstractProcessor {
            private Map<String,Boolean> elements = new HashMap<String, Boolean>();
            boolean processDone;
    
            public ElementFinderProcessor() {
                elements.put("com.example.Foo", false);
                elements.put("com.example.Bar", false);
            }
    
            @Override
            public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
                processDone = true;
                Elements util = processingEnv.getElementUtils();
                for(String name : elements.keySet()) {
                    if(util.getTypeElement(name) != null) {
                        elements.put(name, true);
                    }
                }
                return true;
            }
        }
    Are you certain that you have no typos anywhere? or code that is subtly different from what you have posted?




    To answer your original question.

    You can detect classes that have had annotations removed as follows:
    - keep your persistent data in a map, using the name of the outermost TypeElement as the key
    - each run, load the persisted data, remove any belonging to any of the elements being compiled (^1^), add any based on annotations in the round, regenerate source from collected data.

    (1 this is the critical step, don't just remove/replace the data for the annotations being processed - use ProcessingEnvironment.getRootElements() to remove all existing data for all classes being compiled)
    The hickory StateSaver does this and it works fine.

    For classes that have been deleted you can't detect that, and neither can the compiler because it will still see the class files left around from previous compile when the source existed. To tidy this up you need to do a clean and build, which will clean your persisted state from CLASS_OUTPUT :)

    Some IDEs can track the dependencies so that when you delete a source file, they can remove derived files (class files but also generated source and even your persisted state). So as Joe said, you should write your processor to specify all source elements from which things are created, but it can be hard, and not all IDEs use this info.
  • 6. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    Thank you both for all this helpful input. There are many things now, that I have to think about and to try out. I will come back next week after doing this.

    Greetings

    yawah
  • 7. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    It took a while, but now I found the reason, why it did not work in my code. If I add the following to the process method of Bruce's code, it will fail:
             if (roundEnv.processingOver()) {
              Filer filer = processingEnv.getFiler();
              try {
                  filer.createSourceFile("com/example/FooBar");
              } catch (IOException vException) {
                  processingEnv.getMessager().printMessage(Kind.ERROR, "Error when creating FooBar");
              }
             }
    And if you replace
    com/example/FooBar
    by
    com.example.FooBar
    It succeeds.

    I will need a while now, to figure out, if this solves my problem completely. I will let you know as soon as possible.

    Greetings

    yawah
  • 8. Re: Determine removed annotations and/or deleted classes
    608410 Newbie
    Currently Being Moderated
    To be expected since [Filer.createSourceFile|http://java.sun.com/javase/6/docs/api/javax/annotation/processing/Filer.html#createSourceFile(java.lang.CharSequence,%20javax.lang.model.element.Element...)] says
    Parameters:
    name - canonical (fully qualified) name of the principal type being declared in this file...
  • 9. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    brucechapman wrote:
    To be expected since [Filer.createSourceFile|http://java.sun.com/javase/6/docs/api/javax/annotation/processing/Filer.html#createSourceFile(java.lang.CharSequence,%20javax.lang.model.element.Element...)] says
    Parameters:
    name - canonical (fully qualified) name of the principal type being declared in this file...
    Yes, you are right. If I had read the apidoc more thoroughly before using the method, I would have saved a lot of time...
  • 10. Re: Determine removed annotations and/or deleted classes
    843793 Newbie
    Currently Being Moderated
    So this is how it looks now:

    I log every class and method, that has an annotation, that is processed by my processor.
    Everytime, the processor is initialized, I check all logged classes. If one does not exist, it gets removed. If one does not hold any method which is annotated by an annotation, that is processed by my processor, it gets removed.
    When the processor creates the class, all originating TypeElements are passed as parameter of the createSourceFile-Method.

    I did only superficial tests, but it seems to work now - at least, when my processor get's called. Unfortunately eclipse does not call the processor, when I delete a file that contains annotated elements. The same applies, when I remove the last annotation in a file. That's not perfect, but as Bruce stated, it seems to be normal.
    As soon as I edit another annotated class or do a clean on my project everything is fine.

    Thank you both again for your helpful and informative answers.

    Greetings

    yawah