AbstractPatternFormatter.java

1
/*
2
 * Copyright 2004 the original author or authors.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package com.ancientprogramming.fixedformat4j.format.impl;
17
18
import com.ancientprogramming.fixedformat4j.format.AbstractFixedFormatter;
19
import com.ancientprogramming.fixedformat4j.format.FormatInstructions;
20
21
import java.time.format.DateTimeFormatter;
22
import java.util.concurrent.ConcurrentHashMap;
23
24
/**
25
 * Base class for pattern-based date/time formatters.
26
 *
27
 * <p>Overrides {@link #stripPadding} to restore padding characters that belong
28
 * to the date/time value rather than to the field padding. This is necessary when
29
 * {@code paddingChar} equals a character that can also appear within a formatted date
30
 * (e.g. {@code '0'} with a pattern like {@code yyyyMMddHHmmss} where seconds may be
31
 * {@code 00}).
32
 *
33
 * <p>After stripping padding, if the result is shorter than the formatted output length
34
 * for the pattern, it is re-padded to that length so that the underlying date parser
35
 * receives a well-formed string.
36
 *
37
 * <p>The formatted length for each pattern is computed once per (formatter subclass, pattern)
38
 * pair and cached in a {@link ClassValue}-backed map. Values are automatically eligible for GC
39
 * when the formatter's defining classloader is collected, preventing classloader leaks in
40
 * hot-reload and multi-classloader environments.
41
 *
42
 * @param <T> the date/time type handled by this formatter
43
 * @author Jacob von Eyben - <a href="https://eybenconsult.com">https://eybenconsult.com</a>
44
 * @since 1.6.1
45
 */
