From 596ea093bae16cf1b8323e9b91572cd6a1d73020 Mon Sep 17 00:00:00 2001 From: John Blum Date: Sat, 9 May 2020 20:53:39 -0700 Subject: [PATCH] Add null-safe asType(:Object, :Class) method to cast the given source Object into an instance of the given Class type. --- .../geode/core/util/ObjectUtils.java | 42 +++++- .../geode/core/util/ObjectUtilsUnitTests.java | 129 +++++++++++++++++- 2 files changed, 165 insertions(+), 6 deletions(-) diff --git a/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java b/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java index 6570481f..da17bd29 100644 --- a/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java +++ b/spring-geode/src/main/java/org/springframework/geode/core/util/ObjectUtils.java @@ -13,7 +13,6 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ - package org.springframework.geode.core.util; import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray; @@ -29,19 +28,25 @@ import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.geode.pdx.PdxInstance; + import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * The {@link ObjectUtils} class is an abstract utility class with operations for {@link Object objects}. * * @author John Blum * @see java.lang.Object + * @see java.lang.reflect.Constructor + * @see java.lang.reflect.Field * @see java.lang.reflect.Method + * @see org.apache.geode.pdx.PdxInstance * @since 1.0.0 */ @SuppressWarnings("all") @@ -49,6 +54,37 @@ public abstract class ObjectUtils extends org.springframework.util.ObjectUtils { private static final Logger logger = LoggerFactory.getLogger(ObjectUtils.class); + /** + * Tries to cast the given source {@link Object} into an instance of the given {@link Class} type. + * + * This method is cable of handling GemFire/Geode {@link PdxInstance} types. + * + * @param desired {@link Class type} of the source {@link Object}. + * @param source {@link Object} to evaluate. + * @param type desired target {@link Class} type; must not be {@literal null}. + * @return the source {@link Object} cast to an instance of the given {@link Class} type. + * @throws IllegalArgumentException if the source {@link Object} is not an instance of + * the given {@link Class} type or the {@link Class} type is {@literal null}. + * @see org.apache.geode.pdx.PdxInstance + * @see java.lang.Class + * @see java.lang.Object + */ + public static @Nullable T asType(@Nullable Object source, @NonNull Class type) { + + Assert.notNull(type, "Class type must not be null"); + + Object target = source instanceof PdxInstance + ? ((PdxInstance) source).getObject() + : source; + + return target == null ? null + : Optional.of(target) + .filter(type::isInstance) + .map(type::cast) + .orElseThrow(() -> newIllegalArgumentException("Object [%s] is not an instance of type [%s]", + nullSafeClassName(target), type.getName())); + } + /** * Executes the given {@link ExceptionThrowingOperation} handling any checked {@link Exception} thrown during * the normal execution of the operation by rethrowing an {@link IllegalStateException} wrapping diff --git a/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java index d91d64a8..9f8de780 100644 --- a/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java +++ b/spring-geode/src/test/java/org/springframework/geode/core/util/ObjectUtilsUnitTests.java @@ -13,10 +13,13 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ - package org.springframework.geode.core.util; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newRuntimeException; import java.lang.reflect.Field; @@ -25,16 +28,129 @@ import java.util.Optional; import org.junit.Test; +import org.apache.geode.pdx.PdxInstance; + /** - * Unit tests for {@link ObjectUtils}. + * Unit Tests for {@link ObjectUtils}. * * @author John Blum + * @see java.lang.reflect.Field + * @see java.lang.reflect.Method * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.apache.geode.pdx.PdxInstance * @see org.springframework.geode.core.util.ObjectUtils * @since 1.0.0 */ public class ObjectUtilsUnitTests { + @Test + public void asMatchingBaseType() { + + B object = new B(); + A value = ObjectUtils.asType(object, A.class); + + assertThat(value).isNotNull(); + assertThat(value).isSameAs(object); + } + + @Test + public void asMatchingSubType() { + + A object = new B(); + B value = ObjectUtils.asType(object, B.class); + + assertThat(value).isNotNull(); + assertThat(value).isSameAs(object); + } + + @Test(expected = IllegalArgumentException.class) + public void asNonMatchingType() { + + Object object = new B(); + + try { + ObjectUtils.asType(object, C.class); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Object [%s] is not an instance of type [%s]", + object.getClass().getName(), C.class.getName()); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void asPdxInstanceType() { + + PdxInstance mockPdxInstance = mock(PdxInstance.class); + + doReturn(new B()).when(mockPdxInstance).getObject(); + + assertThat(ObjectUtils.asType(mockPdxInstance, B.class)).isInstanceOf(B.class); + + verify(mockPdxInstance, times(1)).getObject(); + } + + @Test(expected = IllegalArgumentException.class) + public void asPdxInstanceTypeReturnNonMatchingType() { + + C source = new C(); + + PdxInstance mockPdxInstance = mock(PdxInstance.class); + + doReturn(source).when(mockPdxInstance).getObject(); + + try { + ObjectUtils.asType(mockPdxInstance, A.class); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Object [%s] is not an instance of type [%s]", + source.getClass().getName(), A.class.getName()); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(mockPdxInstance, times(1)).getObject(); + } + } + + @Test + public void asPdxInstanceTypeReturningNull() { + + PdxInstance mockPdxInstance = mock(PdxInstance.class); + + doReturn(null).when(mockPdxInstance).getObject(); + + assertThat(ObjectUtils.asType(mockPdxInstance, A.class)).isNull(); + + verify(mockPdxInstance, times(1)).getObject(); + } + + @Test + public void asTypeWithNullSource() { + assertThat(ObjectUtils.asType(null, A.class)).isNull(); + } + + @Test(expected = IllegalArgumentException.class) + public void asTypeWithNullType() { + + try { + ObjectUtils.asType(new A(), null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessage("Class type must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + @Test public void doOperationSafelyReturnsResult() { assertThat(ObjectUtils.doOperationSafely(() -> "test")).isEqualTo("test"); @@ -135,7 +251,7 @@ public class ObjectUtilsUnitTests { } @Test(expected = IllegalArgumentException.class) - public void getFieldWithNullFieldThrowsIllegalArgumentException() throws Exception { + public void getFieldWithNullFieldThrowsIllegalArgumentException() { try { ObjectUtils.get(new TestObject(), (Field) null); @@ -265,4 +381,11 @@ public class ObjectUtilsUnitTests { return "TEST"; } } + + static class A { } + + static class B extends A { } + + static class C { } + }