| 1 | package com.ancientprogramming.fixedformat4j.format.impl; | |
| 2 | ||
| 3 | import com.ancientprogramming.fixedformat4j.annotation.Field; | |
| 4 | import com.ancientprogramming.fixedformat4j.annotation.Fields; | |
| 5 | import com.ancientprogramming.fixedformat4j.exception.FixedFormatException; | |
| 6 | import org.slf4j.Logger; | |
| 7 | import org.slf4j.LoggerFactory; | |
| 8 | ||
| 9 | import java.lang.reflect.Method; | |
| 10 | import java.util.ArrayList; | |
| 11 | import java.util.LinkedHashMap; | |
| 12 | import java.util.List; | |
| 13 | ||
| 14 | import static java.lang.String.format; | |
| 15 | ||
| 16 | /** | |
| 17 | * Discovers all {@link Field} and {@link Fields} annotation targets on a class. | |
| 18 | * | |
| 19 | * <p>Pass 1 walks public methods; pass 2 walks declared fields including superclasses. | |
| 20 | * Field annotations take priority over method annotations for the same property. | |
| 21 | * A conflict (both annotated) is logged as a warning. | |
| 22 | * | |
| 23 | * @author Jacob von Eyben - <a href="https://eybenconsult.com">https://eybenconsult.com</a> | |
| 24 | * @since 1.6.0 | |
| 25 | */ | |
| 26 | class AnnotationScanner { | |
| 27 | ||
| 28 | private static final Logger LOG = LoggerFactory.getLogger(AnnotationScanner.class); | |
| 29 | ||
| 30 | /** | |
| 31 | * Returns all annotation targets for the given class in declaration order. | |
| 32 | */ | |
| 33 | List<AnnotationTarget> scan(Class<?> clazz) { | |
| 34 | LinkedHashMap<String, AnnotationTarget> targets = new LinkedHashMap<>(); | |
| 35 | ||
| 36 | // Pass 1: method annotations | |
| 37 | for (Method method : clazz.getMethods()) { | |
| 38 |
2
1. scan : negated conditional → KILLED 2. scan : negated conditional → KILLED |
if (method.getAnnotation(Field.class) != null || method.getAnnotation(Fields.class) != null) { |
| 39 | targets.put(stripMethodPrefix(method.getName()), AnnotationTarget.ofMethod(method)); | |
| 40 | } | |
| 41 | } | |
| 42 | ||
| 43 | // Pass 2: field annotations — walk class hierarchy | |
| 44 | Class<?> current = clazz; | |
| 45 |
2
1. scan : negated conditional → KILLED 2. scan : negated conditional → KILLED |
while (current != null && current != Object.class) { |
| 46 | for (java.lang.reflect.Field javaField : current.getDeclaredFields()) { | |
| 47 |
2
1. scan : negated conditional → KILLED 2. scan : negated conditional → KILLED |
if (javaField.getAnnotation(Field.class) == null && javaField.getAnnotation(Fields.class) == null) { |
| 48 | continue; | |
| 49 | } | |
| 50 | Method getter = findGetter(clazz, javaField); | |
| 51 | String key = stripMethodPrefix(getter.getName()); | |
| 52 |
1
1. scan : negated conditional → SURVIVED |
if (targets.containsKey(key)) { |
| 53 | LOG.error("Configuration mismatch: @Field annotation found on both field '{}' and its getter method '{}' in class '{}'. The field annotation will be used.", | |
| 54 | javaField.getName(), getter.getName(), clazz.getName()); | |
| 55 | } | |
| 56 | targets.put(key, AnnotationTarget.ofField(getter, javaField)); | |
| 57 | } | |
| 58 | current = current.getSuperclass(); | |
| 59 | } | |
| 60 | ||
| 61 |
1
1. scan : replaced return value with Collections.emptyList for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::scan → KILLED |
return new ArrayList<>(targets.values()); |
| 62 | } | |
| 63 | ||
| 64 | private Method findGetter(Class<?> clazz, java.lang.reflect.Field field) { | |
| 65 | String name = field.getName(); | |
| 66 | String cap = Character.toUpperCase(name.charAt(0)) + name.substring(1); | |
| 67 | try { | |
| 68 |
1
1. findGetter : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::findGetter → KILLED |
return clazz.getMethod("get" + cap); |
| 69 | } catch (NoSuchMethodException e) { | |
| 70 | try { | |
| 71 |
1
1. findGetter : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::findGetter → NO_COVERAGE |
return clazz.getMethod("is" + cap); |
| 72 | } catch (NoSuchMethodException e2) { | |
| 73 | throw new FixedFormatException(format("No getter found for field '%s' in class %s. Expected 'get%s()' or 'is%s()'.", name, clazz.getName(), cap, cap)); | |
| 74 | } | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | String stripMethodPrefix(String name) { | |
| 79 |
2
1. stripMethodPrefix : negated conditional → KILLED 2. stripMethodPrefix : negated conditional → KILLED |
if (name.startsWith("get") || name.startsWith("set")) { |
| 80 |
1
1. stripMethodPrefix : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::stripMethodPrefix → KILLED |
return name.substring(3); |
| 81 |
1
1. stripMethodPrefix : negated conditional → KILLED |
} else if (name.startsWith("is")) { |
| 82 |
1
1. stripMethodPrefix : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::stripMethodPrefix → KILLED |
return name.substring(2); |
| 83 | } else { | |
| 84 |
1
1. stripMethodPrefix : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::stripMethodPrefix → NO_COVERAGE |
return name; |
| 85 | } | |
| 86 | } | |
| 87 | } | |
Mutations | ||
| 38 |
1.1 2.2 |
|
| 45 |
1.1 2.2 |
|
| 47 |
1.1 2.2 |
|
| 52 |
1.1 |
|
| 61 |
1.1 |
|
| 68 |
1.1 |
|
| 71 |
1.1 |
|
| 79 |
1.1 2.2 |
|
| 80 |
1.1 |
|
| 81 |
1.1 |
|
| 82 |
1.1 |
|
| 84 |
1.1 |