46
public abstract class AbstractPatternFormatter<T> extends AbstractFixedFormatter<T> {
47
48
  // Keyed by formatter Class; values are collected when the formatter class's classloader is GC'd.
49
  private static final ClassValue<ConcurrentHashMap<String, Integer>> PATTERN_LENGTH_CACHE =
50
      new ClassValue<>() {
51
        @Override
52
        protected ConcurrentHashMap<String, Integer> computeValue(Class<?> type) {
53 2 1. computeValue : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter$1::computeValue → SURVIVED
2. computeValue : removed call to java/util/concurrent/ConcurrentHashMap::<init> → SURVIVED
          return new ConcurrentHashMap<>();
54
        }
55
      };
56
57
  // DateTimeFormatter is immutable and thread-safe — shared across all threads, no ThreadLocal needed.
58
  private static final ClassValue<ConcurrentHashMap<String, DateTimeFormatter>> FORMATTER_CACHE =
59
      new ClassValue<>() {
60
        @Override
61
        protected ConcurrentHashMap<String, DateTimeFormatter> computeValue(Class<?> type) {
62 2 1. computeValue : removed call to java/util/concurrent/ConcurrentHashMap::<init> → SURVIVED
2. computeValue : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter$2::computeValue → SURVIVED
          return new ConcurrentHashMap<>();
63
        }
64
      };
65
66
  /**
67
   * Strips padding, then re-applies it when the padding character overlaps with
68
   * characters that are significant in the date/time pattern.
69
   *
70
   * <p>For example, with padding {@code '0'} and pattern {@code yyyyMMddHHmmss},
71
   * a field value of {@code "00"} (seconds) would otherwise be stripped to an empty
72
   * string, making the date un-parseable. This override detects that case and
73
   * restores the stripped characters before returning.
74
   *
75
   * <p>{@inheritDoc}
76
   */
77
  @Override
78
  protected String stripPadding(String value, FormatInstructions instructions) {
79 4 1. stripPadding : removed call to com/ancientprogramming/fixedformat4j/annotation/Align::remove → KILLED
2. stripPadding : removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getAlignment → KILLED
3. stripPadding : replaced call to com/ancientprogramming/fixedformat4j/annotation/Align::remove with argument → KILLED
4. stripPadding : removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getPaddingChar → KILLED
    String stripped = instructions.getAlignment().remove(value, instructions.getPaddingChar());
80 4 1. stripPadding : removed conditional - replaced equality check with false → KILLED
2. stripPadding : negated conditional → KILLED
3. stripPadding : removed conditional - replaced equality check with true → KILLED
4. stripPadding : removed call to java/lang/String::isEmpty → KILLED
    if (stripped.isEmpty()) {
81 1 1. stripPadding : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::stripPadding → SURVIVED
      return stripped;
82
    }
83 2 1. stripPadding : removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getFixedFormatPatternData → KILLED
2. stripPadding : removed call to com/ancientprogramming/fixedformat4j/format/data/FixedFormatPatternData::getPattern → KILLED
    String pattern = instructions.getFixedFormatPatternData().getPattern();
84 1 1. stripPadding : removed call to com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::formattedLengthForPattern → KILLED
    int formattedLength = formattedLengthForPattern(pattern);
85 5 1. stripPadding : removed call to java/lang/String::length → SURVIVED
2. stripPadding : removed conditional - replaced comparison check with true → SURVIVED
3. stripPadding : changed conditional boundary → SURVIVED
4. stripPadding : negated conditional → KILLED
5. stripPadding : removed conditional - replaced comparison check with false → KILLED
    if (stripped.length() < formattedLength) {
86 5 1. stripPadding : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::stripPadding → KILLED
2. stripPadding : removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getAlignment → KILLED
3. stripPadding : removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getPaddingChar → KILLED
4. stripPadding : replaced call to com/ancientprogramming/fixedformat4j/annotation/Align::apply with argument → KILLED
5. stripPadding : removed call to com/ancientprogramming/fixedformat4j/annotation/Align::apply → KILLED
      return instructions.getAlignment().apply(stripped, formattedLength, instructions.getPaddingChar());
87
    }
88 1 1. stripPadding : replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::stripPadding → KILLED
    return stripped;
89
  }
90
91
  protected final int formattedLengthForPattern(String pattern) {
92 3 1. formattedLengthForPattern : removed call to java/lang/ClassValue::get → KILLED
2. formattedLengthForPattern : replaced int return with 0 for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::formattedLengthForPattern → KILLED
3. formattedLengthForPattern : removed call to java/lang/Object::getClass → KILLED
    return PATTERN_LENGTH_CACHE.get(getClass())
93 3 1. formattedLengthForPattern : removed call to java/lang/Integer::intValue → KILLED
2. formattedLengthForPattern : removed call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent → KILLED
3. formattedLengthForPattern : replaced call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent with argument → KILLED
        .computeIfAbsent(pattern, this::computeFormattedLengthForPattern);
94
  }
95
96
  protected final DateTimeFormatter formatterForPattern(String pattern) {
97 3 1. formatterForPattern : removed call to java/lang/Object::getClass → KILLED
2. formatterForPattern : removed call to java/lang/ClassValue::get → KILLED
3. formatterForPattern : replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::formatterForPattern → KILLED
    return FORMATTER_CACHE.get(getClass())
98 2 1. formatterForPattern : removed call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent → KILLED
2. formatterForPattern : replaced call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent with argument → KILLED
        .computeIfAbsent(pattern, DateTimeFormatter::ofPattern);
99
  }
100
101
  /**
102
   * Returns the number of characters that this formatter's pattern produces when applied
103
   * to any date/time value. Used to restore padding characters that are part of the
104
   * date/time value rather than field padding.
105
   *
106
   * <p>This method is called at most once per (formatter type, pattern) pair; results are
107
   * cached by {@link #formattedLengthForPattern}.
108
   *
109
   * @param pattern the date/time pattern string
110
   * @return the fixed character length of the formatted output
111
   */
112
  protected abstract int computeFormattedLengthForPattern(String pattern);
113
}

Mutations

53

1.1
Location : computeValue
Killed by : none
replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter$1::computeValue → SURVIVED
Covering tests

2.2
Location : computeValue
Killed by : none
removed call to java/util/concurrent/ConcurrentHashMap::<init> → SURVIVED Covering tests

62

1.1
Location : computeValue
Killed by : none
removed call to java/util/concurrent/ConcurrentHashMap::<init> → SURVIVED
Covering tests

2.2
Location : computeValue
Killed by : none
replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter$2::computeValue → SURVIVED Covering tests

79

1.1
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:testAllSpaceInputParsesToNull()]
removed call to com/ancientprogramming/fixedformat4j/annotation/Align::remove → KILLED

2.2
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:testAllSpaceInputParsesToNull()]
removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getAlignment → KILLED

3.3
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:testAllSpaceInputParsesToNull()]
replaced call to com/ancientprogramming/fixedformat4j/annotation/Align::remove with argument → KILLED

