google / auto

A collection of source code generators for Java.
Apache License 2.0
10.43k stars 1.2k forks source link

Support annotations in generated field #280

Open 573 opened 9 years ago

573 commented 9 years ago

Let's say I have a class ExcelDto:

import com.google.auto.value.AutoValue;
import javax.xml.bind.annotation.*;
import java.util.List;

@XmlRootElement(name = "excel")
@XmlAccessorType(XmlAccessType.FIELD)
@AutoValue
public abstract class ExcelDto {

    @XmlElementWrapper(name = "tables")
    @XmlElement(name = "table")
    public abstract List<TableDto> getTables();

    public static ExcelDto create(List<TableDto> tables) {
        return new AutoValue_ExcelDto(tables);
    }
}

Now I wanted the generator to build an AutoValue_ExcelDto class, but with the field tables annotated like:

...

  @javax.xml.bind.annotation.XmlTransient
  private final List<TableDto> tables;

...

Is that possible, how to achieve this?

dkpalmer commented 7 years ago

+1. I also just reached for this with an Objectify annotated field.

eamonnmcmanus commented 7 years ago

This doesn't fit well into the current AutoValue Extensions API, but we could either add something to that API (for example, the ability to add an arbitrary string before the declaration of any given field), or we could have an annotation that specifies such a string. Then, in the original example, you would just specify that the string is to be "@javax.xml.bind.annotation.XmlTransient", and in the Objectify example you should be able to do something similar.

dkpalmer commented 7 years ago

Both sound reasonable to me. As a String value, I'm guessing the full namespace would need to be specified without an import?

eamonnmcmanus commented 7 years ago

Yes. Alternatively, or as well, we could have a way to specify annotations explicitly. Then we could handle imports correctly. There's already code in AutoAnnotation for rendering annotation instances into code, though that would only be usable if you're able to make an instance of the annotation at compile time. (For annotations that exist before you start, such as the examples here, there should be no problem doing that.)

dkpalmer commented 7 years ago

Personally that sounds more ideal to me vs hoping devs don't fat finger the full path. My $0.02.

dkpalmer commented 7 years ago

Hi. Quick bump on this, @eamonnmcmanus. Any thoughts on whether this might make its way into the roadmap?

eamonnmcmanus commented 7 years ago

No immediate plans, though since I work on App Engine for my day job, making AutoValue work better with Objectify would definitely make sense. Thanks for the ping.

eamonnmcmanus commented 5 years ago

AutoValue now copies annotations onto fields as well as methods, if @CopyAnnotations tells it to and if the annotations can go on fields. Does this address the problem at hand?

eamonnmcmanus commented 5 years ago

I believe the current behaviour of @CopyAnnotations does not help with the Objectify case, because some of the annotations in question apply only to fields. That means we can't apply them to the abstract property methods in the @AutoValue class, which is what we would need to do for @CopyAnnotations to copy them into the fields.

To take an Objectify example, let's say we have a value class that defines a field like this:

  @Id @Index @Expose private String name;

We'd like to convert it into an @AutoValue class that defines a property like this:

  @Something public abstract String getName();

and we'd like the @Something annotation to cause the name field in the generated subclass to be declared as shown. @CopyAnnotations does copy annotations from the abstract property method to the generated field, when that is valid. But if an annotation that you want on the generated field isn't valid on a method then you can't use this. That's the case for the @Id and @Index fields here.

There's a couple of ways we could address problems like these: (1) a mechanism for explicitly describing annotations that should be applied, or (2) a mechanism for inserting arbitrary text before the field declaration.

(1) With an explicit mechanism for annotations we might have this:

  @FieldAnnotations({
    @FieldAnnotation(Id.class), @FieldAnnotation(Index.class), @FieldAnnotation(Expose.class)
  })
  public abstract String getName();

which would cause this to be generated:

import com.google.gson.annotations.Expose;
import com.googlecode.objectify.v4.annotation.Id;
import com.googlecode.objectify.v4.annotation.Index;
...
final class AutoValue_Foo extends Foo {
  @Id @Index @Expose private final String name;
  ...

The annotations here happen not to have parameters, but if we did support parameters that would have to be through some mechanism allowing us to write something like

  @FieldAnnotation(value = Id.class, parameters = {
      @Parameter(name = "string", value = "\"foo\""), @Parameter(name = "integer", value = "23")
  })

or maybe

  @FieldAnnotation(value = Id.class, parameters = "(string = \"foo\", integer = 23)")

to generate:

  @Id(string = "foo", integer = 23)

This is a lot of new API for a relatively obscure case.

(2) With a mechanism for arbitrary text we might have this:

  @ExtraText("@com.googlecode.objectify.v4.annotation.Id @com.googlecode.objectify.v4.annotation.Index @com.google.gson.annotations.Expose")
  public abstract String getName();

which would cause this to be generated:

  @com.googlecode.objectify.v4.annotation.Id @com.googlecode.objectify.v4.annotation.Index @com.google.gson.annotations.Expose private final String name;

A small additional advantage of this approach is that it could be used to declare the generated field as transient. (Also volatile, but I'm not sure what that would be for.)

This is much simpler, although the need to spell out the fully-qualified names of the annotations is sad.

I should also note that neither of these mechanisms gives you a way to say that you don't want the generated field to be final. I suspect that the Objectify use case might require that too.

eamonnmcmanus commented 3 years ago

Another idea, which we've been exploring in conjunction with AutoFactory, is to allow @CopyAnnotations to specify somewhere to copy annotations from. Currently it copies class annotations to generated subclasses and method annotations to generated overriding methods, and it will copy those method annotations to generated fields too if they are applicable. But imagine you could say something like this:

@CopyAnnotations(fromClass = AnnotationTemplates.class, fromFields = {"id", "index", "expose"})
public abstract String getName();

and

// This only exists as a source from which to copy annotations.
public class AnnotationTemplates {
  @Id private Object id;
  @Index private Object index;
  @Expose private Object expose;

  private AnnotationTemplates() {}
}

That would mean that field annotations would be copied from the fields in AnnotationTemplates with the given names. In this case those are dummy fields that are only there as a place to park annotations we want copied. In that case, the copied annotations would be copied the same as copied annotations elsewhere. That means that they will be imported in the usual way in the generated code. And it also means that you can write arbitrary complicated annotations, which will be checked by the compiler, and have them copied into the generated code.