mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00
8329948: Remove string template feature
Reviewed-by: jlahoda
This commit is contained in:
parent
ff3e76fd0c
commit
03e84178eb
66 changed files with 183 additions and 7278 deletions
|
@ -26,8 +26,6 @@
|
|||
package java.lang;
|
||||
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.util.FormatConcatItem;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
|
@ -127,20 +125,6 @@ final class StringConcatHelper {
|
|||
return checkOverflow(lengthCoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix value length and coder into current length and coder.
|
||||
* @param lengthCoder String length with coder packed into higher bits
|
||||
* the upper word.
|
||||
* @param value value to mix in
|
||||
* @return new length and coder
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static long mix(long lengthCoder, FormatConcatItem value) {
|
||||
lengthCoder = value.mix(lengthCoder);
|
||||
return checkOverflow(lengthCoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the stringly representation of boolean value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
|
@ -344,48 +328,6 @@ final class StringConcatHelper {
|
|||
return indexCoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the stringly representation of FormatConcatItem value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
*
|
||||
* @param indexCoder final char index in the buffer, along with coder packed
|
||||
* into higher bits.
|
||||
* @param buf buffer to append to
|
||||
* @param value String value to encode
|
||||
* @return updated index (coder value retained)
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static long prepend(long indexCoder, byte[] buf, FormatConcatItem value) {
|
||||
try {
|
||||
return value.prepend(indexCoder, buf);
|
||||
} catch (Error ex) {
|
||||
throw ex;
|
||||
} catch (Throwable ex) {
|
||||
throw new AssertionError("FormatConcatItem prepend error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends constant and the stringly representation of value into buffer,
|
||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||
*
|
||||
* @param indexCoder final char index in the buffer, along with coder packed
|
||||
* into higher bits.
|
||||
* @param buf buffer to append to
|
||||
* @param value boolean value to encode
|
||||
* @param prefix a constant to prepend before value
|
||||
* @return updated index (coder value retained)
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static long prepend(long indexCoder, byte[] buf,
|
||||
FormatConcatItem value, String prefix) {
|
||||
indexCoder = prepend(indexCoder, buf, value);
|
||||
indexCoder = prepend(indexCoder, buf, prefix);
|
||||
return indexCoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the String with given buffer and coder
|
||||
* @param buf buffer to use
|
||||
|
@ -517,71 +459,6 @@ final class StringConcatHelper {
|
|||
return String.COMPACT_STRINGS ? LATIN1 : UTF16;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize after phase1.
|
||||
*/
|
||||
private static class LateInit {
|
||||
static final MethodHandle GETCHAR_LATIN1_MH;
|
||||
|
||||
static final MethodHandle GETCHAR_UTF16_MH;
|
||||
|
||||
static final MethodHandle PUTCHAR_LATIN1_MH;
|
||||
|
||||
static final MethodHandle PUTCHAR_UTF16_MH;
|
||||
|
||||
static {
|
||||
MethodType getCharMT =
|
||||
MethodType.methodType(char.class,
|
||||
byte[].class, int.class);
|
||||
MethodType putCharMT =
|
||||
MethodType.methodType(void.class,
|
||||
byte[].class, int.class, int.class);
|
||||
GETCHAR_LATIN1_MH = lookupStatic("getCharLatin1", getCharMT);
|
||||
GETCHAR_UTF16_MH = lookupStatic("getCharUTF16", getCharMT);
|
||||
PUTCHAR_LATIN1_MH = lookupStatic("putCharLatin1", putCharMT);
|
||||
PUTCHAR_UTF16_MH = lookupStatic("putCharUTF16", putCharMT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static char getCharLatin1(byte[] buffer, int index) {
|
||||
return (char)buffer[index];
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static char getCharUTF16(byte[] buffer, int index) {
|
||||
return StringUTF16.getChar(buffer, index);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static void putCharLatin1(byte[] buffer, int index, int ch) {
|
||||
buffer[index] = (byte)ch;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static void putCharUTF16(byte[] buffer, int index, int ch) {
|
||||
StringUTF16.putChar(buffer, index, ch);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static MethodHandle selectGetChar(long indexCoder) {
|
||||
return indexCoder < UTF16 ? LateInit.GETCHAR_LATIN1_MH :
|
||||
LateInit.GETCHAR_UTF16_MH;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
static MethodHandle selectPutChar(long indexCoder) {
|
||||
return indexCoder < UTF16 ? LateInit.PUTCHAR_LATIN1_MH :
|
||||
LateInit.PUTCHAR_UTF16_MH;
|
||||
}
|
||||
|
||||
static MethodHandle lookupStatic(String name, MethodType methodType) {
|
||||
try {
|
||||
return MethodHandles.lookup()
|
||||
|
|
|
@ -1,622 +0,0 @@
|
|||
/*
|
||||
* 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.lang;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.FormatProcessor;
|
||||
import java.util.function.Function;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jdk.internal.access.JavaTemplateAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
/**
|
||||
* {@link StringTemplate} is the run-time representation of a string template or
|
||||
* text block template in a template expression.
|
||||
* <p>
|
||||
* In the source code of a Java program, a string template or text block template
|
||||
* contains an interleaved succession of <em>fragment literals</em> and <em>embedded
|
||||
* expressions</em>. The {@link StringTemplate#fragments()} method returns the
|
||||
* fragment literals, and the {@link StringTemplate#values()} method returns the
|
||||
* results of evaluating the embedded expressions. {@link StringTemplate} does not
|
||||
* provide access to the source code of the embedded expressions themselves; it is
|
||||
* not a compile-time representation of a string template or text block template.
|
||||
* <p>
|
||||
* {@link StringTemplate} is primarily used in conjunction with a template processor
|
||||
* to produce a string or other meaningful value. Evaluation of a template expression
|
||||
* first produces an instance of {@link StringTemplate}, representing the right hand side
|
||||
* of the template expression, and then passes the instance to the template processor
|
||||
* given by the template expression.
|
||||
* <p>
|
||||
* For example, the following code contains a template expression that uses the template
|
||||
* processor {@code RAW}, which simply yields the {@link StringTemplate} passed to it:
|
||||
* {@snippet :
|
||||
* int x = 10;
|
||||
* int y = 20;
|
||||
* StringTemplate st = RAW."\{x} + \{y} = \{x + y}";
|
||||
* List<String> fragments = st.fragments();
|
||||
* List<Object> values = st.values();
|
||||
* }
|
||||
* {@code fragments} will be equivalent to {@code List.of("", " + ", " = ", "")},
|
||||
* which includes the empty first and last fragments. {@code values} will be the
|
||||
* equivalent of {@code List.of(10, 20, 30)}.
|
||||
* <p>
|
||||
* The following code contains a template expression with the same template but with a
|
||||
* different template processor, {@code STR}:
|
||||
* {@snippet :
|
||||
* int x = 10;
|
||||
* int y = 20;
|
||||
* String s = STR."\{x} + \{y} = \{x + y}";
|
||||
* }
|
||||
* When the template expression is evaluated, an instance of {@link StringTemplate} is
|
||||
* produced that returns the same lists from {@link StringTemplate#fragments()} and
|
||||
* {@link StringTemplate#values()} as shown above. The {@link StringTemplate#STR} template
|
||||
* processor uses these lists to yield an interpolated string. The value of {@code s} will
|
||||
* be equivalent to {@code "10 + 20 = 30"}.
|
||||
* <p>
|
||||
* The {@code interpolate()} method provides a direct way to perform string interpolation
|
||||
* of a {@link StringTemplate}. Template processors can use the following code pattern:
|
||||
* {@snippet :
|
||||
* List<String> fragments = st.fragments();
|
||||
* List<Object> values = st.values();
|
||||
* ... check or manipulate the fragments and/or values ...
|
||||
* String result = StringTemplate.interpolate(fragments, values);
|
||||
* }
|
||||
* The {@link StringTemplate#process(Processor)} method, in conjunction with
|
||||
* the {@link StringTemplate#RAW} processor, may be used to defer processing of a
|
||||
* {@link StringTemplate}.
|
||||
* {@snippet :
|
||||
* StringTemplate st = RAW."\{x} + \{y} = \{x + y}";
|
||||
* ...other steps...
|
||||
* String result = st.process(STR);
|
||||
* }
|
||||
* The factory methods {@link StringTemplate#of(String)} and
|
||||
* {@link StringTemplate#of(List, List)} can be used to construct a {@link StringTemplate}.
|
||||
*
|
||||
* @see Processor
|
||||
* @see java.util.FormatProcessor
|
||||
*
|
||||
* @implNote Implementations of {@link StringTemplate} must minimally implement the
|
||||
* methods {@link StringTemplate#fragments()} and {@link StringTemplate#values()}.
|
||||
* Instances of {@link StringTemplate} are considered immutable. To preserve the
|
||||
* semantics of string templates and text block templates, the list returned by
|
||||
* {@link StringTemplate#fragments()} must be one element larger than the list returned
|
||||
* by {@link StringTemplate#values()}.
|
||||
*
|
||||
* @since 21
|
||||
*
|
||||
* @jls 15.8.6 Process Template Expressions
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public interface StringTemplate {
|
||||
/**
|
||||
* Returns a list of fragment literals for this {@link StringTemplate}.
|
||||
* The fragment literals are the character sequences preceding each of the embedded
|
||||
* expressions in source code, plus the character sequence following the last
|
||||
* embedded expression. Such character sequences may be zero-length if an embedded
|
||||
* expression appears at the beginning or end of a template, or if two embedded
|
||||
* expressions are directly adjacent in a template.
|
||||
* In the example: {@snippet :
|
||||
* String student = "Mary";
|
||||
* String teacher = "Johnson";
|
||||
* StringTemplate st = RAW."The student \{student} is in \{teacher}'s classroom.";
|
||||
* List<String> fragments = st.fragments(); // @highlight substring="fragments()"
|
||||
* }
|
||||
* {@code fragments} will be equivalent to
|
||||
* {@code List.of("The student ", " is in ", "'s classroom.")}
|
||||
*
|
||||
* @return list of string fragments
|
||||
*
|
||||
* @implSpec the list returned is immutable
|
||||
*/
|
||||
List<String> fragments();
|
||||
|
||||
/**
|
||||
* Returns a list of embedded expression results for this {@link StringTemplate}.
|
||||
* In the example:
|
||||
* {@snippet :
|
||||
* String student = "Mary";
|
||||
* String teacher = "Johnson";
|
||||
* StringTemplate st = RAW."The student \{student} is in \{teacher}'s classroom.";
|
||||
* List<Object> values = st.values(); // @highlight substring="values()"
|
||||
* }
|
||||
* {@code values} will be equivalent to {@code List.of(student, teacher)}
|
||||
*
|
||||
* @return list of expression values
|
||||
*
|
||||
* @implSpec the list returned is immutable
|
||||
*/
|
||||
List<Object> values();
|
||||
|
||||
/**
|
||||
* Returns the string interpolation of the fragments and values for this
|
||||
* {@link StringTemplate}.
|
||||
* @apiNote For better visibility and when practical, it is recommended to use the
|
||||
* {@link StringTemplate#STR} processor instead of invoking the
|
||||
* {@link StringTemplate#interpolate()} method.
|
||||
* {@snippet :
|
||||
* String student = "Mary";
|
||||
* String teacher = "Johnson";
|
||||
* StringTemplate st = RAW."The student \{student} is in \{teacher}'s classroom.";
|
||||
* String result = st.interpolate(); // @highlight substring="interpolate()"
|
||||
* }
|
||||
* In the above example, the value of {@code result} will be
|
||||
* {@code "The student Mary is in Johnson's classroom."}. This is
|
||||
* produced by the interleaving concatenation of fragments and values from the supplied
|
||||
* {@link StringTemplate}. To accommodate concatenation, values are converted to strings
|
||||
* as if invoking {@link String#valueOf(Object)}.
|
||||
*
|
||||
* @return interpolation of this {@link StringTemplate}
|
||||
*
|
||||
* @implSpec The default implementation returns the result of invoking
|
||||
* {@code StringTemplate.interpolate(this.fragments(), this.values())}.
|
||||
*/
|
||||
default String interpolate() {
|
||||
return StringTemplate.interpolate(fragments(), values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of applying the specified processor to this {@link StringTemplate}.
|
||||
* This method can be used as an alternative to string template expressions. For example,
|
||||
* {@snippet :
|
||||
* String student = "Mary";
|
||||
* String teacher = "Johnson";
|
||||
* String result1 = STR."The student \{student} is in \{teacher}'s classroom.";
|
||||
* String result2 = RAW."The student \{student} is in \{teacher}'s classroom.".process(STR); // @highlight substring="process"
|
||||
* }
|
||||
* Produces an equivalent result for both {@code result1} and {@code result2}.
|
||||
*
|
||||
* @param processor the {@link Processor} instance to process
|
||||
*
|
||||
* @param <R> Processor's process result type.
|
||||
* @param <E> Exception thrown type.
|
||||
*
|
||||
* @return constructed object of type {@code R}
|
||||
*
|
||||
* @throws E exception thrown by the template processor when validation fails
|
||||
* @throws NullPointerException if processor is null
|
||||
*
|
||||
* @implSpec The default implementation returns the result of invoking
|
||||
* {@code processor.process(this)}. If the invocation throws an exception that
|
||||
* exception is forwarded to the caller.
|
||||
*/
|
||||
default <R, E extends Throwable> R
|
||||
process(Processor<? extends R, ? extends E> processor) throws E {
|
||||
Objects.requireNonNull(processor, "processor should not be null");
|
||||
|
||||
return processor.process(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a diagnostic string that describes the fragments and values of the supplied
|
||||
* {@link StringTemplate}.
|
||||
*
|
||||
* @param stringTemplate the {@link StringTemplate} to represent
|
||||
*
|
||||
* @return diagnostic string representing the supplied string template
|
||||
*
|
||||
* @throws NullPointerException if stringTemplate is null
|
||||
*/
|
||||
static String toString(StringTemplate stringTemplate) {
|
||||
Objects.requireNonNull(stringTemplate, "stringTemplate should not be null");
|
||||
return "StringTemplate{ fragments = [ \"" +
|
||||
String.join("\", \"", stringTemplate.fragments()) +
|
||||
"\" ], values = " +
|
||||
stringTemplate.values() +
|
||||
" }";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link StringTemplate} as if constructed by invoking
|
||||
* {@code StringTemplate.of(List.of(string), List.of())}. That is, a {@link StringTemplate}
|
||||
* with one fragment and no values.
|
||||
*
|
||||
* @param string single string fragment
|
||||
*
|
||||
* @return StringTemplate composed from string
|
||||
*
|
||||
* @throws NullPointerException if string is null
|
||||
*/
|
||||
static StringTemplate of(String string) {
|
||||
Objects.requireNonNull(string, "string must not be null");
|
||||
JavaTemplateAccess JTA = SharedSecrets.getJavaTemplateAccess();
|
||||
return JTA.of(List.of(string), List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a StringTemplate with the given fragments and values.
|
||||
*
|
||||
* @implSpec The {@code fragments} list size must be one more that the
|
||||
* {@code values} list size.
|
||||
*
|
||||
* @param fragments list of string fragments
|
||||
* @param values list of expression values
|
||||
*
|
||||
* @return StringTemplate composed from string
|
||||
*
|
||||
* @throws IllegalArgumentException if fragments list size is not one more
|
||||
* than values list size
|
||||
* @throws NullPointerException if fragments is null or values is null or if any fragment is null.
|
||||
*
|
||||
* @implNote Contents of both lists are copied to construct immutable lists.
|
||||
*/
|
||||
static StringTemplate of(List<String> fragments, List<?> values) {
|
||||
Objects.requireNonNull(fragments, "fragments must not be null");
|
||||
Objects.requireNonNull(values, "values must not be null");
|
||||
if (values.size() + 1 != fragments.size()) {
|
||||
throw new IllegalArgumentException(
|
||||
"fragments list size is not one more than values list size");
|
||||
}
|
||||
JavaTemplateAccess JTA = SharedSecrets.getJavaTemplateAccess();
|
||||
return JTA.of(fragments, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string that interleaves the elements of values between the
|
||||
* elements of fragments. To accommodate interpolation, values are converted to strings
|
||||
* as if invoking {@link String#valueOf(Object)}.
|
||||
*
|
||||
* @param fragments list of String fragments
|
||||
* @param values list of expression values
|
||||
*
|
||||
* @return String interpolation of fragments and values
|
||||
*
|
||||
* @throws IllegalArgumentException if fragments list size is not one more
|
||||
* than values list size
|
||||
* @throws NullPointerException fragments or values is null or if any of the fragments is null
|
||||
*/
|
||||
static String interpolate(List<String> fragments, List<?> values) {
|
||||
Objects.requireNonNull(fragments, "fragments must not be null");
|
||||
Objects.requireNonNull(values, "values must not be null");
|
||||
int fragmentsSize = fragments.size();
|
||||
int valuesSize = values.size();
|
||||
if (fragmentsSize != valuesSize + 1) {
|
||||
throw new IllegalArgumentException("fragments must have one more element than values");
|
||||
}
|
||||
JavaTemplateAccess JTA = SharedSecrets.getJavaTemplateAccess();
|
||||
return JTA.interpolate(fragments, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine zero or more {@link StringTemplate StringTemplates} into a single
|
||||
* {@link StringTemplate}.
|
||||
* {@snippet :
|
||||
* StringTemplate st = StringTemplate.combine(RAW."\{a}", RAW."\{b}", RAW."\{c}");
|
||||
* assert st.interpolate().equals(STR."\{a}\{b}\{c}");
|
||||
* }
|
||||
* Fragment lists from the {@link StringTemplate StringTemplates} are combined end to
|
||||
* end with the last fragment from each {@link StringTemplate} concatenated with the
|
||||
* first fragment of the next. To demonstrate, if we were to take two strings and we
|
||||
* combined them as follows: {@snippet lang = "java":
|
||||
* String s1 = "abc";
|
||||
* String s2 = "xyz";
|
||||
* String sc = s1 + s2;
|
||||
* assert Objects.equals(sc, "abcxyz");
|
||||
* }
|
||||
* the last character {@code "c"} from the first string is juxtaposed with the first
|
||||
* character {@code "x"} of the second string. The same would be true of combining
|
||||
* {@link StringTemplate StringTemplates}.
|
||||
* {@snippet lang ="java":
|
||||
* StringTemplate st1 = RAW."a\{}b\{}c";
|
||||
* StringTemplate st2 = RAW."x\{}y\{}z";
|
||||
* StringTemplate st3 = RAW."a\{}b\{}cx\{}y\{}z";
|
||||
* StringTemplate stc = StringTemplate.combine(st1, st2);
|
||||
*
|
||||
* assert Objects.equals(st1.fragments(), List.of("a", "b", "c"));
|
||||
* assert Objects.equals(st2.fragments(), List.of("x", "y", "z"));
|
||||
* assert Objects.equals(st3.fragments(), List.of("a", "b", "cx", "y", "z"));
|
||||
* assert Objects.equals(stc.fragments(), List.of("a", "b", "cx", "y", "z"));
|
||||
* }
|
||||
* Values lists are simply concatenated to produce a single values list.
|
||||
* The result is a well-formed {@link StringTemplate} with n+1 fragments and n values, where
|
||||
* n is the total of number of values across all the supplied
|
||||
* {@link StringTemplate StringTemplates}.
|
||||
*
|
||||
* @param stringTemplates zero or more {@link StringTemplate}
|
||||
*
|
||||
* @return combined {@link StringTemplate}
|
||||
*
|
||||
* @throws NullPointerException if stringTemplates is null or if any of the
|
||||
* {@code stringTemplates} are null
|
||||
*
|
||||
* @implNote If zero {@link StringTemplate} arguments are provided then a
|
||||
* {@link StringTemplate} with an empty fragment and no values is returned, as if invoking
|
||||
* <code>StringTemplate.of("")</code> . If only one {@link StringTemplate} argument is provided
|
||||
* then it is returned unchanged.
|
||||
*/
|
||||
static StringTemplate combine(StringTemplate... stringTemplates) {
|
||||
JavaTemplateAccess JTA = SharedSecrets.getJavaTemplateAccess();
|
||||
return JTA.combine(stringTemplates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine a list of {@link StringTemplate StringTemplates} into a single
|
||||
* {@link StringTemplate}.
|
||||
* {@snippet :
|
||||
* StringTemplate st = StringTemplate.combine(List.of(RAW."\{a}", RAW."\{b}", RAW."\{c}"));
|
||||
* assert st.interpolate().equals(STR."\{a}\{b}\{c}");
|
||||
* }
|
||||
* Fragment lists from the {@link StringTemplate StringTemplates} are combined end to
|
||||
* end with the last fragment from each {@link StringTemplate} concatenated with the
|
||||
* first fragment of the next. To demonstrate, if we were to take two strings and we
|
||||
* combined them as follows: {@snippet lang = "java":
|
||||
* String s1 = "abc";
|
||||
* String s2 = "xyz";
|
||||
* String sc = s1 + s2;
|
||||
* assert Objects.equals(sc, "abcxyz");
|
||||
* }
|
||||
* the last character {@code "c"} from the first string is juxtaposed with the first
|
||||
* character {@code "x"} of the second string. The same would be true of combining
|
||||
* {@link StringTemplate StringTemplates}.
|
||||
* {@snippet lang ="java":
|
||||
* StringTemplate st1 = RAW."a\{}b\{}c";
|
||||
* StringTemplate st2 = RAW."x\{}y\{}z";
|
||||
* StringTemplate st3 = RAW."a\{}b\{}cx\{}y\{}z";
|
||||
* StringTemplate stc = StringTemplate.combine(List.of(st1, st2));
|
||||
*
|
||||
* assert Objects.equals(st1.fragments(), List.of("a", "b", "c"));
|
||||
* assert Objects.equals(st2.fragments(), List.of("x", "y", "z"));
|
||||
* assert Objects.equals(st3.fragments(), List.of("a", "b", "cx", "y", "z"));
|
||||
* assert Objects.equals(stc.fragments(), List.of("a", "b", "cx", "y", "z"));
|
||||
* }
|
||||
* Values lists are simply concatenated to produce a single values list.
|
||||
* The result is a well-formed {@link StringTemplate} with n+1 fragments and n values, where
|
||||
* n is the total of number of values across all the supplied
|
||||
* {@link StringTemplate StringTemplates}.
|
||||
*
|
||||
* @param stringTemplates list of {@link StringTemplate}
|
||||
*
|
||||
* @return combined {@link StringTemplate}
|
||||
*
|
||||
* @throws NullPointerException if stringTemplates is null or if any of the
|
||||
* its elements are null
|
||||
*
|
||||
* @implNote If {@code stringTemplates.size() == 0} then a {@link StringTemplate} with
|
||||
* an empty fragment and no values is returned, as if invoking
|
||||
* <code>StringTemplate.of("")</code> . If {@code stringTemplates.size() == 1}
|
||||
* then the first element of the list is returned unchanged.
|
||||
*/
|
||||
static StringTemplate combine(List<StringTemplate> stringTemplates) {
|
||||
JavaTemplateAccess JTA = SharedSecrets.getJavaTemplateAccess();
|
||||
return JTA.combine(stringTemplates.toArray(new StringTemplate[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* This {@link Processor} instance is conventionally used for the string interpolation
|
||||
* of a supplied {@link StringTemplate}.
|
||||
* <p>
|
||||
* For better visibility and when practical, it is recommended that users use the
|
||||
* {@link StringTemplate#STR} processor instead of invoking the
|
||||
* {@link StringTemplate#interpolate()} method.
|
||||
* Example: {@snippet :
|
||||
* int x = 10;
|
||||
* int y = 20;
|
||||
* String result = STR."\{x} + \{y} = \{x + y}"; // @highlight substring="STR"
|
||||
* }
|
||||
* In the above example, the value of {@code result} will be {@code "10 + 20 = 30"}. This is
|
||||
* produced by the interleaving concatenation of fragments and values from the supplied
|
||||
* {@link StringTemplate}. To accommodate concatenation, values are converted to strings
|
||||
* as if invoking {@link String#valueOf(Object)}.
|
||||
* @apiNote {@link StringTemplate#STR} is statically imported implicitly into every
|
||||
* Java compilation unit.
|
||||
*/
|
||||
Processor<String, RuntimeException> STR = StringTemplate::interpolate;
|
||||
|
||||
/**
|
||||
* This {@link Processor} instance is conventionally used to indicate that the
|
||||
* processing of the {@link StringTemplate} is to be deferred to a later time. Deferred
|
||||
* processing can be resumed by invoking the
|
||||
* {@link StringTemplate#process(Processor)} or
|
||||
* {@link Processor#process(StringTemplate)} methods.
|
||||
* {@snippet :
|
||||
* import static java.lang.StringTemplate.RAW;
|
||||
* ...
|
||||
* StringTemplate st = RAW."\{x} + \{y} = \{x + y}";
|
||||
* ...other steps...
|
||||
* String result = STR.process(st);
|
||||
* }
|
||||
* @implNote Unlike {@link StringTemplate#STR}, {@link StringTemplate#RAW} must be
|
||||
* statically imported explicitly.
|
||||
*/
|
||||
Processor<StringTemplate, RuntimeException> RAW = st -> st;
|
||||
|
||||
/**
|
||||
* This interface describes the methods provided by a generalized string template processor. The
|
||||
* primary method {@link Processor#process(StringTemplate)} is used to validate
|
||||
* and compose a result using a {@link StringTemplate StringTemplate's} fragments and values lists.
|
||||
* <p>
|
||||
* For example:
|
||||
* {@snippet :
|
||||
* class MyProcessor implements Processor<String, IllegalArgumentException> {
|
||||
* @Override
|
||||
* public String process(StringTemplate st) throws IllegalArgumentException {
|
||||
* StringBuilder sb = new StringBuilder();
|
||||
* Iterator<String> fragmentsIter = st.fragments().iterator();
|
||||
*
|
||||
* for (Object value : st.values()) {
|
||||
* sb.append(fragmentsIter.next());
|
||||
*
|
||||
* if (value instanceof Boolean) {
|
||||
* throw new IllegalArgumentException("I don't like Booleans");
|
||||
* }
|
||||
*
|
||||
* sb.append(value);
|
||||
* }
|
||||
*
|
||||
* sb.append(fragmentsIter.next());
|
||||
*
|
||||
* return sb.toString();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* MyProcessor myProcessor = new MyProcessor();
|
||||
* try {
|
||||
* int x = 10;
|
||||
* int y = 20;
|
||||
* String result = myProcessor."\{x} + \{y} = \{x + y}";
|
||||
* ...
|
||||
* } catch (IllegalArgumentException ex) {
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
* Implementations of this interface may provide, but are not limited to, validating
|
||||
* inputs, composing inputs into a result, and transforming an intermediate string
|
||||
* result to a non-string value before delivering the final result.
|
||||
* <p>
|
||||
* The user has the option of validating inputs used in composition. For example an SQL
|
||||
* processor could prevent injection vulnerabilities by sanitizing inputs or throwing an
|
||||
* exception of type {@code E} if an SQL statement is a potential vulnerability.
|
||||
* <p>
|
||||
* Composing allows user control over how the result is assembled. Most often, a
|
||||
* user will construct a new string from the string template, with placeholders
|
||||
* replaced by string representations of value list elements. These string
|
||||
* representations are created as if invoking {@link String#valueOf}.
|
||||
* <p>
|
||||
* Transforming allows the processor to return something other than a string. For
|
||||
* instance, a JSON processor could return a JSON object, by parsing the string created
|
||||
* by composition, instead of the composed string.
|
||||
* <p>
|
||||
* {@link Processor} is a {@link FunctionalInterface}. This permits
|
||||
* declaration of a processor using lambda expressions;
|
||||
* {@snippet :
|
||||
* Processor<String, RuntimeException> processor = st -> {
|
||||
* List<String> fragments = st.fragments();
|
||||
* List<Object> values = st.values();
|
||||
* // check or manipulate the fragments and/or values
|
||||
* ...
|
||||
* return StringTemplate.interpolate(fragments, values);
|
||||
* };
|
||||
* }
|
||||
* The {@link StringTemplate#interpolate()} method is available for those processors
|
||||
* that just need to work with the string interpolation;
|
||||
* {@snippet :
|
||||
* Processor<String, RuntimeException> processor = StringTemplate::interpolate;
|
||||
* }
|
||||
* or simply transform the string interpolation into something other than
|
||||
* {@link String};
|
||||
* {@snippet :
|
||||
* Processor<JSONObject, RuntimeException> jsonProcessor = st -> new JSONObject(st.interpolate());
|
||||
* }
|
||||
* @implNote The Java compiler automatically imports {@link StringTemplate#STR}
|
||||
*
|
||||
* @param <R> Processor's process result type
|
||||
* @param <E> Exception thrown type
|
||||
*
|
||||
* @see StringTemplate
|
||||
* @see java.util.FormatProcessor
|
||||
*
|
||||
* @since 21
|
||||
*
|
||||
* @jls 15.8.6 Process Template Expressions
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
@FunctionalInterface
|
||||
public interface Processor<R, E extends Throwable> {
|
||||
|
||||
/**
|
||||
* Constructs a result based on the template fragments and values in the
|
||||
* supplied {@link StringTemplate stringTemplate} object.
|
||||
* @apiNote Processing of a {@link StringTemplate} may include validation according to the particular facts relating
|
||||
* to each situation. The {@code E} type parameter indicates the type of checked exception that is thrown by
|
||||
* {@link #process} if validation fails, ex. {@code java.sql.SQLException}. If no checked exception is expected
|
||||
* then {@link RuntimeException} may be used. Note that unchecked exceptions, such as {@link RuntimeException},
|
||||
* {@link NullPointerException} or {@link IllegalArgumentException} may be thrown as part of the normal
|
||||
* method arguments processing. Details of which exceptions are thrown will be found in the documentation
|
||||
* of the specific implementation.
|
||||
*
|
||||
* @param stringTemplate a {@link StringTemplate} instance
|
||||
*
|
||||
* @return constructed object of type R
|
||||
*
|
||||
* @throws E exception thrown by the template processor when validation fails
|
||||
*/
|
||||
R process(StringTemplate stringTemplate) throws E;
|
||||
|
||||
/**
|
||||
* This factory method can be used to create a {@link Processor} containing a
|
||||
* {@link Processor#process} method derived from a lambda expression. As an example;
|
||||
* {@snippet :
|
||||
* Processor<String, RuntimeException> mySTR = Processor.of(StringTemplate::interpolate);
|
||||
* int x = 10;
|
||||
* int y = 20;
|
||||
* String str = mySTR."\{x} + \{y} = \{x + y}";
|
||||
* }
|
||||
* The result type of the constructed {@link Processor} may be derived from
|
||||
* the lambda expression, thus this method may be used in a var
|
||||
* statement. For example, {@code mySTR} from above can also be declared using;
|
||||
* {@snippet :
|
||||
* var mySTR = Processor.of(StringTemplate::interpolate);
|
||||
* }
|
||||
* {@link RuntimeException} is the assumed exception thrown type.
|
||||
*
|
||||
* @param process a function that takes a {@link StringTemplate} as an argument
|
||||
* and returns the inferred result type
|
||||
*
|
||||
* @return a {@link Processor}
|
||||
*
|
||||
* @param <T> Processor's process result type
|
||||
*/
|
||||
static <T> Processor<T, RuntimeException> of(Function<? super StringTemplate, ? extends T> process) {
|
||||
return process::apply;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in policies using this additional interface have the flexibility to
|
||||
* specialize the composition of the templated string by returning a customized
|
||||
* {@link MethodHandle} from {@link Linkage#linkage linkage}.
|
||||
* These specializations are typically implemented to improve performance;
|
||||
* specializing value types or avoiding boxing and vararg arrays.
|
||||
*
|
||||
* @implNote This interface is sealed to only allow standard processors.
|
||||
*
|
||||
* @sealedGraph
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public sealed interface Linkage permits FormatProcessor {
|
||||
/**
|
||||
* This method creates a {@link MethodHandle} that when invoked with arguments of
|
||||
* those specified in {@code type} returns a result that equals that returned by
|
||||
* the template processor's process method. The difference being that this method
|
||||
* can preview the template's fragments and value types in advance of usage and
|
||||
* thereby has the opportunity to produce a specialized implementation.
|
||||
*
|
||||
* @param fragments string template fragments
|
||||
* @param type method type, includes the StringTemplate receiver as
|
||||
* well as the value types
|
||||
*
|
||||
* @return {@link MethodHandle} for the processor applied to template
|
||||
*
|
||||
* @throws NullPointerException if any of the arguments are null
|
||||
*/
|
||||
MethodHandle linkage(List<String> fragments, MethodType type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -80,7 +80,6 @@ import jdk.internal.reflect.CallerSensitive;
|
|||
import jdk.internal.reflect.Reflection;
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.logger.LoggerFinderLoader;
|
||||
import jdk.internal.logger.LazyLoggers;
|
||||
import jdk.internal.logger.LocalizedLoggerWrapper;
|
||||
|
@ -2490,6 +2489,9 @@ public final class System {
|
|||
public char getUTF16Char(byte[] bytes, int index) {
|
||||
return StringUTF16.getChar(bytes, index);
|
||||
}
|
||||
public void putCharUTF16(byte[] bytes, int index, int ch) {
|
||||
StringUTF16.putChar(bytes, index, ch);
|
||||
}
|
||||
public byte[] getBytesNoRepl(String s, Charset cs) throws CharacterCodingException {
|
||||
return String.getBytesNoRepl(s, cs);
|
||||
}
|
||||
|
@ -2530,6 +2532,10 @@ public final class System {
|
|||
return StringConcatHelper.lookupStatic(name, methodType);
|
||||
}
|
||||
|
||||
public long stringConcatHelperPrepend(long indexCoder, byte[] buf, String value) {
|
||||
return StringConcatHelper.prepend(indexCoder, buf, value);
|
||||
}
|
||||
|
||||
public long stringConcatInitialCoder() {
|
||||
return StringConcatHelper.initialCoder();
|
||||
}
|
||||
|
@ -2538,21 +2544,20 @@ public final class System {
|
|||
return StringConcatHelper.mix(lengthCoder, constant);
|
||||
}
|
||||
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public long stringConcatCoder(char value) {
|
||||
return StringConcatHelper.coder(value);
|
||||
public long stringConcatMix(long lengthCoder, char value) {
|
||||
return StringConcatHelper.mix(lengthCoder, value);
|
||||
}
|
||||
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public long stringBuilderConcatMix(long lengthCoder,
|
||||
StringBuilder sb) {
|
||||
return sb.mix(lengthCoder);
|
||||
public int stringSize(long i) {
|
||||
return Long.stringSize(i);
|
||||
}
|
||||
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public long stringBuilderConcatPrepend(long lengthCoder, byte[] buf,
|
||||
StringBuilder sb) {
|
||||
return sb.prepend(lengthCoder, buf);
|
||||
public int getCharsLatin1(long i, int index, byte[] buf) {
|
||||
return StringLatin1.getChars(i, index, buf);
|
||||
}
|
||||
|
||||
public int getCharsUTF16(long i, int index, byte[] buf) {
|
||||
return StringUTF16.getChars(i, index, buf);
|
||||
}
|
||||
|
||||
public String join(String prefix, String suffix, String delimiter, String[] elements, int size) {
|
||||
|
|
|
@ -28,7 +28,6 @@ package java.lang.invoke;
|
|||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.util.FormatConcatItem;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
import sun.invoke.util.Wrapper;
|
||||
|
||||
|
@ -115,14 +114,8 @@ public final class StringConcatFactory {
|
|||
* While the maximum number of argument slots that indy call can handle is 253,
|
||||
* we do not use all those slots, to let the strategies with MethodHandle
|
||||
* combinators to use some arguments.
|
||||
*
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public static final int MAX_INDY_CONCAT_ARG_SLOTS;
|
||||
// Use static initialize block to avoid MAX_INDY_CONCAT_ARG_SLOTS being treating
|
||||
// as a constant for constant folding.
|
||||
static { MAX_INDY_CONCAT_ARG_SLOTS = 200; }
|
||||
private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200;
|
||||
|
||||
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||
|
||||
|
@ -712,9 +705,6 @@ public final class StringConcatFactory {
|
|||
int idx = classIndex(cl);
|
||||
MethodHandle prepend = PREPENDERS[idx];
|
||||
if (prepend == null) {
|
||||
if (idx == STRING_CONCAT_ITEM) {
|
||||
cl = FormatConcatItem.class;
|
||||
}
|
||||
PREPENDERS[idx] = prepend = JLA.stringConcatHelper("prepend",
|
||||
methodType(long.class, long.class, byte[].class,
|
||||
Wrapper.asPrimitiveType(cl), String.class)).rebind();
|
||||
|
@ -726,9 +716,6 @@ public final class StringConcatFactory {
|
|||
int idx = classIndex(cl);
|
||||
MethodHandle prepend = NO_PREFIX_PREPENDERS[idx];
|
||||
if (prepend == null) {
|
||||
if (idx == STRING_CONCAT_ITEM) {
|
||||
cl = FormatConcatItem.class;
|
||||
}
|
||||
NO_PREFIX_PREPENDERS[idx] = prepend = JLA.stringConcatHelper("prepend",
|
||||
methodType(long.class, long.class, byte[].class,
|
||||
Wrapper.asPrimitiveType(cl))).rebind();
|
||||
|
@ -741,15 +728,13 @@ public final class StringConcatFactory {
|
|||
LONG_IDX = 2,
|
||||
BOOLEAN_IDX = 3,
|
||||
STRING_IDX = 4,
|
||||
STRING_CONCAT_ITEM = 5,
|
||||
TYPE_COUNT = 6;
|
||||
TYPE_COUNT = 5;
|
||||
private static int classIndex(Class<?> cl) {
|
||||
if (cl == String.class) return STRING_IDX;
|
||||
if (cl == int.class) return INT_IDX;
|
||||
if (cl == boolean.class) return BOOLEAN_IDX;
|
||||
if (cl == char.class) return CHAR_IDX;
|
||||
if (cl == long.class) return LONG_IDX;
|
||||
if (FormatConcatItem.class.isAssignableFrom(cl)) return STRING_CONCAT_ITEM;
|
||||
throw new IllegalArgumentException("Unexpected class: " + cl);
|
||||
}
|
||||
|
||||
|
@ -1047,303 +1032,4 @@ public final class StringConcatFactory {
|
|||
// no instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified concatenation method to facilitate {@link StringTemplate}
|
||||
* concatenation. This method returns a single concatenation method that
|
||||
* interleaves fragments and values. fragment|value|fragment|value|...|value|fragment.
|
||||
* The number of fragments must be one more that the number of ptypes.
|
||||
* The total number of slots used by the ptypes must be less than or equal
|
||||
* to {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
|
||||
*
|
||||
* @param fragments list of string fragments
|
||||
* @param ptypes list of expression types
|
||||
*
|
||||
* @return the {@link MethodHandle} for concatenation
|
||||
*
|
||||
* @throws StringConcatException If any of the linkage invariants are violated.
|
||||
* @throws NullPointerException If any of the incoming arguments is null.
|
||||
* @throws IllegalArgumentException If the number of value slots exceed {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
|
||||
*
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public static MethodHandle makeConcatWithTemplate(
|
||||
List<String> fragments,
|
||||
List<Class<?>> ptypes)
|
||||
throws StringConcatException
|
||||
{
|
||||
Objects.requireNonNull(fragments, "fragments is null");
|
||||
Objects.requireNonNull(ptypes, "ptypes is null");
|
||||
ptypes = List.copyOf(ptypes);
|
||||
|
||||
if (fragments.size() != ptypes.size() + 1) {
|
||||
throw new IllegalArgumentException("fragments size not equal ptypes size plus one");
|
||||
}
|
||||
|
||||
if (ptypes.isEmpty()) {
|
||||
return MethodHandles.constant(String.class, fragments.get(0));
|
||||
}
|
||||
|
||||
Class<?>[] ttypes = new Class<?>[ptypes.size()];
|
||||
MethodHandle[] filters = new MethodHandle[ptypes.size()];
|
||||
int slots = 0;
|
||||
|
||||
int pos = 0;
|
||||
for (Class<?> ptype : ptypes) {
|
||||
slots += ptype == long.class || ptype == double.class ? 2 : 1;
|
||||
|
||||
if (MAX_INDY_CONCAT_ARG_SLOTS < slots) {
|
||||
throw new StringConcatException("Too many concat argument slots: " +
|
||||
slots + ", can only accept " + MAX_INDY_CONCAT_ARG_SLOTS);
|
||||
}
|
||||
|
||||
boolean isSpecialized = ptype.isPrimitive();
|
||||
boolean isFormatConcatItem = FormatConcatItem.class.isAssignableFrom(ptype);
|
||||
Class<?> ttype = isSpecialized ? promoteToIntType(ptype) :
|
||||
isFormatConcatItem ? FormatConcatItem.class : Object.class;
|
||||
MethodHandle filter = isFormatConcatItem ? null : stringifierFor(ttype);
|
||||
|
||||
if (filter != null) {
|
||||
filters[pos] = filter;
|
||||
ttype = String.class;
|
||||
}
|
||||
|
||||
ttypes[pos++] = ttype;
|
||||
}
|
||||
|
||||
MethodHandle mh = MethodHandles.dropArguments(newString(), 2, ttypes);
|
||||
|
||||
long initialLengthCoder = INITIAL_CODER;
|
||||
pos = 0;
|
||||
for (String fragment : fragments) {
|
||||
initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, fragment);
|
||||
|
||||
if (ttypes.length <= pos) {
|
||||
break;
|
||||
}
|
||||
|
||||
Class<?> ttype = ttypes[pos];
|
||||
// (long,byte[],ttype) -> long
|
||||
MethodHandle prepender = prepender(fragment, ttype);
|
||||
// (byte[],long,ttypes...) -> String (unchanged)
|
||||
mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,1, 0, 2 + pos);
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
String lastFragment = fragments.getLast();
|
||||
initialLengthCoder -= lastFragment.length();
|
||||
MethodHandle newArrayCombinator = lastFragment.isEmpty() ? newArray() :
|
||||
newArrayWithSuffix(lastFragment);
|
||||
// (long,ttypes...) -> String
|
||||
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, newArrayCombinator,
|
||||
1 // index
|
||||
);
|
||||
|
||||
pos = 0;
|
||||
for (Class<?> ttype : ttypes) {
|
||||
// (long,ttype) -> long
|
||||
MethodHandle mix = mixer(ttypes[pos]);
|
||||
boolean lastPType = pos == ttypes.length - 1;
|
||||
|
||||
if (lastPType) {
|
||||
// (ttype) -> long
|
||||
mix = MethodHandles.insertArguments(mix, 0, initialLengthCoder);
|
||||
// (ttypes...) -> String
|
||||
mh = MethodHandles.foldArgumentsWithCombiner(mh, 0, mix,
|
||||
1 + pos // selected argument
|
||||
);
|
||||
} else {
|
||||
// (long,ttypes...) -> String
|
||||
mh = MethodHandles.filterArgumentsWithCombiner(mh, 0, mix,
|
||||
0, // old-index
|
||||
1 + pos // selected argument
|
||||
);
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
mh = MethodHandles.filterArguments(mh, 0, filters);
|
||||
MethodType mt = MethodType.methodType(String.class, ptypes);
|
||||
mh = mh.viewAsType(mt, true);
|
||||
|
||||
return mh;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method breaks up large concatenations into separate
|
||||
* {@link MethodHandle MethodHandles} based on the number of slots required
|
||||
* per {@link MethodHandle}. Each {@link MethodHandle} after the first will
|
||||
* have an extra {@link String} slot for the result from the previous
|
||||
* {@link MethodHandle}.
|
||||
* {@link #makeConcatWithTemplate}
|
||||
* is used to construct the {@link MethodHandle MethodHandles}. The total
|
||||
* number of slots used by the ptypes is open ended. However, care must
|
||||
* be given when combining the {@link MethodHandle MethodHandles} so that
|
||||
* the combine total does not exceed the 255 slot limit.
|
||||
*
|
||||
* @param fragments list of string fragments
|
||||
* @param ptypes list of expression types
|
||||
* @param maxSlots maximum number of slots per {@link MethodHandle}.
|
||||
*
|
||||
* @return List of {@link MethodHandle MethodHandles}
|
||||
*
|
||||
* @throws IllegalArgumentException If maxSlots is not between 1 and
|
||||
* MAX_INDY_CONCAT_ARG_SLOTS.
|
||||
* @throws StringConcatException If any of the linkage invariants are violated.
|
||||
* @throws NullPointerException If any of the incoming arguments is null.
|
||||
* @throws IllegalArgumentException If the number of value slots exceed {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
|
||||
*
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public static List<MethodHandle> makeConcatWithTemplateCluster(
|
||||
List<String> fragments,
|
||||
List<Class<?>> ptypes,
|
||||
int maxSlots)
|
||||
throws StringConcatException
|
||||
{
|
||||
Objects.requireNonNull(fragments, "fragments is null");
|
||||
Objects.requireNonNull(ptypes, "ptypes is null");
|
||||
|
||||
if (fragments.size() != ptypes.size() + 1) {
|
||||
throw new StringConcatException("fragments size not equal ptypes size plus one");
|
||||
}
|
||||
|
||||
if (maxSlots < 1 || MAX_INDY_CONCAT_ARG_SLOTS < maxSlots) {
|
||||
throw new IllegalArgumentException("maxSlots must be between 1 and " +
|
||||
MAX_INDY_CONCAT_ARG_SLOTS);
|
||||
|
||||
}
|
||||
|
||||
if (ptypes.isEmpty()) {
|
||||
return List.of(MethodHandles.constant(String.class, fragments.get(0)));
|
||||
}
|
||||
|
||||
List<MethodHandle> mhs = new ArrayList<>();
|
||||
List<String> fragmentsSection = new ArrayList<>();
|
||||
List<Class<?>> ptypeSection = new ArrayList<>();
|
||||
int slots = 0;
|
||||
|
||||
int pos = 0;
|
||||
for (Class<?> ptype : ptypes) {
|
||||
boolean lastPType = pos == ptypes.size() - 1;
|
||||
fragmentsSection.add(fragments.get(pos));
|
||||
ptypeSection.add(ptype);
|
||||
|
||||
slots += ptype == long.class || ptype == double.class ? 2 : 1;
|
||||
|
||||
if (maxSlots <= slots || lastPType) {
|
||||
fragmentsSection.add(lastPType ? fragments.get(pos + 1) : "");
|
||||
MethodHandle mh = makeConcatWithTemplate(fragmentsSection,
|
||||
ptypeSection);
|
||||
mhs.add(mh);
|
||||
fragmentsSection.clear();
|
||||
fragmentsSection.add("");
|
||||
ptypeSection.clear();
|
||||
ptypeSection.add(String.class);
|
||||
slots = 1;
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
return mhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a {@link MethodHandle} expecting one input, the
|
||||
* receiver of the supplied getters. This method uses
|
||||
* {@link #makeConcatWithTemplateCluster}
|
||||
* to create the intermediate {@link MethodHandle MethodHandles}.
|
||||
*
|
||||
* @param fragments list of string fragments
|
||||
* @param getters list of getter {@link MethodHandle MethodHandles}
|
||||
* @param maxSlots maximum number of slots per {@link MethodHandle} in
|
||||
* cluster.
|
||||
*
|
||||
* @return the {@link MethodHandle} for concatenation
|
||||
*
|
||||
* @throws IllegalArgumentException If maxSlots is not between 1 and
|
||||
* MAX_INDY_CONCAT_ARG_SLOTS or if the
|
||||
* getters don't use the same argument type
|
||||
* @throws StringConcatException If any of the linkage invariants are violated
|
||||
* @throws NullPointerException If any of the incoming arguments is null
|
||||
* @throws IllegalArgumentException If the number of value slots exceed {@link #MAX_INDY_CONCAT_ARG_SLOTS}.
|
||||
*
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public static MethodHandle makeConcatWithTemplateGetters(
|
||||
List<String> fragments,
|
||||
List<MethodHandle> getters,
|
||||
int maxSlots)
|
||||
throws StringConcatException
|
||||
{
|
||||
Objects.requireNonNull(fragments, "fragments is null");
|
||||
Objects.requireNonNull(getters, "getters is null");
|
||||
|
||||
if (fragments.size() != getters.size() + 1) {
|
||||
throw new StringConcatException("fragments size not equal getters size plus one");
|
||||
}
|
||||
|
||||
if (maxSlots < 1 || MAX_INDY_CONCAT_ARG_SLOTS < maxSlots) {
|
||||
throw new IllegalArgumentException("maxSlots must be between 1 and " +
|
||||
MAX_INDY_CONCAT_ARG_SLOTS);
|
||||
|
||||
}
|
||||
|
||||
if (getters.size() == 0) {
|
||||
throw new StringConcatException("no getters supplied");
|
||||
}
|
||||
|
||||
Class<?> receiverType = null;
|
||||
List<Class<?>> ptypes = new ArrayList<>();
|
||||
|
||||
for (MethodHandle getter : getters) {
|
||||
MethodType mt = getter.type();
|
||||
Class<?> returnType = mt.returnType();
|
||||
|
||||
if (returnType == void.class || mt.parameterCount() != 1) {
|
||||
throw new StringConcatException("not a getter " + mt);
|
||||
}
|
||||
|
||||
if (receiverType == null) {
|
||||
receiverType = mt.parameterType(0);
|
||||
} else if (receiverType != mt.parameterType(0)) {
|
||||
throw new StringConcatException("not the same receiever type " +
|
||||
mt + " needs " + receiverType);
|
||||
}
|
||||
|
||||
ptypes.add(returnType);
|
||||
}
|
||||
|
||||
MethodType resultType = MethodType.methodType(String.class, receiverType);
|
||||
List<MethodHandle> clusters = makeConcatWithTemplateCluster(fragments, ptypes,
|
||||
maxSlots);
|
||||
|
||||
MethodHandle mh = null;
|
||||
Iterator<MethodHandle> getterIterator = getters.iterator();
|
||||
|
||||
for (MethodHandle cluster : clusters) {
|
||||
MethodType mt = cluster.type();
|
||||
MethodHandle[] filters = new MethodHandle[mt.parameterCount()];
|
||||
int pos = 0;
|
||||
|
||||
if (mh != null) {
|
||||
filters[pos++] = mh;
|
||||
}
|
||||
|
||||
while (pos < filters.length) {
|
||||
filters[pos++] = getterIterator.next();
|
||||
}
|
||||
|
||||
cluster = MethodHandles.filterArguments(cluster, 0, filters);
|
||||
mh = MethodHandles.permuteArguments(cluster, resultType,
|
||||
new int[filters.length]);
|
||||
}
|
||||
|
||||
return mh;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
* 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.lang.runtime;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This class implements specialized {@link StringTemplate StringTemplates} produced by
|
||||
* string template bootstrap method callsites generated by the compiler. Instances of this
|
||||
* class are produced by {@link StringTemplateImplFactory}.
|
||||
* <p>
|
||||
* Values are stored by subclassing {@link Carriers.CarrierObject}. This allows specializations
|
||||
* and sharing of value shapes without creating a new class for each shape.
|
||||
* <p>
|
||||
* {@link StringTemplate} fragments are shared via binding to the
|
||||
* {@link java.lang.invoke.CallSite CallSite's} {@link MethodHandle}.
|
||||
* <p>
|
||||
* The {@link StringTemplateImpl} instance also carries
|
||||
* specialized {@link MethodHandle MethodHandles} for producing the values list and interpolation.
|
||||
* These {@link MethodHandle MethodHandles} are also shared by binding to the
|
||||
* {@link java.lang.invoke.CallSite CallSite}.
|
||||
*
|
||||
* @since 21
|
||||
*
|
||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||
* Do not rely on its availability.
|
||||
*/
|
||||
final class StringTemplateImpl extends Carriers.CarrierObject implements StringTemplate {
|
||||
/**
|
||||
* List of string fragments for the string template. This value of this list is shared by
|
||||
* all instances created at the {@link java.lang.invoke.CallSite CallSite}.
|
||||
*/
|
||||
private final List<String> fragments;
|
||||
|
||||
/**
|
||||
* Specialized {@link MethodHandle} used to implement the {@link StringTemplate StringTemplate's}
|
||||
* {@code values} method. This {@link MethodHandle} is shared by all instances created at the
|
||||
* {@link java.lang.invoke.CallSite CallSite}.
|
||||
*/
|
||||
private final MethodHandle valuesMH;
|
||||
|
||||
/**
|
||||
* Specialized {@link MethodHandle} used to implement the {@link StringTemplate StringTemplate's}
|
||||
* {@code interpolate} method. This {@link MethodHandle} is shared by all instances created at the
|
||||
* {@link java.lang.invoke.CallSite CallSite}.
|
||||
*/
|
||||
private final MethodHandle interpolateMH;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param primitiveCount number of primitive slots required (bound at callsite)
|
||||
* @param objectCount number of object slots required (bound at callsite)
|
||||
* @param fragments list of string fragments (bound in (bound at callsite)
|
||||
* @param valuesMH {@link MethodHandle} to produce list of values (bound at callsite)
|
||||
* @param interpolateMH {@link MethodHandle} to produce interpolation (bound at callsite)
|
||||
*/
|
||||
StringTemplateImpl(int primitiveCount, int objectCount,
|
||||
List<String> fragments, MethodHandle valuesMH, MethodHandle interpolateMH) {
|
||||
super(primitiveCount, objectCount);
|
||||
this.fragments = fragments;
|
||||
this.valuesMH = valuesMH;
|
||||
this.interpolateMH = interpolateMH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> fragments() {
|
||||
return fragments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> values() {
|
||||
try {
|
||||
return (List<Object>)valuesMH.invokeExact(this);
|
||||
} catch (RuntimeException | Error ex) {
|
||||
throw ex;
|
||||
} catch (Throwable ex) {
|
||||
throw new RuntimeException("string template values failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interpolate() {
|
||||
try {
|
||||
return (String)interpolateMH.invokeExact(this);
|
||||
} catch (RuntimeException | Error ex) {
|
||||
throw ex;
|
||||
} catch (Throwable ex) {
|
||||
throw new RuntimeException("string template interpolate failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof StringTemplate st &&
|
||||
Objects.equals(fragments(), st.fragments()) &&
|
||||
Objects.equals(values(), st.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(fragments(), values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringTemplate.toString(this);
|
||||
}
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
/*
|
||||
* 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.lang.runtime;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.StringConcatException;
|
||||
import java.lang.invoke.StringConcatFactory;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class synthesizes {@link StringTemplate StringTemplates} based on
|
||||
* fragments and bootstrap method type. Usage is primarily from
|
||||
* {@link java.lang.runtime.TemplateRuntime}.
|
||||
*
|
||||
* @since 21
|
||||
*
|
||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||
* Do not rely on its availability.
|
||||
*/
|
||||
final class StringTemplateImplFactory {
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
StringTemplateImplFactory() {
|
||||
throw new AssertionError("private constructor");
|
||||
}
|
||||
|
||||
/*
|
||||
* {@link StringTemplateImpl} constructor MethodHandle.
|
||||
*/
|
||||
private static final MethodHandle CONSTRUCTOR;
|
||||
|
||||
|
||||
/*
|
||||
* Frequently used method types.
|
||||
*/
|
||||
private static final MethodType MT_STRING_STIMPL =
|
||||
MethodType.methodType(String.class, StringTemplateImpl.class);
|
||||
private static final MethodType MT_LIST_STIMPL =
|
||||
MethodType.methodType(List.class, StringTemplateImpl.class);
|
||||
|
||||
/**
|
||||
* List (for nullable) of MethodHandle;
|
||||
*/
|
||||
private static final MethodHandle TO_LIST;
|
||||
|
||||
static {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
|
||||
MethodType mt = MethodType.methodType(void.class, int.class, int.class, List.class,
|
||||
MethodHandle.class, MethodHandle.class);
|
||||
CONSTRUCTOR = lookup.findConstructor(StringTemplateImpl.class, mt)
|
||||
.asType(mt.changeReturnType(Carriers.CarrierObject.class));
|
||||
|
||||
mt = MethodType.methodType(List.class, Object[].class);
|
||||
TO_LIST = lookup.findStatic(StringTemplateImplFactory.class, "toList", mt);
|
||||
} catch(ReflectiveOperationException ex) {
|
||||
throw new AssertionError("carrier static init fail", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link StringTemplateImpl} constructor.
|
||||
*
|
||||
* @param fragments string template fragments
|
||||
* @param type values types with StringTemplate return
|
||||
*
|
||||
* @return {@link MethodHandle} that can construct a {@link StringTemplateImpl} with arguments
|
||||
* used as values.
|
||||
*/
|
||||
static MethodHandle createStringTemplateImplMH(List<String> fragments, MethodType type) {
|
||||
Carriers.CarrierElements elements = Carriers.CarrierFactory.of(type);
|
||||
MethodHandle[] components = elements
|
||||
.components()
|
||||
.stream()
|
||||
.map(c -> c.asType(c.type().changeParameterType(0, StringTemplateImpl.class)))
|
||||
.toArray(MethodHandle[]::new);
|
||||
Class<?>[] ptypes = elements
|
||||
.components()
|
||||
.stream()
|
||||
.map(c -> c.type().returnType())
|
||||
.toArray(Class<?>[]::new);
|
||||
int[] permute = new int[ptypes.length];
|
||||
|
||||
MethodHandle interpolateMH;
|
||||
MethodType mt;
|
||||
try {
|
||||
interpolateMH = StringConcatFactory.makeConcatWithTemplate(fragments, List.of(ptypes));
|
||||
} catch (StringConcatException ex) {
|
||||
throw new RuntimeException("constructing internal string template", ex);
|
||||
}
|
||||
interpolateMH = MethodHandles.filterArguments(interpolateMH, 0, components);
|
||||
interpolateMH = MethodHandles.permuteArguments(interpolateMH, MT_STRING_STIMPL, permute);
|
||||
|
||||
mt = MethodType.methodType(List.class, ptypes);
|
||||
MethodHandle valuesMH = TO_LIST.asCollector(Object[].class, components.length).asType(mt);
|
||||
valuesMH = MethodHandles.filterArguments(valuesMH, 0, components);
|
||||
valuesMH = MethodHandles.permuteArguments(valuesMH, MT_LIST_STIMPL, permute);
|
||||
|
||||
MethodHandle constructor = MethodHandles.insertArguments(CONSTRUCTOR, 0,
|
||||
elements.primitiveCount(), elements.objectCount(),
|
||||
fragments, valuesMH, interpolateMH);
|
||||
constructor = MethodHandles.foldArguments(elements.initializer(), 0, constructor);
|
||||
|
||||
mt = MethodType.methodType(StringTemplate.class, ptypes);
|
||||
constructor = constructor.asType(mt);
|
||||
|
||||
return constructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic {@link StringTemplate}.
|
||||
*
|
||||
* @param fragments immutable list of string fragments from string template
|
||||
* @param values immutable list of expression values
|
||||
*/
|
||||
private record SimpleStringTemplate(List<String> fragments, List<Object> values)
|
||||
implements StringTemplate {
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringTemplate.toString(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new StringTemplate composed from fragments and values.
|
||||
*
|
||||
* @param fragments array of string fragments
|
||||
* @param values array of expression values
|
||||
*
|
||||
* @return StringTemplate composed from fragments and values
|
||||
*/
|
||||
static StringTemplate newTrustedStringTemplate(String[] fragments, Object[] values) {
|
||||
return new SimpleStringTemplate(List.of(fragments), toList(values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new StringTemplate composed from fragments and values.
|
||||
*
|
||||
* @param fragments list of string fragments
|
||||
* @param values array of expression values
|
||||
*
|
||||
* @return StringTemplate composed from fragments and values
|
||||
*/
|
||||
static StringTemplate newTrustedStringTemplate(List<String> fragments, Object[] values) {
|
||||
return new SimpleStringTemplate(List.copyOf(fragments), toList(values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new StringTemplate composed from fragments and values.
|
||||
*
|
||||
* @param fragments list of string fragments
|
||||
* @param values list of expression values
|
||||
*
|
||||
* @return StringTemplate composed from fragments and values
|
||||
*/
|
||||
|
||||
static StringTemplate newStringTemplate(List<String> fragments, List<?> values) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object> copy = (List<Object>)values.stream().toList();
|
||||
return new SimpleStringTemplate(List.copyOf(fragments), copy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect nullable elements from an array into a unmodifiable list.
|
||||
* Elements are guaranteed to be safe.
|
||||
*
|
||||
* @param elements elements to place in list
|
||||
*
|
||||
* @return unmodifiable list.
|
||||
*/
|
||||
private static List<Object> toList(Object[] elements) {
|
||||
return Arrays.stream(elements).toList();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,269 +0,0 @@
|
|||
/*
|
||||
* 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.lang.runtime;
|
||||
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.ConstantCallSite;
|
||||
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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jdk.internal.access.JavaTemplateAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
/**
|
||||
* Manages string template bootstrap methods. These methods may be used, for example,
|
||||
* by Java compiler implementations to create {@link StringTemplate} instances. For example,
|
||||
* the java compiler will translate the following code;
|
||||
* {@snippet :
|
||||
* int x = 10;
|
||||
* int y = 20;
|
||||
* StringTemplate st = RAW."\{x} + \{y} = \{x + y}";
|
||||
* }
|
||||
* to byte code that invokes the {@link java.lang.runtime.TemplateRuntime#newStringTemplate}
|
||||
* bootstrap method to construct a {@link CallSite} that accepts two integers and produces a new
|
||||
* {@link StringTemplate} instance.
|
||||
* {@snippet :
|
||||
* MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
* MethodType mt = MethodType.methodType(StringTemplate.class, int.class, int.class);
|
||||
* CallSite cs = TemplateRuntime.newStringTemplate(lookup, "", mt, "", " + ", " = ", "");
|
||||
* ...
|
||||
* int x = 10;
|
||||
* int y = 20;
|
||||
* StringTemplate st = (StringTemplate)cs.getTarget().invokeExact(x, y);
|
||||
* }
|
||||
* If the string template requires more than
|
||||
* {@link java.lang.invoke.StringConcatFactory#MAX_INDY_CONCAT_ARG_SLOTS} value slots,
|
||||
* then the java compiler will use the
|
||||
* {@link java.lang.runtime.TemplateRuntime#newLargeStringTemplate} bootstrap method
|
||||
* instead. For example, the java compiler will translate the following code;
|
||||
* {@snippet :
|
||||
* int[] a = new int[1000], b = new int[1000];
|
||||
* ...
|
||||
* StringTemplate st = """
|
||||
* \{a[0]} - \{b[0]}
|
||||
* \{a[1]} - \{b[1]}
|
||||
* ...
|
||||
* \{a[999]} - \{b[999]}
|
||||
* """;
|
||||
* }
|
||||
* to byte code that invokes the {@link java.lang.runtime.TemplateRuntime#newLargeStringTemplate}
|
||||
* bootstrap method to construct a {@link CallSite} that accepts an array of integers and produces a new
|
||||
* {@link StringTemplate} instance.
|
||||
* {@snippet :
|
||||
* MethodType mt = MethodType.methodType(StringTemplate.class, String[].class, Object[].class);
|
||||
* CallSite cs = TemplateRuntime.newStringTemplate(lookup, "", mt);
|
||||
* ...
|
||||
* int[] a = new int[1000], b = new int[1000];
|
||||
* ...
|
||||
* StringTemplate st = (StringTemplate)cs.getTarget().invokeExact(
|
||||
* new String[] { "", " - ", "\n", " - ", "\n", ... " - ", "\n" },
|
||||
* new Object[] { a[0], b[0], a[1], b[1], ..., a[999], b[999]}
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* @since 21
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||
public final class TemplateRuntime {
|
||||
private static final JavaTemplateAccess JTA = SharedSecrets.getJavaTemplateAccess();
|
||||
|
||||
/**
|
||||
* {@link MethodHandle} to {@link TemplateRuntime#defaultProcess}.
|
||||
*/
|
||||
private static final MethodHandle DEFAULT_PROCESS_MH;
|
||||
|
||||
/**
|
||||
* {@link MethodHandle} to {@link TemplateRuntime#newTrustedStringTemplate}.
|
||||
*/
|
||||
private static final MethodHandle NEW_TRUSTED_STRING_TEMPLATE;
|
||||
|
||||
/**
|
||||
* Initialize {@link MethodHandle MethodHandles}.
|
||||
*/
|
||||
static {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
|
||||
MethodType mt = MethodType.methodType(Object.class,
|
||||
List.class, Processor.class, Object[].class);
|
||||
DEFAULT_PROCESS_MH =
|
||||
lookup.findStatic(TemplateRuntime.class, "defaultProcess", mt);
|
||||
|
||||
mt = MethodType.methodType(StringTemplate.class, String[].class, Object[].class);
|
||||
NEW_TRUSTED_STRING_TEMPLATE =
|
||||
lookup.findStatic(StringTemplateImplFactory.class, "newTrustedStringTemplate", mt);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new AssertionError("string bootstrap fail", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private TemplateRuntime() {
|
||||
throw new AssertionError("private constructor");
|
||||
}
|
||||
|
||||
/**
|
||||
* String template bootstrap method for creating string templates.
|
||||
* The static arguments include the fragments list.
|
||||
* The non-static arguments are the values.
|
||||
*
|
||||
* @param lookup method lookup from call site
|
||||
* @param name method name - not used
|
||||
* @param type method type
|
||||
* (ptypes...) -> StringTemplate
|
||||
* @param fragments fragment array for string template
|
||||
*
|
||||
* @return {@link CallSite} to handle create string template
|
||||
*
|
||||
* @throws NullPointerException if any of the arguments is null
|
||||
* @throws Throwable if linkage fails
|
||||
*/
|
||||
public static CallSite newStringTemplate(MethodHandles.Lookup lookup,
|
||||
String name,
|
||||
MethodType type,
|
||||
String... fragments) throws Throwable {
|
||||
Objects.requireNonNull(lookup, "lookup is null");
|
||||
Objects.requireNonNull(name, "name is null");
|
||||
Objects.requireNonNull(type, "type is null");
|
||||
Objects.requireNonNull(fragments, "fragments is null");
|
||||
|
||||
MethodHandle mh = StringTemplateImplFactory
|
||||
.createStringTemplateImplMH(List.of(fragments), type).asType(type);
|
||||
|
||||
return new ConstantCallSite(mh);
|
||||
}
|
||||
|
||||
/**
|
||||
* String template bootstrap method for creating large string templates,
|
||||
* i.e., when the number of value slots exceeds
|
||||
* {@link java.lang.invoke.StringConcatFactory#MAX_INDY_CONCAT_ARG_SLOTS}.
|
||||
* The non-static arguments are the fragments array and values array.
|
||||
*
|
||||
* @param lookup method lookup from call site
|
||||
* @param name method name - not used
|
||||
* @param type method type
|
||||
* (String[], Object[]) -> StringTemplate
|
||||
*
|
||||
* @return {@link CallSite} to handle create large string template
|
||||
*
|
||||
* @throws NullPointerException if any of the arguments is null
|
||||
* @throws Throwable if linkage fails
|
||||
*/
|
||||
public static CallSite newLargeStringTemplate(MethodHandles.Lookup lookup,
|
||||
String name,
|
||||
MethodType type) throws Throwable {
|
||||
Objects.requireNonNull(lookup, "lookup is null");
|
||||
Objects.requireNonNull(name, "name is null");
|
||||
Objects.requireNonNull(type, "type is null");
|
||||
|
||||
return new ConstantCallSite(NEW_TRUSTED_STRING_TEMPLATE.asType(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* String template bootstrap method for static final processors.
|
||||
* The static arguments include the fragments array and a {@link MethodHandle}
|
||||
* to retrieve the value of the static final processor.
|
||||
* The non-static arguments are the values.
|
||||
*
|
||||
* @param lookup method lookup from call site
|
||||
* @param name method name - not used
|
||||
* @param type method type
|
||||
* (ptypes...) -> Object
|
||||
* @param processorGetter {@link MethodHandle} to get static final processor
|
||||
* @param fragments fragments from string template
|
||||
*
|
||||
* @return {@link CallSite} to handle string template processing
|
||||
*
|
||||
* @throws NullPointerException if any of the arguments is null
|
||||
* @throws Throwable if linkage fails
|
||||
*
|
||||
* @implNote this method is likely to be revamped before exiting preview.
|
||||
*/
|
||||
public static CallSite processStringTemplate(MethodHandles.Lookup lookup,
|
||||
String name,
|
||||
MethodType type,
|
||||
MethodHandle processorGetter,
|
||||
String... fragments) throws Throwable {
|
||||
Objects.requireNonNull(lookup, "lookup is null");
|
||||
Objects.requireNonNull(name, "name is null");
|
||||
Objects.requireNonNull(type, "type is null");
|
||||
Objects.requireNonNull(processorGetter, "processorGetter is null");
|
||||
Objects.requireNonNull(fragments, "fragments is null");
|
||||
|
||||
Processor<?, ?> processor = (Processor<?, ?>)processorGetter.invoke();
|
||||
MethodHandle mh = processor instanceof Linkage linkage
|
||||
? linkage.linkage(List.of(fragments), type)
|
||||
: defaultProcessMethodHandle(type, processor, List.of(fragments));
|
||||
|
||||
return new ConstantCallSite(mh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple {@link StringTemplate} and then invokes the processor's process method.
|
||||
*
|
||||
* @param fragments fragments from string template
|
||||
* @param processor {@link Processor} to process
|
||||
* @param values array of expression values
|
||||
*
|
||||
* @return result of processing the string template
|
||||
*
|
||||
* @throws Throwable when {@link Processor#process(StringTemplate)} throws
|
||||
*/
|
||||
private static Object defaultProcess(
|
||||
List<String> fragments,
|
||||
Processor<?, ?> processor,
|
||||
Object[] values
|
||||
) throws Throwable {
|
||||
return processor.process(StringTemplate.of(fragments, Arrays.stream(values).toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a {@link MethodHandle} which is effectively invokes
|
||||
* {@code processor.process(new StringTemplate(fragments, values...)}.
|
||||
*
|
||||
* @return default process {@link MethodHandle}
|
||||
*/
|
||||
private static MethodHandle defaultProcessMethodHandle(
|
||||
MethodType type,
|
||||
Processor<?, ?> processor,
|
||||
List<String> fragments
|
||||
) {
|
||||
MethodHandle mh = MethodHandles.insertArguments(DEFAULT_PROCESS_MH, 0, fragments, processor);
|
||||
return mh.asCollector(Object[].class, type.parameterCount()).asType(type);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
/*
|
||||
* 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.lang.runtime;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.JavaTemplateAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
|
||||
/**
|
||||
* This class provides runtime support for string templates. The methods within
|
||||
* are intended for internal use only.
|
||||
*
|
||||
* @since 21
|
||||
*
|
||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||
* Do not rely on its availability.
|
||||
*/
|
||||
final class TemplateSupport implements JavaTemplateAccess {
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
*/
|
||||
private TemplateSupport() {
|
||||
}
|
||||
|
||||
static {
|
||||
SharedSecrets.setJavaTemplateAccess(new TemplateSupport());
|
||||
}
|
||||
|
||||
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||
|
||||
/**
|
||||
* Returns a StringTemplate composed from fragments and values.
|
||||
*
|
||||
* @implSpec The {@code fragments} list size must be one more that the
|
||||
* {@code values} list size.
|
||||
*
|
||||
* @param fragments list of string fragments
|
||||
* @param values list of expression values
|
||||
*
|
||||
* @return StringTemplate composed from fragments and values
|
||||
*
|
||||
* @throws IllegalArgumentException if fragments list size is not one more
|
||||
* than values list size
|
||||
* @throws NullPointerException if fragments is null or values is null or if any fragment is null.
|
||||
*
|
||||
* @implNote Contents of both lists are copied to construct immutable lists.
|
||||
*/
|
||||
@Override
|
||||
public StringTemplate of(List<String> fragments, List<?> values) {
|
||||
return StringTemplateImplFactory.newStringTemplate(fragments, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string that interleaves the elements of values between the
|
||||
* elements of fragments.
|
||||
*
|
||||
* @param fragments list of String fragments
|
||||
* @param values list of expression values
|
||||
*
|
||||
* @return String interpolation of fragments and values
|
||||
*/
|
||||
@Override
|
||||
public String interpolate(List<String> fragments, List<?> values) {
|
||||
int fragmentsSize = fragments.size();
|
||||
int valuesSize = values.size();
|
||||
if (fragmentsSize == 1) {
|
||||
return fragments.get(0);
|
||||
}
|
||||
int size = fragmentsSize + valuesSize;
|
||||
String[] strings = new String[size];
|
||||
int i = 0, j = 0;
|
||||
for (; j < valuesSize; j++) {
|
||||
strings[i++] = fragments.get(j);
|
||||
strings[i++] = String.valueOf(values.get(j));
|
||||
}
|
||||
strings[i] = fragments.get(j);
|
||||
return JLA.join("", "", "", strings, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine one or more {@link StringTemplate StringTemplates} to produce a combined {@link StringTemplate}.
|
||||
* {@snippet :
|
||||
* StringTemplate st = StringTemplate.combine("\{a}", "\{b}", "\{c}");
|
||||
* assert st.interpolate().equals("\{a}\{b}\{c}");
|
||||
* }
|
||||
*
|
||||
* @param sts zero or more {@link StringTemplate}
|
||||
*
|
||||
* @return combined {@link StringTemplate}
|
||||
*
|
||||
* @throws NullPointerException if sts is null or if any element of sts is null
|
||||
*/
|
||||
@Override
|
||||
public StringTemplate combine(StringTemplate... sts) {
|
||||
Objects.requireNonNull(sts, "sts must not be null");
|
||||
if (sts.length == 0) {
|
||||
return StringTemplate.of("");
|
||||
} else if (sts.length == 1) {
|
||||
return Objects.requireNonNull(sts[0], "string templates should not be null");
|
||||
}
|
||||
int size = 0;
|
||||
for (StringTemplate st : sts) {
|
||||
Objects.requireNonNull(st, "string templates should not be null");
|
||||
size += st.values().size();
|
||||
}
|
||||
String[] combinedFragments = new String[size + 1];
|
||||
Object[] combinedValues = new Object[size];
|
||||
combinedFragments[0] = "";
|
||||
int fragmentIndex = 1;
|
||||
int valueIndex = 0;
|
||||
for (StringTemplate st : sts) {
|
||||
Iterator<String> iterator = st.fragments().iterator();
|
||||
combinedFragments[fragmentIndex - 1] += iterator.next();
|
||||
while (iterator.hasNext()) {
|
||||
combinedFragments[fragmentIndex++] = iterator.next();
|
||||
}
|
||||
for (Object value : st.values()) {
|
||||
combinedValues[valueIndex++] = value;
|
||||
}
|
||||
}
|
||||
return StringTemplateImplFactory.newTrustedStringTemplate(combinedFragments, combinedValues);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,540 +0,0 @@
|
|||
/*
|
||||
* 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.io.IOException;
|
||||
import java.lang.invoke.*;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.Formatter.FormatSpecifier;
|
||||
|
||||
import jdk.internal.access.JavaLangAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.util.FormatConcatItem;
|
||||
import jdk.internal.util.DecimalDigits;
|
||||
import jdk.internal.util.HexDigits;
|
||||
import jdk.internal.util.OctalDigits;
|
||||
|
||||
import static java.lang.invoke.MethodType.methodType;
|
||||
|
||||
/**
|
||||
* A specialized objects used by FormatterBuilder that knows how to insert
|
||||
* themselves into a concatenation performed by StringConcatFactory.
|
||||
*
|
||||
* @since 21
|
||||
*
|
||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||
* Do not rely on its availability.
|
||||
*/
|
||||
class FormatItem {
|
||||
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||
|
||||
private static final MethodHandle CHAR_MIX =
|
||||
JLA.stringConcatHelper("mix",
|
||||
MethodType.methodType(long.class, long.class,char.class));
|
||||
|
||||
private static final MethodHandle STRING_PREPEND =
|
||||
JLA.stringConcatHelper("prepend",
|
||||
MethodType.methodType(long.class, long.class, byte[].class,
|
||||
String.class));
|
||||
|
||||
private static final MethodHandle SELECT_GETCHAR_MH =
|
||||
JLA.stringConcatHelper("selectGetChar",
|
||||
MethodType.methodType(MethodHandle.class, long.class));
|
||||
|
||||
private static final MethodHandle SELECT_PUTCHAR_MH =
|
||||
JLA.stringConcatHelper("selectPutChar",
|
||||
MethodType.methodType(MethodHandle.class, long.class));
|
||||
|
||||
private static long charMix(long lengthCoder, char value) {
|
||||
try {
|
||||
return (long)CHAR_MIX.invokeExact(lengthCoder, value);
|
||||
} catch (Error | RuntimeException ex) {
|
||||
throw ex;
|
||||
} catch (Throwable ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static long stringMix(long lengthCoder, String value) {
|
||||
return JLA.stringConcatMix(lengthCoder, value);
|
||||
}
|
||||
|
||||
private static long stringPrepend(long lengthCoder, byte[] buffer,
|
||||
String value) throws Throwable {
|
||||
return (long)STRING_PREPEND.invokeExact(lengthCoder, buffer, value);
|
||||
}
|
||||
|
||||
private static MethodHandle selectGetChar(long indexCoder) throws Throwable {
|
||||
return (MethodHandle)SELECT_GETCHAR_MH.invokeExact(indexCoder);
|
||||
}
|
||||
|
||||
private static MethodHandle selectPutChar(long indexCoder) throws Throwable {
|
||||
return (MethodHandle)SELECT_PUTCHAR_MH.invokeExact(indexCoder);
|
||||
}
|
||||
|
||||
private static final MethodHandle PUT_CHAR_DIGIT;
|
||||
|
||||
static {
|
||||
try {
|
||||
Lookup lookup = MethodHandles.lookup();
|
||||
PUT_CHAR_DIGIT = lookup.findStatic(FormatItem.class, "putByte",
|
||||
MethodType.methodType(void.class,
|
||||
byte[].class, int.class, int.class));
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new AssertionError("putByte lookup failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void putByte(byte[] buffer, int index, int ch) {
|
||||
buffer[index] = (byte)ch;
|
||||
}
|
||||
|
||||
private FormatItem() {
|
||||
throw new AssertionError("private constructor");
|
||||
}
|
||||
|
||||
/**
|
||||
* Decimal value format item.
|
||||
*/
|
||||
static final class FormatItemDecimal implements FormatConcatItem {
|
||||
private final char groupingSeparator;
|
||||
private final char zeroDigit;
|
||||
private final char minusSign;
|
||||
private final int digitOffset;
|
||||
private final byte[] digits;
|
||||
private final int length;
|
||||
private final boolean isNegative;
|
||||
private final int width;
|
||||
private final byte prefixSign;
|
||||
private final int groupSize;
|
||||
private final long value;
|
||||
private final boolean parentheses;
|
||||
|
||||
FormatItemDecimal(DecimalFormatSymbols dfs, int width, char sign,
|
||||
boolean parentheses, int groupSize, long value) throws Throwable {
|
||||
this.groupingSeparator = dfs.getGroupingSeparator();
|
||||
this.zeroDigit = dfs.getZeroDigit();
|
||||
this.minusSign = dfs.getMinusSign();
|
||||
this.digitOffset = this.zeroDigit - '0';
|
||||
int length = DecimalDigits.INSTANCE.size(value);
|
||||
this.digits = new byte[length];
|
||||
DecimalDigits.INSTANCE.digits(value, this.digits, length, PUT_CHAR_DIGIT);
|
||||
this.isNegative = value < 0L;
|
||||
this.length = this.isNegative ? length - 1 : length;
|
||||
this.width = width;
|
||||
this.groupSize = groupSize;
|
||||
this.value = value;
|
||||
this.parentheses = parentheses && isNegative;
|
||||
this.prefixSign = (byte)(isNegative ? (parentheses ? '\0' : minusSign) : sign);
|
||||
}
|
||||
|
||||
private int signLength() {
|
||||
return (prefixSign != '\0' ? 1 : 0) + (parentheses ? 2 : 0);
|
||||
}
|
||||
|
||||
private int groupLength() {
|
||||
return 0 < groupSize ? (length - 1) / groupSize : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return JLA.stringConcatCoder(zeroDigit) |
|
||||
(lengthCoder +
|
||||
Integer.max(length + signLength() + groupLength(), width));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
|
||||
if (parentheses) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)')');
|
||||
}
|
||||
|
||||
if (0 < groupSize) {
|
||||
int groupIndex = groupSize;
|
||||
|
||||
for (int i = 1; i <= length; i++) {
|
||||
if (groupIndex-- == 0) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder,
|
||||
(int)groupingSeparator);
|
||||
groupIndex = groupSize - 1;
|
||||
}
|
||||
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder,
|
||||
digits[digits.length - i] + digitOffset);
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i <= length; i++) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder,
|
||||
digits[digits.length - i] + digitOffset);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = length + signLength() + groupLength(); i < width; i++) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'0');
|
||||
}
|
||||
|
||||
if (parentheses) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'(');
|
||||
}
|
||||
if (prefixSign != '\0') {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)prefixSign);
|
||||
}
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hexadecimal format item.
|
||||
*/
|
||||
static final class FormatItemHexadecimal implements FormatConcatItem {
|
||||
private final int width;
|
||||
private final boolean hasPrefix;
|
||||
private final long value;
|
||||
private final int length;
|
||||
|
||||
FormatItemHexadecimal(int width, boolean hasPrefix, long value) {
|
||||
this.width = width;
|
||||
this.hasPrefix = hasPrefix;
|
||||
this.value = value;
|
||||
this.length = HexDigits.INSTANCE.size(value);
|
||||
}
|
||||
|
||||
private int prefixLength() {
|
||||
return hasPrefix ? 2 : 0;
|
||||
}
|
||||
|
||||
private int zeroesLength() {
|
||||
return Integer.max(0, width - length - prefixLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return lengthCoder + length + prefixLength() + zeroesLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
HexDigits.INSTANCE.digits(value, buffer, (int)lengthCoder, putCharMH);
|
||||
lengthCoder -= length;
|
||||
|
||||
for (int i = 0; i < zeroesLength(); i++) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'0');
|
||||
}
|
||||
|
||||
if (hasPrefix) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'x');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'0');
|
||||
}
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hexadecimal format item.
|
||||
*/
|
||||
static final class FormatItemOctal implements FormatConcatItem {
|
||||
private final int width;
|
||||
private final boolean hasPrefix;
|
||||
private final long value;
|
||||
private final int length;
|
||||
|
||||
FormatItemOctal(int width, boolean hasPrefix, long value) {
|
||||
this.width = width;
|
||||
this.hasPrefix = hasPrefix;
|
||||
this.value = value;
|
||||
this.length = OctalDigits.INSTANCE.size(value);
|
||||
}
|
||||
|
||||
private int prefixLength() {
|
||||
return hasPrefix && value != 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
private int zeroesLength() {
|
||||
return Integer.max(0, width - length - prefixLength());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return lengthCoder + length + prefixLength() + zeroesLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
OctalDigits.INSTANCE.digits(value, buffer, (int)lengthCoder, putCharMH);
|
||||
lengthCoder -= length;
|
||||
|
||||
for (int i = 0; i < zeroesLength(); i++) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'0');
|
||||
}
|
||||
|
||||
if (hasPrefix && value != 0) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'0');
|
||||
}
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean format item.
|
||||
*/
|
||||
static final class FormatItemBoolean implements FormatConcatItem {
|
||||
private final boolean value;
|
||||
|
||||
FormatItemBoolean(boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return lengthCoder + (value ? "true".length() : "false".length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
|
||||
if (value) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'e');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'u');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'r');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'t');
|
||||
} else {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'e');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'s');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'l');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'a');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'f');
|
||||
}
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Character format item.
|
||||
*/
|
||||
static final class FormatItemCharacter implements FormatConcatItem {
|
||||
private final char value;
|
||||
|
||||
FormatItemCharacter(char value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return charMix(lengthCoder, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)value);
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String format item.
|
||||
*/
|
||||
static final class FormatItemString implements FormatConcatItem {
|
||||
private String value;
|
||||
|
||||
FormatItemString(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return stringMix(lengthCoder, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
return stringPrepend(lengthCoder, buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FormatSpecifier format item.
|
||||
*/
|
||||
static final class FormatItemFormatSpecifier implements FormatConcatItem {
|
||||
private StringBuilder sb;
|
||||
|
||||
FormatItemFormatSpecifier(FormatSpecifier fs, Locale locale, Object value) {
|
||||
this.sb = new StringBuilder(64);
|
||||
Formatter formatter = new Formatter(this.sb, locale);
|
||||
|
||||
try {
|
||||
fs.print(formatter, value, locale);
|
||||
} catch (IOException ex) {
|
||||
throw new AssertionError("FormatItemFormatSpecifier IOException", ex);
|
||||
}
|
||||
}
|
||||
|
||||
FormatItemFormatSpecifier(Locale locale,
|
||||
int flags, int width, int precision,
|
||||
Formattable formattable) {
|
||||
this.sb = new StringBuilder(64);
|
||||
Formatter formatter = new Formatter(this.sb, locale);
|
||||
formattable.formatTo(formatter, flags, width, precision);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return JLA.stringBuilderConcatMix(lengthCoder, sb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
return JLA.stringBuilderConcatPrepend(lengthCoder, buffer, sb);
|
||||
}
|
||||
}
|
||||
|
||||
abstract static sealed class FormatItemModifier implements FormatConcatItem
|
||||
permits FormatItemFillLeft,
|
||||
FormatItemFillRight
|
||||
{
|
||||
private final long itemLengthCoder;
|
||||
protected final FormatConcatItem item;
|
||||
|
||||
FormatItemModifier(FormatConcatItem item) {
|
||||
this.itemLengthCoder = item.mix(0L);
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return (int)itemLengthCoder;
|
||||
}
|
||||
|
||||
long coder() {
|
||||
return itemLengthCoder & ~Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract long mix(long lengthCoder);
|
||||
|
||||
@Override
|
||||
public abstract long prepend(long lengthCoder, byte[] buffer) throws Throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill left format item.
|
||||
*/
|
||||
static final class FormatItemFillLeft extends FormatItemModifier
|
||||
implements FormatConcatItem {
|
||||
private final int width;
|
||||
|
||||
FormatItemFillLeft(int width, FormatConcatItem item) {
|
||||
super(item);
|
||||
this.width = Integer.max(length(), width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return (lengthCoder | coder()) + width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
lengthCoder = item.prepend(lengthCoder, buffer);
|
||||
|
||||
for (int i = length(); i < width; i++) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)' ');
|
||||
}
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill right format item.
|
||||
*/
|
||||
static final class FormatItemFillRight extends FormatItemModifier
|
||||
implements FormatConcatItem {
|
||||
private final int width;
|
||||
|
||||
FormatItemFillRight(int width, FormatConcatItem item) {
|
||||
super(item);
|
||||
this.width = Integer.max(length(), width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return (lengthCoder | coder()) + width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
|
||||
for (int i = length(); i < width; i++) {
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)' ');
|
||||
}
|
||||
|
||||
lengthCoder = item.prepend(lengthCoder, buffer);
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Null format item.
|
||||
*/
|
||||
static final class FormatItemNull implements FormatConcatItem {
|
||||
FormatItemNull() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mix(long lengthCoder) {
|
||||
return lengthCoder + "null".length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long prepend(long lengthCoder, byte[] buffer) throws Throwable {
|
||||
MethodHandle putCharMH = selectPutChar(lengthCoder);
|
||||
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'l');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'l');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'u');
|
||||
putCharMH.invokeExact(buffer, (int)--lengthCoder, (int)'n');
|
||||
|
||||
return lengthCoder;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,299 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2023, 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 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 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) {
|
||||
int max = fragment.length();
|
||||
for (int i = 0; i < max;) {
|
||||
int n = fragment.indexOf('%', i);
|
||||
if (n < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
i = n + 1;
|
||||
if (i >= max) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char c = fragment.charAt(i);
|
||||
if (c == '%' || c == 'n') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
int off = new Formatter.FormatSpecifierParser(null, c, i, fragment, max)
|
||||
.parse();
|
||||
if (off == 0) {
|
||||
return false;
|
||||
}
|
||||
if (i + off == max && needed) {
|
||||
return true;
|
||||
}
|
||||
throw new MissingFormatArgumentException(
|
||||
fragment.substring(i - 1, i + off)
|
||||
+ " 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);
|
||||
|
||||
}
|
|
@ -61,7 +61,6 @@ import java.time.temporal.TemporalAccessor;
|
|||
import java.time.temporal.TemporalQueries;
|
||||
import java.time.temporal.UnsupportedTemporalTypeException;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.math.DoubleConsts;
|
||||
import jdk.internal.math.FormattedFPDecimal;
|
||||
import sun.util.locale.provider.LocaleProviderAdapter;
|
||||
|
|
|
@ -1,489 +0,0 @@
|
|||
/*
|
||||
* 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.io.IOException;
|
||||
import java.lang.invoke.*;
|
||||
import java.lang.invoke.MethodHandles.Lookup;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.spi.NumberFormatProvider;
|
||||
import java.util.FormatItem.*;
|
||||
import java.util.Formatter.*;
|
||||
|
||||
import jdk.internal.util.FormatConcatItem;
|
||||
|
||||
import sun.invoke.util.Wrapper;
|
||||
import sun.util.locale.provider.LocaleProviderAdapter;
|
||||
import sun.util.locale.provider.ResourceBundleBasedAdapter;
|
||||
|
||||
import static java.util.Formatter.Conversion.*;
|
||||
import static java.util.Formatter.Flags.*;
|
||||
import static java.lang.invoke.MethodHandles.*;
|
||||
import static java.lang.invoke.MethodType.*;
|
||||
|
||||
/**
|
||||
* This package private class supports the construction of the {@link MethodHandle}
|
||||
* used by {@link FormatProcessor}.
|
||||
*
|
||||
* @since 21
|
||||
*
|
||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||
* Do not rely on its availability.
|
||||
*/
|
||||
final class FormatterBuilder {
|
||||
private static final Lookup LOOKUP = lookup();
|
||||
|
||||
private final String format;
|
||||
private final Locale locale;
|
||||
private final Class<?>[] ptypes;
|
||||
private final DecimalFormatSymbols dfs;
|
||||
private final boolean isGenericDFS;
|
||||
|
||||
FormatterBuilder(String format, Locale locale, Class<?>[] ptypes) {
|
||||
this.format = format;
|
||||
this.locale = locale;
|
||||
this.ptypes = ptypes;
|
||||
this.dfs = DecimalFormatSymbols.getInstance(locale);
|
||||
this.isGenericDFS = isGenericDFS(this.dfs);
|
||||
}
|
||||
|
||||
private static boolean isGenericDFS(DecimalFormatSymbols dfs) {
|
||||
return dfs.getZeroDigit() == '0' &&
|
||||
dfs.getDecimalSeparator() == '.' &&
|
||||
dfs.getGroupingSeparator() == ',' &&
|
||||
dfs.getMinusSign() == '-';
|
||||
}
|
||||
|
||||
private static Class<?> mapType(Class<?> type) {
|
||||
return type.isPrimitive() || type == String.class ? type : Object.class;
|
||||
}
|
||||
|
||||
private static MethodHandle findStringConcatItemConstructor(Class<?> cls,
|
||||
Class<?>... ptypes) {
|
||||
MethodType methodType = methodType(void.class, ptypes);
|
||||
|
||||
try {
|
||||
MethodHandle mh = LOOKUP.findConstructor(cls, methodType);
|
||||
|
||||
return mh.asType(mh.type().changeReturnType(FormatConcatItem.class));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError("Missing constructor in " +
|
||||
cls + ": " + methodType);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodHandle findMethod(Class<?> cls, String name,
|
||||
Class<?> rType, Class<?>... ptypes) {
|
||||
MethodType methodType = methodType(rType, ptypes);
|
||||
|
||||
try {
|
||||
return LOOKUP.findVirtual(cls, name, methodType);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError("Missing method in " +
|
||||
cls + ": " + name + " " + methodType);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodHandle findStaticMethod(Class<?> cls, String name,
|
||||
Class<?> rType, Class<?>... ptypes) {
|
||||
MethodType methodType = methodType(rType, ptypes);
|
||||
|
||||
try {
|
||||
return LOOKUP.findStatic(cls, name, methodType);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError("Missing static method in " +
|
||||
cls + ": " + name + " " + methodType);
|
||||
}
|
||||
}
|
||||
|
||||
private static final MethodHandle FIDecimal_MH =
|
||||
findStringConcatItemConstructor(FormatItemDecimal.class,
|
||||
DecimalFormatSymbols.class, int.class, char.class, boolean.class,
|
||||
int.class, long.class);
|
||||
|
||||
private static final MethodHandle FIHexadecimal_MH =
|
||||
findStringConcatItemConstructor(FormatItemHexadecimal.class,
|
||||
int.class, boolean.class, long.class);
|
||||
|
||||
private static final MethodHandle FIOctal_MH =
|
||||
findStringConcatItemConstructor(FormatItemOctal.class,
|
||||
int.class, boolean.class, long.class);
|
||||
|
||||
private static final MethodHandle FIBoolean_MH =
|
||||
findStringConcatItemConstructor(FormatItemBoolean.class,
|
||||
boolean.class);
|
||||
|
||||
private static final MethodHandle FICharacter_MH =
|
||||
findStringConcatItemConstructor(FormatItemCharacter.class,
|
||||
char.class);
|
||||
|
||||
private static final MethodHandle FIString_MH =
|
||||
findStringConcatItemConstructor(FormatItemString.class,
|
||||
String.class);
|
||||
|
||||
private static final MethodHandle FIFormatSpecifier_MH =
|
||||
findStringConcatItemConstructor(FormatItemFormatSpecifier.class,
|
||||
FormatSpecifier.class, Locale.class, Object.class);
|
||||
|
||||
private static final MethodHandle FIFormattable_MH =
|
||||
findStringConcatItemConstructor(FormatItemFormatSpecifier.class,
|
||||
Locale.class, int.class, int.class, int.class,
|
||||
Formattable.class);
|
||||
|
||||
private static final MethodHandle FIFillLeft_MH =
|
||||
findStringConcatItemConstructor(FormatItemFillLeft.class,
|
||||
int.class, FormatConcatItem.class);
|
||||
|
||||
private static final MethodHandle FIFillRight_MH =
|
||||
findStringConcatItemConstructor(FormatItemFillRight.class,
|
||||
int.class, FormatConcatItem.class);
|
||||
|
||||
private static final MethodHandle FINull_MH =
|
||||
findStringConcatItemConstructor(FormatItemNull.class);
|
||||
|
||||
private static final MethodHandle NullCheck_MH =
|
||||
findStaticMethod(FormatterBuilder.class, "nullCheck", boolean.class,
|
||||
Object.class);
|
||||
|
||||
private static final MethodHandle FormattableCheck_MH =
|
||||
findStaticMethod(FormatterBuilder.class, "formattableCheck", boolean.class,
|
||||
Object.class);
|
||||
|
||||
private static final MethodHandle ToLong_MH =
|
||||
findStaticMethod(java.util.FormatterBuilder.class, "toLong", long.class,
|
||||
int.class);
|
||||
|
||||
private static final MethodHandle ToString_MH =
|
||||
findStaticMethod(String.class, "valueOf", String.class,
|
||||
Object.class);
|
||||
|
||||
private static final MethodHandle HashCode_MH =
|
||||
findStaticMethod(Objects.class, "hashCode", int.class,
|
||||
Object.class);
|
||||
|
||||
private static boolean nullCheck(Object object) {
|
||||
return object == null;
|
||||
}
|
||||
|
||||
private static boolean formattableCheck(Object object) {
|
||||
return Formattable.class.isAssignableFrom(object.getClass());
|
||||
}
|
||||
|
||||
private static long toLong(int value) {
|
||||
return (long)value & 0xFFFFFFFFL;
|
||||
}
|
||||
|
||||
private static boolean isFlag(int value, int flags) {
|
||||
return (value & flags) != 0;
|
||||
}
|
||||
|
||||
private static boolean validFlags(int value, int flags) {
|
||||
return (value & ~flags) == 0;
|
||||
}
|
||||
|
||||
private static int groupSize(Locale locale, DecimalFormatSymbols dfs) {
|
||||
if (isGenericDFS(dfs)) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
DecimalFormat df;
|
||||
NumberFormat nf = NumberFormat.getNumberInstance(locale);
|
||||
|
||||
if (nf instanceof DecimalFormat) {
|
||||
df = (DecimalFormat)nf;
|
||||
} else {
|
||||
LocaleProviderAdapter adapter = LocaleProviderAdapter
|
||||
.getAdapter(NumberFormatProvider.class, locale);
|
||||
|
||||
if (!(adapter instanceof ResourceBundleBasedAdapter)) {
|
||||
adapter = LocaleProviderAdapter.getResourceBundleBased();
|
||||
}
|
||||
|
||||
String[] all = adapter.getLocaleResources(locale)
|
||||
.getNumberPatterns();
|
||||
|
||||
df = new DecimalFormat(all[0], dfs);
|
||||
}
|
||||
|
||||
return df.isGroupingUsed() ? df.getGroupingSize() : 0;
|
||||
}
|
||||
|
||||
private MethodHandle formatSpecifier(FormatSpecifier fs, Class<?> ptype) {
|
||||
boolean isPrimitive = ptype.isPrimitive();
|
||||
MethodHandle mh = identity(ptype);
|
||||
MethodType mt = mh.type();
|
||||
|
||||
//cannot cast to primitive types as it breaks null values formatting
|
||||
// if (ptype == byte.class || ptype == short.class ||
|
||||
// ptype == Byte.class || ptype == Short.class ||
|
||||
// ptype == Integer.class) {
|
||||
// mt = mt.changeReturnType(int.class);
|
||||
// } else if (ptype == Long.class) {
|
||||
// mt = mt.changeReturnType(long.class);
|
||||
// } else if (ptype == float.class || ptype == Float.class ||
|
||||
// ptype == Double.class) {
|
||||
// mt = mt.changeReturnType(double.class);
|
||||
// } else if (ptype == Boolean.class) {
|
||||
// mt = mt.changeReturnType(boolean.class);
|
||||
// } else if (ptype == Character.class) {
|
||||
// mt = mt.changeReturnType(char.class);
|
||||
// }
|
||||
|
||||
Class<?> itype = mt.returnType();
|
||||
|
||||
if (itype != ptype) {
|
||||
mh = explicitCastArguments(mh, mt);
|
||||
}
|
||||
|
||||
boolean handled = false;
|
||||
int flags = fs.flags;
|
||||
int width = fs.width;
|
||||
int precision = fs.precision;
|
||||
Character conv = fs.dt ? 't' : fs.c;
|
||||
|
||||
switch (Character.toLowerCase(conv)) {
|
||||
case BOOLEAN -> {
|
||||
if (itype == boolean.class && precision == -1) {
|
||||
if (flags == 0 && width == -1 && isPrimitive) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (validFlags(flags, LEFT_JUSTIFY)) {
|
||||
handled = true;
|
||||
mh = filterReturnValue(mh, FIBoolean_MH);
|
||||
}
|
||||
}
|
||||
}
|
||||
case STRING -> {
|
||||
if (flags == 0 && width == -1 && precision == -1) {
|
||||
if (isPrimitive || ptype == String.class) {
|
||||
return null;
|
||||
} else if (itype.isPrimitive()) {
|
||||
return mh;
|
||||
}
|
||||
}
|
||||
|
||||
if (validFlags(flags, LEFT_JUSTIFY) && precision == -1) {
|
||||
if (itype == String.class) {
|
||||
handled = true;
|
||||
mh = filterReturnValue(mh, FIString_MH);
|
||||
} else if (!itype.isPrimitive()) {
|
||||
handled = true;
|
||||
MethodHandle test = FormattableCheck_MH;
|
||||
test = test.asType(test.type().changeParameterType(0, ptype));
|
||||
MethodHandle pass = insertArguments(FIFormattable_MH,
|
||||
0, locale, flags, width, precision);
|
||||
pass = pass.asType(pass.type().changeParameterType(0, ptype));
|
||||
MethodHandle fail = ToString_MH;
|
||||
fail = filterReturnValue(fail, FIString_MH);
|
||||
fail = fail.asType(fail.type().changeParameterType(0, ptype));
|
||||
mh = guardWithTest(test, pass, fail);
|
||||
}
|
||||
}
|
||||
}
|
||||
case CHARACTER -> {
|
||||
if (itype == char.class && precision == -1) {
|
||||
if (flags == 0 && width == -1) {
|
||||
return isPrimitive ? null : mh;
|
||||
}
|
||||
|
||||
if (validFlags(flags, LEFT_JUSTIFY)) {
|
||||
handled = true;
|
||||
mh = filterReturnValue(mh, FICharacter_MH);
|
||||
}
|
||||
}
|
||||
}
|
||||
case DECIMAL_INTEGER -> {
|
||||
if ((itype == int.class || itype == long.class) && precision == -1) {
|
||||
if (itype == int.class) {
|
||||
mh = explicitCastArguments(mh,
|
||||
mh.type().changeReturnType(long.class));
|
||||
}
|
||||
|
||||
if (flags == 0 && isGenericDFS && width == -1) {
|
||||
return mh;
|
||||
} else if (validFlags(flags, PLUS | LEADING_SPACE |
|
||||
ZERO_PAD | GROUP |
|
||||
PARENTHESES)) {
|
||||
handled = true;
|
||||
int zeroPad = isFlag(flags, ZERO_PAD) ? width : -1;
|
||||
char sign = isFlag(flags, PLUS) ? '+' :
|
||||
isFlag(flags, LEADING_SPACE) ? ' ' : '\0';
|
||||
boolean parentheses = isFlag(flags, PARENTHESES);
|
||||
int groupSize = isFlag(flags, GROUP) ?
|
||||
groupSize(locale, dfs) : 0;
|
||||
mh = filterReturnValue(mh,
|
||||
insertArguments(FIDecimal_MH, 0, dfs, zeroPad,
|
||||
sign, parentheses, groupSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
case OCTAL_INTEGER -> {
|
||||
if ((itype == int.class || itype == long.class) &&
|
||||
precision == -1 &&
|
||||
validFlags(flags, ZERO_PAD | ALTERNATE)) {
|
||||
handled = true;
|
||||
|
||||
if (itype == int.class) {
|
||||
mh = filterReturnValue(mh, ToLong_MH);
|
||||
}
|
||||
|
||||
int zeroPad = isFlag(flags, ZERO_PAD) ? width : -1;
|
||||
boolean hasPrefix = isFlag(flags, ALTERNATE);
|
||||
mh = filterReturnValue(mh,
|
||||
insertArguments(FIOctal_MH, 0, zeroPad, hasPrefix));
|
||||
}
|
||||
}
|
||||
case HEXADECIMAL_INTEGER -> {
|
||||
if ((itype == int.class || itype == long.class) &&
|
||||
precision == -1 &&
|
||||
validFlags(flags, ZERO_PAD | ALTERNATE)) {
|
||||
handled = true;
|
||||
|
||||
if (itype == int.class) {
|
||||
mh = filterReturnValue(mh, ToLong_MH);
|
||||
}
|
||||
|
||||
int zeroPad = isFlag(flags, ZERO_PAD) ? width : -1;
|
||||
boolean hasPrefix = isFlag(flags, ALTERNATE);
|
||||
mh = filterReturnValue(mh,
|
||||
insertArguments(FIHexadecimal_MH, 0, zeroPad, hasPrefix));
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
// pass thru
|
||||
}
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
if (!isPrimitive) {
|
||||
MethodHandle test = NullCheck_MH.asType(
|
||||
NullCheck_MH.type().changeParameterType(0, ptype));
|
||||
MethodHandle pass = dropArguments(FINull_MH, 0, ptype);
|
||||
mh = guardWithTest(test, pass, mh);
|
||||
}
|
||||
|
||||
if (0 < width) {
|
||||
if (isFlag(flags, LEFT_JUSTIFY)) {
|
||||
mh = filterReturnValue(mh,
|
||||
insertArguments(FIFillRight_MH, 0, width));
|
||||
} else {
|
||||
mh = filterReturnValue(mh,
|
||||
insertArguments(FIFillLeft_MH, 0, width));
|
||||
}
|
||||
}
|
||||
|
||||
if (!isFlag(flags, UPPERCASE)) {
|
||||
return mh;
|
||||
}
|
||||
}
|
||||
|
||||
mh = insertArguments(FIFormatSpecifier_MH, 0, fs, locale);
|
||||
mh = mh.asType(mh.type().changeParameterType(0, ptype));
|
||||
|
||||
return mh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct concat {@link MethodHandle} for based on format.
|
||||
*
|
||||
* @param fsa list of specifiers
|
||||
*
|
||||
* @return concat {@link MethodHandle} for based on format
|
||||
*/
|
||||
private MethodHandle buildFilters(List<FormatString> fsa,
|
||||
List<String> segments,
|
||||
MethodHandle[] filters) {
|
||||
MethodHandle mh = null;
|
||||
int iParam = 0;
|
||||
StringBuilder segment = new StringBuilder();
|
||||
|
||||
for (FormatString fs : fsa) {
|
||||
int index = fs.index();
|
||||
|
||||
switch (index) {
|
||||
case -2: // fixed string, "%n", or "%%"
|
||||
String string = fs.toString();
|
||||
|
||||
if ("%%".equals(string)) {
|
||||
segment.append('%');
|
||||
} else if ("%n".equals(string)) {
|
||||
segment.append(System.lineSeparator());
|
||||
} else {
|
||||
segment.append(string);
|
||||
}
|
||||
break;
|
||||
case 0: // ordinary index
|
||||
segments.add(segment.toString());
|
||||
segment.setLength(0);
|
||||
|
||||
if (iParam < ptypes.length) {
|
||||
Class<?> ptype = ptypes[iParam];
|
||||
filters[iParam++] = formatSpecifier((FormatSpecifier)fs, ptype);
|
||||
} else {
|
||||
throw new MissingFormatArgumentException(fs.toString());
|
||||
}
|
||||
break;
|
||||
case -1: // relative index
|
||||
default: // explicit index
|
||||
throw new IllegalFormatFlagsException("Indexing not allowed: " + fs.toString());
|
||||
}
|
||||
}
|
||||
|
||||
segments.add(segment.toString());
|
||||
|
||||
return mh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a {@link MethodHandle} to format arguments.
|
||||
*
|
||||
* @return new {@link MethodHandle} to format arguments
|
||||
*/
|
||||
MethodHandle build() {
|
||||
List<String> segments = new ArrayList<>();
|
||||
MethodHandle[] filters = new MethodHandle[ptypes.length];
|
||||
buildFilters(Formatter.parse(format), segments, filters);
|
||||
Class<?>[] ftypes = new Class<?>[filters.length];
|
||||
|
||||
for (int i = 0; i < filters.length; i++) {
|
||||
MethodHandle filter = filters[i];
|
||||
ftypes[i] = filter == null ? ptypes[i] : filter.type().returnType();
|
||||
}
|
||||
|
||||
try {
|
||||
MethodHandle mh = StringConcatFactory.makeConcatWithTemplate(segments,
|
||||
List.of(ftypes));
|
||||
mh = filterArguments(mh, 0, filters);
|
||||
|
||||
return mh;
|
||||
} catch (StringConcatException ex) {
|
||||
throw new AssertionError("concat fail", ex);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue