| 1 | package com.ancientprogramming.fixedformat4j.format.impl; | |
| 2 | ||
| 3 | import com.ancientprogramming.fixedformat4j.exception.FixedFormatException; | |
| 4 | ||
| 5 | import java.util.ArrayList; | |
| 6 | import java.util.List; | |
| 7 | ||
| 8 | import static java.lang.String.format; | |
| 9 | ||
| 10 | /** | |
| 11 | * Reflective access to the Java {@code record} API (JDK 16+) from bytecode compiled at | |
| 12 | * release 11. | |
| 13 | * | |
| 14 | * <p>A record class can only be loaded by a JVM that has the record API, so whenever | |
| 15 | * {@link #isJavaRecord} returns {@code true} the reflective lookups below are guaranteed | |
| 16 | * to resolve. | |
| 17 | * | |
| 18 | * <p>Performance: these lookups run once per record class, inside the | |
| 19 | * {@link ClassMetadataCache} build (guarded by {@link ClassValue}); the per-{@code load()} | |
| 20 | * hot path only sees the cached {@link ConstructorBinding}. | |
| 21 | * | |
| 22 | * @author Jacob von Eyben - <a href="https://eybenconsult.com">https://eybenconsult.com</a> | |
| 23 | * @since 1.9.0 | |
| 24 | */ | |
| 25 | final class JavaRecordSupport { | |
| 26 | ||
| 27 | private JavaRecordSupport() { | |
| 28 | } | |
| 29 | ||
| 30 | /** | |
| 31 | * Returns whether the given class is a Java {@code record}. Checked via the superclass | |
| 32 | * name because {@code java.lang.Record} and {@code Class.isRecord()} are not referenceable | |
| 33 | * at release 11; the compiler forbids extending {@code java.lang.Record} explicitly, so the | |
| 34 | * check is equivalent for loadable classes. | |
| 35 | */ | |
| 36 | static boolean isJavaRecord(Class<?> clazz) { | |
| 37 |
2
1. isJavaRecord : removed call to java/lang/Class::getSuperclass → SURVIVED 2. isJavaRecord : replaced call to java/lang/Class::getSuperclass with receiver → SURVIVED |
Class<?> superclass = clazz.getSuperclass(); |
| 38 |
11
1. isJavaRecord : negated conditional → SURVIVED 2. isJavaRecord : removed conditional - replaced equality check with true → SURVIVED 3. isJavaRecord : removed conditional - replaced equality check with false → SURVIVED 4. isJavaRecord : removed call to java/lang/Class::getName → SURVIVED 5. isJavaRecord : Substituted 1 with 0 → NO_COVERAGE 6. isJavaRecord : removed conditional - replaced equality check with false → SURVIVED 7. isJavaRecord : removed call to java/lang/String::equals → SURVIVED 8. isJavaRecord : Substituted 0 with 1 → KILLED 9. isJavaRecord : replaced boolean return with true for com/ancientprogramming/fixedformat4j/format/impl/JavaRecordSupport::isJavaRecord → KILLED 10. isJavaRecord : negated conditional → KILLED 11. isJavaRecord : removed conditional - replaced equality check with true → KILLED |
return superclass != null && "java.lang.Record".equals(superclass.getName()); |
| 39 | } | |
| 40 | ||
| 41 | /** Name and type of one record component, in declaration order. */ | |
| 42 | static final class ComponentInfo { | |
| 43 | final String name; | |
| 44 | final Class<?> type; | |
| 45 | ||
| 46 | ComponentInfo(String name, Class<?> type) { | |
| 47 |
1
1. <init> : Removed assignment to member variable name → NO_COVERAGE |
this.name = name; |
| 48 |
1
1. <init> : Removed assignment to member variable type → NO_COVERAGE |
this.type = type; |
| 49 | } | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Returns the record components of the given record class in declaration order — the | |
| 54 | * exact parameter list of the canonical constructor. | |
| 55 | */ | |
| 56 | static List<ComponentInfo> components(Class<?> recordClass) { | |
| 57 | try { | |
| 58 |
5
1. components : replaced call to java/lang/reflect/Method::invoke with argument → NO_COVERAGE 2. components : removed call to java/lang/reflect/Method::invoke → NO_COVERAGE 3. components : removed call to java/lang/Class::getMethod → NO_COVERAGE 4. components : Substituted 0 with 1 → NO_COVERAGE 5. components : Substituted 0 with 1 → NO_COVERAGE |
Object[] components = (Object[]) Class.class.getMethod("getRecordComponents").invoke(recordClass); |
| 59 |
1
1. components : removed call to java/util/ArrayList::<init> → NO_COVERAGE |
List<ComponentInfo> result = new ArrayList<>(components.length); |
| 60 | for (Object component : components) { | |
| 61 |
6
1. components : removed call to java/lang/Class::getMethod → NO_COVERAGE 2. components : removed call to java/lang/Object::getClass → NO_COVERAGE 3. components : Substituted 0 with 1 → NO_COVERAGE 4. components : Substituted 0 with 1 → NO_COVERAGE 5. components : removed call to java/lang/reflect/Method::invoke → NO_COVERAGE 6. components : replaced call to java/lang/reflect/Method::invoke with argument → NO_COVERAGE |
String name = (String) component.getClass().getMethod("getName").invoke(component); |
| 62 |
6
1. components : Substituted 0 with 1 → NO_COVERAGE 2. components : Substituted 0 with 1 → NO_COVERAGE 3. components : replaced call to java/lang/reflect/Method::invoke with argument → NO_COVERAGE 4. components : removed call to java/lang/reflect/Method::invoke → NO_COVERAGE 5. components : removed call to java/lang/Class::getMethod → NO_COVERAGE 6. components : removed call to java/lang/Object::getClass → NO_COVERAGE |
Class<?> type = (Class<?>) component.getClass().getMethod("getType").invoke(component); |
| 63 |
2
1. components : removed call to java/util/List::add → NO_COVERAGE 2. components : removed call to com/ancientprogramming/fixedformat4j/format/impl/JavaRecordSupport$ComponentInfo::<init> → NO_COVERAGE |
result.add(new ComponentInfo(name, type)); |
| 64 | } | |
| 65 |
1
1. components : replaced return value with Collections.emptyList for com/ancientprogramming/fixedformat4j/format/impl/JavaRecordSupport::components → NO_COVERAGE |
return result; |
| 66 | } catch (ReflectiveOperationException e) { | |
| 67 |
2
1. components : Substituted 1 with 0 → NO_COVERAGE 2. components : Substituted 0 with 1 → NO_COVERAGE |
throw new FixedFormatException( |
| 68 |
4
1. components : replaced call to java/lang/String::format with argument → NO_COVERAGE 2. components : removed call to java/lang/Class::getName → NO_COVERAGE 3. components : removed call to java/lang/String::format → NO_COVERAGE 4. components : removed call to com/ancientprogramming/fixedformat4j/exception/FixedFormatException::<init> → NO_COVERAGE |
format("unable to read record components of %s", recordClass.getName()), e); |
| 69 | } | |
| 70 | } | |
| 71 | } | |
Mutations | ||
| 37 |
1.1 2.2 |
|
| 38 |
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 10.10 11.11 |
|
| 47 |
1.1 |
|
| 48 |
1.1 |
|
| 58 |
1.1 2.2 3.3 4.4 5.5 |
|
| 59 |
1.1 |
|
| 61 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 62 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 63 |
1.1 2.2 |
|
| 65 |
1.1 |
|
| 67 |
1.1 2.2 |
|
| 68 |
1.1 2.2 3.3 4.4 |