views:

655

answers:

6

I'm about to commit an ugly temporary hack in order to work around a blocking issue while we wait for an external resource to be fixed. Aside from marking it with a big scary comment and a bunch of FIXMEs, I'd love to have the compiler throw an obvious warning message as a reminder so we don't forget to take this out. For example, something like:

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

Is there a way I can cause an intentional compiler warning with a message of my choosing? Failing that, what's the easiest thing to add to the code to throw an existing warning, with perhaps a message in a string on the offending line so it gets printed in the warning message?

EDIT: Deprecated tags don't seem to be doing anything for me:

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

No compiler or runtime errors in eclipse or from sun javac 1.6 (running from ant script), and it's definitely executing the function.

+2  A: 

how about marking the method or class as @Deprecated? docs here. Note that there is both a @Deprecated and a @deprecated - the capital D version is the annotation and the lowercase d is the javadoc version. The javadoc version allows you to specify an arbitrary string explaining what is going on. But compilers are not required to emit a warning when seeing it (though many do). The annotation should always cause a warning, though i don't think you can add an explanation to it.

UPDATE here is the code I just tested with: Sample.java contains:

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

SampleCaller.java contains:

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

when i run "javac Sample.java SampleCaller.java" i get the following output:

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

I am using sun's javac 1.6. If you want an honest to goodness warning rather than just a note, use the -Xlint option. Maybe that will percolate up through Ant properly.

Peter Recore
I don't seem to get an error from the compiler with @Deprecate; edit my q with example code.
pimlottc
hmm. your example only shows the deprecated method. where do you use the method? that's where the warning will show up.
Peter Recore
A: 

Create your own @Hacky annotation.

Pete Kirkham
More details please? I've never written an annotation; how do you force a compiler warning?
pimlottc
A: 

You should use a tool to compile, like ant ou maven. With it, you should define some tasks at compile time which could produce some logs (like messages or warnings) about your FIXME tags, for example.

And if you want some errors, it is possible too. Like stop compilation when you have left some TODO in your code (why not ?)

LDU
The hack is to get it working ASAP, I don't exactly have time to change the build system right now :) But good to think about for the future...
pimlottc
A: 
controlfreak123
also the compiler will emit a warning saying the method you mark with @Deprecated is so...It will tell the user which offending one it is.
controlfreak123
(you aren't using a capital D in your edit of the OP)
controlfreak123
+6  A: 

One technique that I've seen used is to tie this into unit testing (you do unit test, right?). Basically you create a unit test that fails once the external resource fix is achieved. Then you comment that unit test to tell others how to undo your gnarly hack once the issue is resolved.

What's really slick about this approach is that the trigger for undoing your hack is a fix of the core issue itself.

Kevin Day
+1: Really nifty solution. Didn't think of that.
sleske
I heard about this at one of the No Fluff Just Stuff conferences (can't remember who the presenter was). I thought it was pretty slick. I definitely recommend those conferences, though.
Kevin Day
+8  A: 

I think that a custom annotation, which will be processed by the compiler, is the solution. I frequently write custom annotations to do things at runtime, but I never tried to use them at compilation time. So, I can only give you pointers on the tools you may need :

  • Write a custom annotation type. This page explains how to write an annotation.
  • Write an annotation processor, that processes your custom annotation to emit a warning. The tool that runs such annotation processors is called APT. You can find an indroduction on this page. I think what you need in the APT API is AnnotationProcessorEnvironment, which will let you emit warnings.
  • From Java 6, APT is integrated into javac. That is, you can add an annotation processor in the javac command line. This section of the javac manual will tell you how to call your custom annotation processor.

I don't know if this solution is really practicable. I'll try to implement it myself when I find some time.

Edit

I successfully implemented my solution. And as a bonus, I used java's service provider facility to simplify its use. Actually, my solution is a jar that contains 2 classes : the custom annotation and the annotation processor. To use it, just add this jar in the classpath of your project, and annotate whatever you want ! This is working fine right inside my IDE (NetBeans).

Code of the annotation :

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 {

}

Code of the processor :

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),
                            te);
                }
            }
        }
        return true;
    }

}

To enable the resulting jar as a service provider, add the file META-INF/services/javax.annotation.processing.Processor in the jar. This file is an acsii file that must contain the following text :

fr.barjak.hack_processor.Processor
barjak
+1, Great research! This is definitely the "right way" to do it (if a unit test is not practical), and it has the advantage of standing out over and above the regular warnings.
Yishai