Skip to content

Commit

Permalink
Merge pull request #1409 from immutables/1408-with-unary-operator
Browse files Browse the repository at this point in the history
#1408 withUnaryOperator style flag/naming template
  • Loading branch information
elucash authored Oct 15, 2022
2 parents dc8ed6c + 2feeede commit e264c39
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 6 deletions.
17 changes: 15 additions & 2 deletions value-annotations/src/org/immutables/value/Value.java
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@
* mimick style of bean getters. If none specified or if none matches, then raw accessor name
* will be taken literally.
* <p>
* By default only {@code get*} prefix is recognized, along with falling back to use accessor
* By default, only {@code get*} prefix is recognized, along with falling back to use accessor
* name literally. It is up to you if you want to use "get" prefixes or not. Original author is
* leaning towards not using noisy prefixes for attributes in immutable objects, drawing
* similarity with annotation attributes, however usage of "get" is neither recommended, nor
Expand All @@ -469,6 +469,19 @@
*/
String with() default "with*";

/**
* Modify-by-copy method which receives {@link java.util.function.UnaryOperator}
* to transform an attribute before constructing a copy of immutable object.
* This feature is disabled by default, unless you specify a naming template for this method.
* A template can be something like {@code "with*Mapped"}, {@code "update*"},
* or {@code "transform*"} – the choice is yours.
* Can even be {@code "with*"} or {@code "*"} in hope there will be no overload collisions.
* <p>
* Unary operator transforms values, optional values and collection elements. (currently JDK Optional only, use Encodings for custom optional and containers).
* @return naming template. By default, it is empty and feature is disabled.
*/
String withUnaryOperator() default "";

/**
* Add value to collection attribute from iterable
* @return naming template
Expand Down Expand Up @@ -631,7 +644,7 @@
* If {@code includeHashCode} is not empty it will be used as part of generated `hashCode`. This will be a verbatim
* line of code used with the tag-placeholder {@code [[type]]} will be replaced with the simple
* (or relative to top level) name of the abstract value type.
* It's the responsibility of the user to put well-formed code to be put in context, including parenthesis, etc,
* It's the responsibility of the user to put well-formed code to be put in context, including parenthesis, etc.
* use try-see-fix approach here. Other fields will be included as usual, coming after this custom value.
* <p>Examples might give you better ideas how to use it:
* <pre>
Expand Down
20 changes: 20 additions & 0 deletions value-fixture/src/org/immutables/fixture/with/WithMapUnary.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.immutables.fixture.with;

import org.immutables.value.Value;

@Value.Immutable
@Value.Style(withUnaryOperator = "map*")
public interface WithMapUnary extends WithUnaryOperator, WithWithMapUnary {
// inherit all attributes from WithUnaryOperator and generate With-interface
class Builder extends ImmutableWithMapUnary.Builder {}

default void compiledAndUsable() {
WithMapUnary u = new Builder()
.a(1)
.b("B")
.build();

// signatures exist after code generation and compilation
u = u.mapA(a -> a + 2).mapL(l -> l + "!!!").mapO(o -> o + "???");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.immutables.fixture.with;

import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import org.immutables.value.Value;

@Value.Immutable
@Value.Style(withUnaryOperator = "map*")
interface WithUnaryOperator {
int a();
String b();
Optional<String> o();
List<String> l();
OptionalInt i();
OptionalDouble d();
com.google.common.base.Optional<Integer> oi();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.immutables.fixture.with;

import java.util.Optional;
import org.junit.jupiter.api.Test;
import static org.immutables.check.Checkers.check;

public class WithUnaryOperatorTest {
@Test
public void mapAttributes() {
ImmutableWithUnaryOperator m = ImmutableWithUnaryOperator.builder()
.a(1)
.b("b")
.addL("x", "y", "z")
.o("**")
.build();

ImmutableWithUnaryOperator t = m.mapA(a -> a + 1)
.mapB(b -> "[" + b + "]")
.mapL(String::toUpperCase)
.mapO(o -> o.replace('*', '_'));

check(t.a()).is(2);
check(t.b()).is("[b]");
check(t.l()).isOf("X", "Y", "Z");
check(t.o()).is(Optional.of("__"));
}

@Test
public void mapEmptyReturningThis() {
ImmutableWithUnaryOperator m = ImmutableWithUnaryOperator.builder()
.a(1)
.b("b")
.build();

ImmutableWithUnaryOperator t = m.mapO(o -> o + "!!!")
.mapL(l -> l + "???")
.mapI(i -> ++i)
.mapD(d -> d + 0.1)
.mapOi(oi -> oi * 10);

check(t).same(m);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,18 @@ private Object writeReplace() {
*/
[deprecation v]
[type.typeAbstract.relative] [v.names.with](Iterable<[v.consumedElementType]> elements);
[if type.generateWithUnaryOperator]

