immutables
immutables copied to clipboard
Pass annotation to field instead of getter
Consider this class:
@Value.Immutable
@Value.Style(passAnnotations = { Id.class })
@Entity
public abstract class HelloWorld {
@Id
@GeneratedValue
public abstract Optional<Long> id();
}
In this case the @Id annotation is placed on the generated getter. Is it possible to specify that it should be placed on the generated field instead? Like so:
...
public final class ImmutableHelloWorld extends HelloWorld {
@Id // Place it here
private final @Nullable Long id;
/**
* @return The value of the {@code id} attribute
*/
@Override
// No @Id annotation here
public Optional<Long> id() {
return Optional.ofNullable(id);
}
...
}
I looked at #456 and #475 but it's still not clear to me if this is possible.
thank you for raising this. Pass annotation is a special, narrow mechanism, only to replicate same annotation on the generated class on the same element. To generate arbitrary annotation on fields and other derived element, please, use annoration injection mechanism, maven module org.immutables:annotate , see @InjectAnnotation's JavaDoc, there are also some hints in #475
I had org.immutables.value.internal.$processor$.meta.$AnnotationInjections.AnnotationInjection imported because I was missing the org.immutables:annotate artifact and I was skeptical about using it since it seemed to be for internal use. Using the one you mentioned worked.
It's a bit disappointing that this requires a separate interface to do. Seems like a very basic and necessary feature when working with libraries that make heavy use of annotations. Are there any technical reasons why it is done this way? Other than that, I really like this library.
For future readers, creating this annotation:
@InjectAnnotation(type = Id.class, target = InjectAnnotation.Where.FIELD)
public @interface InjectJpaId {
}
And then applying it alongside @Id successfully placed the annotation on the field.
I agree with @peterjohansen. Having to create extra meta-annotations for every annotation we want to use on a field can be cumbersome when you consider all the possible libraries that may be commonly used (JPA, Jackson, validation, etc.).
For some use-cases such as JPA entities, it's almost more natural to manually create the mutable / modifiable class and have Immutables auto-generate the "immutable" interface and type. By default, all fields in the mutable class would have getter methods in the interface but maybe this could be customized with @Value.Style or other annotation to exclude certain fields, for example. The mutable class could extend the yet-to-be generated interface similar to how it is done currently for builders. Is it possible to do something like this?
@peterjohansen does it work for abstracts though?
I'm trying this:
@Immutable
@InjectAnnotation(type=ApiModelProperty.class, target = InjectAnnotation.Where.FIELD)
@ApiModel(value = "test")
static abstract class AbstractTestAPIRequest implements TestDomain.TestProvider {
public abstract List<TestItem> getTestItems();
@ApiModelProperty(value = "bbbb", example = "en")
public abstract String getLanguage();
}
But I get an error: '@InjectAnnotation' not applicable to type
I also tried Style(passAnnotations) instead, but in that case, the immutable is not even generated
@InjectAnnotation is a meta-annotation:
@InjectAnnotation(type=ApiModelProperty.class, target = InjectAnnotation.Where.FIELD)
@interface ImmModelProp {}
@Immutable
@ImmModelProp // <!-- this can be placed on type or individual property
static abstract class AbstractTestAPIRequest implements TestDomain.TestProvider {
public abstract List<TestItem> getTestItems();
@ApiModelProperty(value = "bbbb", example = "en")
public abstract String getLanguage();
}
how would you create with both a field and a "property" with on the same class? also where's the documentation for this additional library? seems it's not documented, in which case please document.
a popular use case would be jpa entities.
Also, if I understand you correctly I would have to create an annotation for every annotation I want to use? that sounds really annoying for JPA
trying to do what you suggested
package com.mckesson.dex.annotation.jpa.immutables;
import javax.persistence.AccessType;
import org.immutables.annotate.InjectAnnotation;
@InjectAnnotation(type= javax.persistence.Access.class, target = { InjectAnnotation.Where.ACCESSOR, InjectAnnotation.Where.FIELD })
public @interface Access {
/**
* (Required) Specification of field- or property-based access.
*/
AccessType value();
}
given this entity:
package com.mckesson.dex.model.principal;
import java.io.Serializable;
import javax.persistence.AccessType;
import javax.persistence.EnumType;
import javax.persistence.ForeignKey;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import com.mckesson.dex.annotation.jpa.immutables.Access;
import com.mckesson.dex.annotation.jpa.immutables.Cache;
import com.mckesson.dex.annotation.jpa.immutables.Cacheable;
import com.mckesson.dex.annotation.jpa.immutables.Column;
import com.mckesson.dex.annotation.jpa.immutables.Entity;
import com.mckesson.dex.annotation.jpa.immutables.Enumerated;
import com.mckesson.dex.annotation.jpa.immutables.Id;
import com.mckesson.dex.annotation.jpa.immutables.JoinTable;
import com.mckesson.dex.annotation.jpa.immutables.ManyToOne;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.immutables.serial.Serial;
import org.immutables.value.Value;
@Entity
@Cacheable
@Value.Immutable
@Serial.Version( 1L )
@Access(AccessType.FIELD)
@Table(name = "country_subdivision")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public interface CountrySubdivision extends Serializable {
@ManyToOne(optional = false)
@JoinTable(
name = "jurisdiction_country_subdivision",
indexes = @Index(name = "idx_jcs_uq", columnList = "jurisdiction,country_subdivision", unique = true),
joinColumns = @javax.persistence.JoinColumn(name = "country_subdivision"),
foreignKey = @ForeignKey(name = "mac_juris_juris_fk"),
inverseJoinColumns = @javax.persistence.JoinColumn(name = "jurisdiction")
)
Jurisdiction getJurisdiction();
@Id
@NotNull
@Access(AccessType.PROPERTY)
@Enumerated(EnumType.STRING)
@Column(name = "code", length = 3, nullable = false, insertable = false, updatable = false)
public CountrySubdivisionCode getCode( );
}
it generated this code
package com.mckesson.dex.model.principal;
import com.google.common.base.MoreObjects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.Var;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.persistence.Access;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import org.hibernate.annotations.Cache;
import org.immutables.value.Generated;
/**
* Immutable implementation of {@link CountrySubdivision}.
* <p>
* Use the builder to create immutable instances:
* {@code ImmutableCountrySubdivision.builder()}.
*/
@Generated(from = "CountrySubdivision", generator = "Immutables")
@SuppressWarnings({"all"})
@ParametersAreNonnullByDefault
@javax.annotation.Generated("org.immutables.processor.ProxyProcessor")
@Immutable
@CheckReturnValue
public final class ImmutableCountrySubdivision implements CountrySubdivision {
@Access
private final Jurisdiction jurisdiction;
@Column
@Enumerated
@Access
private final CountrySubdivisionCode code;
private ImmutableCountrySubdivision(
Jurisdiction jurisdiction,
CountrySubdivisionCode code) {
this.jurisdiction = jurisdiction;
this.code = code;
}
/**
* @return The value of the {@code jurisdiction} attribute
*/
@Entity@ManyToOne@JoinTable@Cacheable@Cache@Access
@Override
public Jurisdiction getJurisdiction() {
return jurisdiction;
}
/**
* @return The value of the {@code code} attribute
*/
@Column@Entity@Cacheable@Enumerated@Cache@Id@Access
@Override
public CountrySubdivisionCode getCode() {
return code;
}
/**
* Copy the current immutable object by setting a value for the {@link CountrySubdivision#getJurisdiction() jurisdiction} attribute.
* A shallow reference equality check is used to prevent copying of the same value by returning {@code this}.
* @param value A new value for jurisdiction
* @return A modified copy of the {@code this} object
*/
public final ImmutableCountrySubdivision withJurisdiction(Jurisdiction value) {
if (this.jurisdiction == value) return this;
Jurisdiction newValue = Objects.requireNonNull(value, "jurisdiction");
return new ImmutableCountrySubdivision(newValue, this.code);
}
/**
* Copy the current immutable object by setting a value for the {@link CountrySubdivision#getCode() code} attribute.
* A value equality check is used to prevent copying of the same value by returning {@code this}.
* @param value A new value for code
* @return A modified copy of the {@code this} object
*/
public final ImmutableCountrySubdivision withCode(CountrySubdivisionCode value) {
if (this.code == value) return this;
CountrySubdivisionCode newValue = Objects.requireNonNull(value, "code");
if (this.code.equals(newValue)) return this;
return new ImmutableCountrySubdivision(this.jurisdiction, newValue);
}
/**
* This instance is equal to all instances of {@code ImmutableCountrySubdivision} that have equal attribute values.
* @return {@code true} if {@code this} is equal to {@code another} instance
*/
@Override
public boolean equals(@Nullable Object another) {
if (this == another) return true;
return another instanceof ImmutableCountrySubdivision
&& equalTo((ImmutableCountrySubdivision) another);
}
private boolean equalTo(ImmutableCountrySubdivision another) {
return jurisdiction.equals(another.jurisdiction)
&& code.equals(another.code);
}
/**
* Computes a hash code from attributes: {@code jurisdiction}, {@code code}.
* @return hashCode value
*/
@Override
public int hashCode() {
@Var int h = 5381;
h += (h << 5) + jurisdiction.hashCode();
h += (h << 5) + code.hashCode();
return h;
}
/**
* Prints the immutable value {@code CountrySubdivision} with attribute values.
* @return A string representation of the value
*/
@Override
public String toString() {
return MoreObjects.toStringHelper("CountrySubdivision")
.omitNullValues()
.add("jurisdiction", jurisdiction)
.add("code", code)
.toString();
}
/**
* Creates an immutable copy of a {@link CountrySubdivision} value.
* Uses accessors to get values to initialize the new immutable instance.
* If an instance is already immutable, it is returned as is.
* @param instance The instance to copy
* @return A copied immutable CountrySubdivision instance
*/
public static ImmutableCountrySubdivision copyOf(CountrySubdivision instance) {
if (instance instanceof ImmutableCountrySubdivision) {
return (ImmutableCountrySubdivision) instance;
}
return ImmutableCountrySubdivision.builder()
.from(instance)
.build();
}
private static final long serialVersionUID = 1L;
/**
* Creates a builder for {@link ImmutableCountrySubdivision ImmutableCountrySubdivision}.
* <pre>
* ImmutableCountrySubdivision.builder()
* .jurisdiction(com.mckesson.dex.model.principal.Jurisdiction) // required {@link CountrySubdivision#getJurisdiction() jurisdiction}
* .code(com.mckesson.dex.model.principal.CountrySubdivisionCode) // required {@link CountrySubdivision#getCode() code}
* .build();
* </pre>
* @return A new ImmutableCountrySubdivision builder
*/
public static ImmutableCountrySubdivision.Builder builder() {
return new ImmutableCountrySubdivision.Builder();
}
/**
* Builds instances of type {@link ImmutableCountrySubdivision ImmutableCountrySubdivision}.
* Initialize attributes and then invoke the {@link #build()} method to create an
* immutable instance.
* <p><em>{@code Builder} is not thread-safe and generally should not be stored in a field or collection,
* but instead used immediately to create instances.</em>
*/
@Generated(from = "CountrySubdivision", generator = "Immutables")
@NotThreadSafe
public static final class Builder {
private static final long INIT_BIT_JURISDICTION = 0x1L;
private static final long INIT_BIT_CODE = 0x2L;
private long initBits = 0x3L;
private @Nullable Jurisdiction jurisdiction;
private @Nullable CountrySubdivisionCode code;
private Builder() {
}
/**
* Fill a builder with attribute values from the provided {@code CountrySubdivision} instance.
* Regular attribute values will be replaced with those from the given instance.
* Absent optional values will not replace present values.
* @param instance The instance from which to copy values
* @return {@code this} builder for use in a chained invocation
*/
@CanIgnoreReturnValue
public final Builder from(CountrySubdivision instance) {
Objects.requireNonNull(instance, "instance");
jurisdiction(instance.getJurisdiction());
code(instance.getCode());
return this;
}
/**
* Initializes the value for the {@link CountrySubdivision#getJurisdiction() jurisdiction} attribute.
* @param jurisdiction The value for jurisdiction
* @return {@code this} builder for use in a chained invocation
*/
@CanIgnoreReturnValue
public final Builder jurisdiction(Jurisdiction jurisdiction) {
this.jurisdiction = Objects.requireNonNull(jurisdiction, "jurisdiction");
initBits &= ~INIT_BIT_JURISDICTION;
return this;
}
/**
* Initializes the value for the {@link CountrySubdivision#getCode() code} attribute.
* @param code The value for code
* @return {@code this} builder for use in a chained invocation
*/
@CanIgnoreReturnValue
public final Builder code(CountrySubdivisionCode code) {
this.code = Objects.requireNonNull(code, "code");
initBits &= ~INIT_BIT_CODE;
return this;
}
/**
* Builds a new {@link ImmutableCountrySubdivision ImmutableCountrySubdivision}.
* @return An immutable instance of CountrySubdivision
* @throws java.lang.IllegalStateException if any required attributes are missing
*/
public ImmutableCountrySubdivision build() {
if (initBits != 0) {
throw new IllegalStateException(formatRequiredAttributesMessage());
}
return new ImmutableCountrySubdivision(jurisdiction, code);
}
private String formatRequiredAttributesMessage() {
List<String> attributes = new ArrayList<>();
if ((initBits & INIT_BIT_JURISDICTION) != 0) attributes.add("jurisdiction");
if ((initBits & INIT_BIT_CODE) != 0) attributes.add("code");
return "Cannot build CountrySubdivision, some of required attributes are not set " + attributes;
}
}
}
I also tried code="[[*]]" and it generated it as @Accessvalue = AccessType.FIELD` with the right import, but that's not a valid annotation usage. If I do this
@InjectAnnotation(type= javax.persistence.Access.class, code="@Access([[*]])", target = { InjectAnnotation.Where.ACCESSOR, InjectAnnotation.Where.FIELD })
it generates the proper annotation on the field but leaves out the import.
version 2.8.2
What gives?