Generated immutable value classes for Java 8+ using annotation processing
—
AutoOneOf generates tagged union (also known as sum types or algebraic data types) implementations that can represent a value that is exactly one of several possible types.
@AutoOneOf(StringOrInteger.Kind.class)
public abstract class StringOrInteger {
public enum Kind {
STRING, INTEGER
}
public abstract Kind getKind();
public abstract String string();
public abstract int integer();
public static StringOrInteger ofString(String s) {
return AutoOneOf_StringOrInteger.string(s);
}
public static StringOrInteger ofInteger(int i) {
return AutoOneOf_StringOrInteger.integer(i);
}
}StringOrInteger stringValue = StringOrInteger.ofString("hello");
StringOrInteger intValue = StringOrInteger.ofInteger(42);
// Pattern matching with switch
String result = processValue(stringValue);
public String processValue(StringOrInteger value) {
switch (value.getKind()) {
case STRING:
return "String: " + value.string();
case INTEGER:
return "Integer: " + value.integer();
}
throw new AssertionError("Unknown kind: " + value.getKind());
}@AutoOneOf(Result.Kind.class)
public abstract class Result<T, E> {
public enum Kind {
SUCCESS, ERROR
}
public abstract Kind getKind();
public abstract T success();
public abstract E error();
public static <T, E> Result<T, E> success(T value) {
return AutoOneOf_Result.success(value);
}
public static <T, E> Result<T, E> error(E error) {
return AutoOneOf_Result.error(error);
}
}Usage:
Result<String, Exception> successResult = Result.success("Operation completed");
Result<String, Exception> errorResult = Result.error(new RuntimeException("Failed"));
// Processing results
public <T, E> void handleResult(Result<T, E> result) {
switch (result.getKind()) {
case SUCCESS:
System.out.println("Success: " + result.success());
break;
case ERROR:
System.err.println("Error: " + result.error().getMessage());
break;
}
}@AutoOneOf(Value.Kind.class)
public abstract class Value {
public enum Kind {
STRING, INTEGER, DOUBLE, BOOLEAN, LIST
}
public abstract Kind getKind();
public abstract String string();
public abstract int integer();
public abstract double doubleValue();
public abstract boolean booleanValue();
public abstract List<Value> list();
public static Value ofString(String s) {
return AutoOneOf_Value.string(s);
}
public static Value ofInteger(int i) {
return AutoOneOf_Value.integer(i);
}
public static Value ofDouble(double d) {
return AutoOneOf_Value.doubleValue(d);
}
public static Value ofBoolean(boolean b) {
return AutoOneOf_Value.booleanValue(b);
}
public static Value ofList(List<Value> list) {
return AutoOneOf_Value.list(list);
}
}Usage:
Value stringVal = Value.ofString("text");
Value numberVal = Value.ofInteger(123);
Value listVal = Value.ofList(Arrays.asList(stringVal, numberVal));
// JSON-like processing
public Object toJson(Value value) {
switch (value.getKind()) {
case STRING:
return value.string();
case INTEGER:
return value.integer();
case DOUBLE:
return value.doubleValue();
case BOOLEAN:
return value.booleanValue();
case LIST:
return value.list().stream()
.map(this::toJson)
.collect(Collectors.toList());
}
throw new AssertionError();
}@AutoOneOf(Optional.Kind.class)
public abstract class Optional<T> {
public enum Kind {
PRESENT, ABSENT
}
public abstract Kind getKind();
public abstract T present();
public abstract Void absent(); // Void for empty case
public static <T> Optional<T> of(T value) {
return AutoOneOf_Optional.present(value);
}
public static <T> Optional<T> empty() {
return AutoOneOf_Optional.absent(null);
}
// Convenience methods
public boolean isPresent() {
return getKind() == Kind.PRESENT;
}
public boolean isEmpty() {
return getKind() == Kind.ABSENT;
}
public T orElse(T defaultValue) {
return isPresent() ? present() : defaultValue;
}
}Usage:
Optional<String> present = Optional.of("value");
Optional<String> empty = Optional.empty();
String result1 = present.orElse("default"); // "value"
String result2 = empty.orElse("default"); // "default"Tagged unions can reference themselves for tree-like structures:
@AutoOneOf(Expression.Kind.class)
public abstract class Expression {
public enum Kind {
NUMBER, VARIABLE, BINARY_OP
}
public abstract Kind getKind();
public abstract double number();
public abstract String variable();
public abstract BinaryOp binaryOp();
public static Expression number(double value) {
return AutoOneOf_Expression.number(value);
}
public static Expression variable(String name) {
return AutoOneOf_Expression.variable(name);
}
public static Expression binaryOp(String operator, Expression left, Expression right) {
return AutoOneOf_Expression.binaryOp(BinaryOp.create(operator, left, right));
}
@AutoValue
public abstract static class BinaryOp {
public abstract String operator();
public abstract Expression left();
public abstract Expression right();
public static BinaryOp create(String operator, Expression left, Expression right) {
return new AutoValue_Expression_BinaryOp(operator, left, right);
}
}
}Usage:
// Build expression: (x + 2) * 3
Expression x = Expression.variable("x");
Expression two = Expression.number(2);
Expression three = Expression.number(3);
Expression xPlusTwo = Expression.binaryOp("+", x, two);
Expression result = Expression.binaryOp("*", xPlusTwo, three);
// Evaluate expression
public double evaluate(Expression expr, Map<String, Double> variables) {
switch (expr.getKind()) {
case NUMBER:
return expr.number();
case VARIABLE:
return variables.get(expr.variable());
case BINARY_OP:
BinaryOp op = expr.binaryOp();
double left = evaluate(op.left(), variables);
double right = evaluate(op.right(), variables);
switch (op.operator()) {
case "+": return left + right;
case "*": return left * right;
// ... other operators
}
throw new IllegalArgumentException("Unknown operator: " + op.operator());
}
throw new AssertionError();
}Tagged unions can have nullable variants:
@AutoOneOf(NullableValue.Kind.class)
public abstract class NullableValue<T> {
public enum Kind {
VALUE, NULL
}
public abstract Kind getKind();
@Nullable
public abstract T value();
public abstract Void nullValue();
public static <T> NullableValue<T> of(@Nullable T value) {
return value != null
? AutoOneOf_NullableValue.value(value)
: AutoOneOf_NullableValue.nullValue(null);
}
public static <T> NullableValue<T> nullValue() {
return AutoOneOf_NullableValue.nullValue(null);
}
}Use tagged unions for comprehensive error handling:
@AutoOneOf(ApiResponse.Kind.class)
public abstract class ApiResponse<T> {
public enum Kind {
SUCCESS, CLIENT_ERROR, SERVER_ERROR, NETWORK_ERROR
}
public abstract Kind getKind();
public abstract T success();
public abstract ClientError clientError();
public abstract ServerError serverError();
public abstract NetworkError networkError();
public static <T> ApiResponse<T> success(T data) {
return AutoOneOf_ApiResponse.success(data);
}
public static <T> ApiResponse<T> clientError(int code, String message) {
return AutoOneOf_ApiResponse.clientError(ClientError.create(code, message));
}
public static <T> ApiResponse<T> serverError(int code, String message) {
return AutoOneOf_ApiResponse.serverError(ServerError.create(code, message));
}
public static <T> ApiResponse<T> networkError(Exception cause) {
return AutoOneOf_ApiResponse.networkError(NetworkError.create(cause));
}
@AutoValue
public abstract static class ClientError {
public abstract int code();
public abstract String message();
static ClientError create(int code, String message) {
return new AutoValue_ApiResponse_ClientError(code, message);
}
}
@AutoValue
public abstract static class ServerError {
public abstract int code();
public abstract String message();
static ServerError create(int code, String message) {
return new AutoValue_ApiResponse_ServerError(code, message);
}
}
@AutoValue
public abstract static class NetworkError {
public abstract Exception cause();
static NetworkError create(Exception cause) {
return new AutoValue_ApiResponse_NetworkError(cause);
}
}
}Usage:
ApiResponse<User> response = apiCall();
switch (response.getKind()) {
case SUCCESS:
User user = response.success();
displayUser(user);
break;
case CLIENT_ERROR:
ClientError error = response.clientError();
showError("Client error " + error.code() + ": " + error.message());
break;
case SERVER_ERROR:
ServerError error = response.serverError();
showError("Server error " + error.code() + ": " + error.message());
break;
case NETWORK_ERROR:
NetworkError error = response.networkError();
showError("Network error: " + error.cause().getMessage());
retry();
break;
}Tagged unions work with builders for complex construction:
@AutoOneOf(Message.Kind.class)
public abstract class Message {
public enum Kind {
TEXT, IMAGE, FILE
}
public abstract Kind getKind();
public abstract TextMessage text();
public abstract ImageMessage image();
public abstract FileMessage file();
@AutoValue
public abstract static class TextMessage {
public abstract String content();
public abstract Optional<String> format();
public static Builder builder() {
return new AutoValue_Message_TextMessage.Builder();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder content(String content);
public abstract Builder format(String format);
public abstract TextMessage build();
}
}
// Similar for ImageMessage and FileMessage...
public static Message text(String content) {
return AutoOneOf_Message.text(
TextMessage.builder().content(content).build());
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-google-auto-value--auto-value