Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javac equivalent of "-D"?

Tags:

java

c++

Is there a way to give the java compiler some kind of variable that is accessible to the running java code?
In C/C++ I can give the compile -DKEY=VALUE and that would cause the preprocessor to have a #define for KEY equals to VALUE. I can then check this value in compile time to effect what code is being compiled.
I found java's -D, but that puts values give the the java command line in System.getProperty(). I want an argument give in compile time, not invocation time.

like image 535
shoosh Avatar asked Jan 14 '16 11:01

shoosh


2 Answers

javac has the

-Akey[=value]

commandline option to pass information to annotation processors.

like image 58
wero Avatar answered Sep 22 '22 06:09

wero


With java annotations it is possible to generate additional code on the fly, which can be configured on command line. It allow to produce more source code, configuration files, xml files, ... The main limitation is that you are allowed only to (re)generate new source files, you cannot modify existing ones.

Below is a short tutorial on how to allow from javac command specify parameters which will be visible in Java code. How usefull is that? Ie. you could specify a boolean option which would disable some parts of code, I am preety sure this parts of code could be removed using tools like proguard - or even optimized out by javac. Other uses is to specify new version number. Those use cases are mostly what c++ marcros are used for.

So, you need :

  • a dummy annotation class which will allow processor to run. It should be specified only once in your application.
  • a processor class which will run for above dummy annotation, and generate options class. It will also read options from javac command line.
  • a dummy Main class for testing purposes.

You will have to also compile your processor file before compiling Main class. This of course is done only when processor class is modified. All the three files are at the bottom. Now the compilation looks as follows (I am on windows):

Compile processor:

javac .\com\example\ConfigWritterAnnotationProcessor.java

Then Main.java with additional parameters to processor:

javac -processor com.example.ConfigWritterAnnotationProcessor -AtextToPrint="Hello World!" -AenablePrint=true ./com/example/Main.java

And thats all, now you may run Main.class and it will use Options class generated during compilation with above parameters set. It will look as follows:

package com.example;
public class Options {
    public static final String textToPrint = "Hello World!";
    public static final boolean enablePrint = true;
}

ProcessorStarterAnnotation.java

package com.example;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
public @interface ProcessorStarterAnnotation {
}

Main.java

package com.example;

@ProcessorStarterAnnotation
public class Main {
    public static void main(String[] args) {
      if ( com.example.Options.enablePrint ) {
        System.out.println(com.example.Options.textToPrint);
      }
      else {
        System.out.println("Print disabled");
      }
    }
}

ConfigWritterAnnotationProcessor.java

package com.example;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Map;
import java.util.Set;

@SupportedAnnotationTypes("com.example.ProcessorStarterAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedOptions({"textToPrint", "enablePrint"})
public class ConfigWritterAnnotationProcessor extends AbstractProcessor {
  private Map<String,String> options;

  @Override
  public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    options = processingEnv.getOptions();
  }

  @Override
  public boolean process(Set<? extends TypeElement> annotations,
                         RoundEnvironment currentRound) {

    if (!currentRound.processingOver()) {
      // This for-s are because processor is also run on newly created Options class.
      for (TypeElement te : annotations) {
        for (Element e : currentRound.getElementsAnnotatedWith(te)) {
          try {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating com.example.Options");
            JavaFileObject javaFile = processingEnv.getFiler().createSourceFile("com.example.Options");
            Writer w = javaFile.openWriter();
            try {
              PrintWriter pw = new PrintWriter(w);
              pw.println("package com.example;");
              pw.println("public class Options {");
              pw.println("    public static final String textToPrint = \"" + options.get("textToPrint") + "\";");
              pw.println("    public static final boolean enablePrint = " + options.get("enablePrint") + ";");
              pw.println("}");
              pw.flush();
            } finally {
              w.close();
            }
          } catch (IOException x) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                x.toString());
          }
        }
      }
    }
    return false;
  }
}
like image 20
marcinj Avatar answered Sep 19 '22 06:09

marcinj