Skip to content

Commit cba3b5f

Browse files
committed
Use garbage-free formatter for s and S patterns
This PR improves #3139, by introducing a new `InstantPatternFormatter` for patterns of the form "ss\.S{n}". Unlike the previous formatter based on `DateTimeFormatter`, the formatter is garbage-free. We also simplify the merging algorithm for pattern formatter factories, by moving the merging logic to the pattern formatter factories themselves. This PR does not contain a separate change log entry, since #3139 has not been published yet. Fixes #3337.
1 parent 2143f84 commit cba3b5f

File tree

2 files changed

+314
-390
lines changed

2 files changed

+314
-390
lines changed

log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/instant/InstantPatternDynamicFormatterTest.java

Lines changed: 34 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.apache.logging.log4j.core.util.internal.instant;
1818

1919
import static java.util.Arrays.asList;
20-
import static java.util.Collections.singletonList;
2120
import static org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.sequencePattern;
2221
import static org.assertj.core.api.Assertions.assertThat;
2322

@@ -32,10 +31,9 @@
3231
import java.util.stream.IntStream;
3332
import java.util.stream.Stream;
3433
import org.apache.logging.log4j.core.time.MutableInstant;
35-
import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.CompositePatternSequence;
36-
import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.DynamicPatternSequence;
37-
import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.PatternSequence;
38-
import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.StaticPatternSequence;
34+
import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.DateTimeFormatterPatternFormatterFactory;
35+
import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.PatternFormatterFactory;
36+
import org.apache.logging.log4j.core.util.internal.instant.InstantPatternDynamicFormatter.SecondPatternFormatterFactory;
3937
import org.apache.logging.log4j.util.Constants;
4038
import org.junit.jupiter.params.ParameterizedTest;
4139
import org.junit.jupiter.params.provider.Arguments;
@@ -47,112 +45,81 @@ class InstantPatternDynamicFormatterTest {
4745
@ParameterizedTest
4846
@MethodSource("sequencingTestCases")
4947
void sequencing_should_work(
50-
final String pattern, final ChronoUnit thresholdPrecision, final List<PatternSequence> expectedSequences) {
51-
final List<PatternSequence> actualSequences = sequencePattern(pattern, thresholdPrecision);
48+
final String pattern,
49+
final ChronoUnit thresholdPrecision,
50+
final List<PatternFormatterFactory> expectedSequences) {
51+
final List<PatternFormatterFactory> actualSequences = sequencePattern(pattern, thresholdPrecision);
5252
assertThat(actualSequences).isEqualTo(expectedSequences);
5353
}
5454

5555
static List<Arguments> sequencingTestCases() {
5656
final List<Arguments> testCases = new ArrayList<>();
5757

5858
// `SSSX` should be treated constant for daily updates
59-
testCases.add(Arguments.of("SSSX", ChronoUnit.DAYS, singletonList(pCom(pDyn("SSS"), pDyn("X")))));
59+
testCases.add(Arguments.of("SSSX", ChronoUnit.DAYS, asList(pMilliSec(), pDyn("X"))));
6060

6161
// `yyyyMMddHHmmssSSSX` instant cache updated hourly
6262
testCases.add(Arguments.of(
6363
"yyyyMMddHHmmssSSSX",
6464
ChronoUnit.HOURS,
65-
asList(
66-
pCom(pDyn("yyyy"), pDyn("MM"), pDyn("dd"), pDyn("HH")),
67-
pCom(pDyn("mm"), pDyn("ss"), pDyn("SSS")),
68-
pDyn("X"))));
65+
asList(pDyn("yyyyMMddHH", ChronoUnit.HOURS), pDyn("mm"), pSec("", 3), pDyn("X"))));
6966

7067
// `yyyyMMddHHmmssSSSX` instant cache updated per minute
7168
testCases.add(Arguments.of(
7269
"yyyyMMddHHmmssSSSX",
7370
ChronoUnit.MINUTES,
74-
asList(
75-
pCom(pDyn("yyyy"), pDyn("MM"), pDyn("dd"), pDyn("HH"), pDyn("mm")),
76-
pCom(pDyn("ss"), pDyn("SSS")),
77-
pDyn("X"))));
71+
asList(pDyn("yyyyMMddHHmm", ChronoUnit.MINUTES), pSec("", 3), pDyn("X"))));
7872

7973
// ISO9601 instant cache updated daily
8074
final String iso8601InstantPattern = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
8175
testCases.add(Arguments.of(
8276
iso8601InstantPattern,
8377
ChronoUnit.DAYS,
8478
asList(
85-
pCom(pDyn("yyyy"), pSta("-"), pDyn("MM"), pSta("-"), pDyn("dd"), pSta("T")),
86-
pCom(
87-
pDyn("HH"),
88-
pSta(":"),
89-
pDyn("mm"),
90-
pSta(":"),
91-
pDyn("ss"),
92-
pSta("."),
93-
pDyn("SSS"),
94-
pDyn("X")))));
79+
pDyn("yyyy'-'MM'-'dd'T'", ChronoUnit.DAYS),
80+
pDyn("HH':'mm':'", ChronoUnit.MINUTES),
81+
pSec(".", 3),
82+
pDyn("X"))));
9583

