IT박스

의도적으로 사용자 정의 Java 컴파일러 경고 메시지를 발생시키는 방법은 무엇입니까?

itboxs 2020. 10. 21. 07:48
반응형

의도적으로 사용자 정의 Java 컴파일러 경고 메시지를 발생시키는 방법은 무엇입니까?


외부 리소스가 수정되기를 기다리는 동안 차단 문제를 해결하기 위해 추악한 임시 해킹을 저 지르려고합니다. 무시 무시한 댓글과 많은 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 스크립트에서 실행)에서 컴파일러 또는 런타임 오류가 없으며 확실히 함수를 실행하고 있습니다.


내가 사용한 한 가지 기술은 이것을 단위 테스트에 묶는 것입니다 (단위 테스트 수행 합니까?). 기본적으로 외부 리소스 수정이 완료되면 실패 하는 단위 테스트를 만듭니다 . 그런 다음 문제가 해결되면 해당 단위 테스트에 주석을 달아 다른 사람에게 형편없는 해킹을 실행 취소하는 방법을 알려줍니다.

이 접근 방식에 대한 정말 멋진 점은 해킹을 실행 취소하는 트리거가 핵심 문제 자체의 수정이라는 것입니다.


컴파일러가 처리 할 커스텀 어노테이션이 해결책이라고 생각합니다. 런타임에 작업을 수행하기 위해 사용자 지정 주석을 자주 작성하지만 컴파일 타임에는 사용하지 않았습니다. 따라서 필요한 도구에 대한 포인터 만 제공 할 수 있습니다.

  • 사용자 지정 주석 유형을 작성합니다. 이 페이지 에서는 주석을 작성하는 방법을 설명합니다.
  • 사용자 지정 주석을 처리하여 경고를 표시하는 주석 프로세서를 작성합니다. 이러한 주석 프로세서를 실행하는 도구를 APT라고합니다. 이 페이지 에서 소개를 찾을 수 있습니다 . APT API에서 필요한 것은 AnnotationProcessorEnvironment이며,이를 통해 경고를 내 보냅니다.
  • Java 6에서 APT는 javac에 통합되었습니다. 즉, javac 명령 줄에 주석 프로세서를 추가 할 수 있습니다. javac 매뉴얼 의이 섹션에서는 커스텀 어노테이션 프로세서를 호출하는 방법을 알려줍니다.

이 솔루션이 실제로 실행 가능한지 모르겠습니다. 시간이 생기면 직접 구현해 보겠습니다.

편집하다

내 솔루션을 성공적으로 구현했습니다. 보너스로 Java의 서비스 제공 업체 기능을 사용하여 사용을 단순화했습니다. 사실, 내 솔루션은 커스텀 어노테이션과 어노테이션 프로세서의 두 가지 클래스를 포함하는 항아리입니다. 그것을 사용하려면 프로젝트의 클래스 경로에이 항아리를 추가하고 원하는대로 주석을 달아주세요! 이것은 내 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를 서비스 제공자로 사용하려면 jar에 파일 META-INF/services/javax.annotation.processing.Processor추가하십시오 . 이 파일은 다음 텍스트를 포함해야하는 acsii 파일입니다.

fr.barjak.hack_processor.Processor

일부 빠르고 더러운 접근 방식 @SuppressWarnings은 의도적으로 잘못된 String인수가 있는 주석 을 사용하는 것입니다 .

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

컴파일러에서 억제 할 특정 경고로 인식하지 않기 때문에 경고가 생성됩니다.

지원되지 않는 @SuppressWarnings ( "FIXME : 이것은 해킹이며 수정되어야합니다.")


하나의 좋은 해킹은 또 다른 가치가 있습니다 ... 저는 일반적으로 hacky 메서드에 사용되지 않는 변수를 도입하여 설명 된 목적으로 컴파일러 경고를 생성합니다.

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

이 미사용 변수는 (컴파일러에 따라) 다음과 같은 경고를 생성합니다.

경고 : 로컬 변수 FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed는 읽지 않습니다.

이 솔루션은 커스텀 어노테이션만큼 좋지는 않지만 사전 준비가 필요하지 않다는 장점이 있습니다 (컴파일러가 이미 사용되지 않은 변수에 대한 경고를 발행하도록 구성되었다고 가정). 이 접근 방식은 단기 해킹에만 적합하다고 제안합니다. 오래 지속되는 해킹의 경우 사용자 지정 주석을 만드는 노력이 정당하다고 주장합니다.


나는 주석으로 이것을하는 라이브러리를 작성했다 : Lightweight Javac @Warning Annotation

사용법은 매우 간단합니다.

// some code...

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

그리고 컴파일러는 텍스트와 함께 경고 메시지를 던집니다.


메서드 또는 클래스를 @Deprecated로 표시하는 것은 어떻습니까? 여기에 문서 . @Deprecated와 @deprecated가 모두 있다는 점에 유의하십시오. 대문자 D 버전은 주석이고 소문자 d는 javadoc 버전입니다. javadoc 버전을 사용하면 진행 상황을 설명하는 임의의 문자열을 지정할 수 있습니다. 그러나 컴파일러는 경고를 볼 때 경고를 내야 할 필요가 없습니다 (많은 사람들이 수행하지만). 주석은 항상 경고를 발생시켜야하지만 설명을 추가 할 수는 없다고 생각합니다.

UPDATE는 방금 테스트 한 코드입니다. 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를 통해 제대로 스며들 것입니다.


주석으로이 작업을 수행 할 수 있습니다!

오류를 발생 시키려면를 사용 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

다음은 주제를 시작하는 데 매우 도움이되는 블로그 게시물입니다.


Small note: what the commentor below is pointing out is that because MarkerProcessor references Marker.class, it has a compile-time dependency on it. I wrote the above example with the assumption that both classes would go in the same JAR file (say, marker.jar), but that's not always possible.

For example, suppose there's an application JAR with the following classes:

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

Then the processor for @Ann exists in a separate JAR, which is used while compiling the application JAR:

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

In that case, AnnProcessor would not be able to reference the type of @Ann directly, because it would create a circular JAR dependency. It would only be able to reference @Ann by String name or TypeElement/TypeMirror.


Here shows a tutorial on annotations and at the bottom it gives an example of defining your own annotations. Unfortunately a quick skimming of the tutorial said that those are only available in the javadoc...

Annotations Used by the Compiler There are three annotation types that are predefined by the language specification itself: @Deprecated, @Override, and @SuppressWarnings.

So it appears that all you can really do is throw in an @Deprecated tag that the compiler will print out or put a custom tag in the javadocs that tells about the hack.


If you're using IntelliJ. You can go to: Preferences>Editor>TODO and add "\bhack.b*" or any other pattern.

If you then make a comment like // HACK: temporary fix to work around server issues

Then in the TODO tool window, it will show up nicely, together with all your other defined patterns, while editing.


You should use a tool to compile, like ant ou maven. With it, you should define some tasks at compile time which could produce some logs (like messages or warnings) about your FIXME tags, for example.

And if you want some errors, it is possible too. Like stop compilation when you have left some TODO in your code (why not ?)


To get any warning at all to appear, I found that unused variables and custom @SuppressWarnings didn't work for me, but an unnecessary cast did:

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

Now when I compile:

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

참고URL : https://stackoverflow.com/questions/1752607/how-to-intentionally-cause-a-custom-java-compiler-warning-message

반응형