From 06b2cc40eed68eb3178ba4263f8046753440d644 Mon Sep 17 00:00:00 2001 From: Dmitrii Nikitin Date: Thu, 21 Jul 2016 19:18:51 +0300 Subject: [PATCH] Get generator support nulls for boxed types --- .../processor/introspection/JavaType.java | 14 ++ .../processor/introspection/JavaTypeTest.java | 65 +++++++ ...nSubscribeExecuteAsBlockingSingleTest.java | 32 ++++ .../generate/GetResolverGenerator.java | 9 + .../generate/GetResolverGeneratorTest.java | 167 ++++++++++++----- .../generate/PutResolverGeneratorTest.java | 75 +++----- .../processor/generate/TestFactory.java | 55 ++++++ .../StorIOContentResolverColumn.java | 2 +- .../generate/GetResolverGenerator.java | 9 + .../generate/GetResolverGeneratorTest.java | 174 +++++++++++++----- .../generate/PutResolverGeneratorTest.java | 57 ++---- .../processor/generate/TestFactory.java | 55 ++++++ .../annotations/StorIOSQLiteColumn.java | 2 +- .../sqlite/impl/DefaultStorIOSQLiteTest.java | 9 + 14 files changed, 546 insertions(+), 179 deletions(-) create mode 100644 storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/TestFactory.java create mode 100644 storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/TestFactory.java diff --git a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java index 5c471e502..f85393361 100644 --- a/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java +++ b/storio-common-annotations-processor/src/main/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaType.java @@ -59,4 +59,18 @@ public static JavaType from(@NotNull TypeMirror typeMirror) { throw new IllegalArgumentException("Unsupported type: " + typeMirror); } } + + public boolean isBoxedType() { + switch (this) { + case BOOLEAN_OBJECT: + case SHORT_OBJECT: + case INTEGER_OBJECT: + case LONG_OBJECT: + case FLOAT_OBJECT: + case DOUBLE_OBJECT: + return true; + default: + return false; + } + } } diff --git a/storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java b/storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java index 1dc7c4d8e..a7d4a9eb2 100644 --- a/storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java +++ b/storio-common-annotations-processor/src/test/java/com/pushtorefresh/storio/common/annotations/processor/introspection/JavaTypeTest.java @@ -154,4 +154,69 @@ public void fromByteArray() { final TypeMirror typeMirror = mockTypeMirror(null, byte[].class.getCanonicalName()); assertThat(JavaType.from(typeMirror)).isEqualTo(BYTE_ARRAY); } + + @Test + public void booleanObjectIsBoxed() { + assertThat(BOOLEAN_OBJECT.isBoxedType()).isTrue(); + } + + @Test + public void shortObjectIsBoxed() { + assertThat(SHORT_OBJECT.isBoxedType()).isTrue(); + } + + @Test + public void integerObjectIsBoxed() { + assertThat(INTEGER_OBJECT.isBoxedType()).isTrue(); + } + + @Test + public void longObjectIsBoxed() { + assertThat(LONG_OBJECT.isBoxedType()).isTrue(); + } + + @Test + public void floatObjectIsBoxed() { + assertThat(FLOAT_OBJECT.isBoxedType()).isTrue(); + } + + @Test + public void doubleObjectIsBoxed() { + assertThat(DOUBLE_OBJECT.isBoxedType()).isTrue(); + } + + @Test + public void shortPrimitiveIsNotBoxed() { + assertThat(SHORT.isBoxedType()).isFalse(); + } + + @Test + public void integerPrimitiveIsNotBoxed() { + assertThat(INTEGER.isBoxedType()).isFalse(); + } + + @Test + public void longPrimitiveIsNotBoxed() { + assertThat(LONG.isBoxedType()).isFalse(); + } + + @Test + public void floatPrimitiveIsNotBoxed() { + assertThat(FLOAT.isBoxedType()).isFalse(); + } + + @Test + public void doublePrimitiveIsNotBoxed() { + assertThat(DOUBLE.isBoxedType()).isFalse(); + } + + @Test + public void stringIsNotBoxed() { + assertThat(STRING.isBoxedType()).isFalse(); + } + + @Test + public void byteArrayIsNotBoxed() { + assertThat(BYTE_ARRAY.isBoxedType()).isFalse(); + } } diff --git a/storio-common/src/test/java/com/pushtorefresh/storio/operations/internal/OnSubscribeExecuteAsBlockingSingleTest.java b/storio-common/src/test/java/com/pushtorefresh/storio/operations/internal/OnSubscribeExecuteAsBlockingSingleTest.java index 998799f66..1141f6a62 100644 --- a/storio-common/src/test/java/com/pushtorefresh/storio/operations/internal/OnSubscribeExecuteAsBlockingSingleTest.java +++ b/storio-common/src/test/java/com/pushtorefresh/storio/operations/internal/OnSubscribeExecuteAsBlockingSingleTest.java @@ -6,6 +6,7 @@ import org.junit.Test; import rx.Single; +import rx.SingleSubscriber; import rx.observers.TestSubscriber; import static org.mockito.Mockito.mock; @@ -65,4 +66,35 @@ public void shouldCallOnErrorIfExceptionOccurred() { verify(preparedOperation, never()).asRxSingle(); verify(preparedOperation, never()).asRxObservable(); } + + @Test + public void shouldCallExecuteAsBlockingEvenIfSubscriberAlreadyUnsubscribed() { + //noinspection unchecked + PreparedOperation preparedOperation = mock(PreparedOperation.class); + + //noinspection unchecked + SingleSubscriber subscriber = new SingleSubscriber() { + + @Override + public void onSuccess(Object value) { + // nothing + } + + @Override + public void onError(Throwable error) { + // nothing + } + }; + + subscriber.unsubscribe(); + + OnSubscribeExecuteAsBlockingSingle + .newInstance(preparedOperation) + .call(subscriber); + + // Even if subscriber is unsubscribed when call was done, + // executeAsBlocking() must be called (for example for Put and Delete operations) + // But we should think about skipping call to executeAsBlocking() for Get Operation in same case + verify(preparedOperation).executeAsBlocking(); + } } diff --git a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java index 556f0242b..4d85954ed 100644 --- a/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java +++ b/storio-content-resolver-annotations-processor/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGenerator.java @@ -99,7 +99,16 @@ private MethodSpec createMapFromCursorMethodSpec(@NotNull StorIOContentResolverT throw new ProcessingException(columnMeta.element, "Can not generate GetResolver for field"); } + final boolean isBoxed = javaType.isBoxedType(); + if (isBoxed) { // otherwise -> if primitive and value from cursor null -> fail early + builder.beginControlFlow("if(!cursor.isNull($L))", columnIndex); + } + builder.addStatement("object.$L = cursor.$L", columnMeta.fieldName, getFromCursor); + + if (isBoxed) { + builder.endControlFlow(); + } } return builder diff --git a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java index e700bfbb2..914e9ec7f 100644 --- a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java +++ b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/GetResolverGeneratorTest.java @@ -1,22 +1,58 @@ package com.pushtorefresh.storio.contentresolver.annotations.processor.generate; import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType; -import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverColumn; import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverType; import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverColumnMeta; import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverTypeMeta; import com.squareup.javapoet.JavaFile; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.io.IOException; +import javax.lang.model.type.TypeKind; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class GetResolverGeneratorTest { + @NotNull + private static final String PART_PACKAGE = "package com.test;\n\n"; + + @NotNull + private static final String PART_IMPORT = + "import android.database.Cursor;\n" + + "import android.support.annotation.NonNull;\n" + + "import com.pushtorefresh.storio.contentresolver.operations.get.DefaultGetResolver;\n" + + "import java.lang.Override;\n" + + "\n"; + + @NotNull + private static final String PART_CLASS = + "/**\n" + + " * Generated resolver for Get Operation\n" + + " */\n" + + "public class TestItemStorIOContentResolverGetResolver extends DefaultGetResolver {\n"; + + @NotNull + private static final String PART_MAP_FROM_CURSOR_WITHOUT_NULL_CHECK = + " /**\n" + + " * {@inheritDoc}\n" + + " */\n" + + " @Override\n" + + " @NonNull\n" + + " public TestItem mapFromCursor(@NonNull Cursor cursor) {\n" + + " TestItem object = new TestItem();\n" + + "\n" + + " object.field1 = cursor.getInt(cursor.getColumnIndex(\"column1\")) == 1;\n" + + " object.field2 = cursor.getString(cursor.getColumnIndex(\"column2\"));\n" + + "\n" + + " return object;\n" + + " }\n"; + @Test public void generateJavaFileTest() throws IOException { final StorIOContentResolverType storIOContentResolverType = mock(StorIOContentResolverType.class); @@ -29,29 +65,66 @@ public void generateJavaFileTest() throws IOException { storIOContentResolverType ); - final StorIOContentResolverColumn storIOContentResolverColumn1 = mock(StorIOContentResolverColumn.class); - when(storIOContentResolverColumn1.name()).thenReturn("column1"); - - //noinspection ConstantConditions - final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta1 = new StorIOContentResolverColumnMeta( - null, - null, + final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta1 = TestFactory.createColumnMetaMock( + TestFactory.createElementMock(TypeKind.BOOLEAN), + "column1", "field1", - JavaType.BOOLEAN, - storIOContentResolverColumn1 + true, + false, + JavaType.BOOLEAN ); storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); - final StorIOContentResolverColumn storIOContentResolverColumn2 = mock(StorIOContentResolverColumn.class); - when(storIOContentResolverColumn2.name()).thenReturn("column2"); + final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = TestFactory.createColumnMetaMock( + TestFactory.createElementMock(TypeKind.OTHER), + "column2", + "field2", + false, + false, + JavaType.STRING + ); + storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); - //noinspection ConstantConditions - final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = new StorIOContentResolverColumnMeta( - null, - null, + final JavaFile javaFile = new GetResolverGenerator().generateJavaFile(storIOContentResolverTypeMeta); + final StringBuilder out = new StringBuilder(); + javaFile.writeTo(out); + + checkFile(out.toString(), + PART_PACKAGE, + PART_IMPORT, + PART_CLASS, + PART_MAP_FROM_CURSOR_WITHOUT_NULL_CHECK); + } + + @Test + public void checksForNullIfBoxedType() throws IOException { + final StorIOContentResolverType storIOContentResolverType = mock(StorIOContentResolverType.class); + + when(storIOContentResolverType.uri()).thenReturn("content://test"); + + final StorIOContentResolverTypeMeta storIOContentResolverTypeMeta = new StorIOContentResolverTypeMeta( + "TestItem", + "com.test", + storIOContentResolverType + ); + + final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta1 = TestFactory.createColumnMetaMock( + TestFactory.createElementMock(TypeKind.BOOLEAN), + "column1", + "field1", + true, + false, + JavaType.BOOLEAN + ); + storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); + + final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = TestFactory.createColumnMetaMock( + TestFactory.createElementMock(TypeKind.OTHER), + "column2", "field2", - JavaType.STRING, - storIOContentResolverColumn2 + false, + false, + JavaType.INTEGER_OBJECT // boxed type ); storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); @@ -59,30 +132,40 @@ public void generateJavaFileTest() throws IOException { final StringBuilder out = new StringBuilder(); javaFile.writeTo(out); - assertThat(out.toString()).isEqualTo("package com.test;\n" + - "\n" + - "import android.database.Cursor;\n" + - "import android.support.annotation.NonNull;\n" + - "import com.pushtorefresh.storio.contentresolver.operations.get.DefaultGetResolver;\n" + - "import java.lang.Override;\n" + - "\n" + - "/**\n" + - " * Generated resolver for Get Operation\n" + - " */\n" + - "public class TestItemStorIOContentResolverGetResolver extends DefaultGetResolver {\n" + + checkFile(out.toString(), + PART_PACKAGE, + PART_IMPORT, + PART_CLASS, " /**\n" + - " * {@inheritDoc}\n" + - " */\n" + - " @Override\n" + - " @NonNull\n" + - " public TestItem mapFromCursor(@NonNull Cursor cursor) {\n" + - " TestItem object = new TestItem();\n" + - "\n" + - " object.field1 = cursor.getInt(cursor.getColumnIndex(\"column1\")) == 1;\n" + - " object.field2 = cursor.getString(cursor.getColumnIndex(\"column2\"));\n" + - "\n" + - " return object;\n" + - " }\n" + - "}\n"); + " * {@inheritDoc}\n" + + " */\n" + + " @Override\n" + + " @NonNull\n" + + " public TestItem mapFromCursor(@NonNull Cursor cursor) {\n" + + " TestItem object = new TestItem();\n" + + "\n" + + " object.field1 = cursor.getInt(cursor.getColumnIndex(\"column1\")) == 1;\n" + + " if(!cursor.isNull(cursor.getColumnIndex(\"column2\"))) {\n" + + " object.field2 = cursor.getInt(cursor.getColumnIndex(\"column2\"));\n" + + " }\n" + + "\n" + + " return object;\n" + + " }\n" + ); + } + + private void checkFile( + @NotNull String actualFile, + @NotNull String partPackage, + @NotNull String partImport, + @NotNull String partClass, + @NotNull String partMapFromCursor + ) { + assertThat(actualFile).isEqualTo( + partPackage + + partImport + + partClass + + partMapFromCursor + + "}\n"); } } diff --git a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGeneratorTest.java b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGeneratorTest.java index 188430c81..d1c5a09b8 100644 --- a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGeneratorTest.java +++ b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/PutResolverGeneratorTest.java @@ -1,6 +1,5 @@ package com.pushtorefresh.storio.contentresolver.annotations.processor.generate; -import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverColumn; import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverType; import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverColumnMeta; import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverTypeMeta; @@ -11,10 +10,8 @@ import java.io.IOException; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - +import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.TestFactory.createColumnMetaMock; +import static com.pushtorefresh.storio.contentresolver.annotations.processor.generate.TestFactory.createElementMock; import static javax.lang.model.type.TypeKind.INT; import static javax.lang.model.type.TypeKind.NONE; import static org.assertj.core.api.Assertions.assertThat; @@ -102,7 +99,8 @@ public void generateJavaFileWithCommonUri() throws IOException { "column1", "column1Field", true, // key - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = createColumnMetaMock( @@ -110,7 +108,8 @@ public void generateJavaFileWithCommonUri() throws IOException { "column2", "column2Field", false, - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -142,7 +141,8 @@ public void generateJavaFileWithOperationSpecificUri() throws IOException { "column1", "column1Field", true, // key - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = createColumnMetaMock( @@ -150,7 +150,8 @@ public void generateJavaFileWithOperationSpecificUri() throws IOException { "column2", "column2Field", false, - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -204,7 +205,8 @@ public void operationSpecificUriShouldHaveHigherPriority() throws IOException { "column1", "column1Field", true, // key - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = createColumnMetaMock( @@ -212,7 +214,8 @@ public void operationSpecificUriShouldHaveHigherPriority() throws IOException { "column2", "column2Field", false, - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -266,7 +269,8 @@ public void shouldUseCommonUriIfSpecifiedOnlyForAnotherOperations() throws IOExc "column1", "column1Field", true, // key - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = createColumnMetaMock( @@ -274,7 +278,8 @@ public void shouldUseCommonUriIfSpecifiedOnlyForAnotherOperations() throws IOExc "column2", "column2Field", false, - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -305,7 +310,8 @@ public void ignoreNullsShouldAddCheckIfObject() throws IOException { "column1", "column1Field", true, // key - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = createColumnMetaMock( @@ -313,7 +319,8 @@ public void ignoreNullsShouldAddCheckIfObject() throws IOException { "column2", "column2Field", false, - true); // ignore nulls + true, + null); // ignore nulls storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -359,7 +366,8 @@ public void ignoreNullsShouldNotAddCheckIfPrimitive() throws IOException { "column1", "column1Field", true, // key - false); + false, + null); storIOContentResolverTypeMeta.columns.put("column1", storIOContentResolverColumnMeta1); final StorIOContentResolverColumnMeta storIOContentResolverColumnMeta2 = createColumnMetaMock( @@ -367,7 +375,8 @@ public void ignoreNullsShouldNotAddCheckIfPrimitive() throws IOException { "column2", "column2Field", false, - true); // ignore nulls + true, + null); // ignore nulls storIOContentResolverTypeMeta.columns.put("column2", storIOContentResolverColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -385,38 +394,6 @@ public void ignoreNullsShouldNotAddCheckIfPrimitive() throws IOException { PART_MAP_TO_CONTENT_VALUES_WITHOUT_NULL_CHECK); // Without check } - @NotNull - private static Element createElementMock(@NotNull TypeKind typeKind) { - final Element objectElement = mock(Element.class); - final TypeMirror typeMirror = mock(TypeMirror.class); - when(objectElement.asType()).thenReturn(typeMirror); - when(typeMirror.getKind()).thenReturn(typeKind); - return objectElement; - } - - @NotNull - private static StorIOContentResolverColumnMeta createColumnMetaMock( - @NotNull Element element, - @NotNull String columnName, - @NotNull String fieldName, - boolean isKey, - boolean ignoreNull - ) { - final StorIOContentResolverColumn storIOSQLiteColumn = mock(StorIOContentResolverColumn.class); - when(storIOSQLiteColumn.name()).thenReturn(columnName); - when(storIOSQLiteColumn.key()).thenReturn(isKey); - when(storIOSQLiteColumn.ignoreNull()).thenReturn(ignoreNull); - - //noinspection ConstantConditions - return new StorIOContentResolverColumnMeta( - null, - element, - fieldName, - null, - storIOSQLiteColumn - ); - } - private void checkFile( @NotNull String actualFile, @NotNull String partPackage, diff --git a/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/TestFactory.java b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/TestFactory.java new file mode 100644 index 000000000..4a14cc1fa --- /dev/null +++ b/storio-content-resolver-annotations-processor/src/test/java/com/pushtorefresh/storio/contentresolver/annotations/processor/generate/TestFactory.java @@ -0,0 +1,55 @@ +package com.pushtorefresh.storio.contentresolver.annotations.processor.generate; + +import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType; +import com.pushtorefresh.storio.contentresolver.annotations.StorIOContentResolverColumn; +import com.pushtorefresh.storio.contentresolver.annotations.processor.introspection.StorIOContentResolverColumnMeta; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public final class TestFactory { + + private TestFactory() { + throw new IllegalStateException("No instances please"); + } + + @NotNull + protected static Element createElementMock(@NotNull TypeKind typeKind) { + final Element objectElement = mock(Element.class); + final TypeMirror typeMirror = mock(TypeMirror.class); + when(objectElement.asType()).thenReturn(typeMirror); + when(typeMirror.getKind()).thenReturn(typeKind); + return objectElement; + } + + @NotNull + protected static StorIOContentResolverColumnMeta createColumnMetaMock( + @NotNull Element element, + @NotNull String columnName, + @NotNull String fieldName, + boolean isKey, + boolean ignoreNull, + @Nullable JavaType javaType) { + + final StorIOContentResolverColumn storIOSQLiteColumn = mock(StorIOContentResolverColumn.class); + when(storIOSQLiteColumn.name()).thenReturn(columnName); + when(storIOSQLiteColumn.key()).thenReturn(isKey); + when(storIOSQLiteColumn.ignoreNull()).thenReturn(ignoreNull); + + //noinspection ConstantConditions + return new StorIOContentResolverColumnMeta( + null, + element, + fieldName, + javaType, + storIOSQLiteColumn + ); + } +} diff --git a/storio-content-resolver-annotations/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/StorIOContentResolverColumn.java b/storio-content-resolver-annotations/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/StorIOContentResolverColumn.java index 41ca2c4bd..fab72365f 100644 --- a/storio-content-resolver-annotations/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/StorIOContentResolverColumn.java +++ b/storio-content-resolver-annotations/src/main/java/com/pushtorefresh/storio/contentresolver/annotations/StorIOContentResolverColumn.java @@ -25,7 +25,7 @@ boolean key() default false; /** - * Optional: indicates that field should not be serialized when it hasn't value + * Optional: indicates that field should not be serialized when it's value is {@code null} * * @return true if column with {@code null} value should be ignored, false otherwise */ diff --git a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java index f50ad2bbb..dd3c68b34 100644 --- a/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java +++ b/storio-sqlite-annotations-processor/src/main/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGenerator.java @@ -99,7 +99,16 @@ private MethodSpec createMapFromCursorMethodSpec(@NotNull StorIOSQLiteTypeMeta s throw new ProcessingException(columnMeta.element, "Can not generate GetResolver for field"); } + final boolean isBoxed = javaType.isBoxedType(); + if (isBoxed) { // otherwise -> if primitive and value from cursor null -> fail early + builder.beginControlFlow("if(!cursor.isNull($L))", columnIndex); + } + builder.addStatement("object.$L = cursor.$L", columnMeta.fieldName, getFromCursor); + + if (isBoxed) { + builder.endControlFlow(); + } } return builder diff --git a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java index 9cd91db39..9b3c243c8 100644 --- a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java +++ b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/GetResolverGeneratorTest.java @@ -1,22 +1,60 @@ package com.pushtorefresh.storio.sqlite.annotations.processor.generate; -import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn; -import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType; import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType; +import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType; import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteColumnMeta; import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteTypeMeta; import com.squareup.javapoet.JavaFile; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import java.io.IOException; +import javax.lang.model.type.TypeKind; + +import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.TestFactory.createColumnMetaMock; +import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.TestFactory.createElementMock; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class GetResolverGeneratorTest { + @NotNull + private static final String PART_PACKAGE = "package com.test;\n\n"; + + @NotNull + private static final String PART_IMPORT = + "import android.database.Cursor;\n" + + "import android.support.annotation.NonNull;\n" + + "import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver;\n" + + "import java.lang.Override;\n" + + "\n"; + + @NotNull + private static final String PART_CLASS = + "/**\n" + + " * Generated resolver for Get Operation\n" + + " */\n" + + "public class TestItemStorIOSQLiteGetResolver extends DefaultGetResolver {\n"; + + @NotNull + private static final String PART_MAP_FROM_CURSOR_WITHOUT_NULL_CHECK = + " /**\n" + + " * {@inheritDoc}\n" + + " */\n" + + " @Override\n" + + " @NonNull\n" + + " public TestItem mapFromCursor(@NonNull Cursor cursor) {\n" + + " TestItem object = new TestItem();\n" + + "\n" + + " object.field1 = cursor.getInt(cursor.getColumnIndex(\"column1\")) == 1;\n" + + " object.field2 = cursor.getString(cursor.getColumnIndex(\"column2\"));\n" + + "\n" + + " return object;\n" + + " }\n"; + @Test public void generateJavaFileTest() throws IOException { final StorIOSQLiteType storIOSQLiteType = mock(StorIOSQLiteType.class); @@ -29,29 +67,68 @@ public void generateJavaFileTest() throws IOException { storIOSQLiteType ); - final StorIOSQLiteColumn storIOSQLiteColumn1 = mock(StorIOSQLiteColumn.class); - when(storIOSQLiteColumn1.name()).thenReturn("column1"); - - //noinspection ConstantConditions - final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta1 = new StorIOSQLiteColumnMeta( - null, - null, + final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta1 = createColumnMetaMock( + createElementMock(TypeKind.BOOLEAN), + "column1", "field1", - JavaType.BOOLEAN, - storIOSQLiteColumn1 + true, // key + false, + JavaType.BOOLEAN ); storIOSQLiteTypeMeta.columns.put("column1", storIOSQLiteColumnMeta1); - final StorIOSQLiteColumn storIOSQLiteColumn2 = mock(StorIOSQLiteColumn.class); - when(storIOSQLiteColumn2.name()).thenReturn("column2"); + final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta2 = createColumnMetaMock( + createElementMock(TypeKind.OTHER), + "column2", + "field2", + false, + false, + JavaType.STRING + ); + storIOSQLiteTypeMeta.columns.put("column2", storIOSQLiteColumnMeta2); + + final JavaFile javaFile = new GetResolverGenerator().generateJavaFile(storIOSQLiteTypeMeta); + final StringBuilder out = new StringBuilder(); + javaFile.writeTo(out); + + checkFile( + out.toString(), + PART_PACKAGE, + PART_IMPORT, + PART_CLASS, + PART_MAP_FROM_CURSOR_WITHOUT_NULL_CHECK + ); + } + + @Test + public void checksForNullIfBoxedType() throws IOException { + final StorIOSQLiteType storIOSQLiteType = mock(StorIOSQLiteType.class); + + when(storIOSQLiteType.table()).thenReturn("test_table"); + + final StorIOSQLiteTypeMeta storIOSQLiteTypeMeta = new StorIOSQLiteTypeMeta( + "TestItem", + "com.test", + storIOSQLiteType + ); + + final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta1 = createColumnMetaMock( + createElementMock(TypeKind.BOOLEAN), + "column1", + "field1", + true, // key + false, + JavaType.BOOLEAN + ); + storIOSQLiteTypeMeta.columns.put("column1", storIOSQLiteColumnMeta1); - //noinspection ConstantConditions - final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta2 = new StorIOSQLiteColumnMeta( - null, - null, + final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta2 = createColumnMetaMock( + createElementMock(TypeKind.OTHER), + "column2", "field2", - JavaType.STRING, - storIOSQLiteColumn2 + false, + false, + JavaType.INTEGER_OBJECT // boxed type ); storIOSQLiteTypeMeta.columns.put("column2", storIOSQLiteColumnMeta2); @@ -59,30 +136,41 @@ public void generateJavaFileTest() throws IOException { final StringBuilder out = new StringBuilder(); javaFile.writeTo(out); - assertThat(out.toString()).isEqualTo("package com.test;\n" + - "\n" + - "import android.database.Cursor;\n" + - "import android.support.annotation.NonNull;\n" + - "import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver;\n" + - "import java.lang.Override;\n" + - "\n" + - "/**\n" + - " * Generated resolver for Get Operation\n" + - " */\n" + - "public class TestItemStorIOSQLiteGetResolver extends DefaultGetResolver {\n" + + checkFile( + out.toString(), + PART_PACKAGE, + PART_IMPORT, + PART_CLASS, " /**\n" + - " * {@inheritDoc}\n" + - " */\n" + - " @Override\n" + - " @NonNull\n" + - " public TestItem mapFromCursor(@NonNull Cursor cursor) {\n" + - " TestItem object = new TestItem();\n" + - "\n" + - " object.field1 = cursor.getInt(cursor.getColumnIndex(\"column1\")) == 1;\n" + - " object.field2 = cursor.getString(cursor.getColumnIndex(\"column2\"));\n" + - "\n" + - " return object;\n" + - " }\n" + - "}\n"); + " * {@inheritDoc}\n" + + " */\n" + + " @Override\n" + + " @NonNull\n" + + " public TestItem mapFromCursor(@NonNull Cursor cursor) {\n" + + " TestItem object = new TestItem();\n" + + "\n" + + " object.field1 = cursor.getInt(cursor.getColumnIndex(\"column1\")) == 1;\n" + + " if(!cursor.isNull(cursor.getColumnIndex(\"column2\"))) {\n" + + " object.field2 = cursor.getInt(cursor.getColumnIndex(\"column2\"));\n" + + " }\n" + + "\n" + + " return object;\n" + + " }\n" + ); + } + + private void checkFile( + @NotNull String actualFile, + @NotNull String partPackage, + @NotNull String partImport, + @NotNull String partClass, + @NotNull String partMapFromCursor + ) { + assertThat(actualFile).isEqualTo( + partPackage + + partImport + + partClass + + partMapFromCursor + + "}\n"); } } diff --git a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGeneratorTest.java b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGeneratorTest.java index 7f675ff5e..d5766f7fe 100644 --- a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGeneratorTest.java +++ b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/PutResolverGeneratorTest.java @@ -1,6 +1,5 @@ package com.pushtorefresh.storio.sqlite.annotations.processor.generate; -import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn; import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType; import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteColumnMeta; import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteTypeMeta; @@ -11,10 +10,8 @@ import java.io.IOException; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - +import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.TestFactory.createColumnMetaMock; +import static com.pushtorefresh.storio.sqlite.annotations.processor.generate.TestFactory.createElementMock; import static javax.lang.model.type.TypeKind.INT; import static javax.lang.model.type.TypeKind.NONE; import static org.assertj.core.api.Assertions.assertThat; @@ -102,7 +99,8 @@ public void generateJavaFile() throws IOException { "column1", "column1Field", true, // key - false); + false, + null); storIOSQLiteTypeMeta.columns.put("column1", storIOSQLiteColumnMeta1); final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta2 = createColumnMetaMock( @@ -110,7 +108,8 @@ public void generateJavaFile() throws IOException { "column2", "column2Field", false, - false); + false, + null); storIOSQLiteTypeMeta.columns.put("column2", storIOSQLiteColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -140,7 +139,8 @@ public void ignoreNullsShouldAddCheckIfObject() throws IOException { "column1", "column1Field", true, - false); + false, + null); storIOSQLiteTypeMeta.columns.put("column1", storIOSQLiteColumnMeta1); final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta2 = createColumnMetaMock( @@ -148,7 +148,8 @@ public void ignoreNullsShouldAddCheckIfObject() throws IOException { "column2", "column2Field", false, - true); // ignore nulls + true, + null); // ignore nulls storIOSQLiteTypeMeta.columns.put("column2", storIOSQLiteColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -192,7 +193,8 @@ public void ignoreNullsShouldNotAddCheckIfPrimitive() throws IOException { "column1", "column1Field", true, - false); + false, + null); storIOSQLiteTypeMeta.columns.put("column1", storIOSQLiteColumnMeta1); final StorIOSQLiteColumnMeta storIOSQLiteColumnMeta2 = createColumnMetaMock( @@ -200,7 +202,8 @@ public void ignoreNullsShouldNotAddCheckIfPrimitive() throws IOException { "column2", "column2Field", false, - true); // ignore nulls + true, + null); // ignore nulls storIOSQLiteTypeMeta.columns.put("column2", storIOSQLiteColumnMeta2); final PutResolverGenerator putResolverGenerator = new PutResolverGenerator(); @@ -217,38 +220,6 @@ public void ignoreNullsShouldNotAddCheckIfPrimitive() throws IOException { PART_MAP_TO_CONTENT_VALUES_WITHOUT_NULL_CHECK); // Without check } - @NotNull - private static Element createElementMock(@NotNull TypeKind typeKind) { - final Element objectElement = mock(Element.class); - final TypeMirror typeMirror = mock(TypeMirror.class); - when(objectElement.asType()).thenReturn(typeMirror); - when(typeMirror.getKind()).thenReturn(typeKind); - return objectElement; - } - - @NotNull - private static StorIOSQLiteColumnMeta createColumnMetaMock( - @NotNull Element element, - @NotNull String columnName, - @NotNull String fieldName, - boolean isKey, - boolean ignoreNull - ) { - final StorIOSQLiteColumn storIOSQLiteColumn = mock(StorIOSQLiteColumn.class); - when(storIOSQLiteColumn.name()).thenReturn(columnName); - when(storIOSQLiteColumn.key()).thenReturn(isKey); - when(storIOSQLiteColumn.ignoreNull()).thenReturn(ignoreNull); - - //noinspection ConstantConditions - return new StorIOSQLiteColumnMeta( - null, - element, - fieldName, - null, - storIOSQLiteColumn - ); - } - private void checkFile( @NotNull String actualFile, @NotNull String partPackage, diff --git a/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/TestFactory.java b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/TestFactory.java new file mode 100644 index 000000000..0f0583069 --- /dev/null +++ b/storio-sqlite-annotations-processor/src/test/java/com/pushtorefresh/storio/sqlite/annotations/processor/generate/TestFactory.java @@ -0,0 +1,55 @@ +package com.pushtorefresh.storio.sqlite.annotations.processor.generate; + +import com.pushtorefresh.storio.common.annotations.processor.introspection.JavaType; +import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn; +import com.pushtorefresh.storio.sqlite.annotations.processor.introspection.StorIOSQLiteColumnMeta; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public final class TestFactory { + + private TestFactory() { + throw new IllegalStateException("No instances please"); + } + + @NotNull + public static Element createElementMock(@NotNull TypeKind typeKind) { + final Element objectElement = mock(Element.class); + final TypeMirror typeMirror = mock(TypeMirror.class); + when(objectElement.asType()).thenReturn(typeMirror); + when(typeMirror.getKind()).thenReturn(typeKind); + return objectElement; + } + + @NotNull + public static StorIOSQLiteColumnMeta createColumnMetaMock( + @NotNull Element element, + @NotNull String columnName, + @NotNull String fieldName, + boolean isKey, + boolean ignoreNull, + @Nullable JavaType javaType) { + + final StorIOSQLiteColumn storIOSQLiteColumn = mock(StorIOSQLiteColumn.class); + when(storIOSQLiteColumn.name()).thenReturn(columnName); + when(storIOSQLiteColumn.key()).thenReturn(isKey); + when(storIOSQLiteColumn.ignoreNull()).thenReturn(ignoreNull); + + //noinspection ConstantConditions + return new StorIOSQLiteColumnMeta( + null, + element, + fieldName, + javaType, + storIOSQLiteColumn + ); + } +} diff --git a/storio-sqlite-annotations/src/main/java/com/pushtorefresh/storio/sqlite/annotations/StorIOSQLiteColumn.java b/storio-sqlite-annotations/src/main/java/com/pushtorefresh/storio/sqlite/annotations/StorIOSQLiteColumn.java index 7f32ed40a..5c80319a1 100644 --- a/storio-sqlite-annotations/src/main/java/com/pushtorefresh/storio/sqlite/annotations/StorIOSQLiteColumn.java +++ b/storio-sqlite-annotations/src/main/java/com/pushtorefresh/storio/sqlite/annotations/StorIOSQLiteColumn.java @@ -28,7 +28,7 @@ boolean key() default false; /** - * Optional: indicates that field should not be serialized when it hasn't value + * Optional: indicates that field should not be serialized when it's value is {@code null} * * @return true if column with {@code null} value should be ignored, false otherwise */ diff --git a/storio-sqlite/src/test/java/com/pushtorefresh/storio/sqlite/impl/DefaultStorIOSQLiteTest.java b/storio-sqlite/src/test/java/com/pushtorefresh/storio/sqlite/impl/DefaultStorIOSQLiteTest.java index fc312174f..0de3a04d8 100644 --- a/storio-sqlite/src/test/java/com/pushtorefresh/storio/sqlite/impl/DefaultStorIOSQLiteTest.java +++ b/storio-sqlite/src/test/java/com/pushtorefresh/storio/sqlite/impl/DefaultStorIOSQLiteTest.java @@ -521,6 +521,15 @@ public void deprecatedInternalImplShouldReturnSentToConstructorTypeMapping() thr assertThat(storIOSQLite.typeMappingFinder()).isSameAs(typeMappingFinder); } + @Test + public void internalShouldReturnLowLevel() { + DefaultStorIOSQLite storIOSQLite = DefaultStorIOSQLite.builder() + .sqliteOpenHelper(mock(SQLiteOpenHelper.class)) + .build(); + + assertThat(storIOSQLite.internal()).isSameAs(storIOSQLite.lowLevel()); + } + static class ClassEntity { }