From 71798128d3272e191a10a367b1e7b57d0e2f5851 Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Mon, 8 Dec 2014 12:22:32 +0100 Subject: [PATCH 1/5] - new PrimitiveArrayDiffer - allow using Field properties of bean properties --- .../diff/access/PropertyAwareAccessor.java | 4 + .../comparison/ArrayComparisonStrategy.java | 73 +++++++++++++++++++ .../diff/differ/PrimitiveArrayDiffer.java | 42 +++++++++++ .../differ/PrimitiveArrayDifferFactory.java | 35 +++++++++ .../diff/introspection/PropertyAccessor.java | 31 ++++++-- .../introspection/StandardIntrospector.java | 35 +++++++-- .../de/danielbechler/diff/node/DiffNode.java | 36 ++++++--- 7 files changed, 232 insertions(+), 24 deletions(-) create mode 100644 src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java create mode 100644 src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDiffer.java create mode 100644 src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java diff --git a/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java b/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java index 6eaa6898..0ef97f4f 100644 --- a/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java +++ b/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java @@ -29,4 +29,8 @@ public interface PropertyAwareAccessor extends TypeAwareAccessor, CategoryAware, Set getReadMethodAnnotations(); T getReadMethodAnnotation(Class annotationClass); + + T getAnnotation(Class annotationClass); + + int getFieldModifiers(); } diff --git a/src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java b/src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java new file mode 100644 index 00000000..674253e8 --- /dev/null +++ b/src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java @@ -0,0 +1,73 @@ +/* + ******************************************************************************* + * L O G I T A G S + * Software and Programming + * Dr. Wolfgang Winter + * Germany + * + * All rights reserved + * + * Copyright 2014 Dr. Wolfgang Winter + * + * Licensed 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 + * + * http://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 de.danielbechler.diff.comparison; + +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.danielbechler.diff.node.DiffNode; +import de.danielbechler.diff.node.DiffNode.State; + +public class ArrayComparisonStrategy implements ComparisonStrategy { + + private static final Logger log = LoggerFactory.getLogger(ArrayComparisonStrategy.class); + + public void compare(final DiffNode node, final Class type, final Object working, final Object base) { + if (type == null || !type.isArray()) { + log.warn("Failed to apply ArrayComparisonStrategy: Class is of type " + type); + return; + } + + boolean isEqual = true; + if (type.getComponentType() == int.class) { + isEqual = Arrays.equals((int[]) working, (int[]) base); + } else if (type.getComponentType() == short.class) { + isEqual = Arrays.equals((short[]) working, (short[]) base); + } else if (type.getComponentType() == long.class) { + isEqual = Arrays.equals((long[]) working, (long[]) base); + } else if (type.getComponentType() == byte.class) { + isEqual = Arrays.equals((byte[]) working, (byte[]) base); + } else if (type.getComponentType() == boolean.class) { + isEqual = Arrays.equals((boolean[]) working, (boolean[]) base); + } else if (type.getComponentType() == char.class) { + isEqual = Arrays.equals((char[]) working, (char[]) base); + } else if (type.getComponentType() == double.class) { + isEqual = Arrays.equals((double[]) working, (double[]) base); + } else if (type.getComponentType() == float.class) { + isEqual = Arrays.equals((float[]) working, (float[]) base); + } else if (type.getComponentType() == Object.class) { + isEqual = Arrays.equals((Object[]) working, (Object[]) base); + } + + if (isEqual) { + node.setState(State.UNTOUCHED); + } else { + node.setState(State.CHANGED); + } + } + +} diff --git a/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDiffer.java b/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDiffer.java new file mode 100644 index 00000000..aecde108 --- /dev/null +++ b/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDiffer.java @@ -0,0 +1,42 @@ +package de.danielbechler.diff.differ; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.danielbechler.diff.access.Instances; +import de.danielbechler.diff.comparison.ArrayComparisonStrategy; +import de.danielbechler.diff.comparison.ComparisonStrategy; +import de.danielbechler.diff.node.DiffNode; + +/** + * Differ that allows comparing arrays with primitive elements in an + * all-or-nothing different strategy. No nodes on the array element level are + * created. + * + */ +public class PrimitiveArrayDiffer implements Differ { + + private static final Logger log = LoggerFactory.getLogger(PrimitiveArrayDiffer.class); + + public boolean accepts(Class type) { + return type != null && type.isArray() && type.getComponentType().isPrimitive(); + } + + public DiffNode compare(DiffNode parentNode, Instances instances) { + final DiffNode beanNode = new DiffNode(parentNode, instances.getSourceAccessor(), instances.getType()); + log.debug("PrimitiveArrayDiffer compare node " + beanNode); + + if (instances.areNull() || instances.areSame()) { + beanNode.setState(DiffNode.State.UNTOUCHED); + } else if (instances.hasBeenAdded()) { + beanNode.setState(DiffNode.State.ADDED); + } else if (instances.hasBeenRemoved()) { + beanNode.setState(DiffNode.State.REMOVED); + } else { + ComparisonStrategy strategy = new ArrayComparisonStrategy(); + strategy.compare(beanNode, instances.getType(), instances.getWorking(), instances.getBase()); + } + return beanNode; + } + +} diff --git a/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java b/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java new file mode 100644 index 00000000..4241f5ba --- /dev/null +++ b/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java @@ -0,0 +1,35 @@ +/* + ******************************************************************************* + * L O G I T A G S + * Software and Programming + * Dr. Wolfgang Winter + * Germany + * + * All rights reserved + * + * Copyright 2014 Dr. Wolfgang Winter + * + * Licensed 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 + * + * http://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 de.danielbechler.diff.differ; + +import de.danielbechler.diff.NodeQueryService; + +public class PrimitiveArrayDifferFactory implements DifferFactory { + + public Differ createDiffer(DifferDispatcher differDispatcher, NodeQueryService nodeQueryService) { + return new PrimitiveArrayDiffer(); + } + +} diff --git a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java index 176ea878..d7886afc 100644 --- a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java +++ b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java @@ -16,13 +16,10 @@ package de.danielbechler.diff.introspection; -import de.danielbechler.diff.access.PropertyAwareAccessor; -import de.danielbechler.diff.selector.BeanPropertyElementSelector; -import de.danielbechler.util.Assert; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static java.util.Arrays.asList; import java.lang.annotation.Annotation; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; @@ -31,7 +28,12 @@ import java.util.Set; import java.util.TreeSet; -import static java.util.Arrays.asList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.danielbechler.diff.access.PropertyAwareAccessor; +import de.danielbechler.diff.selector.BeanPropertyElementSelector; +import de.danielbechler.util.Assert; public class PropertyAccessor implements PropertyAwareAccessor { @@ -41,12 +43,14 @@ public class PropertyAccessor implements PropertyAwareAccessor private final Class type; private final Method readMethod; private final Method writeMethod; + private final Field field; - public PropertyAccessor(final String propertyName, final Method readMethod, final Method writeMethod) + public PropertyAccessor(final String propertyName, final Field f, final Method readMethod, final Method writeMethod) { Assert.notNull(propertyName, "propertyName"); Assert.notNull(readMethod, "readMethod"); this.propertyName = propertyName; + this.field = f; this.readMethod = makeAccessible(readMethod); this.writeMethod = makeAccessible(writeMethod); this.type = this.readMethod.getReturnType(); @@ -238,4 +242,17 @@ public String toString() sb.append('}'); return sb.toString(); } + + public T getAnnotation(Class annotationClass) { + T ann = getReadMethodAnnotation(annotationClass); + if (ann != null) { + return ann; + } + return field.getAnnotation(annotationClass); + } + + public int getFieldModifiers() { + return field.getModifiers(); + } + } diff --git a/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java b/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java index 21c3399a..446da213 100644 --- a/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java +++ b/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java @@ -16,17 +16,21 @@ package de.danielbechler.diff.introspection; -import de.danielbechler.diff.access.PropertyAwareAccessor; -import de.danielbechler.diff.instantiation.TypeInfo; -import de.danielbechler.util.Assert; -import de.danielbechler.util.Exceptions; - import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; +import java.lang.reflect.Field; import java.lang.reflect.Method; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.danielbechler.diff.access.PropertyAwareAccessor; +import de.danielbechler.diff.instantiation.TypeInfo; +import de.danielbechler.util.Assert; +import de.danielbechler.util.Exceptions; + /** * Resolves the accessors of a given type by using the standard Java {@link Introspector}. * @@ -34,6 +38,9 @@ */ public class StandardIntrospector implements de.danielbechler.diff.introspection.Introspector { + + private static final Logger log = LoggerFactory.getLogger(StandardIntrospector.class); + public TypeInfo introspect(final Class type) { Assert.notNull(type, "type"); @@ -58,9 +65,10 @@ private TypeInfo internalIntrospect(final Class type) throws IntrospectionExc continue; } final String propertyName = descriptor.getName(); + Field field = findField(type, propertyName); final Method readMethod = descriptor.getReadMethod(); final Method writeMethod = descriptor.getWriteMethod(); - final PropertyAwareAccessor accessor = new PropertyAccessor(propertyName, readMethod, writeMethod); + final PropertyAwareAccessor accessor = new PropertyAccessor(propertyName, field, readMethod, writeMethod); typeInfo.addPropertyAccessor(accessor); } return typeInfo; @@ -87,4 +95,19 @@ private static boolean shouldSkip(final PropertyDescriptor descriptor) } return false; } + + private Field findField(final Class type, final String propertyName) throws IntrospectionException { + String fieldName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); + Class clazz = type; + while (clazz != null) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + log.debug("No such field: " + e.getMessage()); + } + clazz = clazz.getSuperclass(); + } + throw new IntrospectionException("No such field " + fieldName + " in " + type + " class hierarchy"); + } + } diff --git a/src/main/java/de/danielbechler/diff/node/DiffNode.java b/src/main/java/de/danielbechler/diff/node/DiffNode.java index cd7dd638..0e3fa005 100644 --- a/src/main/java/de/danielbechler/diff/node/DiffNode.java +++ b/src/main/java/de/danielbechler/diff/node/DiffNode.java @@ -16,6 +16,17 @@ package de.danielbechler.diff.node; +import static java.util.Collections.unmodifiableSet; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicBoolean; + import de.danielbechler.diff.access.Accessor; import de.danielbechler.diff.access.CategoryAware; import de.danielbechler.diff.access.ExclusionAware; @@ -29,17 +40,6 @@ import de.danielbechler.diff.selector.RootElementSelector; import de.danielbechler.util.Assert; -import java.lang.annotation.Annotation; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.atomic.AtomicBoolean; - -import static java.util.Collections.unmodifiableSet; - /** * Represents a part of an object. It could be the object itself, one of its properties, an item in a * collection or a map entry. A node may have one parent node and any number of children. It also provides @@ -468,6 +468,20 @@ public T getPropertyAnnotation(final Class annotationC return null; } + public T getAnnotation(final Class annotationClass) { + if (accessor instanceof PropertyAwareAccessor) { + return ((PropertyAwareAccessor) accessor).getAnnotation(annotationClass); + } + return null; + } + + public int getFieldModifiers() { + if (accessor instanceof PropertyAwareAccessor) { + return ((PropertyAwareAccessor) accessor).getFieldModifiers(); + } + return 0; + } + /** * If this node represents a bean property, this method will simply return its name. Otherwise it will return the * property name of its closest bean property representing ancestor. This way intermediate nodes like those From 8276da7f44b1a8659def7bf965aa927dfcd6e0a8 Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Mon, 8 Dec 2014 12:44:12 +0100 Subject: [PATCH 2/5] header removed --- .../comparison/ArrayComparisonStrategy.java | 24 ------------------- .../differ/PrimitiveArrayDifferFactory.java | 24 ------------------- 2 files changed, 48 deletions(-) diff --git a/src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java b/src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java index 674253e8..2563b31a 100644 --- a/src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java +++ b/src/main/java/de/danielbechler/diff/comparison/ArrayComparisonStrategy.java @@ -1,27 +1,3 @@ -/* - ******************************************************************************* - * L O G I T A G S - * Software and Programming - * Dr. Wolfgang Winter - * Germany - * - * All rights reserved - * - * Copyright 2014 Dr. Wolfgang Winter - * - * Licensed 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 - * - * http://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 de.danielbechler.diff.comparison; import java.util.Arrays; diff --git a/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java b/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java index 4241f5ba..36fbe762 100644 --- a/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java +++ b/src/main/java/de/danielbechler/diff/differ/PrimitiveArrayDifferFactory.java @@ -1,27 +1,3 @@ -/* - ******************************************************************************* - * L O G I T A G S - * Software and Programming - * Dr. Wolfgang Winter - * Germany - * - * All rights reserved - * - * Copyright 2014 Dr. Wolfgang Winter - * - * Licensed 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 - * - * http://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 de.danielbechler.diff.differ; import de.danielbechler.diff.NodeQueryService; From e3f07af21095a918d4c155f1e4b0d129c7aec70c Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Thu, 11 Dec 2014 19:00:54 +0100 Subject: [PATCH 3/5] tests fixed --- .../diff/access/InstancesTest.java | 194 ++++++++---------- .../PropertyAccessorBuilder.java | 115 +++++------ .../PropertyAccessorCollectionTest.groovy | 2 +- .../PropertyAccessorMapTest.groovy | 2 +- .../introspection/PropertyAccessorTest.groovy | 2 +- 5 files changed, 142 insertions(+), 173 deletions(-) diff --git a/src/test/java/de/danielbechler/diff/access/InstancesTest.java b/src/test/java/de/danielbechler/diff/access/InstancesTest.java index 794fd32f..5fa6106d 100644 --- a/src/test/java/de/danielbechler/diff/access/InstancesTest.java +++ b/src/test/java/de/danielbechler/diff/access/InstancesTest.java @@ -16,116 +16,102 @@ package de.danielbechler.diff.access; -import de.danielbechler.diff.introspection.PropertyAccessor; -import org.testng.annotations.Test; - -import java.lang.reflect.Method; - import static de.danielbechler.diff.helper.MockitoExtensions.returnClass; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.lang.reflect.Method; + +import org.testng.annotations.Test; + +import de.danielbechler.diff.introspection.PropertyAccessor; + /** * @author Daniel Bechler */ -public class InstancesTest -{ - private static TypeAwareAccessor mockTypeAwareAccessorOfType(final Class clazz) - { - final TypeAwareAccessor typeAwareAccessor = mock(TypeAwareAccessor.class); - when(typeAwareAccessor.getType()).then(returnClass(clazz)); - return typeAwareAccessor; - } - - @Test - public void testAreNull_returns_true_when_base_and_working_are_null() - { - final Instances instances = new Instances(RootAccessor.getInstance(), null, null, null); - assertThat(instances.areNull()).isTrue(); - } - - @Test - public void testAreNull_returns_false_when_base_is_not_null() - { - final Instances instances = new Instances(RootAccessor.getInstance(), null, "", null); - assertThat(instances.areNull()).isFalse(); - } - - @Test - public void testAreNull_returns_false_when_working_is_not_null() - { - final Instances instances = new Instances(RootAccessor.getInstance(), "", null, null); - assertThat(instances.areNull()).isFalse(); - } - - @Test - public void testIsPrimitiveTypeReturnsTrueForPrimitiveType() - { - final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(long.class); - assertThat(new Instances(typeAwareAccessor, 1L, 2L, 0L).isPrimitiveType()).isTrue(); - } - - @Test - public void testIsPrimitiveTypeReturnsFalseForPrimitiveWrapperType() - { - final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(Long.class); - assertThat(new Instances(typeAwareAccessor, 1L, 2L, 0L).isPrimitiveType()).isFalse(); - } - - @Test - public void testGetFreshReturnsZeroForPrimitiveNumericTypeIfUndefined() throws Exception - { - final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(long.class); - assertThat(new Instances(typeAwareAccessor, 0, 0, null).getFresh()).isEqualTo(0); - } - - @Test - public void testGetFreshReturnsDefinedDefaultValueForPrimitiveNumericType() throws Exception - { - final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(long.class); - assertThat(new Instances(typeAwareAccessor, 0, 0, 1337).getFresh()).isEqualTo(1337); - } - - @Test - public void testGetFreshReturnsZeroForPrimitiveBooleanTypeIfUndefined() throws Exception - { - final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(boolean.class); - assertThat(new Instances(typeAwareAccessor, true, true, null).getFresh()).isEqualTo(false); - } - - @Test - public void testGetFreshReturnsNullForDefaultLessPrimitiveWrapperType() throws Exception - { - final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(Long.class); - assertThat(new Instances(typeAwareAccessor, 0L, 0L, null).getFresh()).isNull(); - } - - @Test - public void testGetFreshReturnsDefinedDefaultValueForPrimitiveBooleanType() throws Exception - { - final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(boolean.class); - assertThat(new Instances(typeAwareAccessor, true, true, true).getFresh()).isEqualTo(true); - } - - @Test - public void testIsPrimitiveTypeReturnsPrimitiveClassForPrimitiveType() throws Exception - { - final Method readMethod = getClass().getDeclaredMethod("getTestValue"); - final PropertyAccessor accessor = new PropertyAccessor("testValue", readMethod, null); - final Instances instances = new Instances(accessor, 1L, 2L, 0L); - assertThat(instances.getType() == long.class).isTrue(); - } - - @Test - public void testIsPrimitiveTypeReturnsFalseForComplexType() - { - assertThat(new Instances(RootAccessor.getInstance(), "1", "2", null).isPrimitiveType()).isFalse(); - } - - @SuppressWarnings({"MethodMayBeStatic", "UnusedDeclaration"}) - public long getTestValue() - { - return 0L; - } +public class InstancesTest { + private static TypeAwareAccessor mockTypeAwareAccessorOfType(final Class clazz) { + final TypeAwareAccessor typeAwareAccessor = mock(TypeAwareAccessor.class); + when(typeAwareAccessor.getType()).then(returnClass(clazz)); + return typeAwareAccessor; + } + + @Test + public void testAreNull_returns_true_when_base_and_working_are_null() { + final Instances instances = new Instances(RootAccessor.getInstance(), null, null, null); + assertThat(instances.areNull()).isTrue(); + } + + @Test + public void testAreNull_returns_false_when_base_is_not_null() { + final Instances instances = new Instances(RootAccessor.getInstance(), null, "", null); + assertThat(instances.areNull()).isFalse(); + } + + @Test + public void testAreNull_returns_false_when_working_is_not_null() { + final Instances instances = new Instances(RootAccessor.getInstance(), "", null, null); + assertThat(instances.areNull()).isFalse(); + } + + @Test + public void testIsPrimitiveTypeReturnsTrueForPrimitiveType() { + final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(long.class); + assertThat(new Instances(typeAwareAccessor, 1L, 2L, 0L).isPrimitiveType()).isTrue(); + } + + @Test + public void testIsPrimitiveTypeReturnsFalseForPrimitiveWrapperType() { + final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(Long.class); + assertThat(new Instances(typeAwareAccessor, 1L, 2L, 0L).isPrimitiveType()).isFalse(); + } + + @Test + public void testGetFreshReturnsZeroForPrimitiveNumericTypeIfUndefined() throws Exception { + final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(long.class); + assertThat(new Instances(typeAwareAccessor, 0, 0, null).getFresh()).isEqualTo(0); + } + + @Test + public void testGetFreshReturnsDefinedDefaultValueForPrimitiveNumericType() throws Exception { + final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(long.class); + assertThat(new Instances(typeAwareAccessor, 0, 0, 1337).getFresh()).isEqualTo(1337); + } + + @Test + public void testGetFreshReturnsZeroForPrimitiveBooleanTypeIfUndefined() throws Exception { + final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(boolean.class); + assertThat(new Instances(typeAwareAccessor, true, true, null).getFresh()).isEqualTo(false); + } + + @Test + public void testGetFreshReturnsNullForDefaultLessPrimitiveWrapperType() throws Exception { + final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(Long.class); + assertThat(new Instances(typeAwareAccessor, 0L, 0L, null).getFresh()).isNull(); + } + + @Test + public void testGetFreshReturnsDefinedDefaultValueForPrimitiveBooleanType() throws Exception { + final TypeAwareAccessor typeAwareAccessor = mockTypeAwareAccessorOfType(boolean.class); + assertThat(new Instances(typeAwareAccessor, true, true, true).getFresh()).isEqualTo(true); + } + + @Test + public void testIsPrimitiveTypeReturnsPrimitiveClassForPrimitiveType() throws Exception { + final Method readMethod = getClass().getDeclaredMethod("getTestValue"); + final PropertyAccessor accessor = new PropertyAccessor("testValue", null, readMethod, null); + final Instances instances = new Instances(accessor, 1L, 2L, 0L); + assertThat(instances.getType() == long.class).isTrue(); + } + + @Test + public void testIsPrimitiveTypeReturnsFalseForComplexType() { + assertThat(new Instances(RootAccessor.getInstance(), "1", "2", null).isPrimitiveType()).isFalse(); + } + + @SuppressWarnings({ "MethodMayBeStatic", "UnusedDeclaration" }) + public long getTestValue() { + return 0L; + } } diff --git a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorBuilder.java b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorBuilder.java index 68913d9f..f40aedba 100644 --- a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorBuilder.java +++ b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorBuilder.java @@ -21,79 +21,62 @@ /** * @author Daniel Bechler */ -final class PropertyAccessorBuilder -{ - private PropertyAccessorBuilder() - { - } +final class PropertyAccessorBuilder { + private PropertyAccessorBuilder() { + } - public static NameAndType forPropertyOf(final Class targetType) - { - final Builder builder = new Builder(); - builder.targetType = targetType; - return builder; - } + public static NameAndType forPropertyOf(final Class targetType) { + final Builder builder = new Builder(); + builder.targetType = targetType; + return builder; + } - public interface Buildable - { - PropertyAccessor build(); - } + public interface Buildable { + PropertyAccessor build(); + } - public interface NameAndType - { - ReadOnly property(String name, Class type); - } + public interface NameAndType { + ReadOnly property(String name, Class type); + } - public interface ReadOnly extends Buildable - { - Buildable readOnly(boolean readOnly); - } + public interface ReadOnly extends Buildable { + Buildable readOnly(boolean readOnly); + } - public static class Builder implements NameAndType, ReadOnly, Buildable - { - private String propertyName; - private Class propertyType; - private Class targetType; - private boolean readOnly; + public static class Builder implements NameAndType, ReadOnly, Buildable { + private String propertyName; + private Class propertyType; + private Class targetType; + private boolean readOnly; - public ReadOnly property(final String name, final Class type) - { - this.propertyName = name; - this.propertyType = type; - return this; - } + public ReadOnly property(final String name, final Class type) { + this.propertyName = name; + this.propertyType = type; + return this; + } - public Buildable readOnly(final boolean readOnly) - { - this.readOnly = readOnly; - return this; - } + public Buildable readOnly(final boolean readOnly) { + this.readOnly = readOnly; + return this; + } - public PropertyAccessor build() - { - try - { - final Method readMethod = targetType.getDeclaredMethod(name("get")); - final Method writeMethod; - if (readOnly) - { - writeMethod = null; - } - else - { - writeMethod = targetType.getDeclaredMethod(name("set"), propertyType); - } - return new PropertyAccessor(propertyName, readMethod, writeMethod); - } - catch (final NoSuchMethodException e) - { - throw new RuntimeException(e); - } - } + public PropertyAccessor build() { + try { + final Method readMethod = targetType.getDeclaredMethod(name("get")); + final Method writeMethod; + if (readOnly) { + writeMethod = null; + } else { + writeMethod = targetType.getDeclaredMethod(name("set"), propertyType); + } + return new PropertyAccessor(propertyName, null, readMethod, writeMethod); + } catch (final NoSuchMethodException e) { + throw new RuntimeException(e); + } + } - private String name(final String prefix) - { - return prefix + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); - } - } + private String name(final String prefix) { + return prefix + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); + } + } } diff --git a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorCollectionTest.groovy b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorCollectionTest.groovy index 8f192a0a..5e86b2fb 100644 --- a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorCollectionTest.groovy +++ b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorCollectionTest.groovy @@ -71,7 +71,7 @@ public class PropertyAccessorCollectionTest extends Specification { def PropertyAccessor createCollectionPropertyAccessor(boolean readOnly) { Method readMethod = ObjectWithCollection.getMethod("getCollection") Method writeMethod = readOnly ? null : ObjectWithCollection.getMethod("setCollection", Collection) - return new PropertyAccessor("collection", readMethod, writeMethod) + return new PropertyAccessor("collection", null, readMethod, writeMethod) } } diff --git a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorMapTest.groovy b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorMapTest.groovy index 383879ba..d24c316e 100644 --- a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorMapTest.groovy +++ b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorMapTest.groovy @@ -67,6 +67,6 @@ public class PropertyAccessorMapTest extends Specification { private static PropertyAccessor createMapPropertyAccessor(boolean readOnly) throws NoSuchMethodException { final Method readMethod = ObjectWithMap.class.getMethod("getMap") final Method writeMethod = readOnly ? null : ObjectWithMap.class.getMethod("setMap", Map.class) - return new PropertyAccessor("map", readMethod, writeMethod) + return new PropertyAccessor("map", null, readMethod, writeMethod) } } diff --git a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorTest.groovy b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorTest.groovy index 6f871882..68e3b9e5 100644 --- a/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorTest.groovy +++ b/src/test/java/de/danielbechler/diff/introspection/PropertyAccessorTest.groovy @@ -54,7 +54,7 @@ public class PropertyAccessorTest extends Specification { ObjectWithStringAndUnsupportedWriteMethod target = new ObjectWithStringAndUnsupportedWriteMethod("foo") Method readMethod = target.getClass().getMethod("getValue") Method writeMethod = target.getClass().getMethod("setValue", String) - propertyAccessor = new PropertyAccessor("value", readMethod, writeMethod) + propertyAccessor = new PropertyAccessor("value", null, readMethod, writeMethod) when: propertyAccessor.set(target, "bar") then: From 562ad3d78aa52cbb3b0b81faa266ff17f2879c85 Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Sat, 13 Dec 2014 16:06:46 +0100 Subject: [PATCH 4/5] fixed Exception for non JavaBean classes --- .../diff/introspection/PropertyAccessor.java | 330 ++++++++---------- .../introspection/StandardIntrospector.java | 100 +++--- 2 files changed, 186 insertions(+), 244 deletions(-) diff --git a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java index d7886afc..c841a81f 100644 --- a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java +++ b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java @@ -35,224 +35,176 @@ import de.danielbechler.diff.selector.BeanPropertyElementSelector; import de.danielbechler.util.Assert; -public class PropertyAccessor implements PropertyAwareAccessor -{ - private static final Logger logger = LoggerFactory.getLogger(PropertyAccessor.class); +public class PropertyAccessor implements PropertyAwareAccessor { + private static final Logger logger = LoggerFactory.getLogger(PropertyAccessor.class); - private final String propertyName; - private final Class type; - private final Method readMethod; - private final Method writeMethod; + private final String propertyName; + private final Class type; + private final Method readMethod; + private final Method writeMethod; private final Field field; - public PropertyAccessor(final String propertyName, final Field f, final Method readMethod, final Method writeMethod) - { - Assert.notNull(propertyName, "propertyName"); - Assert.notNull(readMethod, "readMethod"); - this.propertyName = propertyName; + public PropertyAccessor(final String propertyName, final Field f, final Method readMethod, final Method writeMethod) { + Assert.notNull(propertyName, "propertyName"); + Assert.notNull(readMethod, "readMethod"); + this.propertyName = propertyName; this.field = f; - this.readMethod = makeAccessible(readMethod); - this.writeMethod = makeAccessible(writeMethod); - this.type = this.readMethod.getReturnType(); - } + this.readMethod = makeAccessible(readMethod); + this.writeMethod = makeAccessible(writeMethod); + this.type = this.readMethod.getReturnType(); + } - private static Method makeAccessible(final Method method) - { - if (method != null && !method.isAccessible()) - { - logger.debug("Making method accessible: {}", method.toString()); - method.setAccessible(true); - } - return method; - } + private static Method makeAccessible(final Method method) { + if (method != null && !method.isAccessible()) { + logger.debug("Making method accessible: {}", method.toString()); + method.setAccessible(true); + } + return method; + } - public final Set getCategoriesFromAnnotation() - { - final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); - if (annotation != null) - { - return new TreeSet(asList(annotation.categories())); - } - return Collections.emptySet(); - } + public final Set getCategoriesFromAnnotation() { + final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); + if (annotation != null) { + return new TreeSet(asList(annotation.categories())); + } + return Collections.emptySet(); + } - public boolean isExcludedByAnnotation() - { - final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); - return annotation != null && annotation.excluded(); - } + public boolean isExcludedByAnnotation() { + final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); + return annotation != null && annotation.excluded(); + } - public String getPropertyName() - { - return this.propertyName; - } + public String getPropertyName() { + return this.propertyName; + } - /** - * @return The annotations of the getter used to access this property. - */ - public Set getReadMethodAnnotations() - { - return new LinkedHashSet(asList(readMethod.getAnnotations())); - } + /** + * @return The annotations of the getter used to access this property. + */ + public Set getReadMethodAnnotations() { + return new LinkedHashSet(asList(readMethod.getAnnotations())); + } - public T getReadMethodAnnotation(final Class annotationClass) - { - final Set annotations = getReadMethodAnnotations(); - assert (annotations != null) : "Something is wrong here. " + - "The contract of getReadAnnotations() guarantees a non-null return value."; - for (final Annotation annotation : annotations) - { - if (annotationClass.isAssignableFrom(annotation.annotationType())) - { - return annotationClass.cast(annotation); - } - } - return null; - } + public T getReadMethodAnnotation(final Class annotationClass) { + final Set annotations = getReadMethodAnnotations(); + assert (annotations != null) : "Something is wrong here. " + + "The contract of getReadAnnotations() guarantees a non-null return value."; + for (final Annotation annotation : annotations) { + if (annotationClass.isAssignableFrom(annotation.annotationType())) { + return annotationClass.cast(annotation); + } + } + return null; + } - public BeanPropertyElementSelector getElementSelector() - { - return new BeanPropertyElementSelector(this.propertyName); - } + public BeanPropertyElementSelector getElementSelector() { + return new BeanPropertyElementSelector(this.propertyName); + } - public Object get(final Object target) - { - if (target == null) - { - return null; - } - try - { - return readMethod.invoke(target); - } - catch (final Exception cause) - { - throw new PropertyReadException(propertyName, target.getClass(), cause); - } - } + public Object get(final Object target) { + if (target == null) { + return null; + } + try { + return readMethod.invoke(target); + } catch (final Exception cause) { + throw new PropertyReadException(propertyName, target.getClass(), cause); + } + } - public void set(final Object target, final Object value) - { - if (target == null) - { - logger.info("Couldn't set new value '{}' for property '{}' " + - "because the target object is null", value, propertyName); - } - else if (writeMethod == null) - { - logger.debug("No setter found for property '{}'", propertyName); - tryToReplaceContentOfCollectionTypes(target, value); - } - else - { - invokeWriteMethod(target, value); - } - } + public void set(final Object target, final Object value) { + if (target == null) { + logger.info("Couldn't set new value '{}' for property '{}' " + "because the target object is null", value, + propertyName); + } else if (writeMethod == null) { + logger.debug("No setter found for property '{}'", propertyName); + tryToReplaceContentOfCollectionTypes(target, value); + } else { + invokeWriteMethod(target, value); + } + } - public void unset(final Object target) - { - set(target, null); - } + public void unset(final Object target) { + set(target, null); + } - @SuppressWarnings("unchecked") - private void tryToReplaceContentOfCollectionTypes(final Object target, final Object value) - { - if (Collection.class.isAssignableFrom(readMethod.getReturnType())) - { - if (tryToReplaceCollectionContent((Collection) get(target), (Collection) value)) - { - return; - } - } - if (Map.class.isAssignableFrom(readMethod.getReturnType())) - { - if (tryToReplaceMapContent((Map) get(target), (Map) value)) - { - return; - } - } - logger.info("Couldn't set new value '{}' for property '{}'", value, propertyName); - } + @SuppressWarnings("unchecked") + private void tryToReplaceContentOfCollectionTypes(final Object target, final Object value) { + if (Collection.class.isAssignableFrom(readMethod.getReturnType())) { + if (tryToReplaceCollectionContent((Collection) get(target), (Collection) value)) { + return; + } + } + if (Map.class.isAssignableFrom(readMethod.getReturnType())) { + if (tryToReplaceMapContent((Map) get(target), (Map) value)) { + return; + } + } + logger.info("Couldn't set new value '{}' for property '{}'", value, propertyName); + } - private void invokeWriteMethod(final Object target, final Object value) - { - try - { - writeMethod.invoke(target, value); - } - catch (final Exception cause) - { - throw new PropertyWriteException(propertyName, target.getClass(), value, cause); - } - } + private void invokeWriteMethod(final Object target, final Object value) { + try { + writeMethod.invoke(target, value); + } catch (final Exception cause) { + throw new PropertyWriteException(propertyName, target.getClass(), value, cause); + } + } - private static boolean tryToReplaceCollectionContent(final Collection target, - final Collection value) - { - if (target == null) - { - return false; - } - try - { - target.clear(); - target.addAll(value); - return true; - } - catch (final Exception unmodifiable) - { - logger.debug("Failed to replace content of existing Collection", unmodifiable); - return false; - } - } + private static boolean tryToReplaceCollectionContent(final Collection target, final Collection value) { + if (target == null) { + return false; + } + try { + target.clear(); + target.addAll(value); + return true; + } catch (final Exception unmodifiable) { + logger.debug("Failed to replace content of existing Collection", unmodifiable); + return false; + } + } - private static boolean tryToReplaceMapContent(final Map target, - final Map value) - { - if (target == null) - { - return false; - } - try - { - target.clear(); - target.putAll(value); - return true; - } - catch (final Exception unmodifiable) - { - logger.debug("Failed to replace content of existing Map", unmodifiable); - return false; - } - } + private static boolean tryToReplaceMapContent(final Map target, final Map value) { + if (target == null) { + return false; + } + try { + target.clear(); + target.putAll(value); + return true; + } catch (final Exception unmodifiable) { + logger.debug("Failed to replace content of existing Map", unmodifiable); + return false; + } + } - public Class getType() - { - return this.type; - } + public Class getType() { + return this.type; + } - @Override - public String toString() - { - final StringBuilder sb = new StringBuilder("PropertyAccessor{"); - sb.append("propertyName='").append(propertyName).append('\''); - sb.append(", type=").append(type.getCanonicalName()); - sb.append(", source=").append(readMethod.getDeclaringClass().getCanonicalName()); - sb.append(", hasWriteMethod=").append(writeMethod != null); - sb.append('}'); - return sb.toString(); - } + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("PropertyAccessor{"); + sb.append("propertyName='").append(propertyName).append('\''); + sb.append(", type=").append(type.getCanonicalName()); + sb.append(", source=").append(readMethod.getDeclaringClass().getCanonicalName()); + sb.append(", hasWriteMethod=").append(writeMethod != null); + sb.append('}'); + return sb.toString(); + } public T getAnnotation(Class annotationClass) { T ann = getReadMethodAnnotation(annotationClass); if (ann != null) { return ann; } - return field.getAnnotation(annotationClass); + return field == null ? null : field.getAnnotation(annotationClass); } public int getFieldModifiers() { - return field.getModifiers(); + return field == null ? 0 : field.getModifiers(); } } diff --git a/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java b/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java index 446da213..c3adc3c4 100644 --- a/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java +++ b/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java @@ -32,69 +32,59 @@ import de.danielbechler.util.Exceptions; /** - * Resolves the accessors of a given type by using the standard Java {@link Introspector}. - * + * Resolves the accessors of a given type by using the standard Java + * {@link Introspector}. + * * @author Daniel Bechler */ -public class StandardIntrospector implements de.danielbechler.diff.introspection.Introspector -{ +public class StandardIntrospector implements de.danielbechler.diff.introspection.Introspector { private static final Logger log = LoggerFactory.getLogger(StandardIntrospector.class); - public TypeInfo introspect(final Class type) - { - Assert.notNull(type, "type"); - try - { - return internalIntrospect(type); - } - catch (final IntrospectionException e) - { - throw Exceptions.escalate(e); - } - } + public TypeInfo introspect(final Class type) { + Assert.notNull(type, "type"); + try { + return internalIntrospect(type); + } catch (final IntrospectionException e) { + throw Exceptions.escalate(e); + } + } - private TypeInfo internalIntrospect(final Class type) throws IntrospectionException - { - final TypeInfo typeInfo = new TypeInfo(type); - final PropertyDescriptor[] descriptors = getBeanInfo(type).getPropertyDescriptors(); - for (final PropertyDescriptor descriptor : descriptors) - { - if (shouldSkip(descriptor)) - { - continue; - } - final String propertyName = descriptor.getName(); + private TypeInfo internalIntrospect(final Class type) throws IntrospectionException { + final TypeInfo typeInfo = new TypeInfo(type); + final PropertyDescriptor[] descriptors = getBeanInfo(type).getPropertyDescriptors(); + for (final PropertyDescriptor descriptor : descriptors) { + if (shouldSkip(descriptor)) { + continue; + } + final String propertyName = descriptor.getName(); Field field = findField(type, propertyName); - final Method readMethod = descriptor.getReadMethod(); - final Method writeMethod = descriptor.getWriteMethod(); + final Method readMethod = descriptor.getReadMethod(); + final Method writeMethod = descriptor.getWriteMethod(); final PropertyAwareAccessor accessor = new PropertyAccessor(propertyName, field, readMethod, writeMethod); - typeInfo.addPropertyAccessor(accessor); - } - return typeInfo; - } + typeInfo.addPropertyAccessor(accessor); + } + return typeInfo; + } - protected BeanInfo getBeanInfo(final Class type) throws IntrospectionException - { - return Introspector.getBeanInfo(type); - } + protected BeanInfo getBeanInfo(final Class type) throws IntrospectionException { + return Introspector.getBeanInfo(type); + } - private static boolean shouldSkip(final PropertyDescriptor descriptor) - { - if (descriptor.getName().equals("class")) // Java & Groovy - { - return true; - } - if (descriptor.getName().equals("metaClass")) // Groovy - { - return true; - } - if (descriptor.getReadMethod() == null) - { - return true; - } - return false; - } + private static boolean shouldSkip(final PropertyDescriptor descriptor) { + if (descriptor.getName().equals("class")) // Java & Groovy + { + return true; + } + if (descriptor.getName().equals("metaClass")) // Groovy + { + return true; + } + if (descriptor.getReadMethod() == null) { + return true; + } + return false; + } private Field findField(final Class type, final String propertyName) throws IntrospectionException { String fieldName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1); @@ -103,11 +93,11 @@ private Field findField(final Class type, final String propertyName) throws I try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { - log.debug("No such field: " + e.getMessage()); } clazz = clazz.getSuperclass(); } - throw new IntrospectionException("No such field " + fieldName + " in " + type + " class hierarchy"); + log.info("No Java Bean: No such field " + fieldName + " in " + type + " class hierarchy"); + return null; } } From 9e28cf0316bf26a335cf94bed955b052df43f8a5 Mon Sep 17 00:00:00 2001 From: Wolfgang Date: Sat, 13 Dec 2014 16:14:51 +0100 Subject: [PATCH 5/5] code formatting changed --- .../diff/introspection/PropertyAccessor.java | 326 ++++++++++-------- .../introspection/StandardIntrospector.java | 96 +++--- 2 files changed, 240 insertions(+), 182 deletions(-) diff --git a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java index c841a81f..9fc050d0 100644 --- a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java +++ b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java @@ -35,165 +35,213 @@ import de.danielbechler.diff.selector.BeanPropertyElementSelector; import de.danielbechler.util.Assert; -public class PropertyAccessor implements PropertyAwareAccessor { - private static final Logger logger = LoggerFactory.getLogger(PropertyAccessor.class); +public class PropertyAccessor implements PropertyAwareAccessor +{ + private static final Logger logger = LoggerFactory.getLogger(PropertyAccessor.class); - private final String propertyName; - private final Class type; - private final Method readMethod; - private final Method writeMethod; + private final String propertyName; + private final Class type; + private final Method readMethod; + private final Method writeMethod; private final Field field; - public PropertyAccessor(final String propertyName, final Field f, final Method readMethod, final Method writeMethod) { - Assert.notNull(propertyName, "propertyName"); - Assert.notNull(readMethod, "readMethod"); - this.propertyName = propertyName; + public PropertyAccessor(final String propertyName, final Field f, final Method readMethod, final Method writeMethod) + { + Assert.notNull(propertyName, "propertyName"); + Assert.notNull(readMethod, "readMethod"); + this.propertyName = propertyName; this.field = f; - this.readMethod = makeAccessible(readMethod); - this.writeMethod = makeAccessible(writeMethod); - this.type = this.readMethod.getReturnType(); - } + this.readMethod = makeAccessible(readMethod); + this.writeMethod = makeAccessible(writeMethod); + this.type = this.readMethod.getReturnType(); + } - private static Method makeAccessible(final Method method) { - if (method != null && !method.isAccessible()) { - logger.debug("Making method accessible: {}", method.toString()); - method.setAccessible(true); - } - return method; - } + private static Method makeAccessible(final Method method) + { + if (method != null && !method.isAccessible()) + { + logger.debug("Making method accessible: {}", method.toString()); + method.setAccessible(true); + } + return method; + } - public final Set getCategoriesFromAnnotation() { - final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); - if (annotation != null) { - return new TreeSet(asList(annotation.categories())); - } - return Collections.emptySet(); - } + public final Set getCategoriesFromAnnotation() + { + final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); + if (annotation != null) + { + return new TreeSet(asList(annotation.categories())); + } + return Collections.emptySet(); + } - public boolean isExcludedByAnnotation() { - final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); - return annotation != null && annotation.excluded(); - } + public boolean isExcludedByAnnotation() + { + final ObjectDiffProperty annotation = readMethod.getAnnotation(ObjectDiffProperty.class); + return annotation != null && annotation.excluded(); + } - public String getPropertyName() { - return this.propertyName; - } + public String getPropertyName() + { + return this.propertyName; + } - /** - * @return The annotations of the getter used to access this property. - */ - public Set getReadMethodAnnotations() { - return new LinkedHashSet(asList(readMethod.getAnnotations())); - } + /** + * @return The annotations of the getter used to access this property. + */ + public Set getReadMethodAnnotations() + { + return new LinkedHashSet(asList(readMethod.getAnnotations())); + } - public T getReadMethodAnnotation(final Class annotationClass) { - final Set annotations = getReadMethodAnnotations(); - assert (annotations != null) : "Something is wrong here. " - + "The contract of getReadAnnotations() guarantees a non-null return value."; - for (final Annotation annotation : annotations) { - if (annotationClass.isAssignableFrom(annotation.annotationType())) { - return annotationClass.cast(annotation); - } - } - return null; - } + public T getReadMethodAnnotation(final Class annotationClass) + { + final Set annotations = getReadMethodAnnotations(); + assert (annotations != null) : "Something is wrong here. " + + "The contract of getReadAnnotations() guarantees a non-null return value."; + for (final Annotation annotation : annotations) + { + if (annotationClass.isAssignableFrom(annotation.annotationType())) + { + return annotationClass.cast(annotation); + } + } + return null; + } - public BeanPropertyElementSelector getElementSelector() { - return new BeanPropertyElementSelector(this.propertyName); - } + public BeanPropertyElementSelector getElementSelector() + { + return new BeanPropertyElementSelector(this.propertyName); + } - public Object get(final Object target) { - if (target == null) { - return null; - } - try { - return readMethod.invoke(target); - } catch (final Exception cause) { - throw new PropertyReadException(propertyName, target.getClass(), cause); - } - } + public Object get(final Object target) + { + if (target == null) + { + return null; + } + try + { + return readMethod.invoke(target); + } + catch (final Exception cause) + { + throw new PropertyReadException(propertyName, target.getClass(), cause); + } + } - public void set(final Object target, final Object value) { - if (target == null) { - logger.info("Couldn't set new value '{}' for property '{}' " + "because the target object is null", value, - propertyName); - } else if (writeMethod == null) { - logger.debug("No setter found for property '{}'", propertyName); - tryToReplaceContentOfCollectionTypes(target, value); - } else { - invokeWriteMethod(target, value); - } - } + public void set(final Object target, final Object value) + { + if (target == null) + { + logger.info("Couldn't set new value '{}' for property '{}' " + + "because the target object is null", value, propertyName); + } + else if (writeMethod == null) + { + logger.debug("No setter found for property '{}'", propertyName); + tryToReplaceContentOfCollectionTypes(target, value); + } + else + { + invokeWriteMethod(target, value); + } + } - public void unset(final Object target) { - set(target, null); - } + public void unset(final Object target) + { + set(target, null); + } - @SuppressWarnings("unchecked") - private void tryToReplaceContentOfCollectionTypes(final Object target, final Object value) { - if (Collection.class.isAssignableFrom(readMethod.getReturnType())) { - if (tryToReplaceCollectionContent((Collection) get(target), (Collection) value)) { - return; - } - } - if (Map.class.isAssignableFrom(readMethod.getReturnType())) { - if (tryToReplaceMapContent((Map) get(target), (Map) value)) { - return; - } - } - logger.info("Couldn't set new value '{}' for property '{}'", value, propertyName); - } + @SuppressWarnings("unchecked") + private void tryToReplaceContentOfCollectionTypes(final Object target, final Object value) + { + if (Collection.class.isAssignableFrom(readMethod.getReturnType())) + { + if (tryToReplaceCollectionContent((Collection) get(target), (Collection) value)) + { + return; + } + } + if (Map.class.isAssignableFrom(readMethod.getReturnType())) + { + if (tryToReplaceMapContent((Map) get(target), (Map) value)) + { + return; + } + } + logger.info("Couldn't set new value '{}' for property '{}'", value, propertyName); + } - private void invokeWriteMethod(final Object target, final Object value) { - try { - writeMethod.invoke(target, value); - } catch (final Exception cause) { - throw new PropertyWriteException(propertyName, target.getClass(), value, cause); - } - } + private void invokeWriteMethod(final Object target, final Object value) + { + try + { + writeMethod.invoke(target, value); + } + catch (final Exception cause) + { + throw new PropertyWriteException(propertyName, target.getClass(), value, cause); + } + } - private static boolean tryToReplaceCollectionContent(final Collection target, final Collection value) { - if (target == null) { - return false; - } - try { - target.clear(); - target.addAll(value); - return true; - } catch (final Exception unmodifiable) { - logger.debug("Failed to replace content of existing Collection", unmodifiable); - return false; - } - } + private static boolean tryToReplaceCollectionContent(final Collection target, + final Collection value) + { + if (target == null) + { + return false; + } + try + { + target.clear(); + target.addAll(value); + return true; + } + catch (final Exception unmodifiable) + { + logger.debug("Failed to replace content of existing Collection", unmodifiable); + return false; + } + } - private static boolean tryToReplaceMapContent(final Map target, final Map value) { - if (target == null) { - return false; - } - try { - target.clear(); - target.putAll(value); - return true; - } catch (final Exception unmodifiable) { - logger.debug("Failed to replace content of existing Map", unmodifiable); - return false; - } - } + private static boolean tryToReplaceMapContent(final Map target, + final Map value) + { + if (target == null) + { + return false; + } + try + { + target.clear(); + target.putAll(value); + return true; + } + catch (final Exception unmodifiable) + { + logger.debug("Failed to replace content of existing Map", unmodifiable); + return false; + } + } - public Class getType() { - return this.type; - } + public Class getType() + { + return this.type; + } - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("PropertyAccessor{"); - sb.append("propertyName='").append(propertyName).append('\''); - sb.append(", type=").append(type.getCanonicalName()); - sb.append(", source=").append(readMethod.getDeclaringClass().getCanonicalName()); - sb.append(", hasWriteMethod=").append(writeMethod != null); - sb.append('}'); - return sb.toString(); - } + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder("PropertyAccessor{"); + sb.append("propertyName='").append(propertyName).append('\''); + sb.append(", type=").append(type.getCanonicalName()); + sb.append(", source=").append(readMethod.getDeclaringClass().getCanonicalName()); + sb.append(", hasWriteMethod=").append(writeMethod != null); + sb.append('}'); + return sb.toString(); + } public T getAnnotation(Class annotationClass) { T ann = getReadMethodAnnotation(annotationClass); diff --git a/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java b/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java index c3adc3c4..3d86cb32 100644 --- a/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java +++ b/src/main/java/de/danielbechler/diff/introspection/StandardIntrospector.java @@ -32,59 +32,69 @@ import de.danielbechler.util.Exceptions; /** - * Resolves the accessors of a given type by using the standard Java - * {@link Introspector}. - * + * Resolves the accessors of a given type by using the standard Java {@link Introspector}. + * * @author Daniel Bechler */ -public class StandardIntrospector implements de.danielbechler.diff.introspection.Introspector { +public class StandardIntrospector implements de.danielbechler.diff.introspection.Introspector +{ private static final Logger log = LoggerFactory.getLogger(StandardIntrospector.class); - public TypeInfo introspect(final Class type) { - Assert.notNull(type, "type"); - try { - return internalIntrospect(type); - } catch (final IntrospectionException e) { - throw Exceptions.escalate(e); - } - } + public TypeInfo introspect(final Class type) + { + Assert.notNull(type, "type"); + try + { + return internalIntrospect(type); + } + catch (final IntrospectionException e) + { + throw Exceptions.escalate(e); + } + } - private TypeInfo internalIntrospect(final Class type) throws IntrospectionException { - final TypeInfo typeInfo = new TypeInfo(type); - final PropertyDescriptor[] descriptors = getBeanInfo(type).getPropertyDescriptors(); - for (final PropertyDescriptor descriptor : descriptors) { - if (shouldSkip(descriptor)) { - continue; - } - final String propertyName = descriptor.getName(); + private TypeInfo internalIntrospect(final Class type) throws IntrospectionException + { + final TypeInfo typeInfo = new TypeInfo(type); + final PropertyDescriptor[] descriptors = getBeanInfo(type).getPropertyDescriptors(); + for (final PropertyDescriptor descriptor : descriptors) + { + if (shouldSkip(descriptor)) + { + continue; + } + final String propertyName = descriptor.getName(); Field field = findField(type, propertyName); - final Method readMethod = descriptor.getReadMethod(); - final Method writeMethod = descriptor.getWriteMethod(); + final Method readMethod = descriptor.getReadMethod(); + final Method writeMethod = descriptor.getWriteMethod(); final PropertyAwareAccessor accessor = new PropertyAccessor(propertyName, field, readMethod, writeMethod); - typeInfo.addPropertyAccessor(accessor); - } - return typeInfo; - } + typeInfo.addPropertyAccessor(accessor); + } + return typeInfo; + } - protected BeanInfo getBeanInfo(final Class type) throws IntrospectionException { - return Introspector.getBeanInfo(type); - } + protected BeanInfo getBeanInfo(final Class type) throws IntrospectionException + { + return Introspector.getBeanInfo(type); + } - private static boolean shouldSkip(final PropertyDescriptor descriptor) { - if (descriptor.getName().equals("class")) // Java & Groovy - { - return true; - } - if (descriptor.getName().equals("metaClass")) // Groovy - { - return true; - } - if (descriptor.getReadMethod() == null) { - return true; - } - return false; - } + private static boolean shouldSkip(final PropertyDescriptor descriptor) + { + if (descriptor.getName().equals("class")) // Java & Groovy + { + return true; + } + if (descriptor.getName().equals("metaClass")) // Groovy + { + return true; + } + if (descriptor.getReadMethod() == null) + { + return true; + } + return false; + } private Field findField(final Class type, final String propertyName) throws IntrospectionException { String fieldName = propertyName.substring(0, 1).toLowerCase() + propertyName.substring(1);