diff --git a/conjure-java-core/src/integrationInput/java/com/palantir/insensitive/InsensitiveEnum.java b/conjure-java-core/src/integrationInput/java/com/palantir/insensitive/InsensitiveEnum.java
new file mode 100644
index 000000000..34c087da5
--- /dev/null
+++ b/conjure-java-core/src/integrationInput/java/com/palantir/insensitive/InsensitiveEnum.java
@@ -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.
+ *
+ *
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}.
+ *
+ *
For example, {@code InsensitiveEnum.valueOf("corrupted value").get()} will return {@link
+ * InsensitiveEnum.Value#UNKNOWN}, but {@link InsensitiveEnum#toString} will return "corrupted
+ * value".
+ *
+ *
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 accept(Visitor 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 visitOne();
+
+ T visitTwo();
+
+ /** Value of 100. */
+ T visitOneHundred();
+
+ T visitUnknown(String unknownValue);
+ }
+}
diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/FeatureFlags.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/FeatureFlags.java
index 042656fe9..9947903df 100644
--- a/conjure-java-core/src/main/java/com/palantir/conjure/java/FeatureFlags.java
+++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/FeatureFlags.java
@@ -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,
}
diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/EnumGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/EnumGenerator.java
index 12db2513a..965439285 100644
--- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/EnumGenerator.java
+++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/EnumGenerator.java
@@ -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;
@@ -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;
@@ -49,20 +52,24 @@ public final class EnumGenerator {
private EnumGenerator() {}
- public static JavaFile generateEnumType(EnumDefinition typeDef) {
+ public static JavaFile generateEnumType(EnumDefinition typeDef, Set 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) {
TypeSpec.Builder wrapper = TypeSpec.classBuilder(typeDef.getTypeName().getName())
.addAnnotation(ConjureAnnotations.getConjureGeneratedAnnotation(EnumGenerator.class))
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
@@ -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(
@@ -201,11 +208,16 @@ private static MethodSpec createConstructor(ClassName enumClass) {
.build();
}
- private static MethodSpec createValueOf(ClassName thisClass, Iterable values) {
+ private static MethodSpec createValueOf(
+ ClassName thisClass,
+ Iterable values,
+ Set 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()
diff --git a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ObjectGenerator.java b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ObjectGenerator.java
index ae1875b36..41592b765 100644
--- a/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ObjectGenerator.java
+++ b/conjure-java-core/src/main/java/com/palantir/conjure/java/types/ObjectGenerator.java
@@ -46,8 +46,7 @@ public Set generateTypes(List 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));
diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/EnumTests.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/EnumTests.java
index 48732bcec..a4268d4c3 100644
--- a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/EnumTests.java
+++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/EnumTests.java
@@ -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;
@@ -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");
diff --git a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java
index 45b33ab8a..a525616d5 100644
--- a/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java
+++ b/conjure-java-core/src/test/java/com/palantir/conjure/java/types/ObjectGeneratorTests.java
@@ -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 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(
diff --git a/conjure-java-core/src/test/resources/example-compat-enum.yml b/conjure-java-core/src/test/resources/example-compat-enum.yml
new file mode 100644
index 000000000..1b4454bbc
--- /dev/null
+++ b/conjure-java-core/src/test/resources/example-compat-enum.yml
@@ -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.
diff --git a/conjure-java/src/main/java/com/palantir/conjure/java/cli/CliConfiguration.java b/conjure-java/src/main/java/com/palantir/conjure/java/cli/CliConfiguration.java
index 08a9f1008..fcc34af6d 100644
--- a/conjure-java/src/main/java/com/palantir/conjure/java/cli/CliConfiguration.java
+++ b/conjure-java/src/main/java/com/palantir/conjure/java/cli/CliConfiguration.java
@@ -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;
+ }
}
}
diff --git a/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java b/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java
index e5da51c3e..6898ede35 100644
--- a/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java
+++ b/conjure-java/src/main/java/com/palantir/conjure/java/cli/ConjureJavaCli.java
@@ -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 unmatchedOptions;
@@ -169,6 +175,7 @@ CliConfiguration getConfiguration() {
.notNullAuthAndBody(notNullAuthAndBody)
.undertowServicePrefix(undertowServicePrefix)
.useImmutableBytes(useImmutableBytes)
+ .useInsensitiveEnums(useInsensitiveEnums)
.build();
}
diff --git a/conjure-java/src/test/java/com/palantir/conjure/java/cli/ConjureJavaCliTest.java b/conjure-java/src/test/java/com/palantir/conjure/java/cli/ConjureJavaCliTest.java
index 06584da93..ff13aa456 100644
--- a/conjure-java/src/test/java/com/palantir/conjure/java/cli/ConjureJavaCliTest.java
+++ b/conjure-java/src/test/java/com/palantir/conjure/java/cli/ConjureJavaCliTest.java
@@ -72,7 +72,8 @@ public void parseFeatureFlags() {
"--retrofitCompletableFutures",
"--jerseyBinaryAsResponse",
"--requireNotNullAuthAndBodyParams",
- "--useImmutableBytes"
+ "--useImmutableBytes",
+ "--useInsensitiveEnums"
};
CliConfiguration expectedConfiguration = CliConfiguration.builder()
.input(targetFile)
@@ -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);