9684
// ISO9601 instant cache updated per minute
9785
testCases.add(Arguments.of(
9886
iso8601InstantPattern,
9987
ChronoUnit.MINUTES,
100-
asList(
101-
pCom(
102-
pDyn("yyyy"),
103-
pSta("-"),
104-
pDyn("MM"),
105-
pSta("-"),
106-
pDyn("dd"),
107-
pSta("T"),
108-
pDyn("HH"),
109-
pSta(":"),
110-
pDyn("mm"),
111-
pSta(":")),
112-
pCom(pDyn("ss"), pSta("."), pDyn("SSS")),
113-
pDyn("X"))));
88+
asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(".", 3), pDyn("X"))));
11489

11590
// ISO9601 instant cache updated per second
11691
testCases.add(Arguments.of(
11792
iso8601InstantPattern,
11893
ChronoUnit.SECONDS,
119-
asList(
120-
pCom(
121-
pDyn("yyyy"),
122-
pSta("-"),
123-
pDyn("MM"),
124-
pSta("-"),
125-
pDyn("dd"),
126-
pSta("T"),
127-
pDyn("HH"),
128-
pSta(":"),
129-
pDyn("mm"),
130-
pSta(":"),
131-
pDyn("ss"),
132-
pSta(".")),
133-
pDyn("SSS"),
134-
pDyn("X"))));
94+
asList(pDyn("yyyy'-'MM'-'dd'T'HH':'mm':'", ChronoUnit.MINUTES), pSec(".", 3), pDyn("X"))));
95+
96+
// Seconds and micros
97+
testCases.add(Arguments.of(
98+
"HH:mm:ss.SSSSSS", ChronoUnit.MINUTES, asList(pDyn("HH':'mm':'", ChronoUnit.MINUTES), pSec(".", 6))));
13599

136100
return testCases;
137101
}
138102

139-
private static CompositePatternSequence pCom(final PatternSequence... sequences) {
140-
return new CompositePatternSequence(asList(sequences));
103+
private static DateTimeFormatterPatternFormatterFactory pDyn(final String pattern) {
104+
return new DateTimeFormatterPatternFormatterFactory(pattern);
105+
}
106+
107+
private static DateTimeFormatterPatternFormatterFactory pDyn(final String pattern, final ChronoUnit precision) {
108+
return new DateTimeFormatterPatternFormatterFactory(pattern, precision);
141109
}
142110

143-
private static DynamicPatternSequence pDyn(final String pattern) {
144-
return new DynamicPatternSequence(pattern);
111+
private static SecondPatternFormatterFactory pSec(String separator, int fractionalDigits) {
112+
return new SecondPatternFormatterFactory(true, separator, fractionalDigits);
145113
}
146114

147-
private static StaticPatternSequence pSta(final String literal) {
148-
return new StaticPatternSequence(literal);
115+
private static SecondPatternFormatterFactory pMilliSec() {
116+
return new SecondPatternFormatterFactory(false, "", 3);
149117
}
150118

151119
@ParameterizedTest
152120
@ValueSource(
153121
strings = {
154122
// Basics
155-
"S",
156123
"SSSSSSS",
157124
"SSSSSSSSS",
158125
"n",
@@ -163,8 +130,7 @@ private static StaticPatternSequence pSta(final String literal) {
163130
"yyyy-MM-dd HH:mm:ss,SSSSSSS",
164131
"yyyy-MM-dd HH:mm:ss,SSSSSSSS",
165132
"yyyy-MM-dd HH:mm:ss,SSSSSSSSS",
166-
"yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS",
167-
"yyyy-MM-dd'T'HH:mm:ss.SXXX"
133+
"yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS"
168134
})
169135
void should_recognize_patterns_of_nano_precision(final String pattern) {
170136
assertPatternPrecision(pattern, ChronoUnit.NANOS);
@@ -351,7 +317,9 @@ static Stream<Arguments> formatterInputs(final String pattern) {
351317

352318
private static MutableInstant randomInstant() {
353319
final MutableInstant instant = new MutableInstant();
354-
final long epochSecond = RANDOM.nextInt(1_621_280_470); // 2021-05-17 21:41:10
320+
// In the 1970's some time zones had sub-minute offsets to UTC, e.g., Africa/Monrovia
321+
final int startEighties = 315_532_800;
322+
final long epochSecond = startEighties + RANDOM.nextInt(1_621_280_470 - startEighties); // 2021-05-17 21:41:10
355323
final int epochSecondNano = randomNanos();
356324
instant.initFromEpochSecond(epochSecond, epochSecondNano);
357325
return instant;

0 commit comments

Comments
 (0)