Skip to content

Commit

Permalink
Document @AutoOneOf in the AutoValue user guide.
Browse files Browse the repository at this point in the history
RELNOTES=Document `@AutoOneOf` in the user guide.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=190151357
  • Loading branch information
eamonnmcmanus authored and ronshapiro committed Mar 23, 2018
1 parent 6607dd7 commit f25bf7e
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 1 deletion.
4 changes: 3 additions & 1 deletion value/src/main/java/com/google/auto/value/AutoOneOf.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
* throw new AssertionError();
* }}</pre>
*
* <!-- TODO(emcmanus): replace this example with a link to yet-to-be-written documentation. -->
* <p>{@code @AutoOneOf} is explained in more detail in the
* <a href="https://github.com/google/auto/blob/master/value/userguide/howto.md#oneof">user
* guide</a>.
*
* @author Chris Nokleberg
* @author Éamonn McManus
Expand Down
105 changes: 105 additions & 0 deletions value/userguide/howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ How do I...
* ... [**memoize** ("cache") derived properties?](#memoize)
* ... [memoize the result of `hashCode` or
`toString`?](#memoize_hash_tostring)
* ... [make a class where only one of its properties is ever set?](#oneof)

## <a name="builder"></a>... also generate a builder for my value class?

Expand Down Expand Up @@ -426,3 +427,107 @@ abstract class Foo {
}
```
## <a name="oneof"></a>... make a class where only one of its properties is ever set?
Often, the best way to do this is using inheritance. Although one
`@AutoValue` class can't inherit from another, two `@AutoValue` classes can
inherit from a common parent.

```java
public abstract class StringOrInteger {
public abstract String representation();

public static StringOrInteger ofString(String s) {
return new AutoValue_StringOrInteger_StringValue(s);
}

public static StringOrInteger ofInteger(int i) {
return new AutoValue_StringOrInteger_IntegerValue(i);
}

@AutoValue
abstract class StringValue extends StringOrInteger {
abstract String string();

@Override
public String representation() {
return '"' + string() + '"';
}
}

@AutoValue
abstract class IntegerValue extends StringOrInteger {
abstract int integer();

@Override
public String representation() {
return Integer.toString(integer());
}
}
}
```

So any `StringOrInteger` instance is actually either a `StringValue` or an
`IntegerValue`. Clients only care about the `representation()` method, so they
don't need to know which it is.
But if clients of your class may want to take different actions depending on
which property is set, there is an alternative to `@AutoValue` called
`@AutoOneOf`. This effectively creates a
[*tagged union*](https://en.wikipedia.org/wiki/Tagged_union).
Here is `StringOrInteger` written using `@AutoOneOf`, with the
`representation()` method moved to a separate client class:
```java
@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);
}
}
public class Client {
public String representation(StringOrInteger stringOrInteger) {
switch (stringOrInteger.getKind()) {
case STRING:
return '"' + stringOrInteger.string() + '"';
case INTEGER:
return Integer.toString(stringOrInteger.integer());
}
throw new AssertionError(stringOrInteger.getKind());
}
}
```
Switching on an enum like this can lead to more robust code than using
`instanceof` checks, especially if a tool like [Error
Prone](http://errorprone.info/bugpattern/MissingCasesInEnumSwitch) can alert you
if you add a new variant without updating all your switches. (On the other hand,
if nothing outside your class references `getKind()`, you should consider if a
solution using inheritance might be better.)
There must be an enum such as `Kind`, though it doesn't have to be called `Kind`
and it doesn't have to be nested inside the `@AutoOneOf` class. There must be an
abstract method returning the enum, though it doesn't have to be called
`getKind()`. For every value of the enum, there must be an abstract method with
the same name (ignoring case and underscores). An `@AutoOneOf` class called
`Foo` will then get a generated class called `AutoOneOf_Foo` that has a static
factory method for each property, with the same name. In the example, the
`STRING` value in the enum corresponds to the `string()` property and to the
`AutoOneOf_StringOrInteger.string` factory method.

Properties in an `@AutoOneOf` class cannot be null. Instead of a
`StringOrInteger` with a `@Nullable String`, you probably want a
`@Nullable StringOrInteger` or an `Optional<StringOrInteger>`.

2 changes: 2 additions & 0 deletions value/userguide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ How do I...
* ... [**memoize** ("cache") derived properties?](howto.md#memoize)
* ... [memoize the result of `hashCode` or
`toString`?](howto.md#memoize_hash_tostring)
* ... [make a class where only one of its properties is ever
set?](howto.md#oneof)

<!-- TODO(kevinb): should the above be only a selected subset? -->

Expand Down

0 comments on commit f25bf7e

Please sign in to comment.