/**
* Copy the current immutable object with elements that replace the content of [sourceDocRef v],
* transformed by passing each element to the provided {@code UnaryOperator}.
* It always returns a new copy unless collection is empty.
* @param operator Unary operator to transform each element of [v.name]
* @return A modified copy of {@code this} object
*/
[deprecation v]
[type.typeAbstract.relative] [v.names.withUnaryOperator](java.util.function.UnaryOperator<[v.wrappedElementType]> operator);
[/if]
[else if v.optionalType]

/**
Expand All @@ -561,6 +573,18 @@ private Object writeReplace() {
*/
[deprecation v]
[type.typeAbstract.relative] [v.names.with]([v.rawType][if not v.jdkSpecializedOptional]<[v.consumedElementType]>[/if] optional);
[if type.generateWithUnaryOperator]

/**
* Copy the current immutable object by transforming optional value of [sourceDocRef v]
* using the provided {@code UnaryOperator}.
* It always returns a new copy if optional is present, otherwise {@code this} is returned.
* @param operator Unary operator to transform optional value of [v.name]
* @return A modified copy of {@code this} object
*/
[deprecation v]
[type.typeAbstract.relative] [v.names.withUnaryOperator](java.util.function.UnaryOperator<[v.wrappedElementType]> operator);
[/if]
[else if v.mapType]
[for gE = v.consumedElementType, uK = v.unwrappedElementType, wK = v.wrappedElementType, uV = v.unwrappedSecondaryElementType, wV = v.wrappedSecondaryElementType]

Expand Down Expand Up @@ -588,6 +612,17 @@ private Object writeReplace() {
*/
[deprecation v]
[type.typeAbstract.relative] [v.names.with]([v.atNullability][v.type] value);
[if type.generateWithUnaryOperator]

/**
* Copy the current immutable object by transforming value of [sourceDocRef v]
* using the provided {@code UnaryOperator}. It always returns a new copy.
* @param operator Unary operator to transform value of [v.name]
* @return A modified copy of {@code this} object
*/
[deprecation v]
[type.typeAbstract.relative] [v.names.withUnaryOperator](java.util.function.UnaryOperator<[v.wrappedElementType]> operator);
[/if]
[/if]
[/for]
}
Expand Down Expand Up @@ -3496,6 +3531,24 @@ public final [type.typeImmutable.relative] [v.names.with]([v.atNullability]Itera
.collect(java.util.stream.Collectors.toUnmodifiable[v.rawCollectionType]())[else][immutableCollectionCopyOf v 'elements'][/if];
[generateReturnCopyContextual type v]
}
[if type.generateWithUnaryOperator]

/**
* Copy the current immutable object with elements that replace the content of [sourceDocRef v],
* transformed by passing each element to the provided {@code UnaryOperator}.
* It always returns a new copy unless collection is empty.
* @param operator Unary operator to transform each element of [v.name]
* @return A modified copy of {@code this} object
*/[-- TODO support type.forceEqualsInWithers and change javadoc --]
[deprecation v]
public final [type.typeImmutable.relative] [v.names.withUnaryOperator](java.util.function.UnaryOperator<[v.wrappedElementType]> operator) {
if ([if v.nullable]this.[v.name] == null || [/if]this.[v.name].isEmpty()) return this;
java.util.List<[v.wrappedElementType]> elements = this.[v.name].stream().map(operator)
.collect(java.util.stream.Collectors.toList());
[immutableImplementationType v] newValue = [immutableCollectionCopyOf v 'elements'];
[generateReturnCopyContextual type v]
}
[/if]
[else if v.optionalType]

/**
Expand All @@ -3509,7 +3562,7 @@ public final [type.typeImmutable.relative] [v.names.with]([unwrappedOptionalType
[if v.primitiveElement or (v.jdkSpecializedOptional or v.optionalAcceptNullable)]
[immutableImplementationType v] newValue = value;
[else]
[immutableImplementationType v] newValue = [maybeCopyOf v][requireNonNull type](value, "[v.name]")[/maybeCopyOf];
[unwrappedOptionalType v] newValue = [maybeCopyOf v][requireNonNull type](value, "[v.name]")[/maybeCopyOf];
[/if]
[if type.forceEqualsInWithers]
if ([objectsEqual type](this.[v.name], newValue)) return this;
Expand Down Expand Up @@ -3576,6 +3629,28 @@ public final [type.typeImmutable.relative] [v.names.with]([v.rawType][if not v.j
[/if]
[generateReturnCopy type v 'value']
}
[if type.generateWithUnaryOperator]

/**
* Copy the current immutable object by transforming optional value of [sourceDocRef v]
* using the provided {@code UnaryOperator}.
* It always returns a new copy if optional is present, otherwise {@code this} is returned.
* @param operator Unary operator to transform optional value of [v.name]
* @return A modified copy of {@code this} object
*/[-- TODO maybe support type.forceEqualsInWithers and change javadoc --]
[deprecation v]
public final [type.typeImmutable.relative] [v.names.withUnaryOperator](java.util.function.UnaryOperator<[v.wrappedElementType]> operator) {
[if v.jdkOptional]
if (this.[v.name] == null) return this;
[immutableImplementationType v] value = operator.apply(this.[v.name]);
[generateReturnCopy type v 'value']
[else]
if (!this.[v.name].[optionalPresent v]) return this;
[immutableImplementationType v] value = [optionalOf v](operator.apply(this.[v.name].[optionalGet v]));
[generateReturnCopy type v 'value']
[/if]
}
[/if]
[else if v.mapType]
[for gE = v.consumedElementType, uK = v.unwrappedElementType, wK = v.wrappedElementType, uV = v.unwrappedSecondaryElementType, wV = v.wrappedSecondaryElementType]

