diff --git a/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java b/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java index 6eaa6898..290f722f 100644 --- a/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java +++ b/src/main/java/de/danielbechler/diff/access/PropertyAwareAccessor.java @@ -26,6 +26,10 @@ public interface PropertyAwareAccessor extends TypeAwareAccessor, CategoryAware, { String getPropertyName(); + Set getFieldAnnotations(); + + T getFieldAnnotation(Class annotationClass); + Set getReadMethodAnnotations(); T getReadMethodAnnotation(Class annotationClass); diff --git a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java index 176ea878..8207d3bb 100644 --- a/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java +++ b/src/main/java/de/danielbechler/diff/introspection/PropertyAccessor.java @@ -83,6 +83,58 @@ public String getPropertyName() return this.propertyName; } + /** + * Private function to allow looking for the field recursively up the superclasses. + * + * @param clazz + * @return + */ + private Set getFieldAnnotations(final Class clazz) + { + try + { + return new LinkedHashSet(asList(clazz.getDeclaredField(propertyName).getAnnotations())); + } catch (NoSuchFieldException e) + { + if (clazz.getSuperclass() != null) + { + return getFieldAnnotations(clazz.getSuperclass()); + } + else + { + logger.debug("Cannot find propertyName: {}, declaring class: {}", propertyName, clazz); + return new LinkedHashSet(0); + } + } + } + + /** + * @return The annotations of the field, or an empty set if there is no field with the name derived from the getter. + */ + public Set getFieldAnnotations() + { + return getFieldAnnotations(readMethod.getDeclaringClass()); + } + + /** + * @return The given annotation of the field, or null if not annotated or if there is no field with the name derived + * from the getter. + */ + public T getFieldAnnotation(final Class annotationClass) + { + final Set annotations = getFieldAnnotations(); + 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; + } + /** * @return The annotations of the getter used to access this property. */ diff --git a/src/main/java/de/danielbechler/diff/node/DiffNode.java b/src/main/java/de/danielbechler/diff/node/DiffNode.java index 430ca0b9..b04152a2 100644 --- a/src/main/java/de/danielbechler/diff/node/DiffNode.java +++ b/src/main/java/de/danielbechler/diff/node/DiffNode.java @@ -443,6 +443,37 @@ public final void visitParents(final Visitor visitor) } } + /** + * If this node represents a bean property this method returns all annotations of its field. + * + * Only works for fields having a name that matches the name derived from the getter. + * + * @return The annotations of the field, or an empty set if there is no field with the name derived from the getter. + */ + public Set getFieldAnnotations() + { + if (accessor instanceof PropertyAwareAccessor) + { + return unmodifiableSet(((PropertyAwareAccessor) accessor).getFieldAnnotations()); + } + return unmodifiableSet(Collections.emptySet()); + } + + /** + * @param annotationClass the annotation we are looking for + * @param + * @return The given annotation of the field, or null if not annotated or if there is no field with the name derived + * from the getter. + */ + public T getFieldAnnotation(final Class annotationClass) + { + if (accessor instanceof PropertyAwareAccessor) + { + return ((PropertyAwareAccessor) accessor).getFieldAnnotation(annotationClass); + } + return null; + } + /** * If this node represents a bean property this method returns all annotations of its getter. * diff --git a/src/test/java/de/danielbechler/diff/node/DiffNodeFieldAnnotationsTest.groovy b/src/test/java/de/danielbechler/diff/node/DiffNodeFieldAnnotationsTest.groovy new file mode 100644 index 00000000..02cdc63d --- /dev/null +++ b/src/test/java/de/danielbechler/diff/node/DiffNodeFieldAnnotationsTest.groovy @@ -0,0 +1,129 @@ +/* + * Copyright 2014 Daniel Bechler + * + * 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.node + +import de.danielbechler.diff.access.Accessor +import de.danielbechler.diff.introspection.PropertyAccessor +import de.danielbechler.diff.selector.BeanPropertyElementSelector +import spock.lang.Specification + +class DiffNodeFieldAnnotationsTest extends Specification { + + def 'getFieldAnnotation(): returns null if not PropertyAwareAccessor'() { + given: + def accessor = Mock(Accessor) { + getElementSelector() >> new BeanPropertyElementSelector('f') + } + def diffNode = new DiffNode(null, accessor, A) + expect: + diffNode.fieldAnnotations.size() == 0 + diffNode.getFieldAnnotation(SomeFieldAnnotation) == null + } + + def 'getFieldAnnotation(): class has the field and annotated'() { + given: + def accessor = new PropertyAccessor("f", A.getMethod("getF"), A.getMethod("setF", String)) + def diffNode = new DiffNode(null, accessor, A) + expect: + diffNode.fieldAnnotations.size() == 1 + diffNode.getFieldAnnotation(SomeFieldAnnotation) != null + diffNode.getFieldAnnotation(SomeFieldAnnotation).annotationType() == SomeFieldAnnotation + } + + def 'getFieldAnnotation(): class does not have the field, or different name'() { + given: + def accessor = new PropertyAccessor("F", ADiffName.getMethod("getF"), null) + def diffNode = new DiffNode(null, accessor, ADiffName) + expect: + diffNode.fieldAnnotations.size() == 0 + diffNode.getFieldAnnotation(SomeFieldAnnotation) == null + } + + def 'getFieldAnnotation(): inheritance'() { + given: + def accessor = new PropertyAccessor("f", AB.getMethod("getF"), AB.getMethod("setF", String)) + def diffNode = new DiffNode(null, accessor, AB) + expect: + diffNode.fieldAnnotations.size() == 1 + diffNode.getFieldAnnotation(SomeFieldAnnotation) != null + diffNode.getFieldAnnotation(SomeFieldAnnotation).annotationType() == SomeFieldAnnotation + } + + def 'getFieldAnnotation(): inheritance, overridden getter'() { + given: + def accessor = new PropertyAccessor("f", ABGetter.getMethod("getF"), ABGetter.getMethod("setF", String)) + def diffNode = new DiffNode(null, accessor, ABGetter) + expect: + diffNode.fieldAnnotations.size() == 1 + diffNode.getFieldAnnotation(SomeFieldAnnotation) != null + diffNode.getFieldAnnotation(SomeFieldAnnotation).annotationType() == SomeFieldAnnotation + } + + def 'getFieldAnnotation(): inheritance, not annotated'() { + given: + def accessor = new PropertyAccessor("f", NAB.getMethod("getF"), NAB.getMethod("setF", String)) + def diffNode = new DiffNode(null, accessor, NAB) + expect: + diffNode.fieldAnnotations.size() == 0 + diffNode.getFieldAnnotation(SomeFieldAnnotation) == null + } + + def 'getFieldAnnotation(): inheritance, overridden getter, not annotated'() { + given: + def accessor = new PropertyAccessor("f", NABGetter.getMethod("getF"), NABGetter.getMethod("setF", String)) + def diffNode = new DiffNode(null, accessor, NABGetter) + expect: + diffNode.fieldAnnotations.size() == 0 + diffNode.getFieldAnnotation(SomeFieldAnnotation) == null + } + + public static class A { + @SomeFieldAnnotation + String f; + } + + public static class NA { + String f; + } + + public static class ADiffName { + public String getF() { + return null; + } + } + + public static class AB extends A { + } + + public static class ABGetter extends A { + @Override + public String getF() { + return null; + } + } + + public static class NAB extends NA { + } + + public static class NABGetter extends NA { + @Override + public String getF() { + return null; + } + } + +} diff --git a/src/test/java/de/danielbechler/diff/node/SomeFieldAnnotation.java b/src/test/java/de/danielbechler/diff/node/SomeFieldAnnotation.java new file mode 100644 index 00000000..99a32db9 --- /dev/null +++ b/src/test/java/de/danielbechler/diff/node/SomeFieldAnnotation.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013 Daniel Bechler + * + * 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.node; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SomeFieldAnnotation { +}