Skip to content

Commit cc5230f

Browse files
authored
Merge pull request #1582 from hwellmann/optional-with-default
Optional with default
2 parents 6df4e0f + 3588bec commit cc5230f

7 files changed

Lines changed: 238 additions & 4 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2025 Immutables Authors and Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package org.immutables.fixture.jdkonly;
17+
18+
import java.util.Optional;
19+
20+
import org.immutables.value.Value;
21+
22+
@Value.Immutable
23+
public interface OptionalDefault extends WithOptionalDefault {
24+
@Value.Default
25+
default Optional<String> text() {
26+
return Optional.of("foo");
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2025 Immutables Authors and Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package org.immutables.fixture.jdkonly;
17+
18+
import java.util.OptionalDouble;
19+
20+
import org.immutables.value.Value;
21+
22+
@Value.Immutable
23+
public interface OptionalDoubleDefault extends WithOptionalDoubleDefault {
24+
@Value.Default
25+
default OptionalDouble magic() {
26+
return OptionalDouble.of(42);
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2025 Immutables Authors and Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package org.immutables.fixture.jdkonly;
17+
18+
import java.util.OptionalInt;
19+
20+
import org.immutables.value.Value;
21+
22+
@Value.Immutable
23+
public interface OptionalIntDefault extends WithOptionalIntDefault {
24+
@Value.Default
25+
default OptionalInt magic() {
26+
return OptionalInt.of(42);
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Copyright 2025 Immutables Authors and Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package org.immutables.fixture.jdkonly;
17+
18+
import java.util.OptionalLong;
19+
20+
import org.immutables.value.Value;
21+
22+
@Value.Immutable
23+
public interface OptionalLongDefault extends WithOptionalLongDefault {
24+
@Value.Default
25+
default OptionalLong magic() {
26+
return OptionalLong.of(42);
27+
}
28+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright 2025 Immutables Authors and Contributors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package org.immutables.fixture.jdkonly;
17+
18+
import static org.immutables.check.Checkers.check;
19+
20+
import java.util.Optional;
21+
import java.util.OptionalDouble;
22+
import java.util.OptionalInt;
23+
import java.util.OptionalLong;
24+
25+
import org.junit.jupiter.api.Test;
26+
27+
public class JdkOptionalDefaultTest {
28+
29+
@Test
30+
public void optionalDefault() {
31+
check(ImmutableOptionalDefault.builder().build().text()).is(Optional.of("foo"));
32+
check(ImmutableOptionalDefault.builder().text(Optional.empty()).build().text()).is(Optional.empty());
33+
check(ImmutableOptionalDefault.builder().text(Optional.of("bar")).build().text()).is(Optional.of("bar"));
34+
ImmutableOptionalDefault value = ImmutableOptionalDefault.builder().text("bar").build();
35+
check(value.text()).is(Optional.of("bar"));
36+
check(value.withText("blah").text()).is(Optional.of("blah"));
37+
check(value.withText(Optional.of("blah")).text()).is(Optional.of("blah"));
38+
check(value.withText(Optional.empty()).text()).is(Optional.empty());
39+
}
40+
41+
@Test
42+
public void optionalIntDefault() {
43+
check(ImmutableOptionalIntDefault.builder().build().magic()).is(OptionalInt.of(42));
44+
check(ImmutableOptionalIntDefault.builder().magic(OptionalInt.empty()).build().magic()).is(OptionalInt.empty());
45+
check(ImmutableOptionalIntDefault.builder().magic(OptionalInt.of(17)).build().magic()).is(OptionalInt.of(17));
46+
ImmutableOptionalIntDefault value = ImmutableOptionalIntDefault.builder().magic(17).build();
47+
check(value.magic()).is(OptionalInt.of(17));
48+
check(value.withMagic(99).magic()).is(OptionalInt.of(99));
49+
check(value.withMagic(OptionalInt.of(99)).magic()).is(OptionalInt.of(99));
50+
check(value.withMagic(OptionalInt.empty()).magic()).is(OptionalInt.empty());
51+
}
52+
53+
@Test
54+
public void optionalLongDefault() {
55+
check(ImmutableOptionalLongDefault.builder().build().magic()).is(OptionalLong.of(42));
56+
check(ImmutableOptionalLongDefault.builder().magic(OptionalLong.empty()).build().magic()).is(OptionalLong.empty());
57+
check(ImmutableOptionalLongDefault.builder().magic(OptionalLong.of(17)).build().magic()).is(OptionalLong.of(17));
58+
ImmutableOptionalLongDefault value = ImmutableOptionalLongDefault.builder().magic(17).build();
59+
check(value.magic()).is(OptionalLong.of(17));
60+
check(value.withMagic(99).magic()).is(OptionalLong.of(99));
61+
check(value.withMagic(OptionalLong.of(99)).magic()).is(OptionalLong.of(99));
62+
check(value.withMagic(OptionalLong.empty()).magic()).is(OptionalLong.empty());
63+
}
64+
65+
@Test
66+
public void optionalDoubleDefault() {
67+
check(ImmutableOptionalDoubleDefault.builder().build().magic()).is(OptionalDouble.of(42));
68+
check(ImmutableOptionalDoubleDefault.builder().magic(OptionalDouble.empty()).build().magic()).is(OptionalDouble.empty());
69+
check(ImmutableOptionalDoubleDefault.builder().magic(OptionalDouble.of(17)).build().magic()).is(OptionalDouble.of(17));
70+
ImmutableOptionalDoubleDefault value = ImmutableOptionalDoubleDefault.builder().magic(17).build();
71+
check(value.magic()).is(OptionalDouble.of(17));
72+
check(value.withMagic(99).magic()).is(OptionalDouble.of(99));
73+
check(value.withMagic(OptionalDouble.of(99)).magic()).is(OptionalDouble.of(99));
74+
check(value.withMagic(OptionalDouble.empty()).magic()).is(OptionalDouble.empty());
75+
}
76+
}

value-processor/src/org/immutables/value/processor/Immutables.generator

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,15 @@ return new [type.typeValue.relativeRaw][type.generics.diamond]([output.linesShor
995995
[type.typeAbstract.relative] [v.names.with]([v.atNullability][tuNullable v][if v.multimapType][guava].collect.Multimap[else]java.util.Map[/if]<[gE], ? extends [wV]> entries);
996996
[/for]
997997
[else]
998+
[if v.optionalWithDefault]
999+
/**
1000+
* Copy the current immutable object by setting a <i>present</i> value for the optional [sourceDocRef v] attribute.
1001+
* @param value The value for [v.name][if v.optionalAcceptNullable], {@code null} is accepted as {@code [optionalEmpty v]}[/if]
1002+
* @return A modified copy of {@code this} object
1003+
*/
1004+
[deprecation v]
1005+
[type.typeAbstract.relative] [v.names.with]([v.unwrappedElementType] value);
1006+
[/if]
9981007

9991008
/**
10001009
* Copy the current immutable object by setting a value for the [sourceDocRef v] attribute.
@@ -2599,6 +2608,25 @@ checkNotIsSet([v.names.isSet](), "[v.names.raw]");
25992608
[mandatorySetInBuilder v]
26002609
return [builderReturnThis type];
26012610
}
2611+
2612+
[if v.optionalWithDefault]
2613+
/**
2614+
* Initializes the optional value [sourceDocRef v] to [v.name].
2615+
* <p><em>If not set, this attribute will have a default value as returned by the initializer of [sourceDocRef v].</em>
2616+
* @param [v.name] The value for [v.name][if v.optionalAcceptNullable], {@code null} is accepted as {@code [optionalEmpty v]}[/if]
2617+
* @return {@code this} builder for chained invocation
2618+
*/
2619+
[eachLine v.elementInitializerInjectedAnnotations]
2620+
[atCanIgnoreReturnValue type]
2621+
[deprecation v]
2622+
[builderInitAccess v]final [builderReturnType type] [v.names.init]([v.unwrappedElementType] [v.name]) {
2623+
[checkNotIsSet v]
2624+
this.[v.name] = [optionalOf v]([maybeCopyOf v][v.name][/maybeCopyOf]);
2625+
[nondefaultSetInBuilder v]
2626+
return [builderReturnThis type];
2627+
}
2628+
[/if]
2629+
26022630
[if v.isAttributeBuilder]
26032631

26042632
/**
@@ -4322,6 +4350,25 @@ public final [type.typeImmutable.relative] [v.names.with]([v.atNullability][tuNu
43224350
}
43234351
[/for]
43244352
[else]
4353+
[if v.optionalWithDefault]
4354+
/**
4355+
* Copy the current immutable object by setting a <em>present</em> value for the optional [sourceDocRef v] attribute.
4356+
* @param value The value for [v.name][if v.optionalAcceptNullable], {@code null} is accepted as {@code [optionalEmpty v]}[/if]
4357+
* @return A modified copy or {@code this} if not changed
4358+
*/
4359+
[deprecation v]
4360+
public final [type.typeImmutable.relative] [v.names.with]([v.unwrappedElementType] value) {
4361+
[immutableImplementationType v] newValue = [optionalOf v]([maybeCopyOf v]value[/maybeCopyOf]);
4362+
[if v.forceEqualsInWithers]
4363+
if ([objectsEqual type](this.[v.name], newValue)) return this;
4364+
[else if v.hasSimpleScalarElementType andnot v.enumType]
4365+
if ([objectsEqual type](this.[v.name], newValue)) return this;
4366+
[else]
4367+
if (this.[v.name] == newValue) return this;
4368+
[/if]
4369+
[generateReturnCopyContextual type v]
4370+
}
4371+
[/if]
43254372

43264373
/**
43274374
* Copy the current immutable object by setting a value for the [sourceDocRef v] attribute.

value-processor/src/org/immutables/value/processor/meta/ValueAttribute.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public final class ValueAttribute extends TypeIntrospectionBase implements HasSt
8282

8383
public AttributeNames names;
8484
public boolean isGenerateDefault;
85+
public boolean isOptionalWithDefault;
8586
public @Nullable Object constantDefault;
8687
public boolean isGenerateDerived;
8788
public boolean isGenerateAbstract;
@@ -688,7 +689,7 @@ public boolean hasVirtualImpl() {
688689
}
689690

690691
public String getUnwrappedElementType() {
691-
return isContainerType() && nullElements.ban()
692+
return (isContainerType() && nullElements.ban()) || isOptionalWithDefault
692693
? unwrapType(firstTypeParameter())
693694
: getElementType();
694695
}
@@ -1628,9 +1629,7 @@ private void validateTypeAndAnnotations() {
16281629

16291630
if (isGenerateDefault && isOptionalType()) {
16301631
typeKind = AttributeTypeKind.REGULAR;
1631-
report()
1632-
.annotationNamed(DefaultMirror.simpleName())
1633-
.warning(About.UNTYPE, "@Value.Default on a optional attribute make it lose its special treatment");
1632+
isOptionalWithDefault = true;
16341633
}
16351634

16361635
if (isContainerType() && containingType.isUseStrictBuilder()) {

0 commit comments

Comments
 (0)