Skip to content

Commit b254c01

Browse files
committed
unrolling
1 parent 9db0a6d commit b254c01

File tree

4 files changed

+329
-55
lines changed

4 files changed

+329
-55
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation. Oracle designates this
9+
* particular file as subject to the "Classpath" exception as provided
10+
* by Oracle in the LICENSE file that accompanied this code.
11+
*
12+
* This code is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
* version 2 for more details (a copy is included in the LICENSE file that
16+
* accompanied this code).
17+
*
18+
* You should have received a copy of the GNU General Public License version
19+
* 2 along with this work; if not, write to the Free Software Foundation,
20+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*
22+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
* or visit www.oracle.com if you need additional information or have any
24+
* questions.
25+
*/
26+
27+
package build.tools.dtpfgenerator;
28+
29+
import java.io.IOException;
30+
import java.nio.file.Files;
31+
import java.nio.file.Paths;
32+
import java.time.ZonedDateTime;
33+
import java.time.ZoneId;
34+
import java.util.stream.IntStream;
35+
import java.util.stream.Stream;
36+
import java.util.stream.Collectors;
37+
38+
/**
39+
* Generator for DateTimePrinterParserFactory class that creates optimized
40+
* formatter and parser implementations based on the number of printer parsers.
41+
*/
42+
public class DateTimePrinterParserGenerator {
43+
private static String DESTINATION_DIR = "build/gensrc";
44+
private static int copyrightYear;
45+
46+
public static void main(String[] args) throws Exception {
47+
String currentArg = null;
48+
try {
49+
for (int i = 0; i < args.length; i++) {
50+
currentArg = args[i];
51+
switch (currentArg) {
52+
case "-o":
53+
DESTINATION_DIR = args[++i];
54+
break;
55+
case "-year":
56+
copyrightYear = Integer.parseInt(args[++i]);
57+
break;
58+
default:
59+
throw new RuntimeException();
60+
}
61+
}
62+
} catch (RuntimeException e) {
63+
System.err.println("unknown or incomplete arg(s): " + currentArg);
64+
usage();
65+
System.exit(1);
66+
}
67+
68+
if (copyrightYear == 0) {
69+
copyrightYear = ZonedDateTime.now(ZoneId.of("America/Los_Angeles")).getYear();
70+
}
71+
72+
generateDateTimePrinterParserFactory();
73+
}
74+
75+
private static void usage() {
76+
System.err.println("Usage: java DateTimePrinterParserGenerator [options]");
77+
System.err.println("\t-o dir output directory (default: ./build/gensrc)");
78+
System.err.println("\t-year year copyright year in output");
79+
}
80+
81+
private static void generateDateTimePrinterParserFactory() throws Exception {
82+
String templateFile = "src/java.base/share/classes/java/time/format/DateTimePrinterParserFactory.java.template";
83+
84+
if (!Files.exists(Paths.get(templateFile))) {
85+
System.err.println("Template file does not exist: " + templateFile);
86+
return;
87+
}
88+
89+
Files.createDirectories(Paths.get(DESTINATION_DIR, "java", "time", "format"));
90+
Files.write(Paths.get(DESTINATION_DIR, "java", "time", "format", "DateTimePrinterParserFactory.java"),
91+
Files.lines(Paths.get(templateFile))
92+
.flatMap(l -> {
93+
if (l.startsWith("%%%%CASES-FORMAT:")) {
94+
return generateDateTimePrinterCases(l, "%%%%CASES-FORMAT:", false); // formatter cases
95+
} else if (l.startsWith("%%%%CASES-PARSE:")) {
96+
return generateDateTimePrinterCases(l, "%%%%CASES-PARSE:", true); // parser cases
97+
} else {
98+
return Stream.of(l);
99+
}
100+
})
101+
.collect(Collectors.toList()),
102+
java.nio.file.StandardOpenOption.CREATE, java.nio.file.StandardOpenOption.TRUNCATE_EXISTING);
103+
}
104+
105+
private static Stream<String> generateDateTimePrinterCases(String line, String prefix, boolean isParse) {
106+
// Parse the range, defaulting to 4-16 if no range is specified
107+
int start = 4;
108+
int end = 16;
109+
110+
String rangePart = line.substring(prefix.length(), line.length() - 4); // Remove trailing%%%%
111+
String[] parts = rangePart.split("-");
112+
if (parts.length == 2) {
113+
try {
114+
start = Integer.parseInt(parts[0]);
115+
end = Integer.parseInt(parts[1]);
116+
} catch (NumberFormatException e) {
117+
// Use defaults if parsing fails
118+
}
119+
}
120+
121+
return IntStream.rangeClosed(start, end)
122+
.mapToObj(i -> {
123+
if (isParse) {
124+
// Generate parser cases
125+
var sb = new StringBuilder(" case ").append(i).append(" -> (context, text, position)\n");
126+
if (i == 1) {
127+
// Special case for 1 - direct method reference used instead
128+
// This shouldn't happen since case 1 is handled separately in the template
129+
sb.append(" -> printerParsers[0].parse(context, text, position);");
130+
} else {
131+
// For i >= 2, build the sequence
132+
sb.append(" -> (position = printerParsers[0].parse(context, text, position)) < 0\n");
133+
for (int j = 1; j < i - 1; j++) {
134+
sb.append(" ? position : (position = printerParsers[").append(j).append("].parse(context, text, position)) < 0\n");
135+
}
136+
// The last parser in the chain doesn't check for failure, just returns its result
137+
if (i > 1) {
138+
sb.append(" ? position : printerParsers[").append(i - 1).append("].parse(context, text, position);");
139+
}
140+
}
141+
return sb.toString();
142+
} else {
143+
// Generate formatter cases (original behavior)
144+
var sb = new StringBuilder(" case ").append(i).append(" -> (context, buf, optional)\n")
145+
.append(" -> ");
146+
for (int j = 0; j < i; j++) {
147+
sb.append("printerParsers[").append(j).append("].format(context, buf, optional)");
148+
if (j < i - 1) {
149+
sb.append("\n && ");
150+
}
151+
}
152+
return sb.append(";").toString();
153+
}
154+
});
155+
}
156+
}

