mostrar mensaje java compiler-warnings javac

mostrar - ¿Cómo causar intencionalmente un mensaje de advertencia de compilador Java personalizado?



alert en java netbeans (11)

Estoy a punto de cometer un feo truco temporal para solucionar un problema de bloqueo mientras esperamos que se solucione un recurso externo. Además de marcarlo con un gran comentario aterrador y un montón de FIXME, me gustaría que el compilador envíe un mensaje de advertencia obvio como recordatorio para que no nos olvidemos de sacar esto. Por ejemplo, algo como:

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

¿Hay alguna manera en que pueda causar una advertencia intencional del compilador con un mensaje de mi elección? En su defecto, ¿qué es lo más fácil de agregar al código para lanzar una advertencia existente, tal vez con un mensaje en una cadena en la línea ofensiva para que se imprima en el mensaje de advertencia?

EDITAR: las etiquetas obsoletas no parecen estar haciendo nada por mí:

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

No hay errores de compilador o tiempo de ejecución en eclipse ni de sun javac 1.6 (ejecutándose desde script de ant), y definitivamente está ejecutando la función.


¿Qué tal si marcamos el método o la clase como @Deprecated? documentos aquí . Tenga en cuenta que hay un @Deprecated y un @deprecated: la versión D de capital es la anotación y d la versión en minúscula es javadoc. La versión de javadoc le permite especificar una cadena arbitraria que explica lo que está sucediendo. Pero los compiladores no están obligados a emitir una advertencia al verlo (aunque muchos lo hacen). La anotación siempre debe causar una advertencia, aunque no creo que pueda agregarle una explicación.

ACTUALIZAR aquí es el código que acabo de probar con: Sample.java contiene:

public class Sample { @Deprecated public static void foo() { System.out.println("I am a hack"); } }

SampleCaller.java contiene:

public class SampleCaller{ public static void main(String [] args) { Sample.foo(); } }

cuando ejecuto "javac Sample.java SampleCaller.java" obtengo el siguiente resultado:

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

Estoy usando sun''s javac 1.6. Si desea una advertencia honesta a bondad en lugar de solo una nota, use la opción -Xlint. Tal vez eso se filtrará a través de Ant correctamente.


Creo que una anotación personalizada, que será procesada por el compilador, es la solución. Con frecuencia escribo anotaciones personalizadas para hacer cosas en tiempo de ejecución, pero nunca intenté usarlas en el momento de la compilación. Entonces, solo puedo darte consejos sobre las herramientas que puedas necesitar:

  • Escribe un tipo de anotación personalizado. Esta página explica cómo escribir una anotación.
  • Escriba un procesador de anotaciones, que procese su anotación personalizada para emitir una advertencia. La herramienta que ejecuta dichos procesadores de anotación se llama APT. Puedes encontrar una inducción en esta página . Creo que lo que necesitas en la APT API es AnnotationProcessorEnvironment, que te permitirá emitir advertencias.
  • Desde Java 6, APT está integrado en javac. Es decir, puede agregar un procesador de anotación en la línea de comando javac. Esta sección del manual javac le dirá cómo llamar a su procesador de anotación personalizado.

No sé si esta solución es realmente practicable. Trataré de implementarlo yo mismo cuando encuentre algo de tiempo.

Editar

Implementé mi solución con éxito Y como beneficio adicional, utilicé las instalaciones del proveedor de servicios de Java para simplificar su uso. En realidad, mi solución es un jar que contiene 2 clases: la anotación personalizada y el procesador de anotación. Para usarlo, simplemente agregue este jar en el classpath de su proyecto, ¡y anote lo que quiera! Esto funciona bien dentro de mi IDE (NetBeans).

Código de la anotación:

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 { }

Código del procesador:

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; } }

Para habilitar el contenedor resultante como proveedor de servicios, agregue el archivo META-INF/services/javax.annotation.processing.Processor en el META-INF/services/javax.annotation.processing.Processor . Este archivo es un archivo acsii que debe contener el siguiente texto:

fr.barjak.hack_processor.Processor


Deberías usar una herramienta para compilar, como ant ou maven. Con él, debe definir algunas tareas en tiempo de compilación que podrían producir algunos registros (como mensajes o advertencias) sobre sus etiquetas FIXME, por ejemplo.

Y si quieres algunos errores, también es posible. Como detener la compilación cuando has dejado TODO en tu código (¿por qué no?)


Escribí una biblioteca que hace esto con anotaciones: Lightweight Javac @Warning Annotation

El uso es muy simple:

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

Y el compilador lanzará un mensaje de advertencia con su texto


