Skip to content

Commit

Permalink
#889 annotation injection can print array of attribute names in order
Browse files Browse the repository at this point in the history
  • Loading branch information
elucash committed Dec 27, 2018
1 parent 7d56be7 commit 445ed07
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 22 deletions.
2 changes: 2 additions & 0 deletions annotate/src/org/immutables/annotate/InjectAnnotation.java
Expand Up @@ -106,6 +106,8 @@
* attributes.
* <li>{@code [[!name]]} inserts the simple name of the target attribute (or type) into annotation
* code, insertions are literal, without any quoutes etc.
* <li>{@code [[*names]]} inserts the simple names of all attributes defined by abstract value
* type as as comma separated array initializer of quoted string literals. {@code {"a", "b", "c"}}
* <li>{@code [[}<em>attr_name</em>{@code ]]} inserts source formatted value of injection
* annotation into code, where <em>attr_name</em> is one of the name of injection annotation
* attributes.
Expand Down
Expand Up @@ -47,6 +47,13 @@ public void fieldAnnotationFromAccessor() throws Exception {
check(a.b()).is("");
}

@Test
public void attributeNamesArray() throws Exception {
ToInj a = ImmutableOnAccessorToField.class.getAnnotation(ToInj.class);
check(a.a()).is(0);
check(a.attrs()).isOf("a", "b");
}

