Groovy の Canonical アノテーションっぽいのを Xtend で

@Data が使えたら楽なんですが mutable な物の場合 @Data が使えません。とりあえず equals, hashCode, toString を追加したいなーという時用の Active Annotation 書いてみました。

適当なので、commons-lang に思いっきり依存してます。

import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import org.eclipse.xtend.lib.macro.AbstractClassProcessor
import org.eclipse.xtend.lib.macro.Active
import org.eclipse.xtend.lib.macro.TransformationContext
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Active(CanonitalProcessor)
annotation Canonical {
}
class CanonitalProcessor extends AbstractClassProcessor {
  override doTransform(MutableClassDeclaration annotatedClass, 
    extension TransformationContext context) {
    
    annotatedClass.addMethod("toString") [
      returnType = string
      body = ['''return «toJavaCode(newTypeReference("org.apache.commons.lang3.builder.ToStringBuilder"))».reflectionToString(this);''']
      addAnnotation(Override.findTypeGlobally)
    ]
    annotatedClass.addMethod("equals") [
      returnType = primitiveBoolean
      addParameter("value", object)
      body = ['''return «toJavaCode(newTypeReference("org.apache.commons.lang3.builder.EqualsBuilder"))».reflectionEquals(this, value, false);''']
      addAnnotation(Override.findTypeGlobally)
    ]
    annotatedClass.addMethod("hashCode") [
      returnType = primitiveInt
      body = ['''return «toJavaCode(newTypeReference("org.apache.commons.lang3.builder.HashCodeBuilder"))».reflectionHashCode(this, false);''']
      addAnnotation(Override.findTypeGlobally)
    ]
    // .java には Canonical アノテーションを残さない
    annotatedClass.findAnnotation(Canonical.findTypeGlobally).remove
  }
}

使う方は、

@Canonical
class User {
  @Property int id
  @Property String name
}

これでこんな .java を吐きます。

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;

@SuppressWarnings("all")
public class User {
  private int _id;
  
  public int getId() {
    return this._id;
  }
  
  public void setId(final int id) {
    this._id = id;
  }
  
  private String _name;
  
  public String getName() {
    return this._name;
  }
  
  public void setName(final String name) {
    this._name = name;
  }
  
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this);
  }
  
  @Override
  public boolean equals(final Object value) {
    return EqualsBuilder.reflectionEquals(this, value, false);
  }
  
  @Override
  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this, false);
  }
}