如何故意导致自定义java编译器警告消息?

时间:2009-11-17 23:27:35

标签: java compiler-warnings javac

我正要提交一个丑陋的临时黑客,以便在我们等待修复外部资源时解决阻塞问题。除了用一个可怕的评论和一堆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脚本运行)中没有编译器或运行时错误,它肯定正在执行该函数。

11 个答案:

答案 0 :(得分:82)

我认为将由编译器处理的自定义注释是解决方案。我经常编写自定义注释来在运行时执行操作,但我从未尝试在编译时使用它们。所以,我只能指出你可能需要的工具:

  • 编写自定义注释类型。 This page解释了如何编写注释。
  • 编写注释处理器,处理您的自定义注释以发出警告。运行此类注释处理器的工具称为APT。你可以在this page找到一个产品。我认为您在APT API中需要的是AnnotationProcessorEnvironment,它可以让您发出警告。
  • 从Java 6开始,APT被集成到javac中。也就是说,您可以在javac命令行中添加注释处理器。 javac手册的This section将告诉您如何调用自定义注释处理器。

我不知道这个解决方案是否真的可行。当我找到一些时间时,我会尝试自己实现它。

修改

我成功实施了我的解决方案。作为奖励,我使用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作为服务提供者启用,请在jar中添加文件META-INF/services/javax.annotation.processing.Processor。此文件是一个必须包含以下文本的acsii文件:

fr.barjak.hack_processor.Processor

答案 1 :(得分:34)

我见过的一种技术是将其与单元测试联系起来(你单元测试,对吧?)。基本上,一旦实现外部资源修复,您就会创建失败的单元测试。然后你评论单元测试,告诉别人一旦问题解决后如何撤消你的粗暴黑客。

这种方法的真正含义是撤消黑客攻击的触发器是核心问题本身的修复。

答案 2 :(得分:12)

一个好的黑客应该得到另一个...我通常通过在hacky方法中引入一个未使用的变量来为所描述的目的生成编译器警告,因此:

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

这个未使用的变量将生成一个警告(取决于您的编译器),如下所示:

WARNING: The local variable FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed is never read.

此解决方案不如自定义注释好,但它的优点是无需提前准备(假设编译器已配置为对未使用的变量发出警告)。我建议这种方法只适用于短暂的黑客攻击。对于长期存在的黑客攻击,我认为创建自定义注释的努力是合理的。

答案 3 :(得分:12)

一些快速且不那么脏的方法,可能是使用@SuppressWarnings注释与故意错误的String参数:

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

这将生成警告,因为编译器无法将其识别为要禁止的特定警告:

  

不支持的@SuppressWarnings(“FIXME:这是一个黑客,应该是   固定的。“)

答案 4 :(得分:8)

我写了一个用注释执行此操作的库:Lightweight Javac @Warning Annotation

用法非常简单:

// some code...

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

编译器将在您的文本

中发出警告消息

答案 5 :(得分:5)

如何将方法或类标记为@Deprecated? docs here。请注意,有@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正确渗透。

答案 6 :(得分:3)

我们可以使用注释来做到这一点!

要引发错误,请使用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

这是一篇博客文章,我发现非常有帮助开始这个主题:

小注意:下面的评论员指出的是因为MarkerProcessor引用Marker.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在编译应用程序JAR时使用:

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

在这种情况下,AnnProcessor将无法直接引用@Ann的类型,因为它会创建循环JAR依赖项。它只能通过@Ann名称或String / TypeElement引用TypeMirror

答案 7 :(得分:2)

Here显示了注释教程,底部给出了定义自己的注释的示例。不幸的是,快速略读教程说这些仅在javadoc中可用...

  

编译器使用的注释有三种注释类型   由语言规范本身预定义的:@Deprecated,   @Override和@SuppressWarnings。

所以你可以真正做的就是抛出一个@Deprecated标签,编译器将打印出来,或者在javadocs中放入一个自定义标签来告诉黑客攻击。

答案 8 :(得分:0)

您应该使用工具进行编译,例如ant ou maven。有了它,你应该在编译时定义一些任务,这些任务可以产生一些关于你的FIXME标签的日志(比如消息或警告)。

如果你想要一些错误,也有可能。就像你在代码中留下一些TODO一样停止编译(为什么不呢?)

答案 9 :(得分:0)

为了得到任何警告,我发现未使用的变量和自定义@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

答案 10 :(得分:0)

如果您正在使用IntelliJ。您可以转到:首选项&gt;编辑器&gt; TODO并添加“\ bhack.b *”或任何其他模式。

如果您发表评论,请// HACK: temporary fix to work around server issues

然后在TODO工具窗口中,它会在编辑时很好地与所有其他定义的模式一起显示。