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

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

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

Есть ли способ, которым я могу вызвать преднамеренное компилятор с сообщением по моему выбору? В противном случае, что проще всего добавить к коду, чтобы выбросить существующее предупреждение, возможно, сообщение в строке на строке нарушения, чтобы оно печаталось в предупреждающем сообщении?

EDIT: Устаревшие теги, кажется, ничего не делают для меня:

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

Нет ошибок компилятора или времени выполнения в eclipse или от sun javac 1.6 (работает от ant script), и он определенно выполняет функцию.

Ответ 1

Один из методов, который я видел, - это связать это с модульным тестированием (вы делаете unit test, правильно?). В основном вы создаете unit test, который выходит из строя после достижения исправления внешнего ресурса. Затем вы прокомментируете, что unit test, чтобы сообщить другим, как отменить ваш gnarly hack, как только проблема будет решена.

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

Ответ 2

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

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

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

Edit

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

}

Чтобы включить полученный баннер в качестве поставщика услуг, добавьте файл META-INF/services/javax.annotation.processing.Processor в банку. Этот файл является файлом acsii, который должен содержать следующий текст:

fr.barjak.hack_processor.Processor

Ответ 3

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

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

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

WARNING: The local variable FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed is never read.

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

Ответ 4

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

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

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

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

Ответ 5

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

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

// some code...

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

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

Ответ 6

как насчет маркировки метода или класса как @Deprecated? docs here. Обратите внимание, что есть как @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 должным образом.

Ответ 7

Мы можем сделать это с помощью аннотаций!

Чтобы поднять ошибку, используйте 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

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

Ответ 8

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

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

Затем в окне инструмента TODO он будет отображаться хорошо вместе со всеми вашими другими шаблонами при редактировании.