Как намеренно вызвать пользовательское предупреждение компилятора 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 70

11 ответов:

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

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

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

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

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

Edit

Я успешно реализовал свое решение. И в качестве бонуса я использовал средство поставщика услуг java, чтобы упростить его использование. На самом деле, мое решение-это jar, который содержит 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;
    }

}

чтобы включить результирующий 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.")

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

Unsupported @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. Версия документации позволяет указать произвольную строку, объясняющую, что происходит. Но компиляторы не обязаны выдавать предупреждение при его просмотре (хотя многие это делают). Аннотация всегда должна вызывать предупреждение, хотя я не думаю, что можно добавить объяснение этому.

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

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

Я использую javac солнца 1.6. Если вы хотите получить честное предупреждение, а не просто заметку, используйте опцию-Xlint. Может быть, это будет просачиваться через муравья должным образом.

мы можем сделать это с аннотациями!

чтобы вызвать ошибку, используйте 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

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