| 1 | package com.ancientprogramming.fixedformat4j.format.impl; | |
| 2 | ||
| 3 | import com.ancientprogramming.fixedformat4j.exception.FixedFormatException; | |
| 4 | ||
| 5 | import java.lang.invoke.MethodHandle; | |
| 6 | import java.lang.invoke.MethodHandles; | |
| 7 | import java.lang.reflect.Constructor; | |
| 8 | import java.util.HashMap; | |
| 9 | import java.util.List; | |
| 10 | import java.util.Map; | |
| 11 | ||
| 12 | import static java.lang.String.format; | |
| 13 | ||
| 14 | /** | |
| 15 | * Immutable binding of parsed field values to the canonical constructor of a Java | |
| 16 | * {@code record}: the constructor {@link MethodHandle} plus a precomputed map from each | |
| 17 | * load {@link FieldDescriptor} to its parameter position (component accessor name equals | |
| 18 | * canonical-constructor parameter name). | |
| 19 | * | |
| 20 | * <p>Built once per record class and cached by {@link ClassMetadataCache}. The | |
| 21 | * per-{@code load()} cost is one {@code Object[]} allocation and a single constructor | |
| 22 | * invoke through the handle — the same invocation mechanism the setter path uses. | |
| 23 | * | |
| 24 | * <p>A load descriptor with no matching component (e.g. an annotated derived accessor | |
| 25 | * declared in the record body) is left unmapped and its parsed value is dropped — the same | |
| 26 | * semantics as a {@code @Field} getter without a setter on a conventional POJO. | |
| 27 | * | |
| 28 | * @author Jacob von Eyben - <a href="https://eybenconsult.com">https://eybenconsult.com</a> | |
| 29 | * @since 1.9.0 | |
| 30 | */ | |
| 31 | final class ConstructorBinding { | |
| 32 | ||
| 33 | private final MethodHandle constructorHandle; | |
| 34 | private final Object[] defaults; | |
| 35 | private final Map<FieldDescriptor, Integer> parameterIndexByDescriptor; | |
| 36 | ||
| 37 | private ConstructorBinding(MethodHandle constructorHandle, Object[] defaults, | |
| 38 | Map<FieldDescriptor, Integer> parameterIndexByDescriptor) { | |
| 39 |
1
1. <init> : Removed assignment to member variable constructorHandle → NO_COVERAGE |
this.constructorHandle = constructorHandle; |
| 40 |
1
1. <init> : Removed assignment to member variable defaults → NO_COVERAGE |
this.defaults = defaults; |
| 41 |
1
1. <init> : Removed assignment to member variable parameterIndexByDescriptor → NO_COVERAGE |
this.parameterIndexByDescriptor = parameterIndexByDescriptor; |
| 42 | } | |
| 43 | ||
| 44 | static ConstructorBinding forRecord(Class<?> recordClass, List<FieldDescriptor> descriptors) { | |
| 45 |
1
1. forRecord : removed call to com/ancientprogramming/fixedformat4j/format/impl/JavaRecordSupport::components → NO_COVERAGE |
List<JavaRecordSupport.ComponentInfo> components = JavaRecordSupport.components(recordClass); |
| 46 | ||
| 47 |
1
1. forRecord : removed call to java/util/List::size → NO_COVERAGE |
Class<?>[] parameterTypes = new Class<?>[components.size()]; |
| 48 |
1
1. forRecord : removed call to java/util/List::size → NO_COVERAGE |
Object[] defaults = new Object[components.size()]; |
| 49 |
4
1. forRecord : removed call to java/util/HashMap::<init> → NO_COVERAGE 2. forRecord : removed call to java/util/List::size → NO_COVERAGE 3. forRecord : Substituted 2 with 3 → NO_COVERAGE 4. forRecord : Replaced integer multiplication with division → NO_COVERAGE |
Map<String, Integer> indexByComponentName = new HashMap<>(components.size() * 2); |
| 50 |
6
1. forRecord : removed conditional - replaced comparison check with true → NO_COVERAGE 2. forRecord : negated conditional → NO_COVERAGE 3. forRecord : Substituted 0 with 1 → NO_COVERAGE 4. forRecord : removed call to java/util/List::size → NO_COVERAGE 5. forRecord : removed conditional - replaced comparison check with false → NO_COVERAGE 6. forRecord : changed conditional boundary → NO_COVERAGE |
for (int i = 0; i < components.size(); i++) { |
| 51 |
1
1. forRecord : removed call to java/util/List::get → NO_COVERAGE |
JavaRecordSupport.ComponentInfo component = components.get(i); |
| 52 | parameterTypes[i] = component.type; | |
| 53 |
1
1. forRecord : removed call to com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE |
defaults[i] = primitiveDefault(component.type); |
| 54 |
3
1. forRecord : replaced call to java/util/Map::put with argument → NO_COVERAGE 2. forRecord : removed call to java/util/Map::put → NO_COVERAGE 3. forRecord : removed call to java/lang/Integer::valueOf → NO_COVERAGE |
indexByComponentName.put(component.name, i); |
| 55 | } | |
| 56 | ||
| 57 |
4
1. forRecord : Replaced integer multiplication with division → NO_COVERAGE 2. forRecord : Substituted 2 with 3 → NO_COVERAGE 3. forRecord : removed call to java/util/HashMap::<init> → NO_COVERAGE 4. forRecord : removed call to java/util/List::size → NO_COVERAGE |
Map<FieldDescriptor, Integer> parameterIndexByDescriptor = new HashMap<>(descriptors.size() * 2); |
| 58 | for (FieldDescriptor desc : descriptors) { | |
| 59 |
3
1. forRecord : removed call to java/util/Map::get → NO_COVERAGE 2. forRecord : removed call to java/lang/reflect/Method::getName → NO_COVERAGE 3. forRecord : replaced call to java/util/Map::get with argument → NO_COVERAGE |
Integer index = indexByComponentName.get(desc.target.getter.getName()); |
| 60 |
6
1. forRecord : removed conditional - replaced equality check with false → NO_COVERAGE 2. forRecord : negated conditional → NO_COVERAGE 3. forRecord : negated conditional → NO_COVERAGE 4. forRecord : removed conditional - replaced equality check with false → NO_COVERAGE 5. forRecord : removed conditional - replaced equality check with true → NO_COVERAGE 6. forRecord : removed conditional - replaced equality check with true → NO_COVERAGE |
if (desc.isLoadField && index != null) { |
| 61 |
2
1. forRecord : removed call to java/util/Map::put → NO_COVERAGE 2. forRecord : replaced call to java/util/Map::put with argument → NO_COVERAGE |
parameterIndexByDescriptor.put(desc, index); |
| 62 | } | |
| 63 | } | |
| 64 | ||
| 65 |
3
1. forRecord : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::forRecord → NO_COVERAGE 2. forRecord : removed call to com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::canonicalConstructorHandle → NO_COVERAGE 3. forRecord : removed call to com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::<init> → NO_COVERAGE |
return new ConstructorBinding(canonicalConstructorHandle(recordClass, parameterTypes), |
| 66 | defaults, parameterIndexByDescriptor); | |
| 67 | } | |
| 68 | ||
| 69 | /** Returns a fresh argument array pre-filled with primitive defaults ({@code 0}, {@code false}, …). */ | |
| 70 | Object[] newArgs() { | |
| 71 |
2
1. newArgs : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::newArgs → NO_COVERAGE 2. newArgs : removed call to [Ljava/lang/Object;::clone → NO_COVERAGE |
return defaults.clone(); |
| 72 | } | |
| 73 | ||
| 74 | /** | |
| 75 | * Writes a parsed value into the argument slot bound to the given descriptor. A | |
| 76 | * {@code null} value or an unbound descriptor leaves the slot at its default — mirroring | |
| 77 | * the setter path, which skips the setter in both cases. | |
| 78 | */ | |
| 79 | void assign(FieldDescriptor desc, Object value, Object[] args) { | |
| 80 |
2
1. assign : removed call to java/util/Map::get → NO_COVERAGE 2. assign : replaced call to java/util/Map::get with argument → NO_COVERAGE |
Integer index = parameterIndexByDescriptor.get(desc); |
| 81 |
6
1. assign : negated conditional → NO_COVERAGE 2. assign : removed conditional - replaced equality check with false → NO_COVERAGE 3. assign : removed conditional - replaced equality check with true → NO_COVERAGE 4. assign : negated conditional → NO_COVERAGE 5. assign : removed conditional - replaced equality check with false → NO_COVERAGE 6. assign : removed conditional - replaced equality check with true → NO_COVERAGE |
if (index != null && value != null) { |
| 82 |
1
1. assign : removed call to java/lang/Integer::intValue → NO_COVERAGE |
args[index] = value; |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | Object newInstance(Class<?> recordClass, Object[] args) { | |
| 87 | try { | |
| 88 |
2
1. newInstance : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::newInstance → NO_COVERAGE 2. newInstance : removed call to java/lang/invoke/MethodHandle::invokeWithArguments → NO_COVERAGE |
return constructorHandle.invokeWithArguments(args); |
| 89 | } catch (Throwable e) { | |
| 90 |
2
1. newInstance : Substituted 0 with 1 → NO_COVERAGE 2. newInstance : Substituted 1 with 0 → NO_COVERAGE |
throw new FixedFormatException( |
| 91 |
4
1. newInstance : replaced call to java/lang/String::format with argument → NO_COVERAGE 2. newInstance : removed call to java/lang/Class::getName → NO_COVERAGE 3. newInstance : removed call to java/lang/String::format → NO_COVERAGE 4. newInstance : removed call to com/ancientprogramming/fixedformat4j/exception/FixedFormatException::<init> → NO_COVERAGE |
format("unable to create instance of %s through its canonical constructor", recordClass.getName()), e); |
| 92 | } | |
| 93 | } | |
| 94 | ||
| 95 | private static MethodHandle canonicalConstructorHandle(Class<?> recordClass, Class<?>[] parameterTypes) { | |
| 96 | try { | |
| 97 |
1
1. canonicalConstructorHandle : removed call to java/lang/Class::getDeclaredConstructor → NO_COVERAGE |
Constructor<?> canonical = recordClass.getDeclaredConstructor(parameterTypes); |
| 98 |
3
1. canonicalConstructorHandle : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::canonicalConstructorHandle → NO_COVERAGE 2. canonicalConstructorHandle : removed call to java/lang/invoke/MethodHandles$Lookup::unreflectConstructor → NO_COVERAGE 3. canonicalConstructorHandle : removed call to java/lang/invoke/MethodHandles::lookup → NO_COVERAGE |
return MethodHandles.lookup().unreflectConstructor(canonical); |
| 99 | } catch (NoSuchMethodException | IllegalAccessException e) { | |
| 100 |
2
1. canonicalConstructorHandle : Substituted 1 with 0 → NO_COVERAGE 2. canonicalConstructorHandle : Substituted 0 with 1 → NO_COVERAGE |
throw new FixedFormatException( |
| 101 |
4
1. canonicalConstructorHandle : removed call to com/ancientprogramming/fixedformat4j/exception/FixedFormatException::<init> → NO_COVERAGE 2. canonicalConstructorHandle : removed call to java/lang/Class::getName → NO_COVERAGE 3. canonicalConstructorHandle : replaced call to java/lang/String::format with argument → NO_COVERAGE 4. canonicalConstructorHandle : removed call to java/lang/String::format → NO_COVERAGE |
format("unable to access the canonical constructor of %s", recordClass.getName()), e); |
| 102 | } | |
| 103 | } | |
| 104 | ||
| 105 | private static Object primitiveDefault(Class<?> type) { | |
| 106 |
4
1. primitiveDefault : negated conditional → NO_COVERAGE 2. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 3. primitiveDefault : removed call to java/lang/Class::isPrimitive → NO_COVERAGE 4. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE |
if (!type.isPrimitive()) return null; |
| 107 |
4
1. primitiveDefault : negated conditional → NO_COVERAGE 2. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 3. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 4. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE |
if (type == boolean.class) return Boolean.FALSE; |
| 108 |
6
1. primitiveDefault : negated conditional → NO_COVERAGE 2. primitiveDefault : removed call to java/lang/Character::valueOf → NO_COVERAGE 3. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 4. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 5. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE 6. primitiveDefault : Substituted 0 with 1 → NO_COVERAGE |
if (type == char.class) return (char) 0; |
| 109 |
6
1. primitiveDefault : removed call to java/lang/Byte::valueOf → NO_COVERAGE 2. primitiveDefault : Substituted 0 with 1 → NO_COVERAGE 3. primitiveDefault : negated conditional → NO_COVERAGE 4. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 5. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 6. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE |
if (type == byte.class) return (byte) 0; |
| 110 |
6
1. primitiveDefault : Substituted 0 with 1 → NO_COVERAGE 2. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 3. primitiveDefault : negated conditional → NO_COVERAGE 4. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 5. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE 6. primitiveDefault : removed call to java/lang/Short::valueOf → NO_COVERAGE |
if (type == short.class) return (short) 0; |
| 111 |
6
1. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 2. primitiveDefault : negated conditional → NO_COVERAGE 3. primitiveDefault : Substituted 0 with 1 → NO_COVERAGE 4. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 5. primitiveDefault : removed call to java/lang/Integer::valueOf → NO_COVERAGE 6. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE |
if (type == int.class) return 0; |
| 112 |
6
1. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 2. primitiveDefault : Substituted 0 with 1 → NO_COVERAGE 3. primitiveDefault : negated conditional → NO_COVERAGE 4. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 5. primitiveDefault : removed call to java/lang/Long::valueOf → NO_COVERAGE 6. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE |
if (type == long.class) return 0L; |
| 113 |
6
1. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 2. primitiveDefault : Substituted 0.0 with 1.0 → NO_COVERAGE 3. primitiveDefault : negated conditional → NO_COVERAGE 4. primitiveDefault : removed call to java/lang/Float::valueOf → NO_COVERAGE 5. primitiveDefault : removed conditional - replaced equality check with false → NO_COVERAGE 6. primitiveDefault : removed conditional - replaced equality check with true → NO_COVERAGE |
if (type == float.class) return 0F; |
| 114 |
3
1. primitiveDefault : Substituted 0.0 with 1.0 → NO_COVERAGE 2. primitiveDefault : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/ConstructorBinding::primitiveDefault → NO_COVERAGE 3. primitiveDefault : removed call to java/lang/Double::valueOf → NO_COVERAGE |
return 0D; |
| 115 | } | |
| 116 | } | |
Mutations | ||
| 39 |
1.1 |
|
| 40 |
1.1 |
|
| 41 |
1.1 |
|
| 45 |
1.1 |
|
| 47 |
1.1 |
|
| 48 |
1.1 |
|
| 49 |
1.1 2.2 3.3 4.4 |
|
| 50 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 51 |
1.1 |
|
| 53 |
1.1 |
|
| 54 |
1.1 2.2 3.3 |
|
| 57 |
1.1 2.2 3.3 4.4 |
|
| 59 |
1.1 2.2 3.3 |
|
| 60 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 61 |
1.1 2.2 |
|
| 65 |
1.1 2.2 3.3 |
|
| 71 |
1.1 2.2 |
|
| 80 |
1.1 2.2 |
|
| 81 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 82 |
1.1 |
|
| 88 |
1.1 2.2 |
|
| 90 |
1.1 2.2 |
|
| 91 |
1.1 2.2 3.3 4.4 |
|
| 97 |
1.1 |
|
| 98 |
1.1 2.2 3.3 |
|
| 100 |
1.1 2.2 |
|
| 101 |
1.1 2.2 3.3 4.4 |
|
| 106 |
1.1 2.2 3.3 4.4 |
|
| 107 |
1.1 2.2 3.3 4.4 |
|
| 108 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 109 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 110 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 111 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 112 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 113 |
1.1 2.2 3.3 4.4 5.5 6.6 |
|
| 114 |
1.1 2.2 3.3 |