Как преднамеренно вызвать пользовательское предупреждающее сообщение компилятора Java? - PullRequest
73 голосов
/ 18 ноября 2009

Я собираюсь совершить уродливый временный взлом, чтобы обойти проблему блокировки, пока мы ждем исправления внешнего ресурса. Кроме того, чтобы пометить его большим пугающим комментарием и кучей 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), и он определенно выполняет функцию.

Ответы [ 11 ]

82 голосов
/ 18 ноября 2009

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

  • Написать пользовательский тип аннотации. Эта страница объясняет, как написать аннотацию.
  • Напишите процессор аннотаций, который обрабатывает ваши пользовательские аннотации для выдачи предупреждения. Инструмент, который запускает такие процессоры аннотаций, называется 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;
    }

}

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

fr.barjak.hack_processor.Processor
34 голосов
/ 18 ноября 2009

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

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

12 голосов
/ 21 февраля 2015

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

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

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

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

12 голосов
/ 21 июля 2011

Один хороший взлом заслуживает другого ... Обычно я генерирую предупреждения компилятора для описанной цели, вводя неиспользуемую переменную в хакерский метод, таким образом: /** * @deprecated "Temporary hack to work around remote server quirks" */ @Deprecated private void doSomeHackyStuff() { <strong>int FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed;</strong> ... }

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

WARNING: The local variable FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed is never read.

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

8 голосов
/ 10 января 2015

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

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

// some code...

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

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

5 голосов
/ 18 ноября 2009

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

3 голосов
/ 26 апреля 2015

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

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

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

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

2 голосов
/ 18 ноября 2009

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

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

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

0 голосов
/ 27 января 2017

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

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

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

0 голосов
/ 23 декабря 2016

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