Para que apareciera alguna advertencia, descubrí que las variables no utilizadas y las @SuppressWarnings personalizadas no funcionaban para mí, pero sí un lanzamiento innecesario:

public class Example { public void warn() { String fixmePlease = (String)"Hello"; } }

Ahora cuando compilo:

$ javac -Xlint:all Example.java ExampleTest.java:12: warning: [cast] redundant cast to String String s = (String) "Hello!"; ^ 1 warning


Podemos hacer esto con anotaciones!

Para generar un error, use Messager para enviar un mensaje con Diagnostic.Kind.ERROR . Ejemplo corto:

processingEnv.getMessager().printMessage( Diagnostic.Kind.ERROR, "Something happened!", element);

Aquí hay una anotación bastante simple que escribí solo para probar esto.

Esta anotación de @Marker indica que el objetivo es una interfaz de marcador:

package marker; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Marker { }

Y el procesador de anotaciones causa un error si no es así:

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)); } } }

Por ejemplo, estos son los usos correctos de @Marker :

  • @Marker interface Example {}

  • @Marker interface Example extends Serializable {}

Pero estos usos de @Marker causarán un error de compilación:

  • @Marker class Example {}

  • @Marker interface Example { void method(); }

Aquí hay una publicación en el blog que encontré muy útil para comenzar con el tema:

Nota pequeña: lo que el comentarista a continuación señala es que debido a que MarkerProcessor referencia a Marker.class , tiene una dependencia de tiempo de compilación en él. Escribí el ejemplo anterior con la suposición de que ambas clases irían en el mismo archivo JAR (por ejemplo, marker.jar ), pero eso no siempre es posible.

Por ejemplo, supongamos que hay una aplicación JAR con las siguientes clases:

com.acme.app.Main com.acme.app.@Ann com.acme.app.AnnotatedTypeA (uses @Ann) com.acme.app.AnnotatedTypeB (uses @Ann)

Entonces, el procesador para @Ann existe en un JAR separado, que se usa al compilar el JAR de la aplicación:

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

En ese caso, AnnProcessor no podría referenciar el tipo de @Ann directamente, porque crearía una dependencia circular de JAR. Solo podría hacer referencia a @Ann por String name o TypeElement / TypeMirror .


Si estás usando IntelliJ. Puede ir a: Preferencias> Editor> TODO y agregar "/ bhack.b *" o cualquier otro patrón.

Si luego hace un comentario como // HACK: temporary fix to work around server issues

Luego, en la ventana de la herramienta TODO, se mostrará muy bien, junto con todos los otros patrones definidos, durante la edición.


Un acercamiento rápido y no tan sucio, puede ser usar una anotación @SuppressWarnings con un argumento de String deliberadamente incorrecto:

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

Esto generará una advertencia porque el compilador no lo reconoce como una advertencia específica para suprimir:

Unsupported @SuppressWarnings ("FIXME: esto es un truco y debería ser reparado").


Un buen truco merece otro ... Normalmente genero advertencias de compilación para el propósito descrito al introducir una variable no utilizada en el método hacky, por lo tanto:

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

Esta variable no utilizada generará una advertencia que (dependiendo de su compilador) se verá más o menos así:

WARNING: The local variable FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed is never read.

Esta solución no es tan buena como una anotación personalizada, pero tiene la ventaja de que no requiere preparación previa (suponiendo que el compilador ya esté configurado para emitir advertencias para las variables no utilizadas). Sugeriría que este enfoque solo sea adecuado para hacks de corta duración. Para hacks de larga duración, yo diría que el esfuerzo para crear una anotación personalizada estaría justificado.


Una técnica que he visto utilizar es unir esto a la prueba unitaria (prueba de unidad, ¿verdad?). Básicamente, usted crea una prueba de unidad que falla una vez que se logra la corrección de recursos externos. Luego, comente esa prueba unitaria para decirle a los demás cómo deshacer su retorcido hack una vez que se resuelva el problema.

Lo realmente astuto de este enfoque es que el desencadenante para deshacer su hack es una solución al problema central en sí mismo.


Here muestra un tutorial sobre anotaciones y en la parte inferior se muestra un ejemplo de cómo definir sus propias anotaciones. Desafortunadamente, un destello rápido del tutorial dice que solo están disponibles en el javadoc ...

Anotaciones utilizadas por el compilador Existen tres tipos de anotaciones que están predefinidas por la especificación del lenguaje: @Deprecated, @Override y @SuppressWarnings.

Por lo tanto, parece que todo lo que realmente se puede hacer es arrojar una etiqueta @Deprecated que el compilador imprimirá o poner una etiqueta personalizada en los javadocs que cuente sobre el truco.