LoginSignup
0
1

More than 5 years have passed since last update.

注釈処理で遭遇したよくわからないエラー※未解決

Posted at

自作の注釈処理を作ってたらよくわからないエラーに遭遇した。
わかっている範囲でどういうエラーかメモ。

環境

Java

openjdk 10.0.2

Gradle

Gradle 4.10

Eclipse

4.8.0 (Photon)Pleiades

実装

フォルダ構成
|-settings.gradle
|-build.gradle
|-foo/
| `-src/main/java/
|   `-foo/
|     |-Foo.java
|     |-ConcreteClass.java
|     |-MyAnnotation.java
|     `-MyInterface.java
|
`-bar/
  `src/main/
   |-java/
   | `-bar/
   |   `-MyProcessor.java
   `-resources/
     `-META-INF/services/
       `-javax.annotation.processing.Processor
  • foo, bar のマルチプロジェクト
  • bar が注釈処理を実装していて、 foo がそれを利用している
MyProcessor.java
package bar;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;

public class MyProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!annotations.isEmpty()) {
            try {
                JavaFileObject javaFileObject = this.processingEnv.getFiler().createSourceFile("foo.AutoGeneratedClass");
                try (BufferedWriter writer = new BufferedWriter(javaFileObject.openWriter())) {
                    writer.write(
                        "package foo;\n" +
                        "import foo.MyInterface;\n" +
                        "public class AutoGeneratedClass implements MyInterface {}"
                    );
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

        return true;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of("foo.MyAnnotation");
    }
}
  • MyAnnotation アノテーションを処理対象とした注釈処理を実装
  • MyInterface インターフェースを実装した AutoGeneratedClass というクラスを自動生成している
MyInterface.java
package foo;

public interface MyInterface {}
  • ただのインターフェース
MyAnnotation.java
package foo;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    Class<? extends MyInterface> clazz();
}
  • 処理対象のアノテーション
  • clazz 要素は、 MyInterface を上限とする Class インスタンスを受け付ける
ConcreteClass.java
package foo;

public class ConcreteClass extends AutoGeneratedClass {}
  • 注釈処理が自動生成する AutoGeneratedClass を継承して ConcreteClass を作る
Foo.java
package foo;

@MyAnnotation(clazz=ConcreteClass.class)
public class Foo {}
  • MyAnnotation を実際に使っているクラス
  • clazz 要素には ConcreteClassClass インスタンスを設定している

クラス図で整理

apt.jpg

発生する事象

これをコンパイルすると次のようになる。

> gradle :foo:compileJava
> Task :foo:compileJava FAILED
...\foo\src\main\java\foo\ConcreteClass.java:3: エラー: シンボルを見つけられません
public class ConcreteClass extends AutoGeneratedClass {
                                   ^
  シンボル: クラス AutoGeneratedClass
...\foo\src\main\java\foo\Foo.java:3: エラー: 不適合な型: Class<ConcreteClass>をClass<? extends MyInterface>に変換できません:
@MyAnnotation(clazz=ConcreteClass.class)
                                 ^
エラー2個
  • 自動生成されるはずの AutoGeneratedClass が見つけられず ConcreteClass のコンパイルに失敗している

いろいろ試す

Eclipse だと発生しない

