web-dev-qa-db-ja.com

意図的にカスタムJavaコンパイラ警告メッセージを発生させる方法は?

外部リソースが修正されるのを待つ間にブロッキングの問題を回避するために、Iい一時的なハックをしようとしています。大きな恐ろしいコメントと多数のFIXMEでマークを付けることは別として、コンパイラーにリマインダーとして明らかな警告メッセージを投げてもらいたいので、これを忘れないようにします。たとえば、次のようなもの:

[javac] com.foo.Hacky.Java:192: warning: FIXME temporary hack to work around library bug, remove me when library is fixed!

私が選んだメッセージで意図的なコンパイラ警告を引き起こす方法はありますか?それに失敗すると、既存の警告をスローするためにコードに追加する最も簡単なことは何ですか?おそらく警告メッセージに印刷されるように問題のある行の文字列にメッセージがありますか?

編集:廃止されたタグは私のために何もしていないようです:

/**
 * @deprecated "Temporary hack to work around remote server quirks"
 */
@Deprecated
private void doSomeHackyStuff() { ... }

EclipseまたはSun javac 1.6(antスクリプトから実行)でコンパイラまたはランタイムエラーが発生せず、間違いなく関数を実行しています。

74
pimlottc

私が使用したのを見たテクニックの1つは、これを単体テストに結びつけることです(あなたはdo単体テストですよね?)。基本的に、外部リソースの修正が達成されたら、failsとなる単体テストを作成します。次に、そのユニットテストをコメントして、問題が解決したら、ひどいハックを元に戻す方法を他の人に伝えます。

このアプローチについて本当に洗練されているのは、ハックを取り消すための引き金がコアの問題自体の修正であるということです。

36
Kevin Day

コンパイラーによって処理されるカスタム注釈が解決策だと思います。実行時に処理するカスタムアノテーションを頻繁に作成しますが、コンパイル時にそれらを使用することはありません。だから、私はあなたにあなたが必要とするかもしれないツールへのポインタを与えることができるだけです:

  • カスタム注釈タイプを作成します。 このページ は注釈の書き方を説明しています。
  • カスタムアノテーションを処理して警告を発するアノテーションプロセッサを作成します。このような注釈プロセッサを実行するツールは、APTと呼ばれます。紹介は このページ にあります。 APT APIで必要なのはAnnotationProcessorEnvironmentであり、これにより警告を発行できます。
  • From Java 6、APTはjavacに統合されています。つまり、javacコマンドラインで注釈プロセッサを追加できます。 javacマニュアルのこのセクション では、カスタムアノテーションプロセッサを呼び出す方法について説明します。

この解決策が本当に実行可能かどうかはわかりません。時間があれば、自分で実装してみます。

編集

ソリューションを正常に実装しました。また、ボーナスとして、Javaのサービスプロバイダー機能を使用して、Javaの使用を簡素化しました。実際、私のソリューションは、カスタムアノテーションとアノテーションプロセッサの2つのクラスを含むjarです。それを使用するには、プロジェクトのクラスパスにこのjarを追加し、必要なものに注釈を付けます!これは、私のIDE(NetBeans)内で正常に動作しています。

注釈のコード:

package fr.barjak.hack;

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

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
public @interface Hack {

}

プロセッサのコード:

package fr.barjak.hack_processor;

import Java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

@SupportedAnnotationTypes("fr.barjak.hack.Hack")
public class Processor extends AbstractProcessor {

    private ProcessingEnvironment env;

    @Override
    public synchronized void init(ProcessingEnvironment pe) {
        this.env = pe;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            for (TypeElement te : annotations) {
                final Set< ? extends Element> elts = roundEnv.getElementsAnnotatedWith(te);
                for (Element elt : elts) {
                    env.getMessager().printMessage(Kind.WARNING,
                            String.format("%s : thou shalt not hack %s", roundEnv.getRootElements(), elt),
                            elt);
                }
            }
        }
        return true;
    }

}

結果のjarをサービスプロバイダーとして有効にするには、ファイルMETA-INF/services/javax.annotation.processing.Processor jar。このファイルはacsiiファイルであり、次のテキストを含める必要があります。

fr.barjak.hack_processor.Processor
85
barjak

いくつかの迅速でそれほど汚くないアプローチは、意図的に間違ったString引数で@SuppressWarningsアノテーションを使用することです。

