Como intencionalmente causar uma mensagem de aviso do compilador java personalizado?

Estou prestes a cometer um hack temporário e feio para resolver um problema de bloqueio enquanto esperamos que um recurso externo seja corrigido. Além de marcá-lo com um grande comentário assustador e um monte de FIXMEs, eu adoraria ter o compilador lançar uma mensagem de aviso óbvia como um lembrete para que não nos esqueçamos de tirar isso. Por exemplo, algo como:

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

Existe uma maneira que eu possa causar um aviso de compilador intencional com uma mensagem de minha escolha? Caso contrário, qual é a coisa mais fácil de adicionar ao código para lançar um aviso existente, talvez com uma mensagem em uma cadeia na linha incorreta para que seja impressa na mensagem de aviso?

EDIT: tags depreciadas não parecem estar fazendo nada para mim:

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

Nenhum compilador ou erro de tempo de execução no eclipse ou do sun javac 1.6 (rodando a partir do script ant), e está definitivamente executando a function.

Uma técnica que eu vi usado é amarrar isso em testes de unidade (você faz teste de unidade, certo?). Basicamente, você cria um teste de unidade que falha quando a correção do recurso externo é alcançada. Então você comenta esse teste unitário para dizer aos outros como desfazer seu hack intrincado quando o problema for resolvido.

O que é realmente interessante nessa abordagem é que o gatilho para desfazer seu hack é uma correção da questão central em si.

Eu acho que uma anotação personalizada, que será processada pelo compilador, é a solução. Eu freqüentemente escrevo annotations personalizadas para fazer coisas em tempo de execução, mas nunca tentei usá-las em tempo de compilation. Então, só posso dar dicas sobre as ferramentas que você pode precisar:

  • Escreva um tipo de anotação personalizado. Esta página explica como escrever uma anotação.
  • Escreva um processador de annotations, que processa sua anotação personalizada para emitir um aviso. A ferramenta que executa esses processadores de anotação é chamada de APT. Você pode encontrar uma introdução nesta página . Eu acho que o que você precisa na API do APT é AnnotationProcessorEnvironment, que permitirá que você emita avisos.
  • Do Java 6, o APT é integrado ao javac. Ou seja, você pode adicionar um processador de anotação na linha de comando do javac. Esta seção do manual do javac lhe dirá como chamar seu processador de annotations personalizado.

Não sei se esta solução é realmente praticável. Vou tentar implementá-lo quando encontrar algum tempo.

Editar

Eu implementei com sucesso minha solução. E como bônus, usei o recurso de provedor de serviços do java para simplificar seu uso. Na verdade, minha solução é um jar que contém duas classs: a anotação personalizada e o processador de anotação. Para usá-lo, basta adicionar este jar no classpath do seu projeto e anotar o que você quiser! Isso está funcionando bem dentro do meu IDE (NetBeans).

Código da anotação:

 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 do processador:

 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 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 ativar o jar resultante como um provedor de serviços, inclua o arquivo META-INF/services/javax.annotation.processing.Processor no jar. Este arquivo é um arquivo acsii que deve conter o seguinte texto:

 fr.barjak.hack_processor.Processor 

Um bom hack merece outro … Eu normalmente gero avisos de compilador para o propósito descrito introduzindo uma variável não usada no método hacky, assim:

  / **
  * @depreciado "hack temporário para contornar as peculiaridades do servidor remoto"
  * /
 @Descontinuada
 Vazio privado doSomeHackyStuff () {
     int FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed;
     ...
 }

Esta variável não utilizada irá gerar um aviso que (dependendo do seu compilador) será algo como isto:

  AVISO: A variável local FIXMEtemporaryHackToWorkAroundLibraryBugRemoveMeWhenLibraryIsFixed nunca é lida. 

Essa solução não é tão boa quanto uma anotação personalizada, mas tem a vantagem de não requerer preparação antecipada (supondo que o compilador já esteja configurado para emitir avisos para variables ​​não usadas). Eu sugeriria que essa abordagem é adequada apenas para hacks de curta duração. Para hacks de longa duração, eu argumentaria que o esforço para criar uma anotação personalizada seria justificado.

Uma abordagem rápida e não tão suja, pode ser usar uma anotação @SuppressWarnings com um argumento deliberadamente errado de String :

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

