Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions make/ToolsJdk.gmk
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ TOOL_OSX_TOBIN = $(JAVA_SMALL) -Djava.awt.headless=true -cp $(BUILDTOOLS_OUTPUTD
TOOL_CLDRCONVERTER = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
build.tools.cldrconverter.CLDRConverter

TOOL_DTPFACTORYGENERATOR = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
build.tools.dtpfactorygenerator.DateTimePrinterParserFactoryGenerator

TOOL_INTPOLY = $(JAVA_SMALL) -cp $(BUILDTOOLS_OUTPUTDIR)/jdk_tools_classes \
build.tools.intpoly.FieldGen

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package build.tools.dtpfactorygenerator;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
* A standalone generator to create DateTimePrinterParserFactory.java from template.
* This separates the datetime printer/parser factory generation from the CLDR converter.
*/
public class DateTimePrinterParserFactoryGenerator {

private static String templateFile;
private static String destinationDir = "build/gensrc";

public static void main(String[] args) throws Exception {
if (args.length != 0) {
String currentArg = null;
try {
for (int i = 0; i < args.length; i++) {
currentArg = args[i];
switch (currentArg) {
case "-dtpftempfile":
templateFile = args[++i];
break;
case "-o":
destinationDir = args[++i];
break;
case "-help":
usage();
System.exit(0);
break;
default:
throw new RuntimeException();
}
}
} catch (RuntimeException e) {
System.err.println("Error: unknown or incomplete arg(s): " + currentArg);
usage();
System.exit(1);
}
}

if (templateFile == null) {
System.err.println("Error: -dtpftempfile is required");
usage();
System.exit(1);
}

// Generate java.time.format.DateTimePrinterParserFactory.java
generateDateTimePrinterParserFactory();
}

private static void usage() {
System.err.println("Usage: java DateTimePrinterParserFactoryGenerator [options]");
System.err.println("\t-help output this usage message and exit");
System.err.println("\t-dtpftempfile template file for java.time.format.DateTimePrinterParserFactory.java");
System.err.println("\t-o dir output directory (default: ./build/gensrc)");
}

private static void generateDateTimePrinterParserFactory() throws Exception {
Files.createDirectories(Paths.get(destinationDir, "java", "time", "format"));
Files.write(Paths.get(destinationDir, "java", "time", "format", "DateTimePrinterParserFactory.java"),
Files.lines(Paths.get(templateFile))
.flatMap(l -> {
if (l.startsWith("%%%%CASES-FORMAT:")) {
return generateDateTimePrinterCases(l, "%%%%CASES-FORMAT:", false); // formatter cases
} else if (l.startsWith("%%%%CASES-PARSE:")) {
return generateDateTimePrinterCases(l, "%%%%CASES-PARSE:", true); // parser cases
} else {
return Stream.of(l);
}
})
.collect(Collectors.toList()),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}

private static Stream<String> generateDateTimePrinterCases(String line, String prefix, boolean isParse) {
// Parse the range, defaulting to 1-16 if no range is specified
int start = 1;
int end = 16;

String rangePart = line.substring(prefix.length(), line.length() - 4); // Remove trailing%%%%
String[] parts = rangePart.split("-");
if (parts.length == 2) {
try {
start = Integer.parseInt(parts[0]);
end = Integer.parseInt(parts[1]);
} catch (NumberFormatException e) {
// Use defaults if parsing fails
}
}

return IntStream.rangeClosed(start, end)
.mapToObj(i -> {
if (isParse) {
// Generate parser cases
var sb = new StringBuilder(" case ").append(i).append(" -> (context, text, position)\n");
if (i == 1) {
// Special case for 1 - direct method reference used instead
// This shouldn't happen since case 1 is handled separately in the template
sb.append(" -> printerParsers[0].parse(context, text, position);");
} else {
// For i >= 2, build the sequence
sb.append(" -> (position = printerParsers[0].parse(context, text, position)) < 0\n");
for (int j = 1; j < i - 1; j++) {
sb.append(" ? position : (position = printerParsers[").append(j).append("].parse(context, text, position)) < 0\n");
}
// The last parser in the chain doesn't check for failure, just returns its result
if (i > 1) {
sb.append(" ? position : printerParsers[").append(i - 1).append("].parse(context, text, position);");
}
}
return sb.toString();
} else {
// Generate formatter cases (original behavior)
var sb = new StringBuilder(" case ").append(i).append(" -> (context, buf, optional)\n")
.append(" -> ");
for (int j = 0; j < i; j++) {
sb.append("printerParsers[").append(j).append("].format(context, buf, optional)");
if (j < i - 1) {
sb.append("\n && ");
}
}
return sb.append(";").toString();
}
});
}
}
17 changes: 16 additions & 1 deletion make/modules/java.base/Gensrc.gmk
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ include gensrc/GensrcVarHandles.gmk
CLDR_DATA_DIR := $(TOPDIR)/make/data/cldr/common
GENSRC_DIR := $(SUPPORT_OUTPUTDIR)/gensrc/java.base
CLDR_GEN_DONE := $(GENSRC_DIR)/_cldr-gensrc.marker
DTPFACTORY_GEN_DONE := $(GENSRC_DIR)/_dtpfactory-gensrc.marker
TZ_DATA_DIR := $(MODULE_SRC)/share/data/tzdata
ZONENAME_TEMPLATE := $(MODULE_SRC)/share/classes/java/time/format/ZoneName.java.template
DTPFACTORY_TEMPLATE := $(MODULE_SRC)/share/classes/java/time/format/DateTimePrinterParserFactory.java.template

# The `-utf8` option is used even for US English, as some names
# may contain non-ASCII characters, such as “Türkiye”.
Expand All @@ -68,7 +70,20 @@ $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \
-utf8)
$(TOUCH) $@

TARGETS += $(CLDR_GEN_DONE)
# Generate DateTimePrinterParserFactory.java from template
$(DTPFACTORY_GEN_DONE): $(DTPFACTORY_TEMPLATE) $(BUILD_TOOLS_JDK)
$(call MakeDir, $(GENSRC_DIR))
$(call LogInfo, Processing DateTimePrinterParserFactory template for java.base)
$(call ExecuteWithLog, $@, \
$(TOOL_DTPFACTORYGENERATOR) \
-dtpftempfile $(DTPFACTORY_TEMPLATE) \
-o $(GENSRC_DIR))
$(TOUCH) $@

# Ensure both CLDR and DateTimePrinterParserFactory generation complete before continuing
$(CLDR_GEN_DONE) $(DTPFACTORY_GEN_DONE):

TARGETS += $(CLDR_GEN_DONE) $(DTPFACTORY_GEN_DONE)

################################################################################

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2447,34 +2447,7 @@ private DateTimeFormatter toFormatter(Locale locale, ResolverStyle resolverStyle
}

//-----------------------------------------------------------------------
/**
* Strategy for formatting/parsing date-time information.
* <p>
* The printer may format any part, or the whole, of the input date-time object.
* Typically, a complete format is constructed from a number of smaller
* units, each outputting a single field.
* <p>
* The parser may parse any piece of text from the input, storing the result
* in the context. Typically, each individual parser will just parse one
* field, such as the day-of-month, storing the value in the context.
* Once the parse is complete, the caller will then resolve the parsed values
* to create the desired object, such as a {@code LocalDate}.
* <p>
* The parse position will be updated during the parse. Parsing will start at
* the specified index and the return value specifies the new parse position
* for the next parser. If an error occurs, the returned index will be negative
* and will have the error position encoded using the complement operator.
*
* @implSpec
* This interface must be implemented with care to ensure other classes operate correctly.
* All implementations that can be instantiated must be final, immutable and thread-safe.
* <p>
* The context is not a thread-safe object and a new instance will be created
* for each format that occurs. The context must not be stored in an instance
* variable or shared with any other threads.
*/
interface DateTimePrinterParser {

interface DateTimePrinter {
/**
* Prints the date-time object to the buffer.
* <p>
Expand All @@ -2494,6 +2467,9 @@ interface DateTimePrinterParser {
* @throws DateTimeException if the date-time cannot be printed successfully
*/
boolean format(DateTimePrintContext context, StringBuilder buf, boolean optional);
}

interface DateTimeParser {

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

/**
* Strategy for formatting/parsing date-time information.
* <p>
* The printer may format any part, or the whole, of the input date-time object.
* Typically, a complete format is constructed from a number of smaller
* units, each outputting a single field.
* <p>
* The parser may parse any piece of text from the input, storing the result
* in the context. Typically, each individual parser will just parse one
* field, such as the day-of-month, storing the value in the context.
* Once the parse is complete, the caller will then resolve the parsed values
* to create the desired object, such as a {@code LocalDate}.
* <p>
* The parse position will be updated during the parse. Parsing will start at
* the specified index and the return value specifies the new parse position
* for the next parser. If an error occurs, the returned index will be negative
* and will have the error position encoded using the complement operator.
*
* @implSpec
* This interface must be implemented with care to ensure other classes operate correctly.
* All implementations that can be instantiated must be final, immutable and thread-safe.
* <p>
* The context is not a thread-safe object and a new instance will be created
* for each format that occurs. The context must not be stored in an instance
* variable or shared with any other threads.
*/
interface DateTimePrinterParser extends DateTimePrinter, DateTimeParser {
}

//-----------------------------------------------------------------------
/**
* Composite printer and parser.
*/
static final class CompositePrinterParser implements DateTimePrinterParser {
private final DateTimePrinterParser[] printerParsers;
private final boolean optional;
private final DateTimePrinter formatter;
private final DateTimeParser parser;

private CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boolean optional) {
this(printerParsers.toArray(new DateTimePrinterParser[0]), optional);
Expand All @@ -2527,6 +2534,8 @@ private CompositePrinterParser(List<DateTimePrinterParser> printerParsers, boole
private CompositePrinterParser(DateTimePrinterParser[] printerParsers, boolean optional) {
this.printerParsers = printerParsers;
this.optional = optional;
this.formatter = DateTimePrinterParserFactory.createFormatter(printerParsers);
this.parser = DateTimePrinterParserFactory.createParser(printerParsers, optional);
}

/**
Expand All @@ -2546,38 +2555,16 @@ public CompositePrinterParser withOptional(boolean optional) {
public boolean format(DateTimePrintContext context, StringBuilder buf, boolean optional) {
int length = buf.length();
boolean effectiveOptional = optional | this.optional;
for (DateTimePrinterParser pp : printerParsers) {
if (!pp.format(context, buf, effectiveOptional)) {
buf.setLength(length); // reset buffer
return true;
}
if (!formatter.format(context, buf, effectiveOptional)) {
buf.setLength(length); // reset buffer
return true;
}
return true;
}

@Override
public int parse(DateTimeParseContext context, CharSequence text, int position) {
if (optional) {
context.startOptional();
int pos = position;
for (DateTimePrinterParser pp : printerParsers) {
pos = pp.parse(context, text, pos);
if (pos < 0) {
context.endOptional(false);
return position; // return original position
}
}
context.endOptional(true);
return pos;
} else {
for (DateTimePrinterParser pp : printerParsers) {
position = pp.parse(context, text, position);
if (position < 0) {
break;
}
}
return position;
}
return parser.parse(context, text, position);
}

@Override
Expand Down
Loading