DEV Community

Juan Sedano
Juan Sedano

Posted on • Originally published at jsedano.dev on

A custom annotation to apply currying in Java

I created a custom annotation in order to apply currying to a method or constructor.

You can find the complete code for this here: curry.

For more information on currying check this post and for a background on annotations in Java check this one.

First we create the annotation:

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface Curry {}
Enter fullscreen mode Exit fullscreen mode

We only need the annotation to be processed and then discarded (we don’t need to have it available at run time or recorded on the .class) so we use @Retention(RetentionPolicy.SOURCE).

This annotation will only be used on methods and constructors so we set those as the Target on @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}).

Then we create a processor for the annotation:

@SupportedAnnotationTypes("dev.jsedano.curry.annotation.Curry")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@AutoService(Processor.class)
public class CurryProcessor extends AbstractProcessor {
Enter fullscreen mode Exit fullscreen mode

Using @SupportedAnnotationTypes("dev.jsedano.curry.annotation.Curry") we say that this processor will only look for that particular annotation.

@SupportedSourceVersion(SourceVersion.RELEASE_17) here we are saying the Java version supported.

The last one is pretty interesting, @AutoService(Processor.class) is from a Google library called auto-service. In order for the Java compiler to use a processor the class needs to be declared inside the jar on the META-INF/servicesdirectory on the javax.annotation.processing.Processor file, the auto-service library does that for you.

Then we need to implement the process method on out custom processor.

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Enter fullscreen mode Exit fullscreen mode

Inside our class we get the processingEnv object that provides us with functionality such as getMessager() that we use to print a warning message on compile time when the @Curry annotation is used on a method with 1 or more than 10 parameters:

otherMethods.stream()
    .forEach(
        e ->
            processingEnv
                .getMessager()
                .printMessage(
                    Diagnostic.Kind.MANDATORY_WARNING,
                    "incorrect number of parameters, allowed only between 2 and 1O, will not generate code for this one",
                    e));
Enter fullscreen mode Exit fullscreen mode

We also have this one getFiler() which allows us to create Java source files:

JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(builderClassName);
Enter fullscreen mode Exit fullscreen mode

On the tests module of the project we declare some methods with the @Curry annotation, for example this constructor:

@Curry
public AnnotatedClass(
    boolean aBoolean, List<String> aStringList, int aNumber, char aChar, float aFloat) {
  this.aBoolean = aBoolean;
  this.aStringList = aStringList;
  this.aNumber = aNumber;
  this.aChar = aChar;
  this.aFloat = aFloat;
}
Enter fullscreen mode Exit fullscreen mode

After running mvn clean verify on the parent module we can see the autogenerated code under target/generated-sources/annotations/dev.jsedano.curry.tests:

public static java.util.function.Function<java.lang.Boolean,java.util.function.Function<java.util.List<java.lang.String>,java.util.function.Function<java.lang.Integer,java.util.function.Function<java.lang.Character,java.util.function.Function<java.lang.Float,dev.jsedano.curry.tests.AnnotatedClass>>>>> pentaConstructor(dev.jsedano.curry.util.function.PentaFunction<java.lang.Boolean,java.util.List<java.lang.String>,java.lang.Integer,java.lang.Character,java.lang.Float,dev.jsedano.curry.tests.AnnotatedClass> function) {
    return v0->v1->v2->v3->v4-> function.apply(v0,v1,v2,v3,v4);
}
Enter fullscreen mode Exit fullscreen mode

It is not pretty looking, but we can use it to then curry the five parameter constructor of the example class:

var pentaConstructor = AnnotatedClassCurryer.pentaConstructor(AnnotatedClass::new);
Enter fullscreen mode Exit fullscreen mode

You can see another example here, but if you want to compile it you need to do mvn clean install on the curryer module of curry.

@Curry
public static String wget(
    int connectionTimeout,
    int readTimeout,
    boolean followRedirects,
    String requestMethod,
    String address) {
  try {
    URL url = new URL(address);
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    con.setRequestMethod(requestMethod);
    con.setConnectTimeout(connectionTimeout);
    con.setReadTimeout(readTimeout);
    con.setInstanceFollowRedirects(followRedirects);
    BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
    String inputLine;
    StringBuffer content = new StringBuffer();
    while ((inputLine = in.readLine()) != null) {
      content.append(inputLine);
    }
    in.close();
    return address + " " + content.toString();
  } catch (Exception e) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    e.printStackTrace(pw);
    String stackTrace = sw.toString();
    return address + " " + stackTrace.substring(0, stackTrace.indexOf("\n"));
  }
}
Enter fullscreen mode Exit fullscreen mode

Then we can set the values we need in a curried way and use it:

public static void main(String[] args) {
  var get =
      WgetVersion2Curryer.wget(WgetVersion2::wget)
          .apply(100)
          .apply(100)
          .apply(false)
          .apply("GET");

  List.of(
          "https://www.google.com",
          "https://www.wikipedia.org",
          "asdf",
          "https://docs.oracle.com/javase/10/docs/api/java/net/package-summary.html",
          "https://jsedano.dev",
          "https://raw.githubusercontent.com/center-key/clabe-validator/main/clabe.ts")
      .parallelStream()
      .map(get)
      .forEach(System.out::println);
}
Enter fullscreen mode Exit fullscreen mode

Download the complete code from this post here: curry.

Top comments (0)