blob: a3c8a308ba9c9b4b1bb885255d73b4f2fef782f3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.avro.compiler.specific;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertThat;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.equalTo;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.avro.LogicalTypes;
import org.apache.avro.Schema;
import org.apache.avro.SchemaBuilder;
import org.apache.avro.generic.GenericData.StringType;
import org.junit.*;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
@RunWith(JUnit4.class)
public class TestSpecificCompiler {
private static final Logger LOG = LoggerFactory.getLogger(TestSpecificCompiler.class);
@Rule
public TemporaryFolder OUTPUT_DIR = new TemporaryFolder();
@Rule
public TestName name = new TestName();
private File outputFile;
@Before
public void setUp() {
this.outputFile = new File(this.OUTPUT_DIR.getRoot(), "SimpleRecord.java");
}
private File src = new File("src/test/resources/simple_record.avsc");
static void assertCompilesWithJavaCompiler(File dstDir, Collection<SpecificCompiler.OutputFile> outputs)
throws IOException {
assertCompilesWithJavaCompiler(dstDir, outputs, false);
}
/** Uses the system's java compiler to actually compile the generated code. */
static void assertCompilesWithJavaCompiler(File dstDir, Collection<SpecificCompiler.OutputFile> outputs,
boolean ignoreWarnings) throws IOException {
if (outputs.isEmpty()) {
return; // Nothing to compile!
}
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
List<File> javaFiles = new ArrayList<>();
for (SpecificCompiler.OutputFile o : outputs) {
javaFiles.add(o.writeToDestination(null, dstDir));
}
final List<Diagnostic<?>> warnings = new ArrayList<>();
DiagnosticListener<JavaFileObject> diagnosticListener = diagnostic -> {
switch (diagnostic.getKind()) {
case ERROR:
// Do not add these to warnings because they will fail the compile, anyway.
LOG.error("{}", diagnostic);
break;
case WARNING:
case MANDATORY_WARNING:
LOG.warn("{}", diagnostic);
warnings.add(diagnostic);
break;
case NOTE:
case OTHER:
LOG.debug("{}", diagnostic);
break;
}
};
JavaCompiler.CompilationTask cTask = compiler.getTask(null, fileManager, diagnosticListener,
Collections.singletonList("-Xlint:all"), null, fileManager.getJavaFileObjects(javaFiles.toArray(new File[0])));
boolean compilesWithoutError = cTask.call();
assertTrue(compilesWithoutError);
if (!ignoreWarnings) {
assertEquals("Warnings produced when compiling generated code with -Xlint:all", 0, warnings.size());
}
}
private static Schema createSampleRecordSchema(int numStringFields, int numDoubleFields) {
SchemaBuilder.FieldAssembler<Schema> sb = SchemaBuilder.record("sample.record").fields();
for (int i = 0; i < numStringFields; i++) {
sb.name("sf_" + i).type().stringType().noDefault();
}
for (int i = 0; i < numDoubleFields; i++) {
sb.name("df_" + i).type().doubleType().noDefault();
}
return sb.endRecord();
}
private SpecificCompiler createCompiler() throws IOException {
Schema.Parser parser = new Schema.Parser();
Schema schema = parser.parse(this.src);
SpecificCompiler compiler = new SpecificCompiler(schema);
String velocityTemplateDir = "src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/";
compiler.setTemplateDir(velocityTemplateDir);
compiler.setStringType(StringType.CharSequence);
return compiler;
}
@Test
public void testCanReadTemplateFilesOnTheFilesystem() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.compileToDestination(this.src, OUTPUT_DIR.getRoot());
assertTrue(new File(OUTPUT_DIR.getRoot(), "SimpleRecord.java").exists());
}
@Test
public void testPublicFieldVisibility() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.setFieldVisibility(SpecificCompiler.FieldVisibility.PUBLIC);
assertFalse(compiler.deprecatedFields());
assertTrue(compiler.publicFields());
assertFalse(compiler.privateFields());
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
// No line, once trimmed, should start with a deprecated field declaration
// nor a private field declaration. Since the nested builder uses private
// fields, we cannot do the second check.
line = line.trim();
assertFalse("Line started with a deprecated field declaration: " + line,
line.startsWith("@Deprecated public int value"));
}
}
}
@Test
public void testCreateAllArgsConstructor() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
boolean foundAllArgsConstructor = false;
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while (!foundAllArgsConstructor && (line = reader.readLine()) != null) {
foundAllArgsConstructor = line.contains("All-args constructor");
}
}
assertTrue(foundAllArgsConstructor);
}
@Test
public void testMaxValidParameterCounts() throws Exception {
Schema validSchema1 = createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT, 0);
assertCompilesWithJavaCompiler(new File(OUTPUT_DIR.getRoot(), name.getMethodName() + "1"),
new SpecificCompiler(validSchema1).compile());
createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT - 2, 1);
assertCompilesWithJavaCompiler(new File(OUTPUT_DIR.getRoot(), name.getMethodName() + "2"),
new SpecificCompiler(validSchema1).compile());
}
@Test
public void testInvalidParameterCounts() throws Exception {
Schema invalidSchema1 = createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT + 1, 0);
SpecificCompiler compiler = new SpecificCompiler(invalidSchema1);
assertCompilesWithJavaCompiler(new File(OUTPUT_DIR.getRoot(), name.getMethodName() + "1"), compiler.compile());
Schema invalidSchema2 = createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT, 10);
compiler = new SpecificCompiler(invalidSchema2);
assertCompilesWithJavaCompiler(new File(OUTPUT_DIR.getRoot(), name.getMethodName() + "2"), compiler.compile());
}
@Test
public void testMaxParameterCounts() throws Exception {
Schema validSchema1 = createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT, 0);
assertTrue(new SpecificCompiler(validSchema1).compile().size() > 0);
Schema validSchema2 = createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT - 2, 1);
assertTrue(new SpecificCompiler(validSchema2).compile().size() > 0);
Schema validSchema3 = createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT - 1, 1);
assertTrue(new SpecificCompiler(validSchema3).compile().size() > 0);
Schema validSchema4 = createSampleRecordSchema(SpecificCompiler.MAX_FIELD_PARAMETER_UNIT_COUNT + 1, 0);
assertTrue(new SpecificCompiler(validSchema4).compile().size() > 0);
}
@Test(expected = RuntimeException.class)
public void testCalcAllArgConstructorParameterUnitsFailure() {
Schema nonRecordSchema = SchemaBuilder.array().items().booleanType();
new SpecificCompiler().calcAllArgConstructorParameterUnits(nonRecordSchema);
}
@Test
public void testPublicDeprecatedFieldVisibility() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.setFieldVisibility(SpecificCompiler.FieldVisibility.PUBLIC_DEPRECATED);
assertTrue(compiler.deprecatedFields());
assertTrue(compiler.publicFields());
assertFalse(compiler.privateFields());
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
// No line, once trimmed, should start with a public field declaration
line = line.trim();
assertFalse("Line started with a public field declaration: " + line, line.startsWith("public int value"));
}
}
}
@Test
public void testPrivateFieldVisibility() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.setFieldVisibility(SpecificCompiler.FieldVisibility.PRIVATE);
assertFalse(compiler.deprecatedFields());
assertFalse(compiler.publicFields());
assertTrue(compiler.privateFields());
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line = null;
while ((line = reader.readLine()) != null) {
// No line, once trimmed, should start with a public field declaration
// or with a deprecated public field declaration
line = line.trim();
assertFalse("Line started with a public field declaration: " + line, line.startsWith("public int value"));
assertFalse("Line started with a deprecated field declaration: " + line,
line.startsWith("@Deprecated public int value"));
}
}
}
@Test
public void testSettersCreatedByDefault() throws IOException {
SpecificCompiler compiler = createCompiler();
assertTrue(compiler.isCreateSetters());
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
int foundSetters = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
// We should find the setter in the main class
line = line.trim();
if (line.startsWith("public void setValue(")) {
foundSetters++;
}
}
}
assertEquals("Found the wrong number of setters", 1, foundSetters);
}
@Test
public void testSettersNotCreatedWhenOptionTurnedOff() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.setCreateSetters(false);
assertFalse(compiler.isCreateSetters());
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
// No setter should be found
line = line.trim();
assertFalse("No line should include the setter: " + line, line.startsWith("public void setValue("));
}
}
}
@Test
public void testSettingOutputCharacterEncoding() throws Exception {
SpecificCompiler compiler = createCompiler();
// Generated file in default encoding
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
byte[] fileInDefaultEncoding = new byte[(int) this.outputFile.length()];
FileInputStream is = new FileInputStream(this.outputFile);
is.read(fileInDefaultEncoding);
is.close(); // close input stream otherwise delete might fail
if (!this.outputFile.delete()) {
throw new IllegalStateException("unable to delete " + this.outputFile); // delete otherwise compiler might not
// overwrite because src timestamp hasn't
// changed.
}
// Generate file in another encoding (make sure it has different number of bytes
// per character)
String differentEncoding = Charset.defaultCharset().equals(Charset.forName("UTF-16")) ? "UTF-32" : "UTF-16";
compiler.setOutputCharacterEncoding(differentEncoding);
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
byte[] fileInDifferentEncoding = new byte[(int) this.outputFile.length()];
is = new FileInputStream(this.outputFile);
is.read(fileInDifferentEncoding);
is.close();
// Compare as bytes
assertThat("Generated file should contain different bytes after setting non-default encoding",
fileInDefaultEncoding, not(equalTo(fileInDifferentEncoding)));
// Compare as strings
assertThat("Generated files should contain the same characters in the proper encodings",
new String(fileInDefaultEncoding), equalTo(new String(fileInDifferentEncoding, differentEncoding)));
}
@Test
public void testJavaTypeWithDecimalLogicalTypeEnabled() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(true);
Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT));
Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT));
Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG));
Schema decimalSchema = LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES));
Schema uuidSchema = LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING));
// Date/time types should always use upper level java classes
// Decimal type target class depends on configuration
// UUID should always be CharSequence since we haven't added its
// support in SpecificRecord
Assert.assertEquals("Should use LocalDate for date type", "java.time.LocalDate", compiler.javaType(dateSchema));
Assert.assertEquals("Should use LocalTime for time-millis type", "java.time.LocalTime",
compiler.javaType(timeSchema));
Assert.assertEquals("Should use DateTime for timestamp-millis type", "java.time.Instant",
compiler.javaType(timestampSchema));
Assert.assertEquals("Should use Java BigDecimal type", "java.math.BigDecimal", compiler.javaType(decimalSchema));
Assert.assertEquals("Should use Java CharSequence type", "java.lang.CharSequence", compiler.javaType(uuidSchema));
}
@Test
public void testJavaTypeWithDecimalLogicalTypeDisabled() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(false);
Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT));
Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT));
Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG));
Schema decimalSchema = LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES));
Schema uuidSchema = LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING));
// Date/time types should always use upper level java classes
// Decimal type target class depends on configuration
// UUID should always be CharSequence since we haven't added its
// support in SpecificRecord
Assert.assertEquals("Should use LocalDate for date type", "java.time.LocalDate", compiler.javaType(dateSchema));
Assert.assertEquals("Should use LocalTime for time-millis type", "java.time.LocalTime",
compiler.javaType(timeSchema));
Assert.assertEquals("Should use DateTime for timestamp-millis type", "java.time.Instant",
compiler.javaType(timestampSchema));
Assert.assertEquals("Should use ByteBuffer type", "java.nio.ByteBuffer", compiler.javaType(decimalSchema));
Assert.assertEquals("Should use Java CharSequence type", "java.lang.CharSequence", compiler.javaType(uuidSchema));
}
@Test
public void testJavaTypeWithDateTimeTypes() throws Exception {
SpecificCompiler compiler = createCompiler();
Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT));
Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT));
Schema timeMicrosSchema = LogicalTypes.timeMicros().addToSchema(Schema.create(Schema.Type.LONG));
Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG));
Schema timestampMicrosSchema = LogicalTypes.timestampMicros().addToSchema(Schema.create(Schema.Type.LONG));
// Date/time types should always use upper level java classes
Assert.assertEquals("Should use java.time.LocalDate for date type", "java.time.LocalDate",
compiler.javaType(dateSchema));
Assert.assertEquals("Should use java.time.LocalTime for time-millis type", "java.time.LocalTime",
compiler.javaType(timeSchema));
Assert.assertEquals("Should use java.time.Instant for timestamp-millis type", "java.time.Instant",
compiler.javaType(timestampSchema));
Assert.assertEquals("Should use java.time.LocalTime for time-micros type", "java.time.LocalTime",
compiler.javaType(timeMicrosSchema));
Assert.assertEquals("Should use java.time.Instant for timestamp-micros type", "java.time.Instant",
compiler.javaType(timestampMicrosSchema));
}
@Test
public void testJavaUnbox() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(false);
Schema intSchema = Schema.create(Schema.Type.INT);
Schema longSchema = Schema.create(Schema.Type.LONG);
Schema floatSchema = Schema.create(Schema.Type.FLOAT);
Schema doubleSchema = Schema.create(Schema.Type.DOUBLE);
Schema boolSchema = Schema.create(Schema.Type.BOOLEAN);
Assert.assertEquals("Should use int for Type.INT", "int", compiler.javaUnbox(intSchema, false));
Assert.assertEquals("Should use long for Type.LONG", "long", compiler.javaUnbox(longSchema, false));
Assert.assertEquals("Should use float for Type.FLOAT", "float", compiler.javaUnbox(floatSchema, false));
Assert.assertEquals("Should use double for Type.DOUBLE", "double", compiler.javaUnbox(doubleSchema, false));
Assert.assertEquals("Should use boolean for Type.BOOLEAN", "boolean", compiler.javaUnbox(boolSchema, false));
// see AVRO-2569
Schema nullSchema = Schema.create(Schema.Type.NULL);
Assert.assertEquals("Should use void for Type.NULL", "void", compiler.javaUnbox(nullSchema, true));
Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT));
Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT));
Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG));
// Date/time types should always use upper level java classes, even though
// their underlying representations are primitive types
Assert.assertEquals("Should use LocalDate for date type", "java.time.LocalDate",
compiler.javaUnbox(dateSchema, false));
Assert.assertEquals("Should use LocalTime for time-millis type", "java.time.LocalTime",
compiler.javaUnbox(timeSchema, false));
Assert.assertEquals("Should use DateTime for timestamp-millis type", "java.time.Instant",
compiler.javaUnbox(timestampSchema, false));
}
@Test
public void testJavaUnboxDateTime() throws Exception {
SpecificCompiler compiler = createCompiler();
Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT));
Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT));
Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG));
// Date/time types should always use upper level java classes, even though
// their underlying representations are primitive types
Assert.assertEquals("Should use java.time.LocalDate for date type", "java.time.LocalDate",
compiler.javaUnbox(dateSchema, false));
Assert.assertEquals("Should use java.time.LocalTime for time-millis type", "java.time.LocalTime",
compiler.javaUnbox(timeSchema, false));
Assert.assertEquals("Should use java.time.Instant for timestamp-millis type", "java.time.Instant",
compiler.javaUnbox(timestampSchema, false));
}
@Test
public void testNullableLogicalTypesJavaUnboxDecimalTypesEnabled() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(true);
// Nullable types should return boxed types instead of primitive types
Schema nullableDecimalSchema1 = Schema.createUnion(Schema.create(Schema.Type.NULL),
LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES)));
Schema nullableDecimalSchema2 = Schema.createUnion(
LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES)), Schema.create(Schema.Type.NULL));
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableDecimalSchema1, false),
"java.math.BigDecimal");
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableDecimalSchema2, false),
"java.math.BigDecimal");
}
@Test
public void testNullableLogicalTypesJavaUnboxDecimalTypesDisabled() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(false);
// Since logical decimal types are disabled, a ByteBuffer is expected.
Schema nullableDecimalSchema1 = Schema.createUnion(Schema.create(Schema.Type.NULL),
LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES)));
Schema nullableDecimalSchema2 = Schema.createUnion(
LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES)), Schema.create(Schema.Type.NULL));
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableDecimalSchema1, false),
"java.nio.ByteBuffer");
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableDecimalSchema2, false),
"java.nio.ByteBuffer");
}
@Test
public void testNullableTypesJavaUnbox() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(false);
// Nullable types should return boxed types instead of primitive types
Schema nullableIntSchema1 = Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.INT));
Schema nullableIntSchema2 = Schema.createUnion(Schema.create(Schema.Type.INT), Schema.create(Schema.Type.NULL));
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableIntSchema1, false), "java.lang.Integer");
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableIntSchema2, false), "java.lang.Integer");
Schema nullableLongSchema1 = Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.LONG));
Schema nullableLongSchema2 = Schema.createUnion(Schema.create(Schema.Type.LONG), Schema.create(Schema.Type.NULL));
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableLongSchema1, false), "java.lang.Long");
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableLongSchema2, false), "java.lang.Long");
Schema nullableFloatSchema1 = Schema.createUnion(Schema.create(Schema.Type.NULL), Schema.create(Schema.Type.FLOAT));
Schema nullableFloatSchema2 = Schema.createUnion(Schema.create(Schema.Type.FLOAT), Schema.create(Schema.Type.NULL));
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableFloatSchema1, false), "java.lang.Float");
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableFloatSchema2, false), "java.lang.Float");
Schema nullableDoubleSchema1 = Schema.createUnion(Schema.create(Schema.Type.NULL),
Schema.create(Schema.Type.DOUBLE));
Schema nullableDoubleSchema2 = Schema.createUnion(Schema.create(Schema.Type.DOUBLE),
Schema.create(Schema.Type.NULL));
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableDoubleSchema1, false),
"java.lang.Double");
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableDoubleSchema2, false),
"java.lang.Double");
Schema nullableBooleanSchema1 = Schema.createUnion(Schema.create(Schema.Type.NULL),
Schema.create(Schema.Type.BOOLEAN));
Schema nullableBooleanSchema2 = Schema.createUnion(Schema.create(Schema.Type.BOOLEAN),
Schema.create(Schema.Type.NULL));
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableBooleanSchema1, false),
"java.lang.Boolean");
Assert.assertEquals("Should return boxed type", compiler.javaUnbox(nullableBooleanSchema2, false),
"java.lang.Boolean");
}
@Test
public void testGetUsedConversionClassesForNullableLogicalTypes() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(true);
Schema nullableDecimal1 = Schema.createUnion(Schema.create(Schema.Type.NULL),
LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES)));
Schema schemaWithNullableDecimal1 = Schema.createRecord("WithNullableDecimal", "", "", false,
Collections.singletonList(new Schema.Field("decimal", nullableDecimal1, "", null)));
final Collection<String> usedConversionClasses = compiler.getUsedConversionClasses(schemaWithNullableDecimal1);
Assert.assertEquals(1, usedConversionClasses.size());
Assert.assertEquals("org.apache.avro.Conversions.DecimalConversion", usedConversionClasses.iterator().next());
}
@Test
public void testGetUsedConversionClassesForNullableLogicalTypesInNestedRecord() throws Exception {
SpecificCompiler compiler = createCompiler();
final Schema schema = new Schema.Parser().parse(
"{\"type\":\"record\",\"name\":\"NestedLogicalTypesRecord\",\"namespace\":\"org.apache.avro.codegentest.testdata\",\"doc\":\"Test nested types with logical types in generated Java classes\",\"fields\":[{\"name\":\"nestedRecord\",\"type\":{\"type\":\"record\",\"name\":\"NestedRecord\",\"fields\":[{\"name\":\"nullableDateField\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"date\"}]}]}}]}");
final Collection<String> usedConversionClasses = compiler.getUsedConversionClasses(schema);
Assert.assertEquals(1, usedConversionClasses.size());
Assert.assertEquals("org.apache.avro.data.TimeConversions.DateConversion", usedConversionClasses.iterator().next());
}
@Test
public void testGetUsedConversionClassesForNullableLogicalTypesInArray() throws Exception {
SpecificCompiler compiler = createCompiler();
final Schema schema = new Schema.Parser().parse(
"{\"type\":\"record\",\"name\":\"NullableLogicalTypesArray\",\"namespace\":\"org.apache.avro.codegentest.testdata\",\"doc\":\"Test nested types with logical types in generated Java classes\",\"fields\":[{\"name\":\"arrayOfLogicalType\",\"type\":{\"type\":\"array\",\"items\":[\"null\",{\"type\":\"int\",\"logicalType\":\"date\"}]}}]}");
final Collection<String> usedConversionClasses = compiler.getUsedConversionClasses(schema);
Assert.assertEquals(1, usedConversionClasses.size());
Assert.assertEquals("org.apache.avro.data.TimeConversions.DateConversion", usedConversionClasses.iterator().next());
}
@Test
public void testGetUsedConversionClassesForNullableLogicalTypesInArrayOfRecords() throws Exception {
SpecificCompiler compiler = createCompiler();
final Schema schema = new Schema.Parser().parse(
"{\"type\":\"record\",\"name\":\"NestedLogicalTypesArray\",\"namespace\":\"org.apache.avro.codegentest.testdata\",\"doc\":\"Test nested types with logical types in generated Java classes\",\"fields\":[{\"name\":\"arrayOfRecords\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"RecordInArray\",\"fields\":[{\"name\":\"nullableDateField\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"date\"}]}]}}}]}");
final Collection<String> usedConversionClasses = compiler.getUsedConversionClasses(schema);
Assert.assertEquals(1, usedConversionClasses.size());
Assert.assertEquals("org.apache.avro.data.TimeConversions.DateConversion", usedConversionClasses.iterator().next());
}
@Test
public void testGetUsedConversionClassesForNullableLogicalTypesInUnionOfRecords() throws Exception {
SpecificCompiler compiler = createCompiler();
final Schema schema = new Schema.Parser().parse(
"{\"type\":\"record\",\"name\":\"NestedLogicalTypesUnion\",\"namespace\":\"org.apache.avro.codegentest.testdata\",\"doc\":\"Test nested types with logical types in generated Java classes\",\"fields\":[{\"name\":\"unionOfRecords\",\"type\":[\"null\",{\"type\":\"record\",\"name\":\"RecordInUnion\",\"fields\":[{\"name\":\"nullableDateField\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"date\"}]}]}]}]}");
final Collection<String> usedConversionClasses = compiler.getUsedConversionClasses(schema);
Assert.assertEquals(1, usedConversionClasses.size());
Assert.assertEquals("org.apache.avro.data.TimeConversions.DateConversion", usedConversionClasses.iterator().next());
}
@Test
public void testGetUsedConversionClassesForNullableLogicalTypesInMapOfRecords() throws Exception {
SpecificCompiler compiler = createCompiler();
final Schema schema = new Schema.Parser().parse(
"{\"type\":\"record\",\"name\":\"NestedLogicalTypesMap\",\"namespace\":\"org.apache.avro.codegentest.testdata\",\"doc\":\"Test nested types with logical types in generated Java classes\",\"fields\":[{\"name\":\"mapOfRecords\",\"type\":{\"type\":\"map\",\"values\":{\"type\":\"record\",\"name\":\"RecordInMap\",\"fields\":[{\"name\":\"nullableDateField\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"date\"}]}]},\"avro.java.string\":\"String\"}}]}");
final Collection<String> usedConversionClasses = compiler.getUsedConversionClasses(schema);
Assert.assertEquals(1, usedConversionClasses.size());
Assert.assertEquals("org.apache.avro.data.TimeConversions.DateConversion", usedConversionClasses.iterator().next());
}
@Test
public void testLogicalTypesWithMultipleFields() throws Exception {
Schema logicalTypesWithMultipleFields = new Schema.Parser()
.parse(new File("src/test/resources/logical_types_with_multiple_fields.avsc"));
assertCompilesWithJavaCompiler(new File(OUTPUT_DIR.getRoot(), name.getMethodName()),
new SpecificCompiler(logicalTypesWithMultipleFields).compile(), true);
}
@Test
public void testUnionAndFixedFields() throws Exception {
Schema unionTypesWithMultipleFields = new Schema.Parser()
.parse(new File("src/test/resources/union_and_fixed_fields.avsc"));
assertCompilesWithJavaCompiler(new File(this.outputFile, name.getMethodName()),
new SpecificCompiler(unionTypesWithMultipleFields).compile());
}
@Test
public void testLogicalTypesWithMultipleFieldsDateTime() throws Exception {
Schema logicalTypesWithMultipleFields = new Schema.Parser()
.parse(new File("src/test/resources/logical_types_with_multiple_fields.avsc"));
assertCompilesWithJavaCompiler(new File(this.outputFile, name.getMethodName()),
new SpecificCompiler(logicalTypesWithMultipleFields).compile());
}
@Test
public void testConversionInstanceWithDecimalLogicalTypeDisabled() throws Exception {
final SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(false);
final Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT));
final Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT));
final Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG));
final Schema decimalSchema = LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES));
final Schema uuidSchema = LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING));
Assert.assertEquals("Should use date conversion for date type",
"new org.apache.avro.data.TimeConversions.DateConversion()", compiler.conversionInstance(dateSchema));
Assert.assertEquals("Should use time conversion for time type",
"new org.apache.avro.data.TimeConversions.TimeMillisConversion()", compiler.conversionInstance(timeSchema));
Assert.assertEquals("Should use timestamp conversion for date type",
"new org.apache.avro.data.TimeConversions.TimestampMillisConversion()",
compiler.conversionInstance(timestampSchema));
Assert.assertEquals("Should use null for decimal if the flag is off", "null",
compiler.conversionInstance(decimalSchema));
Assert.assertEquals("Should use null for decimal if the flag is off", "null",
compiler.conversionInstance(uuidSchema));
}
@Test
public void testConversionInstanceWithDecimalLogicalTypeEnabled() throws Exception {
SpecificCompiler compiler = createCompiler();
compiler.setEnableDecimalLogicalType(true);
Schema dateSchema = LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT));
Schema timeSchema = LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT));
Schema timestampSchema = LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG));
Schema decimalSchema = LogicalTypes.decimal(9, 2).addToSchema(Schema.create(Schema.Type.BYTES));
Schema uuidSchema = LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING));
Assert.assertEquals("Should use date conversion for date type",
"new org.apache.avro.data.TimeConversions.DateConversion()", compiler.conversionInstance(dateSchema));
Assert.assertEquals("Should use time conversion for time type",
"new org.apache.avro.data.TimeConversions.TimeMillisConversion()", compiler.conversionInstance(timeSchema));
Assert.assertEquals("Should use timestamp conversion for date type",
"new org.apache.avro.data.TimeConversions.TimestampMillisConversion()",
compiler.conversionInstance(timestampSchema));
Assert.assertEquals("Should use null for decimal if the flag is off",
"new org.apache.avro.Conversions.DecimalConversion()", compiler.conversionInstance(decimalSchema));
Assert.assertEquals("Should use null for decimal if the flag is off", "null",
compiler.conversionInstance(uuidSchema));
}
@Test
public void testPojoWithOptionalTurnedOffByDefault() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.compileToDestination(this.src, OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
assertFalse(line.contains("Optional"));
}
}
}
@Test
public void testPojoWithOptionalCreatedWhenOptionTurnedOn() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.setGettersReturnOptional(true);
// compiler.setCreateOptionalGetters(true);
compiler.compileToDestination(this.src, OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
int optionalFound = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.contains("Optional")) {
optionalFound++;
}
}
}
assertEquals(9, optionalFound);
}
@Test
public void testPojoWithOptionalCreateForNullableFieldsWhenOptionTurnedOn() throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.setGettersReturnOptional(true);
compiler.setOptionalGettersForNullableFieldsOnly(true);
compiler.compileToDestination(this.src, OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
int optionalFound = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.contains("Optional")) {
optionalFound++;
}
}
}
assertEquals(5, optionalFound);
}
@Test
public void testPojoWithOptionalCreatedWhenOptionalForEverythingTurnedOn() throws IOException {
SpecificCompiler compiler = createCompiler();
// compiler.setGettersReturnOptional(true);
compiler.setCreateOptionalGetters(true);
compiler.compileToDestination(this.src, OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
int optionalFound = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.contains("Optional")) {
optionalFound++;
}
}
}
assertEquals(17, optionalFound);
}
@Test
public void testPojoWithOptionalOnlyWhenNullableCreatedTurnedOnAndGettersReturnOptionalTurnedOff()
throws IOException {
SpecificCompiler compiler = createCompiler();
compiler.setOptionalGettersForNullableFieldsOnly(true);
compiler.compileToDestination(this.src, OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
// no optionals since gettersReturnOptionalOnlyForNullable is false
assertFalse(line.contains("Optional"));
}
}
}
@Test
public void testAdditionalToolsAreInjectedIntoTemplate() throws Exception {
SpecificCompiler compiler = createCompiler();
List<Object> customTools = new ArrayList<>();
customTools.add(new String());
compiler.setAdditionalVelocityTools(customTools);
compiler.setTemplateDir("src/test/resources/templates_with_custom_tools/");
compiler.compileToDestination(this.src, this.OUTPUT_DIR.getRoot());
assertTrue(this.outputFile.exists());
int itWorksFound = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(this.outputFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.contains("It works!")) {
itWorksFound++;
}
}
}
assertEquals(1, itWorksFound);
}
}