make/modules/java.base/Gensrc.gmk

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ GENSRC_DIR := $(SUPPORT_OUTPUTDIR)/gensrc/java.base
4646
CLDR_GEN_DONE := $(GENSRC_DIR)/_cldr-gensrc.marker
4747
TZ_DATA_DIR := $(MODULE_SRC)/share/data/tzdata
4848
ZONENAME_TEMPLATE := $(MODULE_SRC)/share/classes/java/time/format/ZoneName.java.template
49+
DTPFACTORY_TEMPLATE := $(MODULE_SRC)/share/classes/java/time/format/DateTimePrinterParserFactory.java.template
4950

5051
# The `-utf8` option is used even for US English, as some names
5152
# may contain non-ASCII characters, such as “Türkiye”.
@@ -54,7 +55,9 @@ $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \
5455
$(wildcard $(CLDR_DATA_DIR)/supplemental/*.xml) \
5556
$(wildcard $(TZ_DATA_DIR)/*) \
5657
$(ZONENAME_TEMPLATE) \
57-
$(BUILD_TOOLS_JDK)
58+
$(DTPFACTORY_TEMPLATE) \
59+
$(BUILD_TOOLS_JDK) \
60+
$(DTPF_GEN_DONE)
5861
$(call MakeDir, $(GENSRC_DIR))
5962
$(call LogInfo, Processing CLDR data for java.base)
6063
$(call ExecuteWithLog, $@, \
@@ -68,6 +71,17 @@ $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \
6871
-utf8)
6972
$(TOUCH) $@
7073

74+
DTPF_GEN_DONE := $(GENSRC_DIR)/_dtpf-gensrc.marker
75+
76+
$(DTPF_GEN_DONE): $(DTPFACTORY_TEMPLATE) $(BUILD_TOOLS_JDK)
77+
$(call MakeDir, $(GENSRC_DIR))
78+
$(call LogInfo, Generating DateTimePrinterParserFactory)
79+
$(call ExecuteWithLog, $@, \
80+
$(JAVA) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes build.tools.dtpfgenerator.DateTimePrinterParserGenerator \
81+
-year $(COPYRIGHT_YEAR) \
82+
-o $(GENSRC_DIR))
83+
$(TOUCH) $@
84+
7185
TARGETS += $(CLDR_GEN_DONE)
7286

7387
################################################################################

src/java.base/share/classes/java/time/format/DateTimeFormatterBuilder.java

Lines changed: 41 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,34 +2447,7 @@ private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle
24472447
}
24482448

24492449
//-----------------------------------------------------------------------
2450-
/**
2451-
* Strategy for formatting/parsing date-time information.
2452-
* <p>
2453-
* The printer may format any part, or the whole, of the input date-time object.
2454-
* Typically, a complete format is constructed from a number of smaller
2455-
* units, each outputting a single field.
2456-
* <p>
2457-
* The parser may parse any piece of text from the input, storing the result
2458-
* in the context. Typically, each individual parser will just parse one
2459-
* field, such as the day-of-month, storing the value in the context.
2460-
* Once the parse is complete, the caller will then resolve the parsed values
2461-
* to create the desired object, such as a {@code LocalDate}.
2462-
* <p>
2463-
* The parse position will be updated during the parse. Parsing will start at
2464-
* the specified index and the return value specifies the new parse position
2465-
* for the next parser. If an error occurs, the returned index will be negative
2466-
* and will have the error position encoded using the complement operator.
2467-
*
2468-
* @implSpec
2469-
* This interface must be implemented with care to ensure other classes operate correctly.
2470-
* All implementations that can be instantiated must be final, immutable and thread-safe.
2471-
* <p>
2472-
* The context is not a thread-safe object and a new instance will be created
2473-
* for each format that occurs. The context must not be stored in an instance
2474-
* variable or shared with any other threads.
2475-
*/
2476-
interface DateTimePrinterParser {
2477-
2450+
interface DateTimePrinter {
24782451
/**
24792452
* Prints the date-time object to the buffer.
24802453
* <p>
@@ -2494,6 +2467,9 @@ interface DateTimePrinterParser {
24942467
* @throws DateTimeException if the date-time cannot be printed successfully
24952468
*/
24962469
boolean format(DateTimePrintContext context, StringBuilder buf, boolean optional);
2470+
}
2471+
2472+
interface DateTimeParser {
24972473

24982474
/**
24992475
* Parses text into date-time information.
@@ -2512,13 +2488,44 @@ interface DateTimePrinterParser {
25122488
int parse(DateTimeParseContext context, CharSequence text, int position);
25132489
}
25142490

2491+
/**
2492+
* Strategy for formatting/parsing date-time information.
2493+
* <p>
2494+
* The printer may format any part, or the whole, of the input date-time object.
2495+
* Typically, a complete format is constructed from a number of smaller
2496+
* units, each outputting a single field.
2497+
* <p>
2498+
* The parser may parse any piece of text from the input, storing the result
2499+
* in the context. Typically, each individual parser will just parse one
2500+
* field, such as the day-of-month, storing the value in the context.
2501+
* Once the parse is complete, the caller will then resolve the parsed values
2502+
* to create the desired object, such as a {@code LocalDate}.
2503+
* <p>
2504+
* The parse position will be updated during the parse. Parsing will start at
2505+
* the specified index and the return value specifies the new parse position
2506+
* for the next parser. If an error occurs, the returned index will be negative
2507+
* and will have the error position encoded using the complement operator.
2508+
*
2509+
* @implSpec
2510+
* This interface must be implemented with care to ensure other classes operate correctly.
2511+
* All implementations that can be instantiated must be final, immutable and thread-safe.
2512+
* <p>
2513+
* The context is not a thread-safe object and a new instance will be created
2514+
* for each format that occurs. The context must not be stored in an instance
2515+
* variable or shared with any other threads.
2516+
*/
2517+
interface DateTimePrinterParser extends DateTimePrinter, DateTimeParser {
2518+
}
2519+
25152520
//-----------------------------------------------------------------------
25162521
/**
25172522
* Composite printer and parser.
25182523
*/
25192524
static final class CompositePrinterParser implements DateTimePrinterParser {
25202525
private final DateTimePrinterParser[] printerParsers;
25212526
private final boolean optional;
2527+
private final DateTimePrinter formatter;
2528+
private final DateTimeParser parser;
25222529

25232530
private CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) {
25242531
this(printerParsers.toArray(new DateTimePrinterParser[0]), optional);
@@ -2527,6 +2534,8 @@ private CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boole
25272534
private CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) {
25282535
this.printerParsers = printerParsers;
25292536
this.optional = optional;
2537+
this.formatter = DateTimePrinterParserFactory.createFormatter(printerParsers);
2538+
this.parser = DateTimePrinterParserFactory.createParser(printerParsers, optional);
25302539
}
25312540

25322541
/**
@@ -2546,38 +2555,16 @@ public CompositePrinterParser withOptional(boolean optional) {
25462555
public boolean format(DateTimePrintContext context, StringBuilder buf, boolean optional) {
25472556
int length = buf.length();
25482557
boolean effectiveOptional = optional | this.optional;
2549-
for (DateTimePrinterParser pp : printerParsers) {
2550-
if (!pp.format(context, buf, effectiveOptional)) {
2551-
buf.setLength(length); // reset buffer
2552-
return true;
2553-
}
2558+
if (!formatter.format(context, buf, effectiveOptional)) {
2559+
buf.setLength(length); // reset buffer
2560+
return true;
25542561
}
25552562
return true;
25562563
}
25572564

25582565
@Override
25592566
public int parse(DateTimeParseContext context, CharSequence text, int position) {
2560-
if (optional) {
2561-
context.startOptional();
2562-
int pos = position;
2563-
for (DateTimePrinterParser pp : printerParsers) {
2564-
pos = pp.parse(context, text, pos);
2565-
if (pos < 0) {
2566-
context.endOptional(false);
2567-
return position; // return original position
2568-
}
2569-
}
2570-
context.endOptional(true);
2571-
return pos;
2572-
} else {
2573-
for (DateTimePrinterParser pp : printerParsers) {
2574-
position = pp.parse(context, text, position);
2575-
if (position < 0) {
2576-
break;
2577-
}
2578-
}
2579-
return position;
2580-
}
2567+
return parser.parse(context, text, position);
25812568
}
25822569

25832570
@Override

0 commit comments

Comments
 (0)