Skip to content

Commit

Permalink
Add the '--useInsensitiveEnums' flag to allow case-insensitive enum v…
Browse files Browse the repository at this point in the history
…alues (#232)
  • Loading branch information
carterkozak authored and bulldozer-bot[bot] committed Feb 13, 2019
1 parent 4a2e5fa commit 6fdbc16
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.palantir.insensitive;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.palantir.conjure.java.lib.internal.ConjureEnums;
import com.palantir.logsafe.Preconditions;
import java.util.Locale;
import javax.annotation.Generated;

/**
* This enumerates the numbers 1:2 also 100.
*
* <p>This class is used instead of a native enum to support unknown values. Rather than throw an
* exception, the {@link InsensitiveEnum#valueOf} method defaults to a new instantiation of {@link
* InsensitiveEnum} where {@link InsensitiveEnum#get} will return {@link
* InsensitiveEnum.Value#UNKNOWN}.
*
* <p>For example, {@code InsensitiveEnum.valueOf("corrupted value").get()} will return {@link
* InsensitiveEnum.Value#UNKNOWN}, but {@link InsensitiveEnum#toString} will return "corrupted
* value".
*
* <p>There is no method to access all instantiations of this class, since they cannot be known at
* compile time.
*/
@Generated("com.palantir.conjure.java.types.EnumGenerator")
public final class InsensitiveEnum {
public static final InsensitiveEnum ONE = new InsensitiveEnum(Value.ONE, "ONE");

public static final InsensitiveEnum TWO = new InsensitiveEnum(Value.TWO, "TWO");

/** Value of 100. */
public static final InsensitiveEnum ONE_HUNDRED =
new InsensitiveEnum(Value.ONE_HUNDRED, "ONE_HUNDRED");

private final Value value;

private final String string;

private InsensitiveEnum(Value value, String string) {
this.value = value;
this.string = string;
}

public Value get() {
return this.value;
}

@Override
@JsonValue
public String toString() {
return this.string;
}

@Override
public boolean equals(Object other) {
return (this == other)
|| (other instanceof InsensitiveEnum
&& this.string.equals(((InsensitiveEnum) other).string));
}

@Override
public int hashCode() {
return this.string.hashCode();
}

@JsonCreator
public static InsensitiveEnum valueOf(String value) {
Preconditions.checkNotNull(value, "value cannot be null");
value = value.toUpperCase(Locale.ENGLISH);
switch (value) {
case "ONE":
return ONE;
case "TWO":
return TWO;
case "ONE_HUNDRED":
return ONE_HUNDRED;
default:
ConjureEnums.validate(value);
return new InsensitiveEnum(Value.UNKNOWN, value);
}
}

public <T> T accept(Visitor<T> visitor) {
switch (value) {
case ONE:
return visitor.visitOne();
case TWO:
return visitor.visitTwo();
case ONE_HUNDRED:
return visitor.visitOneHundred();
default:
return visitor.visitUnknown(string);
}
}

@Generated("com.palantir.conjure.java.types.EnumGenerator")
public enum Value {
ONE,

TWO,

/** Value of 100. */
ONE_HUNDRED,

UNKNOWN
}

@Generated("com.palantir.conjure.java.types.EnumGenerator")
public interface Visitor<T> {
T visitOne();

T visitTwo();

/** Value of 100. */
T visitOneHundred();

T visitUnknown(String unknownValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,10 @@ public enum FeatureFlags {
* Use the conjure immutable "Bytes" class over ByteBuffer.
*/
UseImmutableBytes,

/**
* Enums valueOf function will use a case-insensitive lookup. Note that this is not allowed by the conjure
* specification, however may be enabled for backwards compatibility.
*/
CaseInsensitiveEnums,
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.palantir.conjure.java.ConjureAnnotations;
import com.palantir.conjure.java.FeatureFlags;
import com.palantir.conjure.java.lib.internal.ConjureEnums;
import com.palantir.conjure.spec.EnumDefinition;
import com.palantir.conjure.spec.EnumValueDefinition;
Expand All @@ -36,6 +37,8 @@
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.lang.model.element.Modifier;
import org.apache.commons.lang3.StringUtils;

Expand All @@ -49,20 +52,24 @@ public final class EnumGenerator {

private EnumGenerator() {}

public static JavaFile generateEnumType(EnumDefinition typeDef) {
public static JavaFile generateEnumType(EnumDefinition typeDef, Set<FeatureFlags> featureFlags) {
String typePackage = typeDef.getTypeName().getPackage();
ClassName thisClass = ClassName.get(typePackage, typeDef.getTypeName().getName());
ClassName enumClass = ClassName.get(typePackage, typeDef.getTypeName().getName(), "Value");
ClassName visitorClass = ClassName.get(typePackage, typeDef.getTypeName().getName(), "Visitor");

return JavaFile.builder(typePackage, createSafeEnum(typeDef, thisClass, enumClass, visitorClass))
return JavaFile.builder(typePackage, createSafeEnum(typeDef, thisClass, enumClass, visitorClass, featureFlags))
.skipJavaLangImports(true)
.indent(" ")
.build();
}

private static TypeSpec createSafeEnum(
EnumDefinition typeDef, ClassName thisClass, ClassName enumClass, ClassName visitorClass) {
EnumDefinition typeDef,
ClassName thisClass,
ClassName enumClass,
ClassName visitorClass,
Set<FeatureFlags> featureFlags) {
TypeSpec.Builder wrapper = TypeSpec.classBuilder(typeDef.getTypeName().getName())
.addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(EnumGenerator.class))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
Expand All @@ -86,7 +93,7 @@ private static TypeSpec createSafeEnum(
.build())
.addMethod(createEquals(thisClass))
.addMethod(createHashCode())
.addMethod(createValueOf(thisClass, typeDef.getValues()))
.addMethod(createValueOf(thisClass, typeDef.getValues(), featureFlags))
.addMethod(generateAcceptVisitMethod(visitorClass, typeDef.getValues()));

typeDef.getDocs().ifPresent(
Expand Down Expand Up @@ -201,11 +208,16 @@ private static MethodSpec createConstructor(ClassName enumClass) {
.build();
}

private static MethodSpec createValueOf(ClassName thisClass, Iterable<EnumValueDefinition> values) {
private static MethodSpec createValueOf(
ClassName thisClass,
Iterable<EnumValueDefinition> values,
Set<FeatureFlags> featureFlags) {
ParameterSpec param = ParameterSpec.builder(ClassName.get(String.class), "value").build();

CodeBlock.Builder parser = CodeBlock.builder()
.beginControlFlow("switch ($N)", param);
CodeBlock.Builder parser = CodeBlock.builder();
if (featureFlags.contains(FeatureFlags.CaseInsensitiveEnums)) {
parser.addStatement("value = value.toUpperCase($T.ENGLISH)", Locale.class);
}
parser.beginControlFlow("switch ($N)", param);
for (EnumValueDefinition value : values) {
parser.add("case $S:\n", value.getValue())
.indent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ public Set<JavaFile> generateTypes(List<TypeDefinition> types) {
return UnionGenerator.generateUnionType(
typeMapper, typeDef.accept(TypeDefinitionVisitor.UNION));
} else if (typeDef.accept(TypeDefinitionVisitor.IS_ENUM)) {
return EnumGenerator.generateEnumType(
typeDef.accept(TypeDefinitionVisitor.ENUM));
return EnumGenerator.generateEnumType(typeDef.accept(TypeDefinitionVisitor.ENUM), featureFlags);
} else if (typeDef.accept(TypeDefinitionVisitor.IS_ALIAS)) {
return AliasGenerator.generateAliasType(
typeMapper, typeDef.accept(TypeDefinitionVisitor.ALIAS));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.palantir.logsafe.testing.Assertions.assertThatLoggableExceptionThrownBy;
import static org.assertj.core.api.Assertions.assertThat;

import com.palantir.insensitive.InsensitiveEnum;
import com.palantir.product.EnumExample;
import org.junit.Test;

Expand Down Expand Up @@ -48,6 +49,17 @@ public void testVisitUnknown() {
assertThat(enumExample.accept(Visitor.INSTANCE)).isEqualTo("SOME_VALUE");
}

@Test
public void testInsensitiveEnum_lowerCase() {
assertThat(InsensitiveEnum.valueOf("one")).isEqualTo(InsensitiveEnum.ONE);
}

@Test
public void testInsensitiveEnum_lowerCaseUnknown() {
InsensitiveEnum value = InsensitiveEnum.valueOf("notknown");
assertThat(value.get()).isEqualTo(InsensitiveEnum.Value.UNKNOWN);
}

@Test
public void testNullValidationUsesSafeLoggable() {
assertThatLoggableExceptionThrownBy(() -> EnumExample.valueOf(null)).hasLogMessage("value cannot be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ public void testObjectGenerator_byteBufferCompatibility() throws IOException {
assertThatFilesAreTheSame(files, REFERENCE_FILES_FOLDER);
}


@Test
public void testObjectGenerator_insensitiveEnum() throws IOException {
ConjureDefinition def = Conjure.parse(
ImmutableList.of(new File("src/test/resources/example-compat-enum.yml")));
List<Path> files = new ObjectGenerator(Collections.singleton(FeatureFlags.CaseInsensitiveEnums))
.emit(def, folder.getRoot());

assertThatFilesAreTheSame(files, REFERENCE_FILES_FOLDER);
}

@Test
public void testConjureImports() throws IOException {
ConjureDefinition conjure = Conjure.parse(
Expand Down
12 changes: 12 additions & 0 deletions conjure-java-core/src/test/resources/example-compat-enum.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
types:
definitions:
default-package: com.palantir.insensitive
objects:
InsensitiveEnum:
docs: |
This enumerates the numbers 1:2 also 100.
values:
- ONE
- TWO
- value: ONE_HUNDRED
docs: Value of 100.
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,9 @@ Builder undertowServicePrefix(boolean flag) {
Builder useImmutableBytes(boolean flag) {
return flag ? addFeatureFlags(FeatureFlags.UseImmutableBytes) : this;
}

Builder useInsensitiveEnums(boolean flag) {
return flag ? addFeatureFlags(FeatureFlags.CaseInsensitiveEnums) : this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ public static final class GenerateCommand implements Runnable {
description = "Generate binary fields using the immutable 'Bytes' type instead of 'ByteBuffer'")
private boolean useImmutableBytes;

@CommandLine.Option(names = "--useInsensitiveEnums",
defaultValue = "false",
description = "Enums valueOf function will use a case-insensitive lookup. Note that this is not "
+ "allowed by the conjure specification, however may be enabled for backwards compatibility.")
private boolean useInsensitiveEnums;

@CommandLine.Unmatched
private List<String> unmatchedOptions;

Expand Down Expand Up @@ -169,6 +175,7 @@ CliConfiguration getConfiguration() {
.notNullAuthAndBody(notNullAuthAndBody)
.undertowServicePrefix(undertowServicePrefix)
.useImmutableBytes(useImmutableBytes)
.useInsensitiveEnums(useInsensitiveEnums)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ public void parseFeatureFlags() {
"--retrofitCompletableFutures",
"--jerseyBinaryAsResponse",
"--requireNotNullAuthAndBodyParams",
"--useImmutableBytes"
"--useImmutableBytes",
"--useInsensitiveEnums"
};
CliConfiguration expectedConfiguration = CliConfiguration.builder()
.input(targetFile)
Expand All @@ -82,7 +83,8 @@ public void parseFeatureFlags() {
FeatureFlags.RetrofitCompletableFutures,
FeatureFlags.JerseyBinaryAsResponse,
FeatureFlags.RequireNotNullAuthAndBodyParams,
FeatureFlags.UseImmutableBytes))
FeatureFlags.UseImmutableBytes,
FeatureFlags.CaseInsensitiveEnums))
.build();
ConjureJavaCli.GenerateCommand cmd = new CommandLine(new ConjureJavaCli()).parse(args).get(1).getCommand();
assertThat(cmd.getConfiguration()).isEqualTo(expectedConfiguration);
Expand Down

0 comments on commit 6fdbc16

Please sign in to comment.