Expand Down Expand Up @@ -3666,6 +3741,20 @@ public final [type.typeImmutable.relative] [v.names.with]([v.atNullability][v.ty
[generateReturnCopyContextual type v]
[/if]
}
[if type.generateWithUnaryOperator]

/**
* Copy the current immutable object by transforming value of [sourceDocRef v]
* using the provided {@code UnaryOperator}. It always returns a new copy.
* @param operator Unary operator to transform value of [v.name]
* @return A modified copy of {@code this} object
*/[-- TODO maybe support type.forceEqualsInWithers and change javadoc --]
[deprecation v]
public final [type.typeImmutable.relative] [v.names.withUnaryOperator](java.util.function.UnaryOperator<[v.wrappedElementType]> operator) {
[v.atNullabilityLocal][immutableImplementationType v] value = operator.apply(this.[v.name]);
[generateReturnCopy type v 'value']
}
[/if]
[/if]
[/for]
[/if]
Expand Down Expand Up @@ -3706,7 +3795,6 @@ private static <T> T cast(Object object) {
[javadocGenerics type]
* @return An immutable instance of [type.name]
*/
// constructor NOT HERE !!
public static[type.generics.def] [type.typeValue.relative] [type.factoryInstance.simple]() {
return [type.typeImmutable.relative].[type.names.instance]();
}
Expand All @@ -3724,7 +3812,6 @@ public static[type.generics.def] [type.typeValue.relative] [type.factoryInstance
*/
[eachLine type.constructorAnnotations]
[eachLine type.constructorInjectedAnnotations]
// constructor HERE TOO!!!!
public static[type.generics.def] [type.typeValue.relative] [type.factoryOf.simple]([for v in type.constructorArguments][if not for.first], [/if][v.constructorParameterAnnotations][v.atNullability][v.type] [v.name][/for]) {
return [type.factoryOf.simple]([for v in type.constructorArguments][if not for.first], [/if][if v.requiresAlternativeStrictConstructor]([constructorAcceptType v]) [/if][v.name][/for]);
}
Expand All @@ -3740,7 +3827,6 @@ public static[type.generics.def] [type.typeValue.relative] [type.factoryOf.simpl
*/
[eachLine type.constructorAnnotations]
[eachLine type.constructorInjectedAnnotations]
// constructor HERE!!!!
public static[type.generics.def] [type.typeValue.relative] [type.factoryOf.simple]([for v in type.constructorArguments][if not for.first], [/if][v.constructorParameterAnnotations][v.atNullability][constructorAcceptType v] [v.name][/for]) {
return [type.typeImmutable.relative].[type.names.of]([for v in type.constructorArguments][if not for.first], [/if][v.name][/for]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public Class<? extends Annotation> annotationType() {
@Override
public abstract String with();

@Value.Parameter
@Override
public abstract String withUnaryOperator();

@Value.Parameter
@Override
public abstract String add();
Expand Down Expand Up @@ -444,6 +448,7 @@ static StyleInfo infoFrom(StyleMirror input) {
input.get(),
input.init(),
input.with(),
input.withUnaryOperator(),
input.add(),
input.addAll(),
input.put(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class Scheme {
Naming[] get = Naming.fromAll(style.get());
Naming init = Naming.from(style.init());
Naming with = Naming.from(style.with());
Naming withUnaryOperator = Naming.from(style.withUnaryOperator());

Naming add = Naming.from(style.add());
Naming addAll = Naming.from(style.addAll());
Expand Down Expand Up @@ -278,6 +279,10 @@ public final class AttributeNames {
public final String init = apply(scheme.init, false);
public final String with = apply(scheme.with, false);

public String withUnaryOperator() {
return apply(scheme.withUnaryOperator, false);
}

public String add() {
return forCollection().add;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ private ValueMirrors() {}

String with() default "with*";

String withUnaryOperator() default "";

String add() default "add*";

String addAll() default "addAll*";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ public boolean isGenerateCanBuild() {
return !style().canBuild().isEmpty();
}

public boolean isGenerateWithUnaryOperator() {
return !style().withUnaryOperator().isEmpty();
}

public boolean isBeanFriendlyModifiable() {
return style().beanFriendlyModifiables();
}
Expand Down

0 comments on commit e264c39

Please sign in to comment.