immutables
immutables copied to clipboard
Sanitize builder's inputs
Is there any way to "sanitize" inputs passed into the builder? Essentially, the caller might like to pass in non-immutable data and I'd like an easy way to turn it into something immutable. I've got a workaround that involves over-exposing the Builder interface methods, but hoping there may be a cleaner way to do it.
Here's an example where the caller passes in List<String> and Foo (not derived from Immutable):
@Value.Style(visibility = ImplementationVisibility.PACKAGE)
@Value.Immutable
public abstract class XYZ {
abstract Map<String, List<String>> mapUnsafe();
abstract Foo fooUnsafe();
public final Map<String, List<String>> map() {
return mapUnsafe();
}
public final Foo foo() {
return fooUnsafe();
}
public interface Builder {
// These must be public based on current pattern, but don't want visible to callers...
Builder putMapUnsafe(String key, List<String> value);
Builder fooUnsafe(Foo foo);
default Builder putMap(String key, List<String> value) {
return putMapUnsafe(key, List.copyOf(value));
}
default Builder foo(Foo foo) {
return fooUnsafe(foo.immutableCopy());
}
XYZ build();
}
}
The normalization feature might work; but also feels clunky. At a minimum to cover all of the cases, I'd need to store an additional boolean based on the current APIs (I think) to note whether the input has been normalized or not. normalization does become a bit more palatable if there is way to tell the library to not normalize a build:
@Value.Style(visibility = ImplementationVisibility.PACKAGE)
@Value.Immutable
public abstract class XYZ {
public abstract Map<String, List<String>> map();
public abstract Foo foo();
@Check
final XYZ normalize() {
// normalize=false
Builder builder = ImmutableXYZ.builder(false);
for (Entry<...> e : map().entrySet()) {
builder.putMap(e.getKey(), List.copyOf(e.getValue()));
}
builder.foo(foo.immutableCopy());
return builder.build();
}
public interface Builder {
Builder putMap(String key, List<String> value);
Builder foo(Foo foo);
XYZ build();
}
}
Ideally, there would be some per-field way of sanitization. Here's a quick sketch.
@Value.Style(visibility = ImplementationVisibility.PACKAGE)
@Value.Immutable
public abstract class XYZ {
static Entry<String, List<String>> sanitizePutMap(String key, List<String> value) {
return Entry.of(key, List.copyOf(value)));
}
static Foo sanitizeFoo(Foo foo) {
return foo.immutableCopy();
}
@Sanitize("com.example.XYZ.sanitizePutMap")
public abstract Map<String, List<String>> map();
@Sanitize("com.example.XYZ.sanitizeFoo")
public abstract Foo foo();
public interface Builder {
Builder putMap(String key, List<String> value);
Builder foo(Foo foo);
XYZ build();
}
}
Thank you for bringing this out. This might look as something at least partially addressed by immutableCopyOf routines, first introduced in https://github.com/immutables/immutables/issues/325 . Since then, we've added some support for collection/container element conversion too. As was discussed in some other issues, this is still experimental and limited by how it's done (and maybe will always remain that way). Here's some examples:
https://github.com/immutables/immutables/tree/master/value-fixture/src/org/immutables/fixture/routine