@SuppressWarnings("FIXME: this is a hack and should be fixed.")

これは、コンパイラーが抑制する特定の警告として認識しないため、警告を生成します。

サポートされていない@SuppressWarnings( "FIXME:これはハックであり、修正する必要があります。")

13
Luchostein

良いハックは別のものに値します...私は通常、ハッキングメソッドに未使用の変数を導入することにより、説明した目的でコンパイラの警告を生成します。

/**
 * @deprecated "Temporary hack to work around remote server quirks"
 */
@Deprecated
private void doSomeHackyStuff() {
    int FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed;
    ...
}

この未使用の変数は、次のような警告を生成します(コンパイラによって異なります)。

警告:ローカル変数FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixedは読み込まれません。

このソリューションは、カスタムアノテーションほど優れていませんが、事前の準備が不要であるという利点があります(未使用の変数に対して警告を発行するようにコンパイラが既に構成されていると仮定)。このアプローチは、短命のハッキングにのみ適していることをお勧めします。長命のハックの場合、カスタムアノテーションを作成する努力は正当化されると主張します。

12
WReach

私はこれをアノテーションで行うライブラリを書きました: Lightweight Javac @Warning Annotation

使い方はとても簡単です:

// some code...

@Warning("This method should be refactored")
public void someCodeWhichYouNeedAtTheMomentButYouWantToRefactorItLater() {
    // bad stuff going on here...
}

そして、コンパイラはあなたのテキストで警告メッセージを投げます

8

メソッドまたはクラスを@Deprecatedとしてマークしてはどうですか? ここにドキュメント 。 @Deprecatedと@deprecatedの両方があることに注意してください-大文字のDバージョンは注釈であり、小文字のdはjavadocバージョンです。 javadocバージョンでは、何が起こっているかを説明する任意の文字列を指定できます。しかし、コンパイラーは、それを見たときに警告を出す必要はありません(多くの人はそうします)。注釈は常に警告を発生させるはずですが、説明を追加できるとは思いません。

ここでの更新は、私がテストしたばかりのコードです。Sample.Javaには以下が含まれています。

public class Sample {
    @Deprecated
    public static void foo() {
         System.out.println("I am a hack");
    }
}

SampleCaller.Javaには以下が含まれます。

public class SampleCaller{
     public static void main(String [] args) {
         Sample.foo();
     }
}

「javac Sample.Java SampleCaller.Java」を実行すると、次の出力が得られます。

Note: SampleCaller.Java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

Sunのjavac 1.6を使用しています。単なるメモではなく誠実さの警告を表示する場合は、-Xlintオプションを使用します。たぶんそれはAntを通して適切に浸透するでしょう。

5
Peter Recore

これには注釈を使用できます!

エラーを発生させるには、 Messager を使用して Diagnostic.Kind.ERROR でメッセージを送信します。短い例:

processingEnv.getMessager().printMessage(
    Diagnostic.Kind.ERROR, "Something happened!", element);

これをテストするために書いた、非常に簡単な注釈を次に示します。

この@Marker注釈は、ターゲットがマーカーインターフェイスであることを示します。

package marker;

import Java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Marker {
}

そして、注釈プロセッサは、そうでない場合はエラーを引き起こします:

package marker;

import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
import javax.tools.Diagnostic;
import Java.util.Set;

