Как намеренно вызвать пользовательское предупреждение компилятора java?

Я собираюсь совершить уродливый временный взлом, чтобы обойти проблему блокировки, пока мы ждем исправления внешнего ресурса. Помимо маркировки его большим страшным комментарием и кучей FIXMEs, я хотел бы, чтобы компилятор бросил очевидное предупреждающее сообщение в качестве напоминания, чтобы мы не забыли это вынуть. Например, что-то вроде:

[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 script), и он определенно выполняет функцию.

11 ответов


один метод, который я видел, используется, чтобы связать это в модульное тестирование (вы do единица теста, верно?). В основном вы создаете модульный тест, который не Как только исправление внешнего ресурса будет достигнуто. Затем вы прокомментируете этот модульный тест, чтобы рассказать другим, как отменить ваш gnarly hack после решения проблемы.

что действительно скользко в этом подходе, так это то, что триггер для отмены вашего взлома-это исправление самой основной проблемы.


Я думаю, что пользовательская аннотация, которая будет обработана компилятором, является решением. Я часто пишу пользовательские заметки делать во время выполнения, но я никогда не пытался использовать их во время компиляции. Итак, я могу дать вам только указатели на инструменты, которые вам могут понадобиться:

  • напишите пользовательский тип аннотации. на этой странице объясняет, как написать аннотацию.
  • написать обработчика, который обрабатывает пользовательские аннотации излучать предупреждающий. Инструмент, который запускает такие обработчики аннотаций, называется APT. Вы можете найти введение на на этой странице. Я думаю, что вам нужно в API APT-это AnnotationProcessorEnvironment, который позволит вам выдавать предупреждения.
  • из Java 6 APT интегрирован в javac. То есть, вы можете добавить обработчик аннотаций в командной строке javac. в этом разделе руководства javac расскажет вам, как вызвать вашу пользовательскую аннотацию процессор.

Я не знаю, действительно ли это решение осуществимо. Я попытаюсь реализовать его сам, когда найду время.

редактировать

Я успешно реализовал свое решение. И в качестве бонуса я использовал средство поставщика услуг java для упрощения его использования. На самом деле, мое решение-это jar, который содержит 2 класса : пользовательскую аннотацию и процессор аннотаций. Чтобы использовать его, просто добавьте этот jar в classpath вашего проекта и комментировать все, что вы хотите ! Это отлично работает прямо внутри моей 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 в банке. Этот файл является файлом acsii, который должен содержать следующий текст:

fr.barjak.hack_processor.Processor

один хороший Хак заслуживает другого... Обычно я генерирую предупреждения компилятора для описанной цели, вводя неиспользуемую переменную в метод hacky, таким образом:

эта неиспользуемая переменная будет генерировать предупреждение, которое (в зависимости от вашего компилятора) будет выглядеть примерно так:

WARNING: The local variable FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed is never read.

это решение не так приятно, как пользовательская аннотация, но оно имеет то преимущество, что не требует предварительной подготовки (при условии, что компилятор уже настроен выдавать предупреждения для неиспользуемых переменных). Я бы предположил, что этот подход подходит только для недолговечных хаков. Для долгоживущих хаков я бы сказал, что усилия по созданию пользовательской аннотации будут оправданы.


некоторые быстрый и не так грязный подход, может быть, использовать @SuppressWarnings аннотация с намеренно неправильным :

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

Это создаст предупреждение, потому что оно не распознается компилятором как конкретное предупреждение для подавления:

неподдерживаемый @SuppressWarnings ("FIXME: это хак и должен быть зафиксированный.")


Я написал библиотеку, которая делает это с комментариями: Легкий Javac @Предупреждение Аннотация

использование очень простое:

// some code...

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

и компилятор выдаст предупреждающее сообщение с вашим текстом


как насчет маркировки метода или класса как @Deprecated? документы здесь. Обратите внимание, что есть как @Deprecated, так и @deprecated - версия capital D является аннотацией, а нижний регистр d-версией javadoc. Версия документации позволяет указать произвольную строку, объясняющую, что происходит. Но компиляторы не обязаны выдавать предупреждение при его просмотре (хотя многие это делают). Аннотация всегда должна вызывать предупреждение, хотя я не думаю, что можно добавить объяснение этому.

UPDATE вот код, который я только что тестировал: Образец.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.Java SampleCaller.Ява" я получаю следующий вывод:

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

Я использую javac 1.6 sun. Если вы хотите честное предупреждение, а не просто записку, используйте опцию-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

вот сообщение в блоге, которое я нашел очень полезным для начала работы по этому вопросу:


небольшое примечание: то, что комментатор ниже указывает, это потому, что 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:

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

в этом случае AnnProcessor не сможет ссылаться на тип @Ann напрямую, потому что это создаст круговую зависимость JAR. Он мог бы только ссылаться @Ann by String имя или TypeElement/TypeMirror.


здесь показывает учебник по аннотациям, а внизу он дает пример определения ваших собственных аннотаций. К сожалению, быстрый просмотр учебника сказал, что они доступны только в javadoc...

аннотации, используемые компилятором существует три типа аннотаций которые предопределены самой спецификацией языка: @Deprecated, @ Override и @SuppressWarnings.

таким образом, кажется, что все, что вы можете действительно, бросьте тег @Deprecated, который компилятор распечатает или поместит пользовательский тег в javadocs, который говорит о взломе.


вы должны использовать инструмент для компиляции, например ant ou maven. С его помощью вы должны определить некоторые задачи во время компиляции, которые могут создавать некоторые журналы (например, сообщения или предупреждения) о ваших тегах FIXME.

и если вы хотите некоторые ошибки, это тоже возможно. Как остановить компиляцию, когда вы оставили некоторые задачи в своем коде (почему бы и нет ?)


чтобы получить любое предупреждение вообще, я обнаружил, что неиспользуемые переменные и пользовательские @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

Если вы используете IntelliJ. Вы можете перейти к: Настройки>редактор>TODO и добавить " \bhack.Б*" или любой другой узор.

Если вы сделаете комментарий, как // HACK: temporary fix to work around server issues

затем в окне задач, он будет отображаться красиво, вместе со всеми другими моделями, при редактировании.