8

私はJavaコードを生成することになっているAnnotationProcessorを書いています。特定の既存のインターフェイスから派生インターフェイスを生成する必要があります。

この目的のために、元の入力コードのインポートステートメントを見つけて、生成されたJavaファイルに出力できるようにする必要があります。

これはどのように行うことができますか?

4

3 に答える 3

9

注釈プロセッサを使用してインポートステートメントを取得することはできません。ただし、取得できるのは、そのクラスで使用される型です。これはさらに優れています。

使用されているすべての型に import ステートメントがあるわけではないため、ソース コードからの import ステートメントは、クラスで使用されている型を分析するのに十分ではありません。実際のステートメントのみが本当に必要な場合は、ソース ファイルを直接読み取ることができます。

ステートメントだけを見ると、いくつかの問題があります。

  • プロパティなどの完全修飾クラス名java.util.Date date;
  • 同じパッケージからのインポートには、明示的なインポート ステートメントがありません
  • imports ステートメントは、ファイル内のすべてのクラスに対して宣言されています
  • 未使用の import ステートメントはさらに混乱を招く可能性があります

アノテーション プロセッサとMirror APIを使用すると、プロパティの型、メソッド パラメータ、メソッドの戻り値の型などを取得できます。基本的には、メソッドまたはブロックに含まれていないすべての宣言の型です。これで十分です。

クラスのすべての要素を分析し、その型を Set に格納する必要があります。このタスクに役立つユーティリティ クラスがいくつかあります。java.langパッケージ内のタイプは常に暗黙的にインポートされるため、無視できます。

最小限の注釈プロセッサは次のようになります。

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;

@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("*")
public class Processor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        ImportScanner scanner = new ImportScanner();
        scanner.scan(roundEnv.getRootElements(), null);

        Set<String> importedTypes = scanner.getImportedTypes();
        // do something with the types

        return false;
    }

}

ここでのスキャナーElementScanner7は、訪問者のパターンに基づいて拡張されています。すべての要素が実際にインポート可能な型を含むことができるわけではないため、いくつかのビジター メソッドのみを実装し、種類によって要素をフィルター処理します。

import java.util.HashSet;
import java.util.Set;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementScanner7;

public class ImportScanner extends ElementScanner7<Void, Void> {

    private Set<String> types = new HashSet<>();

    public Set<String> getImportedTypes() {
        return types;
    }

    @Override
    public Void visitType(TypeElement e, Void p) {
        for(TypeMirror interfaceType : e.getInterfaces()) {
            types.add(interfaceType.toString());
        }
        types.add(e.getSuperclass().toString());
        return super.visitType(e, p);
    }

    @Override
    public Void visitExecutable(ExecutableElement e, Void p) {
        if(e.getReturnType().getKind() == TypeKind.DECLARED) {
            types.add(e.getReturnType().toString());
        }
        return super.visitExecutable(e, p);
    }

    @Override
    public Void visitTypeParameter(TypeParameterElement e, Void p) {
        if(e.asType().getKind() == TypeKind.DECLARED) {
            types.add(e.asType().toString());
        }
        return super.visitTypeParameter(e, p);
    }

    @Override
    public Void visitVariable(VariableElement e, Void p) {
        if(e.asType().getKind() == TypeKind.DECLARED) {
            types.add(e.asType().toString());
        }
        return super.visitVariable(e, p);
    }

}

このスキャナーは、一連の型を完全修飾パスとして返します。まだ考慮すべき点と実装すべき点がいくつかあります。

  • java.langセットには、同じパッケージの要素と型が含まれています
  • セットには、次のようなジェネリックが含まれていますjava.util.List<String>
  • TypeKind.DECLAREDインポート可能な型である唯一の種類の要素ではありません。またTypeKind.ARRAY、実際に宣言された型を確認して取得します。else if(e.asType().getKind() == TypeKind.ARRAY) // ...クラスに別のものを追加する代わりに、代わりTypeKindVisitor7に使用できます
  • ユースケースによっては、さらに多くのタイプが検出される場合があります。たとえば、注釈にはクラスを引数として含めることができます。
  • Java 1.6 の場合は、それぞれElementScanner6TypeKindVisitor6などの実装を使用します。
于 2013-09-13T02:17:17.797 に答える
4

標準の SDK クラスから import ステートメントを取得する方法がないようです (少なくとも SDK 5-6-7 では)。

ただし、SUN/Oracle の tools.jar 内のいくつかのクラスを使用できます。

import com.sun.source.util.TreePath;
import com.sun.source.util.Trees;

public class MyProcessor extends AbstractProcessor {

    @Override
    public void init(ProcessingEnvironment env) {
        tree = Trees.instance(env);
    }

    @Override
    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) {
        for( Element rootElement : roundEnvironment.getRootElements() ) {
            TreePath path = tree.getPath(rootElement);
            System.out.println( "root element "+rootElement.toString() +" "+path.getCompilationUnit().getImports().size() );
        }
....

Maven 経由で Java ツールの jar を取得するには、このスレッドを参照してください。

TreePathScanner (tools.jar からも) を使用する代替手段があるはずですが、visitImport メソッドがトリガーされることはありませんでした。

于 2013-09-12T23:45:01.683 に答える
1
<dependency>
    <groupId>org.jvnet.sorcerer</groupId>
    <artifactId>sorcerer-javac</artifactId>
    <version>0.8</version>
</dependency>

この依存関係を使用すると問題は解決しますが、残念ながら今回は Java 1.7 までサポートされており、Java 1.8 ソースを正しくコンパイルできません。私の解決策は少しハックですが、この依存関係を使用せずにJava 1.8ソースを使用しても機能します

public final class SorcererJavacUtils {
private static final Pattern IMPORT = Pattern.compile("import\\s+(?<path>[\\w\\\\.]+\\*?)\\s*;");

// com.sun.tools.javac.model.JavacElements
public static Set<String> getImports(Element element, ProcessingEnvironment processingEnv) {
    Elements elements = processingEnv.getElementUtils();
    Class<?> cls = elements.getClass();

    try {
        Method getTreeAndTopLevel = cls.getDeclaredMethod("getTreeAndTopLevel", Element.class);
        getTreeAndTopLevel.setAccessible(true);
        // Pair<JCTree, JCCompilationUnit>
        Object treeTop = getTreeAndTopLevel.invoke(elements, element);

        if (treeTop == null)
            return Collections.emptySet();

        // JCCompilationUnit
        Object toplevel = getFieldValue("snd", treeTop);

        return SorcererJavacUtils.<List<Object>>getFieldValue("defs", toplevel).stream()
                                                                               .map(Object::toString)
                                                                               .map(IMPORT::matcher)
                                                                               .filter(Matcher::find)
                                                                               .map(matcher -> matcher.group("path"))
                                                                               .collect(Collectors.toSet());
    } catch(Exception ignored) {
        return Collections.emptySet();
    }
}

private static <T> T getFieldValue(String name, Object obj) throws IllegalAccessException, NoSuchFieldException {
    Field field = obj.getClass().getDeclaredField(name);
    field.setAccessible(true);
    return (T)field.get(obj);
}

private SorcererJavacUtils() {
}

}

于 2016-05-20T06:08:02.713 に答える