  • このプロジェクトをそのまま Eclipse に取り込んで注釈処理を実行すると、なぜかエラーは発生しない。
  • 試しに Gradle を使わずに、素の javac コマンドのみでコンパイルしてみたが、やはりエラーは発生した

型引数の上限の指定をやめると発生しなくなる

MyAnnotation.java
package foo;

...

public @interface MyAnnotation {
-   Class<? extends MyInterface> clazz();
+   Class<?> clazz();
}
  • MyAnnotationclazz 要素に指定していた MyInterface の上限を外す
コンパイル結果
> gradle :foo:compileJava
...
BUILD SUCCESSFUL in 3s
  • すると、コンパイルが通るようになる
  • 謎い

上限を自動生成されるクラスにするとエラーにならない

MyAnnotation.java
package foo;

...
public @interface MyAnnotation {
-   Class<? extends MyInterface> clazz();
+   Class<? extends AutoGeneratedClass> clazz();
}
  • clazz の上限を MyInterface から自動生成クラスである AutoGeneratedClass に変更する
コンパイル結果
> gradle :foo:compileJava
...
BUILD SUCCESSFUL in 3s
  • コンパイルは通るようになる
  • 謎い

clazz で指定するのを自動生成されたクラスにしてもエラーにならない

Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation(clazz=AutoGeneratedClass.class)
public class Foo {}
  • MyAnnotation の方は Class<? extends MyInterface> の上限指定にしている
コンパイル結果
...
BUILD SUCCESSFUL in 4s
  • こちらもコンパイルは通る
  • インターフェース -> 自動生成クラス -> 自作クラス の関係になったときにエラーになるっぽい

ConcreteClass に依存すること自体は問題ない

Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
public class Foo {
+   private Class<?> clazz = ConcreteClass.class;
}
  • MyAnnotationclazz 要素を削除
  • フィールドで ConcreteClass を使用
  • ConcreteClass.class も参照してみる
コンパイル結果
> gradle :foo:compileJava
BUILD SUCCESSFUL in 3s
  • コンパイルは通る
  • 自動生成されたクラスのサブクラスに依存すること自体は問題ないみたい

処理対象でないアノテーションで参照してもダメ

OtherAnnotation.java
package foo;

...

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OtherAnnotation {
    Class<? extends MyInterface> clazz();
}
  • MyAnnotation と同じ定義の OtherAnnotation.java を作成
Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
+ @OtherAnnotation(clazz=ConcreteClass.class)
public class Foo {}
  • FooOtherAnnotation を追加
  • MyAnnotation の方の clazz 要素は削除している
コンパイル結果
> gradle :foo:compileJava
> Task :foo:compileJava FAILED
...\foo\src\main\java\foo\ConcreteClass.java:3: エラー: シンボルを見つけられません
public class ConcreteClass extends AutoGeneratedClass {
                                   ^
  シンボル: クラス AutoGeneratedClass
...\foo\src\main\java\foo\Foo.java:4: エラー: 不適合な型: Class<ConcreteClass>をClass<? extends MyInterface>に変換できません:
@OtherAnnotation(clazz=ConcreteClass.class)
                                    ^
エラー2個
  • MyAnnotation のときと同じようにエラーになった
  • 注釈処理の対象としたアノテーションかどうかは関係ないっぽい

注釈処理と関係ないところで書いててもダメ

OtherClass.java
package foo;

@OtherAnnotation(clazz=ConcreteClass.class)
public class OtherClass {}
Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
public class Foo {}
  • OtherAnnotation は1つ前のやつと同じもの
  • 注釈処理とは関係ない OtherClass の中で @OtherAnnotation(clazz=ConcreteClass.class) を使用している
コンパイル結果
> Task :foo:compileJava FAILED
...\foo\src\main\java\foo\ConcreteClass.java:3: エラー: シンボルを見つけられません
public class ConcreteClass extends AutoGeneratedClass {
                                   ^
  シンボル: クラス AutoGeneratedClass
...\foo\src\main\java\foo\OtherClass.java:3: エラー: 不適合な型: Class<ConcreteClass>をClass<? extends MyInterface>に変換できません:
@OtherAnnotation(clazz=ConcreteClass.class)
                                    ^
エラー2個
  • 直接の注釈処理対象となるクラス以外であっても、 ConcreteClass.class をアノテーションの要素で参照している場合はエラーになるっぽい

症状を整理

いろいろ試した結果を整理すると、次のような条件を満たすときにエラーになるっぽい?

  • アノテーションの要素で、 Class インスタンスを指定している
  • その Class インスタンスは、「自作インターフェース -> 注釈処理で自動生成されるクラス -> 自作クラス」という階層関係があるなかの「自作クラス」である
  • アノテーション側の Class インスタンスを受け取る要素の定義が、 Class<? extends 自作インターフェース> となっている
  • OpenJDK の注釈処理で実行する

結論

よくわからない。

0
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1