4.4
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:testAllSpaceInputParsesToNull()]
removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getPaddingChar → KILLED

80

1.1
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:testAllSpaceInputParsesToNull()]
removed conditional - replaced equality check with false → KILLED

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

3.3
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
removed conditional - replaced equality check with true → KILLED

4.4
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:testAllSpaceInputParsesToNull()]
removed call to java/lang/String::isEmpty → KILLED

81

1.1
Location : stripPadding
Killed by : none
replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::stripPadding → SURVIVED
Covering tests

83

1.1
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:rightAlignedZeroPaddingRestoresLeadingZerosInDateComponents()]
removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getFixedFormatPatternData → KILLED

2.2
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:rightAlignedZeroPaddingRestoresLeadingZerosInDateComponents()]
removed call to com/ancientprogramming/fixedformat4j/format/data/FixedFormatPatternData::getPattern → KILLED

84

1.1
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
removed call to com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::formattedLengthForPattern → KILLED

85

1.1
Location : stripPadding
Killed by : none
removed call to java/lang/String::length → SURVIVED
Covering tests

2.2
Location : stripPadding
Killed by : none
removed conditional - replaced comparison check with true → SURVIVED Covering tests

3.3
Location : stripPadding
Killed by : none
changed conditional boundary → SURVIVED Covering tests

4.4
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
negated conditional → KILLED

5.5
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
removed conditional - replaced comparison check with false → KILLED

86

1.1
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::stripPadding → KILLED

2.2
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getAlignment → KILLED

3.3
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
removed call to com/ancientprogramming/fixedformat4j/format/FormatInstructions::getPaddingChar → KILLED

4.4
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
replaced call to com/ancientprogramming/fixedformat4j/annotation/Align::apply with argument → KILLED

5.5
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
removed call to com/ancientprogramming/fixedformat4j/annotation/Align::apply → KILLED

88

1.1
Location : stripPadding
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:rightAlignedZeroPaddingRestoresLeadingZerosInDateComponents()]
replaced return value with "" for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::stripPadding → KILLED

92

1.1
Location : formattedLengthForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:rightAlignedZeroPaddingRestoresLeadingZerosInDateComponents()]
removed call to java/lang/ClassValue::get → KILLED

2.2
Location : formattedLengthForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
replaced int return with 0 for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::formattedLengthForPattern → KILLED

3.3
Location : formattedLengthForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:rightAlignedZeroPaddingRestoresLeadingZerosInDateComponents()]
removed call to java/lang/Object::getClass → KILLED

93

1.1
Location : formattedLengthForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:leftAlignedZeroPaddingDoesNotStripZeroValuedTimeComponents()]
removed call to java/lang/Integer::intValue → KILLED

2.2
Location : formattedLengthForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:rightAlignedZeroPaddingRestoresLeadingZerosInDateComponents()]
removed call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent → KILLED

3.3
Location : formattedLengthForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateFormatter]/[method:rightAlignedZeroPaddingRestoresLeadingZerosInDateComponents()]
replaced call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent with argument → KILLED

97

1.1
Location : formatterForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:computeFormattedLengthForPattern_isoPattern_returnsNineteen()]
removed call to java/lang/Object::getClass → KILLED

2.2
Location : formatterForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:computeFormattedLengthForPattern_isoPattern_returnsNineteen()]
removed call to java/lang/ClassValue::get → KILLED

3.3
Location : formatterForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:computeFormattedLengthForPattern_isoPattern_returnsNineteen()]
replaced return value with null for com/ancientprogramming/fixedformat4j/format/impl/AbstractPatternFormatter::formatterForPattern → KILLED

98

1.1
Location : formatterForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:computeFormattedLengthForPattern_isoPattern_returnsNineteen()]
removed call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent → KILLED

2.2
Location : formatterForPattern
Killed by : com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter.[engine:junit-jupiter]/[class:com.ancientprogramming.fixedformat4j.format.impl.TestLocalDateTimeFormatter]/[method:computeFormattedLengthForPattern_isoPattern_returnsNineteen()]
replaced call to java/util/concurrent/ConcurrentHashMap::computeIfAbsent with argument → KILLED

Active mutators

Tests examined


Report generated by PIT 1.23.1 support