Isso gerará um aviso porque não é reconhecido pelo compilador como um aviso específico para suprimir:

Não Suportado @SuppressWarnings (“FIXME: este é um hack e deve ser corrigido.”)

Eu escrevi uma biblioteca que faz isso com annotations: Lightweight Javac @Warning Annotation

O uso é muito simples:

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

E o compilador lançará uma mensagem de aviso com seu texto

Que tal marcar o método ou class como @Decatado? docs aqui . Note que existe tanto um @Deprecated como um @deprecado – a versão maiúscula D é a anotação e a minúscula d é a versão do javadoc. A versão do javadoc permite que você especifique uma string arbitrária explicando o que está acontecendo. Mas os compiladores não são obrigados a emitir um aviso ao visualizá-lo (embora muitos o façam). A anotação deve sempre causar um aviso, embora eu não ache que você possa adicionar uma explicação a ela.

UPDATE aqui é o código que acabei de testar com: Sample.java contém:

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

SampleCaller.java contém:

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

Quando eu corro “javac Sample.java SampleCaller.java” eu recebo a seguinte saída:

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

Eu estou usando o javac do sol 1.6. Se você quiser um aviso de honestidade em vez de apenas uma nota, use a opção -Xlint. Talvez isso se infiltre pela Ant corretamente.

Podemos fazer isso com annotations!

Para gerar um erro, use o Messager para enviar uma mensagem com o Diagnostic.Kind.ERROR . Exemplo Curto:

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

Aqui está uma anotação bastante simples que escrevi apenas para testar isso.

Esta anotação @Marker indica que o alvo é uma interface de marcador:

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

E o processador de anotação causa um erro se não for:

 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 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 exemplo, esses são usos corretos do @Marker :

  •  @Marker interface Example {} 
  •  @Marker interface Example extends Serializable {} 

Mas esses usos do @Marker causarão um erro no compilador:

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

    erro de marcador

Aqui está uma postagem no blog que achei muito útil para começar o assunto:

  • Geração de código usando processadores de anotação na linguagem Java

Pequena nota: o que o comentador abaixo está apontando é que, como o MarkerProcessor referência a Marker.class , ele tem uma dependência em tempo de compilation. Eu escrevi o exemplo acima com a suposição de que ambas as classs iriam no mesmo arquivo JAR (digamos, marker.jar ), mas isso nem sempre é possível.

Por exemplo, suponha que haja um aplicativo JAR com as seguintes classs:

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

Então o processador para @Ann existe em um JAR separado, que é usado durante a compilation do aplicativo JAR:

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

Nesse caso, o AnnProcessor não poderia referenciar o tipo de @Ann diretamente, porque criaria uma dependência JAR circular. Ele só poderia fazer referência a @Ann por String name ou TypeElement / TypeMirror .

Aqui mostra um tutorial sobre annotations e, na parte inferior, dá um exemplo de como definir suas próprias annotations. Infelizmente, um rápido skimming do tutorial disse que aqueles estão disponíveis apenas no javadoc …

Anotações Usadas pelo Compilador Existem três tipos de anotação que são predefinidos pela própria especificação da linguagem: @Deprecated, @Override e @SuppressWarnings.

Assim, parece que tudo o que você pode realmente fazer é lançar uma tag @Deprecated que o compilador imprimirá ou colocará uma tag customizada nos javadocs que informa sobre o hack.

Você deve usar uma ferramenta para compilar, como ant ou maven. Com ele, você deve definir algumas tarefas em tempo de compilation que podem produzir alguns logs (como mensagens ou avisos) sobre suas tags FIXME, por exemplo.

E se você quiser alguns erros, é possível também. Como parar a compilation quando você deixou algum TODO no seu código (porque não?)

Para que qualquer aviso aparecesse, descobri que variables ​​não utilizadas e o @SuppressWarnings não funcionavam para mim, mas um casting desnecessário fez:

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

Agora quando eu compilo:

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

Se você estiver usando o IntelliJ. Você pode ir em: Preferências> Editor> TODO e adicionar “\ bhack.b *” ou qualquer outro padrão.

Se você fizer um comentário como // HACK: temporary fix to work around server issues

Então, na janela da ferramenta TODO, ele aparecerá bem, junto com todos os outros padrões definidos, durante a edição.

Intereting Posts