AnnotationScanner.java

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
Location : scan
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_methodAnnotation_returnsTarget()]
negated conditional → KILLED

2.2
Location : scan
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_methodAnnotation_returnsTarget()]
negated conditional → KILLED

45

1.1
Location : scan
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_orderPreserved()]
negated conditional → KILLED

2.2
Location : scan
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_orderPreserved()]
negated conditional → KILLED

47

1.1
Location : scan
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_methodAnnotation_returnsTarget()]
negated conditional → KILLED

2.2
Location : scan
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_methodAnnotation_returnsTarget()]
negated conditional → KILLED

52

1.1
Location : scan
Killed by : none
negated conditional → SURVIVED
Covering tests

61

1.1
Location : scan
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_methodAnnotation_returnsTarget()]
replaced return value with Collections.emptyList for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::scan → KILLED

68

1.1
Location : findGetter
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_orderPreserved()]
replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::findGetter → KILLED

71

1.1
Location : findGetter
Killed by : none
replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::findGetter → NO_COVERAGE

79

1.1
Location : stripMethodPrefix
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl]/[method:testImportAnnotatedNestedClass()]
negated conditional → KILLED

2.2
Location : stripMethodPrefix
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl]/[method:testIsPrefixedBooleanGetterRoundTrip()]
negated conditional → KILLED

80

1.1
Location : stripMethodPrefix
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestAnnotationScanner]/[method:scan_orderPreserved()]
replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::stripMethodPrefix → KILLED

81

1.1
Location : stripMethodPrefix
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl]/[method:testIsPrefixedBooleanGetterRoundTrip()]
negated conditional → KILLED

82

1.1
Location : stripMethodPrefix
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestFixedFormatManagerImpl]/[method:testIsPrefixedBooleanGetterRoundTrip()]
replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::stripMethodPrefix → KILLED

84

1.1
Location : stripMethodPrefix
Killed by : none
replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AnnotationScanner::stripMethodPrefix → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.17.1