From 316c5a41249045f2d2c054d2c14ac18a249e7c41 Mon Sep 17 00:00:00 2001 From: Mike Roberts Date: Thu, 5 Dec 2019 09:42:15 -0500 Subject: [PATCH] AVRO-2385: generate camelCase method names for UPPER_SNAKE fields Changes the implementation of Avro record name to Java method name converter. Though this breaks no existing tests, it is a breaking change for records named in UPPER_SNAKE style. --- .../compiler/specific/SpecificCompiler.java | 71 ++++++++++++++++--- .../specific/TestSpecificCompiler.java | 27 +++++++ .../specific/TestSpecificCompiler.java | 14 ++++ 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java index 660ec3cf175..241312d7981 100644 --- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java +++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java @@ -50,6 +50,8 @@ import org.apache.avro.JsonProperties; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericData.StringType; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; @@ -647,7 +649,9 @@ private Schema addStringType(Schema s, Map seen) { switch (s.getType()) { case STRING: result = Schema.create(Schema.Type.STRING); - GenericData.setStringType(result, stringType); + if (s.getLogicalType() == null) { + GenericData.setStringType(result, stringType); + } break; case RECORD: result = Schema.createRecord(s.getFullName(), s.getDoc(), null, s.isError()); @@ -679,6 +683,9 @@ private Schema addStringType(Schema s, Map seen) { break; } result.addAllProps(s); + if (s.getLogicalType() != null) { + s.getLogicalType().addToSchema(result); + } seen.put(s, result); return result; } @@ -1148,16 +1155,10 @@ private static String generateMethodName(Schema schema, Field field, String pref String fieldName = mangle(field.name(), schema.isError() ? ERROR_RESERVED_WORDS : ACCESSOR_MUTATOR_RESERVED_WORDS, true); - boolean nextCharToUpper = true; - for (int ii = 0; ii < fieldName.length(); ii++) { - if (fieldName.charAt(ii) == '_') { - nextCharToUpper = true; - } else if (nextCharToUpper) { - methodBuilder.append(Character.toUpperCase(fieldName.charAt(ii))); - nextCharToUpper = false; - } else { - methodBuilder.append(fieldName.charAt(ii)); - } + if (prefix.isEmpty()) { + methodBuilder.append(camelize(fieldName, false)); + } else { + methodBuilder.append(camelize(fieldName, true)); } methodBuilder.append(postfix); @@ -1172,6 +1173,54 @@ private static String generateMethodName(Schema schema, Field field, String pref return methodBuilder.toString(); } + /** + * CamelCase the incoming string, using underscores as the word hints + * + * @param s string to camel-case. Must contain only [a-zA-Z0-9_] + * @param upper should the first letter be capitalized + */ + static String camelize(String s, boolean upper) { + if (StringUtils.contains(s, '_')) { + // use underscores as hints where the capitals go + StringBuilder builder = new StringBuilder(); + for (String part : StringUtils.split(StringUtils.lowerCase(s), '_')) { + builder.append(StringUtils.capitalize(part)); + } + s = builder.toString(); + + // fixup first character + if (upper) { + return StringUtils.capitalize(s); + } else { + return StringUtils.uncapitalize(s); + } + } + + // probably camelcase, split by camel to reassemble + StringBuilder builder = new StringBuilder(); + String[] parts = StringUtils.splitByCharacterTypeCamelCase(s); + + // subsections should retain their casing if all upper + // unless they're in the first section, then their casing should be modified to + // match the starting case + if (StringUtils.isAllUpperCase(parts[0]) && upper) { + builder.append(parts[0]); + } else if (upper) { + builder.append(StringUtils.capitalize(StringUtils.lowerCase(parts[0]))); + } else { + builder.append(StringUtils.lowerCase(parts[0])); + } + + for (String part : ArrayUtils.subarray(parts, 1, parts.length)) { + if (StringUtils.isAllUpperCase(part)) { + builder.append(part); + } else { + builder.append(StringUtils.capitalize(StringUtils.lowerCase(part))); + } + } + return builder.toString(); + } + /** Tests whether an unboxed Java type can be set to null */ public static boolean isUnboxedJavaTypeNullable(Schema schema) { switch (schema.getType()) { diff --git a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java index 2fed7fe02e6..9449be0aece 100644 --- a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java +++ b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java @@ -739,4 +739,31 @@ public void testAdditionalToolsAreInjectedIntoTemplate() throws Exception { } assertEquals(1, itWorksFound); } + + @Test + public void testCamelize() { + assertEquals("snakeCase", SpecificCompiler.camelize("snake_case", false)); + assertEquals("constantCase", SpecificCompiler.camelize("CONSTANT_CASE", false)); + assertEquals("mixedSnake", SpecificCompiler.camelize("Mixed_Snake", false)); + assertEquals("snakeUuid", SpecificCompiler.camelize("snake_UUID", false)); + assertEquals("lower", SpecificCompiler.camelize("lower", false)); + assertEquals("capitalized", SpecificCompiler.camelize("Capitalized", false)); + assertEquals("caps", SpecificCompiler.camelize("CAPS", false)); + assertEquals("camelCased", SpecificCompiler.camelize("camelCased", false)); + assertEquals("camelCased", SpecificCompiler.camelize("CamelCased", false)); + assertEquals("someUUID", SpecificCompiler.camelize("someUUID", false)); + assertEquals("uuidFirst", SpecificCompiler.camelize("UUIDFirst", false)); + + assertEquals("SnakeCase", SpecificCompiler.camelize("snake_case", true)); + assertEquals("ConstantCase", SpecificCompiler.camelize("CONSTANT_CASE", true)); + assertEquals("MixedSnake", SpecificCompiler.camelize("Mixed_Snake", true)); + assertEquals("SnakeUuid", SpecificCompiler.camelize("snake_UUID", true)); + assertEquals("Lower", SpecificCompiler.camelize("lower", true)); + assertEquals("Capitalized", SpecificCompiler.camelize("Capitalized", true)); + assertEquals("CAPS", SpecificCompiler.camelize("CAPS", true)); + assertEquals("CamelCased", SpecificCompiler.camelize("camelCased", true)); + assertEquals("CamelCased", SpecificCompiler.camelize("CamelCased", true)); + assertEquals("SomeUUID", SpecificCompiler.camelize("someUUID", true)); + assertEquals("UUIDFirst", SpecificCompiler.camelize("UUIDFirst", true)); + } } diff --git a/lang/java/ipc/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java b/lang/java/ipc/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java index 32b2edce8e6..41e4ce79d69 100644 --- a/lang/java/ipc/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java +++ b/lang/java/ipc/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java @@ -339,6 +339,13 @@ public void generateGetMethod() { Schema$ = new Field("Schema", Schema.create(Type.STRING), null, null); assertEquals("getSchema$1", SpecificCompiler.generateGetMethod(createRecord("test", false, schema, Schema$), Schema$)); + + Field ALL_UPPER_CASE = new Field("ALL_UPPER_CASE", Schema.create(Type.STRING), null, null); + assertEquals("getAllUpperCase", + SpecificCompiler.generateGetMethod(createRecord("test", false, ALL_UPPER_CASE), ALL_UPPER_CASE)); + + Field someUUID = new Field("someUUID", Schema.create(Type.STRING), null, null); + assertEquals("getSomeUUID", SpecificCompiler.generateGetMethod(createRecord("test", false, someUUID), someUUID)); } @Test @@ -415,6 +422,13 @@ public void generateSetMethod() { Schema$ = new Field("Schema", Schema.create(Type.STRING), null, null); assertEquals("setSchema$1", SpecificCompiler.generateSetMethod(createRecord("test", false, schema, Schema$), Schema$)); + + Field ALL_UPPER_CASE = new Field("ALL_UPPER_CASE", Schema.create(Type.STRING), null, null); + assertEquals("setAllUpperCase", + SpecificCompiler.generateSetMethod(createRecord("test", false, ALL_UPPER_CASE), ALL_UPPER_CASE)); + + Field someUUID = new Field("someUUID", Schema.create(Type.STRING), null, null); + assertEquals("setSomeUUID", SpecificCompiler.generateSetMethod(createRecord("test", false, someUUID), someUUID)); } @Test