@SupportedAnnotationTypes("marker.Marker")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public final class MarkerProcessor extends AbstractProcessor {

    private void causeError(String message, Element e) {
        processingEnv.getMessager()
            .printMessage(Diagnostic.Kind.ERROR, message, e);
    }

    private void causeError(
            Element subtype, Element supertype, Element method) {
        String message;
        if (subtype == supertype) {
            message = String.format(
                "@Marker target %s declares a method %s",
                subtype, method);
        } else {
            message = String.format(
                "@Marker target %s has a superinterface " +
                "%s which declares a method %s",
                subtype, supertype, method);
        }

        causeError(message, subtype);
    }

    @Override
    public boolean process(
            Set<? extends TypeElement> annotations,
            RoundEnvironment roundEnv) {

        Elements elementUtils = processingEnv.getElementUtils();
        boolean processMarker = annotations.contains(
            elementUtils.getTypeElement(Marker.class.getName()));
        if (!processMarker)
            return false;

        for (Element e : roundEnv.getElementsAnnotatedWith(Marker.class)) {
            ElementKind kind = e.getKind();

            if (kind != ElementKind.INTERFACE) {
                causeError(String.format(
                    "target of @Marker %s is not an interface", e), e);
                continue;
            }

            if (kind == ElementKind.ANNOTATION_TYPE) {
                causeError(String.format(
                    "target of @Marker %s is an annotation", e), e);
                continue;
            }

            ensureNoMethodsDeclared(e, e);
        }

        return true;
    }

    private void ensureNoMethodsDeclared(
            Element subtype, Element supertype) {
        TypeElement type = (TypeElement) supertype;

        for (Element member : type.getEnclosedElements()) {
            if (member.getKind() != ElementKind.METHOD)
                continue;
            if (member.getModifiers().contains(Modifier.STATIC))
                continue;
            causeError(subtype, supertype, member);
        }

        Types typeUtils = processingEnv.getTypeUtils();
        for (TypeMirror face : type.getInterfaces()) {
            ensureNoMethodsDeclared(subtype, typeUtils.asElement(face));
        }
    }
}

たとえば、これらは@Markerの正しい使用法です。

  • @Marker
    interface Example {}
    
  • @Marker
    interface Example extends Serializable {}
    

ただし、これらの@Markerを使用すると、コンパイラエラーが発生します。

  • @Marker
    class Example {}
    
  • @Marker
    interface Example {
        void method();
    }
    

    marker error

これは、このテーマの開始に非常に役立つことがわかったブログ投稿です。

  • Java language の注釈プロセッサを使用したコード生成

小さなメモ:以下のコメンターが指摘しているのは、MarkerProcessorMarker.classを参照しているため、コンパイル時の依存関係があるということです。上記の例では、両方のクラスが同じJARファイル(たとえば、marker.jar)に入ることを想定して記述しましたが、常に可能であるとは限りません。

たとえば、次のクラスを持つアプリケーションJARがあるとします。

com.acme.app.Main
com.acme.app.@Ann
com.acme.app.AnnotatedTypeA (uses @Ann)
com.acme.app.AnnotatedTypeB (uses @Ann)

次に、@Annのプロセッサは、アプリケーションJARのコンパイル中に使用される別のJARに存在します。

com.acme.proc.AnnProcessor (processes @Ann)

その場合、AnnProcessor@Annのタイプを直接参照できません。循環JAR依存関係が作成されるためです。 String nameまたはTypeElement/TypeMirrorでのみ@Annを参照できます。

3
Radiodef

ここ は注釈に関するチュートリアルを示し、下部に独自の注釈を定義する例を示します。残念ながら、チュートリアルの簡単なスキミングでは、これらはjavadocでのみ利用可能であると述べています...

コンパイラで使用される注釈言語仕様自体で事前定義されている注釈タイプには、@ Deprecated、@ Override、および@SuppressWarningsの3つがあります。

したがって、実際にできるのは、コンパイラがハックについて通知するjavadocsにカスタムタグを出力または配置する@Deprecatedタグを挿入することだけです。

2
Matt Phillips

IntelliJを使用している場合。 「設定」>「エディター」>「TODO」に移動して、「\ bhack.b *」またはその他のパタ​​ーンを追加できます。

その後、// HACK: temporary fix to work around server issues

次に、TODOツールウィンドウに、編集中に他のすべての定義済みパターンとともに、きれいに表示されます。

1
BARJ

Ant ou mavenなどのツールを使用してコンパイルする必要があります。これを使用して、コンパイル時にいくつかのタスクを定義する必要があります。これにより、たとえばFIXMEタグに関するログ(メッセージや警告など)が生成される可能性があります。

また、エラーが必要な場合も可能です。コードにTODOが残っているときにコンパイルを停止するのは好きです(なぜですか?)

0
Lioda

警告を表示するために、未使用の変数とカスタム@SuppressWarningsは機能しませんでしたが、不必要なキャストは機能することがわかりました。

public class Example {
    public void warn() {
        String fixmePlease = (String)"Hello";
    }
}

コンパイルするとき:

$ javac -Xlint:all Example.Java
ExampleTest.Java:12: warning: [cast] redundant cast to String
        String s = (String) "Hello!";
                   ^
1 warning
0
Andy Balaam