@Test
public void typeAndBuilderFromType() throws Exception {
ToInj t = ImmutableOnTypeAndBuilder.class.getAnnotation(ToInj.class);
Expand Down
17 changes: 14 additions & 3 deletions annotate/test/org/immutable/fixture/annotate/InjAnn.java
Expand Up @@ -27,6 +27,8 @@ public interface InjAnn {
int a();

String b() default "";

String[] attrs() default {};
}

@Retention(RetentionPolicy.RUNTIME)
Expand All @@ -40,8 +42,8 @@ public interface InjAnn {
}

@Retention(RetentionPolicy.RUNTIME)
@InjectAnnotation(code = "@org.immutable.fixture.annotate.InjAnn.ToInj"
+ "(a=[[c]], b=[[d]])",
@InjectAnnotation(
code = "@org.immutable.fixture.annotate.InjAnn.ToInj(a=[[c]], b=[[d]])",
target = Where.INITIALIZER)
@interface Cn3 {
int c();
Expand All @@ -56,7 +58,15 @@ public interface InjAnn {
@interface ImB {}

@Retention(RetentionPolicy.RUNTIME)
@InjectAnnotation(code = "(a=71, b=\"synthetic of [[!name]]\")",
@InjectAnnotation(
code = "(a=0, attrs=[[*names]])",
type = ToInj.class,
target = {Where.IMMUTABLE_TYPE})
@interface AtNames {}

@Retention(RetentionPolicy.RUNTIME)
@InjectAnnotation(
code = "(a=71, b=\"synthetic of [[!name]]\")",
type = ToInj.class,
target = {Where.SYNTHETIC_FIELDS})
@interface BmS {}
Expand All @@ -81,6 +91,7 @@ interface OnTypeToField {
}

@Value.Immutable
@AtNames
interface OnAccessorToField {
@Bn2(a = 44)
int a();
Expand Down
@@ -1,5 +1,5 @@
/*
Copyright 2014 Immutables Authors and Contributors
Copyright 2018 Immutables Authors and Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -17,19 +17,47 @@

import org.immutables.value.Value;

interface InheritedInterface {
int b();
@Value.Enclosing
public interface AttributeOrdering {

int y();
interface A {
int a1();

int a();
}
int a2();
}

interface B extends A {
int b1();

int b2();
}

interface C extends A {
int c1();

int c2();
}

@Value.Immutable
interface D extends B, C {
int d();
}

interface InheritedInterface {
int b();

int y();

int a();
}

@Value.Immutable
public abstract class SourceOrderingEntity implements InheritedInterface {

@Value.Immutable
public abstract class SourceOrderingEntity implements InheritedInterface {
public abstract int z();

public abstract int z();
@Override
public abstract int y();
}

@Override
public abstract int y();
}
17 changes: 14 additions & 3 deletions value-fixture/test/org/immutables/fixture/ValuesTest.java
Expand Up @@ -184,14 +184,25 @@ public void primitiveDefault() {

@Test
public void sourceOrdering() {
SourceOrderingEntity v = ImmutableSourceOrderingEntity.builder()
check(ImmutableAttributeOrdering.SourceOrderingEntity.builder()
.b(2) // b from inherited in source order
.a(1) // a from inherited in source order, y skipped because overriden next
.z(4) // z directly declared in source order
.y(3) // y overriden here, in source order
.build();
.build()).hasToString("SourceOrderingEntity{b=2, a=1, z=4, y=3}");
}

check(v).hasToString("SourceOrderingEntity{b=2, a=1, z=4, y=3}");
@Test
public void moreSourceOrdering() {
check(ImmutableAttributeOrdering.D.builder()
.a1(1)
.a2(2)
.b1(3)
.b2(4)
.c1(5)
.c2(6)
.d(7)
.build()).hasToString("D{a1=1, a2=2, b1=3, b2=4, c1=5, c2=6, d=7}");
}

@Test
Expand Down
@@ -1,7 +1,9 @@
package org.immutables.value.processor.meta;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Joiner.MapJoiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
import java.lang.annotation.Annotation;
import java.util.Arrays;
Expand All @@ -16,6 +18,7 @@
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import org.immutables.generator.AnnotationMirrors;
import org.immutables.generator.StringLiterals;
import org.immutables.mirror.Mirror;
import org.immutables.value.processor.meta.AnnotationInjections.InjectAnnotation.Where;
import org.immutables.value.processor.meta.Proto.Environment;
Expand All @@ -26,7 +29,9 @@ public final class AnnotationInjections {
private static final String P_L = "[[";
private static final String P_ALL = "[[*]]";
private static final String P_SIMPLE_NAME = "[[!name]]";
private static final MapJoiner ATTR_JOINER = Joiner.on(", ").withKeyValueSeparator(" = ");
private static final String P_ALL_NAMES = "[[*names]]";
private static final Joiner COMMA_JOINER = Joiner.on(", ");
private static final MapJoiner ATTR_JOINER = COMMA_JOINER.withKeyValueSeparator(" = ");

private AnnotationInjections() {}

Expand Down Expand Up @@ -77,11 +82,12 @@ public static InjectionInfo infoFrom(InjectAnnotationMirror mirror) {
public static Collection<String> collectInjections(
Element element,
Where target,
Collection<String> attributeNames,
Iterable<AnnotationInjection>... injections) {
Map<String, String> injectionCode = new HashMap<>();
for (Iterable<AnnotationInjection> inj : injections) {
for (AnnotationInjection a : inj) {
a.addIfApplicable(element, target, injectionCode);
a.addIfApplicable(element, target, attributeNames, injectionCode);
}
}
return injectionCode.values();
Expand All @@ -96,7 +102,11 @@ public static final class AnnotationInjection {
this.literals = literals;
}

void addIfApplicable(Element element, Where target, Map<String, String> annotationCode) {
void addIfApplicable(
Element element,
Where target,
Collection<String> attributeNames,
Map<String, String> annotationCode) {
String simpleName = element.getSimpleName().toString();

if (annotationCode.containsKey(info.deduplicationKey)) {
Expand Down Expand Up @@ -127,15 +137,15 @@ void addIfApplicable(Element element, Where target, Map<String, String> annotati
}

String code = info.hasPlaceholders
? interpolateCode(simpleName)
? interpolateCode(simpleName, attributeNames)
: info.code;

annotationCode.put(
info.deduplicationKey,
prependAnnotationTypeIfNecessary(code));
}

private String interpolateCode(String simpleName) {
private String interpolateCode(String simpleName, Collection<String> attributeNames) {
// The implementation is pretty dumb, sorry: no regex, parsing or fancy libraries
String c = info.code;

Expand All @@ -149,6 +159,14 @@ private String interpolateCode(String simpleName) {

c = c.replace(P_SIMPLE_NAME, simpleName);

if (c.contains(P_ALL_NAMES)) {
String literals = FluentIterable.from(attributeNames)
.transform(ToLiteral.FUNCTION)
.join(COMMA_JOINER);

c = c.replace(P_ALL_NAMES, "{" + literals + "}");
}

for (Entry<String, String> e : literals.entrySet()) {
c = c.replace(P_L + e.getKey() + P_R, e.getValue());
}
Expand Down Expand Up @@ -225,4 +243,12 @@ private static String deduplicationKeyFor(String deduplicationKey, String annota
return code;
}
}

private enum ToLiteral implements Function<String, String> {
FUNCTION;
@Override
public String apply(String input) {
return StringLiterals.toLiteral(input);
}
}
}
Expand Up @@ -16,6 +16,7 @@
package org.immutables.value.processor.meta;

import com.google.common.base.Ascii;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
Expand Down Expand Up @@ -1774,6 +1775,7 @@ public Collection<String> elementInitializerInjectedAnnotations() {
private Collection<String> collectInjections(Where target) {
return AnnotationInjections.collectInjections(element,
target,
Collections.singleton(name()),
annotationInjections,
containingType.getDeclaringTypeAnnotationInjections(),
containingType.getDeclaringTypeEnclosingAnnotationInjections(),
Expand All @@ -1798,4 +1800,12 @@ public boolean isNullableInSupertype() {
public String atNullableInSupertypeLocal() {
return nullabilityInSupertype != null ? nullabilityInSupertype.asLocalPrefix() : "";
}

enum ToName implements Function<ValueAttribute, String> {
FUNCTION;
@Override
public String apply(ValueAttribute input) {
return input.name();
}
}
}
Expand Up @@ -22,6 +22,7 @@
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ForwardingCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
Expand Down Expand Up @@ -1834,6 +1835,7 @@ List<AnnotationInjection> getDeclaringTypeEnclosingAnnotationInjections() {
private Collection<String> collectInjections(Where target) {
return AnnotationInjections.collectInjections(element,
target,
Lists.transform(attributes, ValueAttribute.ToName.FUNCTION),
getDeclaringTypeAnnotationInjections(),
getDeclaringTypeEnclosingAnnotationInjections(),
getDeclaringPackageAnnotationInjections());
Expand Down

0 comments on commit 445ed07

Please sign in to comment.