mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-29 12:38:24 +00:00
287 lines
11 KiB
Java
287 lines
11 KiB
Java
/*
|
|
* Copyright (c) 2023, Oracle and/or its affiliates. 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 java.util;
|
|
|
|
import java.lang.invoke.MethodHandle;
|
|
import java.lang.invoke.MethodHandles;
|
|
import java.lang.invoke.MethodType;
|
|
import java.lang.StringTemplate.Processor;
|
|
import java.lang.StringTemplate.Processor.Linkage;
|
|
import java.util.regex.Matcher;
|
|
|
|
import jdk.internal.javac.PreviewFeature;
|
|
|
|
/**
|
|
* This {@link Processor} constructs a {@link String} result using
|
|
* {@link Formatter} specifications and values found in the {@link StringTemplate}.
|
|
* Unlike {@link Formatter}, {@link FormatProcessor} uses the value from the
|
|
* embedded expression that immediately follows, without whitespace, the
|
|
* <a href="../util/Formatter.html#syntax">format specifier</a>.
|
|
* For example:
|
|
* {@snippet :
|
|
* FormatProcessor fmt = FormatProcessor.create(Locale.ROOT);
|
|
* int x = 10;
|
|
* int y = 20;
|
|
* String result = fmt."%05d\{x} + %05d\{y} = %05d\{x + y}";
|
|
* }
|
|
* In the above example, the value of {@code result} will be {@code "00010 + 00020 = 00030"}.
|
|
* <p>
|
|
* Embedded expressions without a preceeding format specifier, use {@code %s}
|
|
* by default.
|
|
* {@snippet :
|
|
* FormatProcessor fmt = FormatProcessor.create(Locale.ROOT);
|
|
* int x = 10;
|
|
* int y = 20;
|
|
* String result1 = fmt."\{x} + \{y} = \{x + y}";
|
|
* String result2 = fmt."%s\{x} + %s\{y} = %s\{x + y}";
|
|
* }
|
|
* In the above example, the value of {@code result1} and {@code result2} will
|
|
* both be {@code "10 + 20 = 30"}.
|
|
* <p>
|
|
* The {@link FormatProcessor} format specification used and exceptions thrown are the
|
|
* same as those of {@link Formatter}.
|
|
* <p>
|
|
* However, there are two significant differences related to the position of arguments.
|
|
* An explict {@code n$} and relative {@code <} index will cause an exception due to
|
|
* a missing argument list.
|
|
* Whitespace appearing between the specification and the embedded expression will
|
|
* also cause an exception.
|
|
* <p>
|
|
* {@link FormatProcessor} allows the use of different locales. For example:
|
|
* {@snippet :
|
|
* Locale locale = Locale.forLanguageTag("th-TH-u-nu-thai");
|
|
* FormatProcessor thaiFMT = FormatProcessor.create(locale);
|
|
* int x = 10;
|
|
* int y = 20;
|
|
* String result = thaiFMT."%4d\{x} + %4d\{y} = %5d\{x + y}";
|
|
* }
|
|
* In the above example, the value of {@code result} will be
|
|
* {@code " \u0E51\u0E50 + \u0E52\u0E50 = \u0E53\u0E50"}.
|
|
* <p>
|
|
* For day to day use, the predefined {@link FormatProcessor#FMT} {@link FormatProcessor}
|
|
* is available. {@link FormatProcessor#FMT} is defined using the {@link Locale#ROOT}.
|
|
* Example: {@snippet :
|
|
* int x = 10;
|
|
* int y = 20;
|
|
* String result = FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}"; // @highlight substring="FMT"
|
|
* }
|
|
* In the above example, the value of {@code result} will be {@code "0x000a + 0x0014 = 0x001E"}.
|
|
*
|
|
* @since 21
|
|
*
|
|
* @see Processor
|
|
*/
|
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
|
public final class FormatProcessor implements Processor<String, RuntimeException>, Linkage {
|
|
/**
|
|
* {@link Locale} used to format
|
|
*/
|
|
private final Locale locale;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param locale {@link Locale} used to format
|
|
*/
|
|
private FormatProcessor(Locale locale) {
|
|
this.locale = locale;
|
|
}
|
|
|
|
/**
|
|
* Create a new {@link FormatProcessor} using the specified locale.
|
|
*
|
|
* @param locale {@link Locale} used to format
|
|
*
|
|
* @return a new instance of {@link FormatProcessor}
|
|
*
|
|
* @throws java.lang.NullPointerException if locale is null
|
|
*/
|
|
public static FormatProcessor create(Locale locale) {
|
|
Objects.requireNonNull(locale);
|
|
return new FormatProcessor(locale);
|
|
}
|
|
|
|
/**
|
|
* Constructs a {@link String} based on the fragments, format
|
|
* specifications found in the fragments and values in the
|
|
* supplied {@link StringTemplate} object. This method constructs a
|
|
* format string from the fragments, gathers up the values and
|
|
* evaluates the expression asif evaulating
|
|
* {@code new Formatter(locale).format(format, values).toString()}.
|
|
* <p>
|
|
* If an embedded expression is not immediately preceded by a
|
|
* specifier then a {@code %s} is inserted in the format.
|
|
*
|
|
* @param stringTemplate a {@link StringTemplate} instance
|
|
*
|
|
* @return constructed {@link String}
|
|
|
|
* @throws IllegalFormatException
|
|
* If a format specifier contains an illegal syntax, a format
|
|
* specifier that is incompatible with the given arguments,
|
|
* a specifier not followed immediately by an embedded expression or
|
|
* other illegal conditions. For specification of all possible
|
|
* formatting errors, see the
|
|
* <a href="../util/Formatter.html#detail">details</a>
|
|
* section of the formatter class specification.
|
|
* @throws NullPointerException if stringTemplate is null
|
|
*
|
|
* @see java.util.Formatter
|
|
*/
|
|
@Override
|
|
public final String process(StringTemplate stringTemplate) {
|
|
Objects.requireNonNull(stringTemplate);
|
|
String format = stringTemplateFormat(stringTemplate.fragments());
|
|
Object[] values = stringTemplate.values().toArray();
|
|
|
|
return new Formatter(locale).format(format, values).toString();
|
|
}
|
|
|
|
/**
|
|
* Constructs a {@link MethodHandle} that when supplied with the values from
|
|
* a {@link StringTemplate} will produce a result equivalent to that provided by
|
|
* {@link FormatProcessor#process(StringTemplate)}. This {@link MethodHandle}
|
|
* is used by {@link FormatProcessor#FMT} and the ilk to perform a more
|
|
* specialized composition of a result. This specialization is done by
|
|
* prescanning the fragments and value types of a {@link StringTemplate}.
|
|
* <p>
|
|
* Process template expressions can be specialized when the processor is
|
|
* of type {@link Linkage} and fetched from a static constant as is
|
|
* {@link FormatProcessor#FMT} ({@code static final FormatProcessor}).
|
|
* <p>
|
|
* Other {@link FormatProcessor FormatProcessors} can be specialized when stored in a static
|
|
* final.
|
|
* For example:
|
|
* {@snippet :
|
|
* FormatProcessor THAI_FMT = FormatProcessor.create(Locale.forLanguageTag("th-TH-u-nu-thai"));
|
|
* }
|
|
* {@code THAI_FMT} will now produce specialized {@link MethodHandle MethodHandles} by way
|
|
* of {@link FormatProcessor#linkage(List, MethodType)}.
|
|
*
|
|
* See {@link FormatProcessor#process(StringTemplate)} for more information.
|
|
*
|
|
* @throws IllegalFormatException
|
|
* If a format specifier contains an illegal syntax, a format
|
|
* specifier that is incompatible with the given arguments,
|
|
* a specifier not followed immediately by an embedded expression or
|
|
* other illegal conditions. For specification of all possible
|
|
* formatting errors, see the
|
|
* <a href="../util/Formatter.html#detail">details</a>
|
|
* section of the formatter class specification.
|
|
* @throws NullPointerException if fragments or type is null
|
|
*
|
|
* @see java.util.Formatter
|
|
*/
|
|
@Override
|
|
public MethodHandle linkage(List<String> fragments, MethodType type) {
|
|
Objects.requireNonNull(fragments);
|
|
Objects.requireNonNull(type);
|
|
String format = stringTemplateFormat(fragments);
|
|
Class<?>[] ptypes = type.dropParameterTypes(0, 1).parameterArray();
|
|
MethodHandle mh = new FormatterBuilder(format, locale, ptypes).build();
|
|
mh = MethodHandles.dropArguments(mh, 0, type.parameterType(0));
|
|
|
|
return mh;
|
|
}
|
|
|
|
/**
|
|
* Find a format specification at the end of a fragment.
|
|
*
|
|
* @param fragment fragment to check
|
|
* @param needed if the specification is needed
|
|
*
|
|
* @return true if the specification is found and needed
|
|
*
|
|
* @throws MissingFormatArgumentException if not at end or found and not needed
|
|
*/
|
|
private static boolean findFormat(String fragment, boolean needed) {
|
|
Matcher matcher = Formatter.FORMAT_SPECIFIER_PATTERN.matcher(fragment);
|
|
String group;
|
|
|
|
while (matcher.find()) {
|
|
group = matcher.group();
|
|
|
|
if (!group.equals("%%") && !group.equals("%n")) {
|
|
if (matcher.end() == fragment.length() && needed) {
|
|
return true;
|
|
}
|
|
|
|
throw new MissingFormatArgumentException(group +
|
|
" is not immediately followed by an embedded expression");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Convert {@link StringTemplate} fragments, containing format specifications,
|
|
* to a form that can be passed on to {@link Formatter}. The method scans each fragment,
|
|
* matching up formatter specifications with the following expression. If no
|
|
* specification is found, the method inserts "%s".
|
|
*
|
|
* @param fragments string template fragments
|
|
*
|
|
* @return format string
|
|
*/
|
|
private static String stringTemplateFormat(List<String> fragments) {
|
|
StringBuilder sb = new StringBuilder();
|
|
int lastIndex = fragments.size() - 1;
|
|
List<String> formats = fragments.subList(0, lastIndex);
|
|
String last = fragments.get(lastIndex);
|
|
|
|
for (String format : formats) {
|
|
if (findFormat(format, true)) {
|
|
sb.append(format);
|
|
} else {
|
|
sb.append(format);
|
|
sb.append("%s");
|
|
}
|
|
}
|
|
|
|
if (!findFormat(last, false)) {
|
|
sb.append(last);
|
|
}
|
|
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* This predefined {@link FormatProcessor} instance constructs a {@link String} result using
|
|
* the Locale.ROOT {@link Locale}. See {@link FormatProcessor} for more details.
|
|
* Example: {@snippet :
|
|
* int x = 10;
|
|
* int y = 20;
|
|
* String result = FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}"; // @highlight substring="FMT"
|
|
* }
|
|
* In the above example, the value of {@code result} will be {@code "0x000a + 0x0014 = 0x001E"}.
|
|
*
|
|
* @see java.util.FormatProcessor
|
|
*/
|
|
public static final FormatProcessor FMT = FormatProcessor.create(Locale.ROOT);
|
|
|
|
}
|