8285932: Implementation of JEP 430 String Templates (Preview)
Reviewed-by: mcimadamore, rriggs, darcy
This commit is contained in:
parent
da2c930262
commit
4aa65cbeef
@ -1823,6 +1823,42 @@ abstract sealed class AbstractStringBuilder implements Appendable, CharSequence
|
|||||||
count += end - off;
|
count += end - off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by StringConcatHelper via JLA. Adds the current builder count to the
|
||||||
|
* accumulation of items being concatenated. If the coder for the builder is
|
||||||
|
* UTF16 then upgrade the whole concatenation to UTF16.
|
||||||
|
*
|
||||||
|
* @param lengthCoder running accumulation of length and coder
|
||||||
|
*
|
||||||
|
* @return updated accumulation of length and coder
|
||||||
|
*/
|
||||||
|
long mix(long lengthCoder) {
|
||||||
|
return (lengthCoder + count) | ((long)coder << 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by StringConcatHelper via JLA. Adds the characters in the builder value to the
|
||||||
|
* concatenation buffer and then updates the running accumulation of length.
|
||||||
|
*
|
||||||
|
* @param lengthCoder running accumulation of length and coder
|
||||||
|
* @param buffer concatenation buffer
|
||||||
|
*
|
||||||
|
* @return running accumulation of length and coder minus the number of characters added
|
||||||
|
*/
|
||||||
|
long prepend(long lengthCoder, byte[] buffer) {
|
||||||
|
lengthCoder -= count;
|
||||||
|
|
||||||
|
if (lengthCoder < ((long)UTF16 << 32)) {
|
||||||
|
System.arraycopy(value, 0, buffer, (int)lengthCoder, count);
|
||||||
|
} else if (coder == LATIN1) {
|
||||||
|
StringUTF16.inflate(value, 0, buffer, (int)lengthCoder, count);
|
||||||
|
} else {
|
||||||
|
System.arraycopy(value, 0, buffer, (int)lengthCoder << 1, count << 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lengthCoder;
|
||||||
|
}
|
||||||
|
|
||||||
private AbstractStringBuilder repeat(char c, int count) {
|
private AbstractStringBuilder repeat(char c, int count) {
|
||||||
int limit = this.count + count;
|
int limit = this.count + count;
|
||||||
ensureCapacityInternal(limit);
|
ensureCapacityInternal(limit);
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
package java.lang;
|
package java.lang;
|
||||||
|
|
||||||
import jdk.internal.misc.Unsafe;
|
import jdk.internal.misc.Unsafe;
|
||||||
|
import jdk.internal.javac.PreviewFeature;
|
||||||
|
import jdk.internal.util.FormatConcatItem;
|
||||||
import jdk.internal.vm.annotation.ForceInline;
|
import jdk.internal.vm.annotation.ForceInline;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandle;
|
import java.lang.invoke.MethodHandle;
|
||||||
@ -43,6 +45,15 @@ final class StringConcatHelper {
|
|||||||
// no instantiation
|
// no instantiation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the coder for the character.
|
||||||
|
* @param value character
|
||||||
|
* @return coder
|
||||||
|
*/
|
||||||
|
static long coder(char value) {
|
||||||
|
return StringLatin1.canEncode(value) ? LATIN1 : UTF16;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for overflow, throw exception on overflow.
|
* Check for overflow, throw exception on overflow.
|
||||||
*
|
*
|
||||||
@ -76,7 +87,7 @@ final class StringConcatHelper {
|
|||||||
* @return new length and coder
|
* @return new length and coder
|
||||||
*/
|
*/
|
||||||
static long mix(long lengthCoder, char value) {
|
static long mix(long lengthCoder, char value) {
|
||||||
return checkOverflow(lengthCoder + 1) | (StringLatin1.canEncode(value) ? 0 : UTF16);
|
return checkOverflow(lengthCoder + 1) | coder(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -116,6 +127,21 @@ final class StringConcatHelper {
|
|||||||
return checkOverflow(lengthCoder);
|
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,
|
* Prepends the stringly representation of boolean value into buffer,
|
||||||
* given the coder and final index. Index is measured in chars, not in bytes!
|
* given the coder and final index. Index is measured in chars, not in bytes!
|
||||||
@ -319,6 +345,49 @@ final class StringConcatHelper {
|
|||||||
return indexCoder;
|
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)
|
||||||
|
private 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);
|
||||||
|
if (prefix != null) indexCoder = prepend(indexCoder, buf, prefix);
|
||||||
|
return indexCoder;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates the String with given buffer and coder
|
* Instantiates the String with given buffer and coder
|
||||||
* @param buf buffer to use
|
* @param buf buffer to use
|
||||||
@ -332,7 +401,8 @@ final class StringConcatHelper {
|
|||||||
} else if (indexCoder == UTF16) {
|
} else if (indexCoder == UTF16) {
|
||||||
return new String(buf, String.UTF16);
|
return new String(buf, String.UTF16);
|
||||||
} else {
|
} else {
|
||||||
throw new InternalError("Storage is not completely initialized, " + (int)indexCoder + " bytes left");
|
throw new InternalError("Storage is not completely initialized, " +
|
||||||
|
(int)indexCoder + " bytes left");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,6 +519,71 @@ final class StringConcatHelper {
|
|||||||
return String.COMPACT_STRINGS ? LATIN1 : UTF16;
|
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) {
|
static MethodHandle lookupStatic(String name, MethodType methodType) {
|
||||||
try {
|
try {
|
||||||
return MethodHandles.lookup().findStatic(StringConcatHelper.class, name, methodType);
|
return MethodHandles.lookup().findStatic(StringConcatHelper.class, name, methodType);
|
||||||
|
621
src/java.base/share/classes/java/lang/StringTemplate.java
Normal file
621
src/java.base/share/classes/java/lang/StringTemplate.java
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -77,10 +77,11 @@ import jdk.internal.reflect.CallerSensitive;
|
|||||||
import jdk.internal.reflect.Reflection;
|
import jdk.internal.reflect.Reflection;
|
||||||
import jdk.internal.access.JavaLangAccess;
|
import jdk.internal.access.JavaLangAccess;
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
import jdk.internal.misc.VM;
|
import jdk.internal.javac.PreviewFeature;
|
||||||
import jdk.internal.logger.LoggerFinderLoader;
|
import jdk.internal.logger.LoggerFinderLoader;
|
||||||
import jdk.internal.logger.LazyLoggers;
|
import jdk.internal.logger.LazyLoggers;
|
||||||
import jdk.internal.logger.LocalizedLoggerWrapper;
|
import jdk.internal.logger.LocalizedLoggerWrapper;
|
||||||
|
import jdk.internal.misc.VM;
|
||||||
import jdk.internal.util.SystemProps;
|
import jdk.internal.util.SystemProps;
|
||||||
import jdk.internal.vm.Continuation;
|
import jdk.internal.vm.Continuation;
|
||||||
import jdk.internal.vm.ContinuationScope;
|
import jdk.internal.vm.ContinuationScope;
|
||||||
@ -2522,6 +2523,23 @@ public final class System {
|
|||||||
return StringConcatHelper.mix(lengthCoder, constant);
|
return StringConcatHelper.mix(lengthCoder, constant);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||||
|
public long stringConcatCoder(char value) {
|
||||||
|
return StringConcatHelper.coder(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||||
|
public long stringBuilderConcatMix(long lengthCoder,
|
||||||
|
StringBuilder sb) {
|
||||||
|
return sb.mix(lengthCoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||||
|
public long stringBuilderConcatPrepend(long lengthCoder, byte[] buf,
|
||||||
|
StringBuilder sb) {
|
||||||
|
return sb.prepend(lengthCoder, buf);
|
||||||
|
}
|
||||||
|
|
||||||
public String join(String prefix, String suffix, String delimiter, String[] elements, int size) {
|
public String join(String prefix, String suffix, String delimiter, String[] elements, int size) {
|
||||||
return String.join(prefix, suffix, delimiter, elements, size);
|
return String.join(prefix, suffix, delimiter, elements, size);
|
||||||
}
|
}
|
||||||
|
@ -27,10 +27,15 @@ package java.lang.invoke;
|
|||||||
|
|
||||||
import jdk.internal.access.JavaLangAccess;
|
import jdk.internal.access.JavaLangAccess;
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.access.SharedSecrets;
|
||||||
|
import jdk.internal.javac.PreviewFeature;
|
||||||
|
import jdk.internal.util.FormatConcatItem;
|
||||||
import jdk.internal.vm.annotation.Stable;
|
import jdk.internal.vm.annotation.Stable;
|
||||||
import sun.invoke.util.Wrapper;
|
import sun.invoke.util.Wrapper;
|
||||||
|
|
||||||
import java.lang.invoke.MethodHandles.Lookup;
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static java.lang.invoke.MethodType.methodType;
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
@ -110,8 +115,14 @@ public final class StringConcatFactory {
|
|||||||
* While the maximum number of argument slots that indy call can handle is 253,
|
* 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
|
* we do not use all those slots, to let the strategies with MethodHandle
|
||||||
* combinators to use some arguments.
|
* combinators to use some arguments.
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
*/
|
*/
|
||||||
private static final int MAX_INDY_CONCAT_ARG_SLOTS = 200;
|
@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 JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
|
||||||
|
|
||||||
@ -321,6 +332,7 @@ public final class StringConcatFactory {
|
|||||||
{
|
{
|
||||||
Objects.requireNonNull(lookup, "Lookup is null");
|
Objects.requireNonNull(lookup, "Lookup is null");
|
||||||
Objects.requireNonNull(name, "Name is null");
|
Objects.requireNonNull(name, "Name is null");
|
||||||
|
Objects.requireNonNull(recipe, "Recipe is null");
|
||||||
Objects.requireNonNull(concatType, "Concat type is null");
|
Objects.requireNonNull(concatType, "Concat type is null");
|
||||||
Objects.requireNonNull(constants, "Constants are null");
|
Objects.requireNonNull(constants, "Constants are null");
|
||||||
|
|
||||||
@ -488,14 +500,12 @@ public final class StringConcatFactory {
|
|||||||
// Use int as the logical type for subword integral types
|
// Use int as the logical type for subword integral types
|
||||||
// (byte and short). char and boolean require special
|
// (byte and short). char and boolean require special
|
||||||
// handling so don't change the logical type of those
|
// handling so don't change the logical type of those
|
||||||
if (cl == byte.class || cl == short.class) {
|
ptypes[i] = promoteToIntType(ptypes[i]);
|
||||||
ptypes[i] = int.class;
|
|
||||||
}
|
|
||||||
// Object, float and double will be eagerly transformed
|
// Object, float and double will be eagerly transformed
|
||||||
// into a (non-null) String as a first step after invocation.
|
// into a (non-null) String as a first step after invocation.
|
||||||
// Set up to use String as the logical type for such arguments
|
// Set up to use String as the logical type for such arguments
|
||||||
// internally.
|
// internally.
|
||||||
else if (cl == Object.class) {
|
if (cl == Object.class) {
|
||||||
if (objFilters == null) {
|
if (objFilters == null) {
|
||||||
objFilters = new MethodHandle[ptypes.length];
|
objFilters = new MethodHandle[ptypes.length];
|
||||||
}
|
}
|
||||||
@ -664,7 +674,6 @@ public final class StringConcatFactory {
|
|||||||
return argPositions;
|
return argPositions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static MethodHandle foldInLastMixers(MethodHandle mh, long initialLengthCoder, int pos, Class<?>[] ptypes, int count) {
|
private static MethodHandle foldInLastMixers(MethodHandle mh, long initialLengthCoder, int pos, Class<?>[] ptypes, int count) {
|
||||||
MethodHandle mix = switch (count) {
|
MethodHandle mix = switch (count) {
|
||||||
case 1 -> mixer(ptypes[pos]);
|
case 1 -> mixer(ptypes[pos]);
|
||||||
@ -710,6 +719,9 @@ public final class StringConcatFactory {
|
|||||||
int idx = classIndex(cl);
|
int idx = classIndex(cl);
|
||||||
MethodHandle prepend = PREPENDERS[idx];
|
MethodHandle prepend = PREPENDERS[idx];
|
||||||
if (prepend == null) {
|
if (prepend == null) {
|
||||||
|
if (idx == STRING_CONCAT_ITEM) {
|
||||||
|
cl = FormatConcatItem.class;
|
||||||
|
}
|
||||||
PREPENDERS[idx] = prepend = JLA.stringConcatHelper("prepend",
|
PREPENDERS[idx] = prepend = JLA.stringConcatHelper("prepend",
|
||||||
methodType(long.class, long.class, byte[].class,
|
methodType(long.class, long.class, byte[].class,
|
||||||
Wrapper.asPrimitiveType(cl), String.class)).rebind();
|
Wrapper.asPrimitiveType(cl), String.class)).rebind();
|
||||||
@ -722,13 +734,15 @@ public final class StringConcatFactory {
|
|||||||
LONG_IDX = 2,
|
LONG_IDX = 2,
|
||||||
BOOLEAN_IDX = 3,
|
BOOLEAN_IDX = 3,
|
||||||
STRING_IDX = 4,
|
STRING_IDX = 4,
|
||||||
TYPE_COUNT = 5;
|
STRING_CONCAT_ITEM = 5,
|
||||||
|
TYPE_COUNT = 6;
|
||||||
private static int classIndex(Class<?> cl) {
|
private static int classIndex(Class<?> cl) {
|
||||||
if (cl == String.class) return STRING_IDX;
|
if (cl == String.class) return STRING_IDX;
|
||||||
if (cl == int.class) return INT_IDX;
|
if (cl == int.class) return INT_IDX;
|
||||||
if (cl == boolean.class) return BOOLEAN_IDX;
|
if (cl == boolean.class) return BOOLEAN_IDX;
|
||||||
if (cl == char.class) return CHAR_IDX;
|
if (cl == char.class) return CHAR_IDX;
|
||||||
if (cl == long.class) return LONG_IDX;
|
if (cl == long.class) return LONG_IDX;
|
||||||
|
if (FormatConcatItem.class.isAssignableFrom(cl)) return STRING_CONCAT_ITEM;
|
||||||
throw new IllegalArgumentException("Unexpected class: " + cl);
|
throw new IllegalArgumentException("Unexpected class: " + cl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -986,6 +1000,33 @@ public final class StringConcatFactory {
|
|||||||
private static final @Stable MethodHandle[] MIXERS = new MethodHandle[TYPE_COUNT];
|
private static final @Stable MethodHandle[] MIXERS = new MethodHandle[TYPE_COUNT];
|
||||||
private static final long INITIAL_CODER = JLA.stringConcatInitialCoder();
|
private static final long INITIAL_CODER = JLA.stringConcatInitialCoder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promote integral types to int.
|
||||||
|
*/
|
||||||
|
private static Class<?> promoteToIntType(Class<?> t) {
|
||||||
|
// use int for subword integral types; still need special mixers
|
||||||
|
// and prependers for char, boolean
|
||||||
|
return t == byte.class || t == short.class ? int.class : t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a stringifier for references and floats/doubles only.
|
||||||
|
* Always returns null for other primitives.
|
||||||
|
*
|
||||||
|
* @param t class to stringify
|
||||||
|
* @return stringifier; null, if not available
|
||||||
|
*/
|
||||||
|
private static MethodHandle stringifierFor(Class<?> t) {
|
||||||
|
if (t == Object.class) {
|
||||||
|
return objectStringifier();
|
||||||
|
} else if (t == float.class) {
|
||||||
|
return floatStringifier();
|
||||||
|
} else if (t == double.class) {
|
||||||
|
return doubleStringifier();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static MethodHandle stringValueOf(Class<?> ptype) {
|
private static MethodHandle stringValueOf(Class<?> ptype) {
|
||||||
try {
|
try {
|
||||||
return MethodHandles.publicLookup()
|
return MethodHandles.publicLookup()
|
||||||
@ -998,4 +1039,304 @@ public final class StringConcatFactory {
|
|||||||
private StringConcatFactory() {
|
private StringConcatFactory() {
|
||||||
// no instantiation
|
// 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;
|
||||||
|
String lastFragment = "";
|
||||||
|
pos = 0;
|
||||||
|
for (String fragment : fragments) {
|
||||||
|
lastFragment = fragment;
|
||||||
|
|
||||||
|
if (ttypes.length <= pos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> ttype = ttypes[pos];
|
||||||
|
// (long,byte[],ttype) -> long
|
||||||
|
MethodHandle prepender = prepender(lastFragment.isEmpty() ? null : fragment, ttype);
|
||||||
|
initialLengthCoder = JLA.stringConcatMix(initialLengthCoder, fragment);
|
||||||
|
// (byte[],long,ttypes...) -> String (unchanged)
|
||||||
|
mh = MethodHandles.filterArgumentsWithCombiner(mh, 1, prepender,1, 0, 2 + pos);
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1004
src/java.base/share/classes/java/lang/runtime/Carriers.java
Normal file
1004
src/java.base/share/classes/java/lang/runtime/Carriers.java
Normal file
File diff suppressed because it is too large
Load Diff
230
src/java.base/share/classes/java/lang/runtime/ReferenceKey.java
Normal file
230
src/java.base/share/classes/java/lang/runtime/ReferenceKey.java
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* 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.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View/wrapper of keys used by the backing {@link ReferencedKeyMap}.
|
||||||
|
* There are two style of keys; one for entries in the backing map and
|
||||||
|
* one for queries to the backing map. This second style avoids the
|
||||||
|
* overhead of a {@link Reference} object.
|
||||||
|
*
|
||||||
|
* @param <T> key type
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*
|
||||||
|
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||||
|
* Do not rely on its availability.
|
||||||
|
*/
|
||||||
|
interface ReferenceKey<T> {
|
||||||
|
/**
|
||||||
|
* {@return the value of the unwrapped key}
|
||||||
|
*/
|
||||||
|
T get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup unused key.
|
||||||
|
*/
|
||||||
|
void unused();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link WeakReference} wrapper key for entries in the backing map.
|
||||||
|
*
|
||||||
|
* @param <T> key type
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*/
|
||||||
|
class WeakKey<T> extends WeakReference<T> implements ReferenceKey<T> {
|
||||||
|
/**
|
||||||
|
* Saved hashcode of the key. Used when {@link WeakReference} is
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
private final int hashcode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*
|
||||||
|
* @param key unwrapped key value
|
||||||
|
* @param queue reference queue
|
||||||
|
*/
|
||||||
|
WeakKey(T key, ReferenceQueue<T> queue) {
|
||||||
|
super(key, queue);
|
||||||
|
this.hashcode = Objects.hashCode(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup unused key. No need to enqueue since the key did not make it
|
||||||
|
* into the map.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void unused() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
// Necessary when removing a null reference
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Necessary when comparing an unwrapped key
|
||||||
|
if (obj instanceof ReferenceKey<?> key) {
|
||||||
|
obj = key.get();
|
||||||
|
}
|
||||||
|
return Objects.equals(get(), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// Use saved hashcode
|
||||||
|
return hashcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getClass().getCanonicalName() + "#" + System.identityHashCode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link SoftReference} wrapper key for entries in the backing map.
|
||||||
|
*
|
||||||
|
* @param <T> key type
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*/
|
||||||
|
class SoftKey<T> extends SoftReference<T> implements ReferenceKey<T> {
|
||||||
|
/**
|
||||||
|
* Saved hashcode of the key. Used when {@link SoftReference} is
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
private final int hashcode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*
|
||||||
|
* @param key unwrapped key value
|
||||||
|
* @param queue reference queue
|
||||||
|
*/
|
||||||
|
SoftKey(T key, ReferenceQueue<T> queue) {
|
||||||
|
super(key, queue);
|
||||||
|
this.hashcode = Objects.hashCode(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup unused key. No need to enqueue since the key did not make it
|
||||||
|
* into the map.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void unused() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
// Necessary when removing a null reference
|
||||||
|
if (obj == this) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Necessary when comparing an unwrapped key
|
||||||
|
if (obj instanceof ReferenceKey<?> key) {
|
||||||
|
obj = key.get();
|
||||||
|
}
|
||||||
|
return Objects.equals(get(), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// Use saved hashcode
|
||||||
|
return hashcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getClass().getCanonicalName() + "#" + System.identityHashCode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for querying the backing map. Avoids the overhead of an
|
||||||
|
* {@link Reference} object.
|
||||||
|
*
|
||||||
|
* @param <T> key type
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*/
|
||||||
|
class StrongKey<T> implements ReferenceKey<T> {
|
||||||
|
T key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*
|
||||||
|
* @param key unwrapped key value
|
||||||
|
*/
|
||||||
|
StrongKey(T key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return the unwrapped key}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T get() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unused() {
|
||||||
|
key = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
// Necessary when comparing an unwrapped key
|
||||||
|
if (obj instanceof ReferenceKey<?> key) {
|
||||||
|
obj = key.get();
|
||||||
|
}
|
||||||
|
return Objects.equals(get(), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// Use unwrapped key hash code
|
||||||
|
return get().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.getClass().getCanonicalName() + "#" + System.identityHashCode(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,334 @@
|
|||||||
|
/*
|
||||||
|
* 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.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides management of {@link Map maps} where it is desirable to
|
||||||
|
* remove entries automatically when the key is garbage collected. This is
|
||||||
|
* accomplished by using a backing map where the keys are either a
|
||||||
|
* {@link WeakReference} or a {@link SoftReference}.
|
||||||
|
* <p>
|
||||||
|
* To create a {@link ReferencedKeyMap} the user must provide a {@link Supplier}
|
||||||
|
* of the backing map and whether {@link WeakReference} or
|
||||||
|
* {@link SoftReference} is to be used.
|
||||||
|
*
|
||||||
|
* {@snippet :
|
||||||
|
* // Use HashMap and WeakReference
|
||||||
|
* Map<Long, String> map = ReferencedKeyMap.create(false, HashMap::new);
|
||||||
|
* map.put(10_000_000L, "a");
|
||||||
|
* map.put(10_000_001L, "b");
|
||||||
|
* map.put(10_000_002L, "c");
|
||||||
|
* map.put(10_000_003L, "d");
|
||||||
|
* map.put(10_000_004L, "e");
|
||||||
|
*
|
||||||
|
* // Use ConcurrentHashMap and SoftReference
|
||||||
|
* map = ReferencedKeyMap.create(true, ConcurrentHashMap::new);
|
||||||
|
* map.put(20_000_000L, "v");
|
||||||
|
* map.put(20_000_001L, "w");
|
||||||
|
* map.put(20_000_002L, "x");
|
||||||
|
* map.put(20_000_003L, "y");
|
||||||
|
* map.put(20_000_004L, "z");
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @implNote Care must be given that the backing map does replacement by
|
||||||
|
* replacing the value in the map entry instead of deleting the old entry and
|
||||||
|
* adding a new entry, otherwise replaced entries may end up with a strongly
|
||||||
|
* referenced key. {@link HashMap} and {@link ConcurrentHashMap} are known
|
||||||
|
* to be safe.
|
||||||
|
*
|
||||||
|
* @param <K> the type of keys maintained by this map
|
||||||
|
* @param <V> the type of mapped values
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*
|
||||||
|
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||||
|
* Do not rely on its availability.
|
||||||
|
*/
|
||||||
|
final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
|
/**
|
||||||
|
* true if {@link SoftReference} keys are to be used,
|
||||||
|
* {@link WeakReference} otherwise.
|
||||||
|
*/
|
||||||
|
private final boolean isSoft;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backing {@link Map}.
|
||||||
|
*/
|
||||||
|
private final Map<ReferenceKey<K>, V> map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ReferenceQueue} for cleaning up {@link ReferenceKey.WeakKey EntryKeys}.
|
||||||
|
*/
|
||||||
|
private final ReferenceQueue<K> stale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*
|
||||||
|
* @param isSoft true if {@link SoftReference} keys are to
|
||||||
|
* be used, {@link WeakReference} otherwise.
|
||||||
|
* @param map backing map
|
||||||
|
*/
|
||||||
|
private ReferencedKeyMap(boolean isSoft, Map<ReferenceKey<K>, V> map) {
|
||||||
|
this.isSoft = isSoft;
|
||||||
|
this.map = map;
|
||||||
|
this.stale = new ReferenceQueue<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ReferencedKeyMap} map.
|
||||||
|
*
|
||||||
|
* @param isSoft true if {@link SoftReference} keys are to
|
||||||
|
* be used, {@link WeakReference} otherwise.
|
||||||
|
* @param supplier {@link Supplier} of the backing map
|
||||||
|
*
|
||||||
|
* @return a new map with {@link Reference} keys
|
||||||
|
*
|
||||||
|
* @param <K> the type of keys maintained by the new map
|
||||||
|
* @param <V> the type of mapped values
|
||||||
|
*/
|
||||||
|
static <K, V> ReferencedKeyMap<K, V>
|
||||||
|
create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) {
|
||||||
|
return new ReferencedKeyMap<K, V>(isSoft, supplier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ReferencedKeyMap} map using
|
||||||
|
* {@link WeakReference} keys.
|
||||||
|
*
|
||||||
|
* @param supplier {@link Supplier} of the backing map
|
||||||
|
*
|
||||||
|
* @return a new map with {@link Reference} keys
|
||||||
|
*
|
||||||
|
* @param <K> the type of keys maintained by the new map
|
||||||
|
* @param <V> the type of mapped values
|
||||||
|
*/
|
||||||
|
static <K, V> ReferencedKeyMap<K, V>
|
||||||
|
create(Supplier<Map<ReferenceKey<K>, V>> supplier) {
|
||||||
|
return new ReferencedKeyMap<K, V>(false, supplier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return a key suitable for a map entry}
|
||||||
|
*
|
||||||
|
* @param key unwrapped key
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private ReferenceKey<K> entryKey(Object key) {
|
||||||
|
if (isSoft) {
|
||||||
|
return new ReferenceKey.SoftKey<>((K)key, stale);
|
||||||
|
} else {
|
||||||
|
return new ReferenceKey.WeakKey<>((K)key, stale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return a key suitable for lookup}
|
||||||
|
*
|
||||||
|
* @param key unwrapped key
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private ReferenceKey<K> lookupKey(Object key) {
|
||||||
|
return new ReferenceKey.StrongKey<>((K)key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
removeStaleReferences();
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
removeStaleReferences();
|
||||||
|
return map.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
Objects.requireNonNull(key, "key must not be null");
|
||||||
|
removeStaleReferences();
|
||||||
|
return map.containsKey(lookupKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
Objects.requireNonNull(value, "value must not be null");
|
||||||
|
removeStaleReferences();
|
||||||
|
return map.containsValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
Objects.requireNonNull(key, "key must not be null");
|
||||||
|
removeStaleReferences();
|
||||||
|
return map.get(lookupKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K key, V newValue) {
|
||||||
|
Objects.requireNonNull(key, "key must not be null");
|
||||||
|
Objects.requireNonNull(newValue, "value must not be null");
|
||||||
|
removeStaleReferences();
|
||||||
|
ReferenceKey<K> entryKey = entryKey(key);
|
||||||
|
// If {@code put} returns non-null then was actually a {@code replace}
|
||||||
|
// and older key was used. In that case the new key was not used and the
|
||||||
|
// reference marked stale.
|
||||||
|
V oldValue = map.put(entryKey, newValue);
|
||||||
|
if (oldValue != null) {
|
||||||
|
entryKey.unused();
|
||||||
|
}
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove(Object key) {
|
||||||
|
// Rely on gc to clean up old key.
|
||||||
|
return map.remove(lookupKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends K, ? extends V> m) {
|
||||||
|
removeStaleReferences();
|
||||||
|
for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
|
||||||
|
K key = entry.getKey();
|
||||||
|
V value = entry.getValue();
|
||||||
|
put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
removeStaleReferences();
|
||||||
|
// Rely on gc to clean up old keys.
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common routine for collecting the current set of keys.
|
||||||
|
*
|
||||||
|
* @return {@link Stream} of valid keys (unwrapped)
|
||||||
|
*/
|
||||||
|
private Stream<K> filterKeySet() {
|
||||||
|
return map.keySet()
|
||||||
|
.stream()
|
||||||
|
.map(ReferenceKey::get)
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<K> keySet() {
|
||||||
|
removeStaleReferences();
|
||||||
|
return filterKeySet().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> values() {
|
||||||
|
removeStaleReferences();
|
||||||
|
return map.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<K, V>> entrySet() {
|
||||||
|
removeStaleReferences();
|
||||||
|
return filterKeySet()
|
||||||
|
.map(k -> new AbstractMap.SimpleEntry<>(k, get(k)))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V putIfAbsent(K key, V newValue) {
|
||||||
|
removeStaleReferences();
|
||||||
|
ReferenceKey<K> entryKey = entryKey(key);
|
||||||
|
// If {@code putIfAbsent} returns non-null then was actually a
|
||||||
|
// {@code replace} and older key was used. In that case the new key was
|
||||||
|
// not used and the reference marked stale.
|
||||||
|
V oldValue = map.putIfAbsent(entryKey, newValue);
|
||||||
|
if (oldValue != null) {
|
||||||
|
entryKey.unused();
|
||||||
|
}
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object key, Object value) {
|
||||||
|
// Rely on gc to clean up old key.
|
||||||
|
return map.remove(lookupKey(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean replace(K key, V oldValue, V newValue) {
|
||||||
|
removeStaleReferences();
|
||||||
|
// If replace is successful then the older key will be used and the
|
||||||
|
// lookup key will suffice.
|
||||||
|
return map.replace(lookupKey(key), oldValue, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V replace(K key, V value) {
|
||||||
|
removeStaleReferences();
|
||||||
|
// If replace is successful then the older key will be used and the
|
||||||
|
// lookup key will suffice.
|
||||||
|
return map.replace(lookupKey(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
removeStaleReferences();
|
||||||
|
return filterKeySet()
|
||||||
|
.map(k -> k + "=" + get(k))
|
||||||
|
.collect(Collectors.joining(", ", "{", "}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes enqueued weak references from map.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void removeStaleReferences() {
|
||||||
|
while (true) {
|
||||||
|
ReferenceKey.WeakKey<K> key = (ReferenceKey.WeakKey<K>)stale.poll();
|
||||||
|
if (key == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
map.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,204 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,269 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
283
src/java.base/share/classes/java/util/Digits.java
Normal file
283
src/java.base/share/classes/java/util/Digits.java
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.util;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
|
||||||
|
import jdk.internal.vm.annotation.Stable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digits provides a fast methodology for converting integers and longs to
|
||||||
|
* ASCII strings.
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*/
|
||||||
|
sealed interface Digits permits Digits.DecimalDigits, Digits.HexDigits, Digits.OctalDigits {
|
||||||
|
/**
|
||||||
|
* Insert digits for long value in buffer from high index to low index.
|
||||||
|
*
|
||||||
|
* @param value value to convert
|
||||||
|
* @param buffer byte buffer to copy into
|
||||||
|
* @param index insert point + 1
|
||||||
|
* @param putCharMH method to put character
|
||||||
|
*
|
||||||
|
* @return the last index used
|
||||||
|
*
|
||||||
|
* @throws Throwable if putCharMH fails (unusual).
|
||||||
|
*/
|
||||||
|
int digits(long value, byte[] buffer, int index,
|
||||||
|
MethodHandle putCharMH) throws Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the number of digits required to represent the long.
|
||||||
|
*
|
||||||
|
* @param value value to convert
|
||||||
|
*
|
||||||
|
* @return number of digits
|
||||||
|
*/
|
||||||
|
int size(long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digits class for decimal digits.
|
||||||
|
*/
|
||||||
|
final class DecimalDigits implements Digits {
|
||||||
|
@Stable
|
||||||
|
private static final short[] DIGITS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of DecimalDigits.
|
||||||
|
*/
|
||||||
|
static final Digits INSTANCE = new DecimalDigits();
|
||||||
|
|
||||||
|
static {
|
||||||
|
short[] digits = new short[10 * 10];
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
short hi = (short) ((i + '0') << 8);
|
||||||
|
|
||||||
|
for (int j = 0; j < 10; j++) {
|
||||||
|
short lo = (short) (j + '0');
|
||||||
|
digits[i * 10 + j] = (short) (hi | lo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DIGITS = digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
private DecimalDigits() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int digits(long value, byte[] buffer, int index,
|
||||||
|
MethodHandle putCharMH) throws Throwable {
|
||||||
|
boolean negative = value < 0;
|
||||||
|
if (!negative) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
long q;
|
||||||
|
int r;
|
||||||
|
while (value <= Integer.MIN_VALUE) {
|
||||||
|
q = value / 100;
|
||||||
|
r = (int)((q * 100) - value);
|
||||||
|
value = q;
|
||||||
|
int digits = DIGITS[r];
|
||||||
|
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits & 0xFF);
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
int iq, ivalue = (int)value;
|
||||||
|
while (ivalue <= -100) {
|
||||||
|
iq = ivalue / 100;
|
||||||
|
r = (iq * 100) - ivalue;
|
||||||
|
ivalue = iq;
|
||||||
|
int digits = DIGITS[r];
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits & 0xFF);
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ivalue < 0) {
|
||||||
|
ivalue = -ivalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int digits = DIGITS[ivalue];
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits & 0xFF);
|
||||||
|
|
||||||
|
if (9 < ivalue) {
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
putCharMH.invokeExact(buffer, --index, (int)'-');
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size(long value) {
|
||||||
|
boolean negative = value < 0;
|
||||||
|
int sign = negative ? 1 : 0;
|
||||||
|
|
||||||
|
if (!negative) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
|
||||||
|
long precision = -10;
|
||||||
|
for (int i = 1; i < 19; i++) {
|
||||||
|
if (value > precision)
|
||||||
|
return i + sign;
|
||||||
|
|
||||||
|
precision = 10 * precision;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 19 + sign;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digits class for hexadecimal digits.
|
||||||
|
*/
|
||||||
|
final class HexDigits implements Digits {
|
||||||
|
@Stable
|
||||||
|
private static final short[] DIGITS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of HexDigits.
|
||||||
|
*/
|
||||||
|
static final Digits INSTANCE = new HexDigits();
|
||||||
|
|
||||||
|
static {
|
||||||
|
short[] digits = new short[16 * 16];
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
short hi = (short) ((i < 10 ? i + '0' : i - 10 + 'a') << 8);
|
||||||
|
|
||||||
|
for (int j = 0; j < 16; j++) {
|
||||||
|
short lo = (short) (j < 10 ? j + '0' : j - 10 + 'a');
|
||||||
|
digits[(i << 4) + j] = (short) (hi | lo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DIGITS = digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
private HexDigits() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int digits(long value, byte[] buffer, int index,
|
||||||
|
MethodHandle putCharMH) throws Throwable {
|
||||||
|
while ((value & ~0xFF) != 0) {
|
||||||
|
int digits = DIGITS[(int) (value & 0xFF)];
|
||||||
|
value >>>= 8;
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits & 0xFF);
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
int digits = DIGITS[(int) (value & 0xFF)];
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits & 0xFF);
|
||||||
|
|
||||||
|
if (0xF < value) {
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size(long value) {
|
||||||
|
return value == 0 ? 1 :
|
||||||
|
67 - Long.numberOfLeadingZeros(value) >> 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Digits class for octal digits.
|
||||||
|
*/
|
||||||
|
final class OctalDigits implements Digits {
|
||||||
|
@Stable
|
||||||
|
private static final short[] DIGITS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of OctalDigits.
|
||||||
|
*/
|
||||||
|
static final Digits INSTANCE = new OctalDigits();
|
||||||
|
|
||||||
|
static {
|
||||||
|
short[] digits = new short[8 * 8];
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
short hi = (short) ((i + '0') << 8);
|
||||||
|
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
short lo = (short) (j + '0');
|
||||||
|
digits[(i << 3) + j] = (short) (hi | lo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DIGITS = digits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
private OctalDigits() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int digits(long value, byte[] buffer, int index,
|
||||||
|
MethodHandle putCharMH) throws Throwable {
|
||||||
|
while ((value & ~0x3F) != 0) {
|
||||||
|
int digits = DIGITS[(int) (value & 0x3F)];
|
||||||
|
value >>>= 6;
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits & 0xFF);
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
int digits = DIGITS[(int) (value & 0x3F)];
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits & 0xFF);
|
||||||
|
|
||||||
|
if (7 < value) {
|
||||||
|
putCharMH.invokeExact(buffer, --index, digits >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size(long value) {
|
||||||
|
return (66 - Long.numberOfLeadingZeros(value)) / 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
539
src/java.base/share/classes/java/util/FormatItem.java
Normal file
539
src/java.base/share/classes/java/util/FormatItem.java
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
* 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.Digits.*;
|
||||||
|
import java.util.Formatter.FormatSpecifier;
|
||||||
|
|
||||||
|
import jdk.internal.access.JavaLangAccess;
|
||||||
|
import jdk.internal.access.SharedSecrets;
|
||||||
|
import jdk.internal.util.FormatConcatItem;
|
||||||
|
|
||||||
|
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, 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,
|
||||||
|
(String)null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static abstract 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
286
src/java.base/share/classes/java/util/FormatProcessor.java
Normal file
286
src/java.base/share/classes/java/util/FormatProcessor.java
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.util;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.lang.StringTemplate.Processor;
|
||||||
|
import java.lang.StringTemplate.Processor.Linkage;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
import jdk.internal.javac.PreviewFeature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This {@link Processor} constructs a {@link String} result using
|
||||||
|
* {@link Formatter} specifications and values found in the {@link StringTemplate}.
|
||||||
|
* Unlike {@link Formatter}, {@link FormatProcessor} uses the value from the
|
||||||
|
* embedded expression that immediately follows, without whitespace, the
|
||||||
|
* <a href="../util/Formatter.html#syntax">format specifier</a>.
|
||||||
|
* For example:
|
||||||
|
* {@snippet :
|
||||||
|
* FormatProcessor fmt = FormatProcessor.create(Locale.ROOT);
|
||||||
|
* int x = 10;
|
||||||
|
* int y = 20;
|
||||||
|
* String result = fmt."%05d\{x} + %05d\{y} = %05d\{x + y}";
|
||||||
|
* }
|
||||||
|
* In the above example, the value of {@code result} will be {@code "00010 + 00020 = 00030"}.
|
||||||
|
* <p>
|
||||||
|
* Embedded expressions without a preceeding format specifier, use {@code %s}
|
||||||
|
* by default.
|
||||||
|
* {@snippet :
|
||||||
|
* FormatProcessor fmt = FormatProcessor.create(Locale.ROOT);
|
||||||
|
* int x = 10;
|
||||||
|
* int y = 20;
|
||||||
|
* String result1 = fmt."\{x} + \{y} = \{x + y}";
|
||||||
|
* String result2 = fmt."%s\{x} + %s\{y} = %s\{x + y}";
|
||||||
|
* }
|
||||||
|
* In the above example, the value of {@code result1} and {@code result2} will
|
||||||
|
* both be {@code "10 + 20 = 30"}.
|
||||||
|
* <p>
|
||||||
|
* The {@link FormatProcessor} format specification used and exceptions thrown are the
|
||||||
|
* same as those of {@link Formatter}.
|
||||||
|
* <p>
|
||||||
|
* However, there are two significant differences related to the position of arguments.
|
||||||
|
* An explict {@code n$} and relative {@code <} index will cause an exception due to
|
||||||
|
* a missing argument list.
|
||||||
|
* Whitespace appearing between the specification and the embedded expression will
|
||||||
|
* also cause an exception.
|
||||||
|
* <p>
|
||||||
|
* {@link FormatProcessor} allows the use of different locales. For example:
|
||||||
|
* {@snippet :
|
||||||
|
* Locale locale = Locale.forLanguageTag("th-TH-u-nu-thai");
|
||||||
|
* FormatProcessor thaiFMT = FormatProcessor.create(locale);
|
||||||
|
* int x = 10;
|
||||||
|
* int y = 20;
|
||||||
|
* String result = thaiFMT."%4d\{x} + %4d\{y} = %5d\{x + y}";
|
||||||
|
* }
|
||||||
|
* In the above example, the value of {@code result} will be
|
||||||
|
* {@code " \u0E51\u0E50 + \u0E52\u0E50 = \u0E53\u0E50"}.
|
||||||
|
* <p>
|
||||||
|
* For day to day use, the predefined {@link FormatProcessor#FMT} {@link FormatProcessor}
|
||||||
|
* is available. {@link FormatProcessor#FMT} is defined using the {@link Locale#ROOT}.
|
||||||
|
* Example: {@snippet :
|
||||||
|
* int x = 10;
|
||||||
|
* int y = 20;
|
||||||
|
* String result = FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}"; // @highlight substring="FMT"
|
||||||
|
* }
|
||||||
|
* In the above example, the value of {@code result} will be {@code "0x000a + 0x0014 = 0x001E"}.
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*
|
||||||
|
* @see Processor
|
||||||
|
*/
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||||
|
public final class FormatProcessor implements Processor<String, RuntimeException>, Linkage {
|
||||||
|
/**
|
||||||
|
* {@link Locale} used to format
|
||||||
|
*/
|
||||||
|
private final Locale locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param locale {@link Locale} used to format
|
||||||
|
*/
|
||||||
|
private FormatProcessor(Locale locale) {
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link FormatProcessor} using the specified locale.
|
||||||
|
*
|
||||||
|
* @param locale {@link Locale} used to format
|
||||||
|
*
|
||||||
|
* @return a new instance of {@link FormatProcessor}
|
||||||
|
*
|
||||||
|
* @throws java.lang.NullPointerException if locale is null
|
||||||
|
*/
|
||||||
|
public static FormatProcessor create(Locale locale) {
|
||||||
|
Objects.requireNonNull(locale);
|
||||||
|
return new FormatProcessor(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link String} based on the fragments, format
|
||||||
|
* specifications found in the fragments and values in the
|
||||||
|
* supplied {@link StringTemplate} object. This method constructs a
|
||||||
|
* format string from the fragments, gathers up the values and
|
||||||
|
* evaluates the expression asif evaulating
|
||||||
|
* {@code new Formatter(locale).format(format, values).toString()}.
|
||||||
|
* <p>
|
||||||
|
* If an embedded expression is not immediately preceded by a
|
||||||
|
* specifier then a {@code %s} is inserted in the format.
|
||||||
|
*
|
||||||
|
* @param stringTemplate a {@link StringTemplate} instance
|
||||||
|
*
|
||||||
|
* @return constructed {@link String}
|
||||||
|
|
||||||
|
* @throws IllegalFormatException
|
||||||
|
* If a format specifier contains an illegal syntax, a format
|
||||||
|
* specifier that is incompatible with the given arguments,
|
||||||
|
* a specifier not followed immediately by an embedded expression or
|
||||||
|
* other illegal conditions. For specification of all possible
|
||||||
|
* formatting errors, see the
|
||||||
|
* <a href="../util/Formatter.html#detail">details</a>
|
||||||
|
* section of the formatter class specification.
|
||||||
|
* @throws NullPointerException if stringTemplate is null
|
||||||
|
*
|
||||||
|
* @see java.util.Formatter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public final String process(StringTemplate stringTemplate) {
|
||||||
|
Objects.requireNonNull(stringTemplate);
|
||||||
|
String format = stringTemplateFormat(stringTemplate.fragments());
|
||||||
|
Object[] values = stringTemplate.values().toArray();
|
||||||
|
|
||||||
|
return new Formatter(locale).format(format, values).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@link MethodHandle} that when supplied with the values from
|
||||||
|
* a {@link StringTemplate} will produce a result equivalent to that provided by
|
||||||
|
* {@link FormatProcessor#process(StringTemplate)}. This {@link MethodHandle}
|
||||||
|
* is used by {@link FormatProcessor#FMT} and the ilk to perform a more
|
||||||
|
* specialized composition of a result. This specialization is done by
|
||||||
|
* prescanning the fragments and value types of a {@link StringTemplate}.
|
||||||
|
* <p>
|
||||||
|
* Process template expressions can be specialized when the processor is
|
||||||
|
* of type {@link Linkage} and fetched from a static constant as is
|
||||||
|
* {@link FormatProcessor#FMT} ({@code static final FormatProcessor}).
|
||||||
|
* <p>
|
||||||
|
* Other {@link FormatProcessor FormatProcessors} can be specialized when stored in a static
|
||||||
|
* final.
|
||||||
|
* For example:
|
||||||
|
* {@snippet :
|
||||||
|
* FormatProcessor THAI_FMT = FormatProcessor.create(Locale.forLanguageTag("th-TH-u-nu-thai"));
|
||||||
|
* }
|
||||||
|
* {@code THAI_FMT} will now produce specialized {@link MethodHandle MethodHandles} by way
|
||||||
|
* of {@link FormatProcessor#linkage(List, MethodType)}.
|
||||||
|
*
|
||||||
|
* See {@link FormatProcessor#process(StringTemplate)} for more information.
|
||||||
|
*
|
||||||
|
* @throws IllegalFormatException
|
||||||
|
* If a format specifier contains an illegal syntax, a format
|
||||||
|
* specifier that is incompatible with the given arguments,
|
||||||
|
* a specifier not followed immediately by an embedded expression or
|
||||||
|
* other illegal conditions. For specification of all possible
|
||||||
|
* formatting errors, see the
|
||||||
|
* <a href="../util/Formatter.html#detail">details</a>
|
||||||
|
* section of the formatter class specification.
|
||||||
|
* @throws NullPointerException if fragments or type is null
|
||||||
|
*
|
||||||
|
* @see java.util.Formatter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MethodHandle linkage(List<String> fragments, MethodType type) {
|
||||||
|
Objects.requireNonNull(fragments);
|
||||||
|
Objects.requireNonNull(type);
|
||||||
|
String format = stringTemplateFormat(fragments);
|
||||||
|
Class<?>[] ptypes = type.dropParameterTypes(0, 1).parameterArray();
|
||||||
|
MethodHandle mh = new FormatterBuilder(format, locale, ptypes).build();
|
||||||
|
mh = MethodHandles.dropArguments(mh, 0, type.parameterType(0));
|
||||||
|
|
||||||
|
return mh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a format specification at the end of a fragment.
|
||||||
|
*
|
||||||
|
* @param fragment fragment to check
|
||||||
|
* @param needed if the specification is needed
|
||||||
|
*
|
||||||
|
* @return true if the specification is found and needed
|
||||||
|
*
|
||||||
|
* @throws MissingFormatArgumentException if not at end or found and not needed
|
||||||
|
*/
|
||||||
|
private static boolean findFormat(String fragment, boolean needed) {
|
||||||
|
Matcher matcher = Formatter.FORMAT_SPECIFIER_PATTERN.matcher(fragment);
|
||||||
|
String group;
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
group = matcher.group();
|
||||||
|
|
||||||
|
if (!group.equals("%%") && !group.equals("%n")) {
|
||||||
|
if (matcher.end() == fragment.length() && needed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new MissingFormatArgumentException(group +
|
||||||
|
" is not immediately followed by an embedded expression");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert {@link StringTemplate} fragments, containing format specifications,
|
||||||
|
* to a form that can be passed on to {@link Formatter}. The method scans each fragment,
|
||||||
|
* matching up formatter specifications with the following expression. If no
|
||||||
|
* specification is found, the method inserts "%s".
|
||||||
|
*
|
||||||
|
* @param fragments string template fragments
|
||||||
|
*
|
||||||
|
* @return format string
|
||||||
|
*/
|
||||||
|
private static String stringTemplateFormat(List<String> fragments) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int lastIndex = fragments.size() - 1;
|
||||||
|
List<String> formats = fragments.subList(0, lastIndex);
|
||||||
|
String last = fragments.get(lastIndex);
|
||||||
|
|
||||||
|
for (String format : formats) {
|
||||||
|
if (findFormat(format, true)) {
|
||||||
|
sb.append(format);
|
||||||
|
} else {
|
||||||
|
sb.append(format);
|
||||||
|
sb.append("%s");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!findFormat(last, false)) {
|
||||||
|
sb.append(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This predefined {@link FormatProcessor} instance constructs a {@link String} result using
|
||||||
|
* the Locale.ROOT {@link Locale}. See {@link FormatProcessor} for more details.
|
||||||
|
* Example: {@snippet :
|
||||||
|
* int x = 10;
|
||||||
|
* int y = 20;
|
||||||
|
* String result = FMT."0x%04x\{x} + 0x%04x\{y} = 0x%04x\{x + y}"; // @highlight substring="FMT"
|
||||||
|
* }
|
||||||
|
* In the above example, the value of {@code result} will be {@code "0x000a + 0x0014 = 0x001E"}.
|
||||||
|
*
|
||||||
|
* @see java.util.FormatProcessor
|
||||||
|
*/
|
||||||
|
public static final FormatProcessor FMT = FormatProcessor.create(Locale.ROOT);
|
||||||
|
|
||||||
|
}
|
@ -36,6 +36,7 @@ import java.io.OutputStream;
|
|||||||
import java.io.OutputStreamWriter;
|
import java.io.OutputStreamWriter;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
@ -60,6 +61,7 @@ import java.time.temporal.TemporalAccessor;
|
|||||||
import java.time.temporal.TemporalQueries;
|
import java.time.temporal.TemporalQueries;
|
||||||
import java.time.temporal.UnsupportedTemporalTypeException;
|
import java.time.temporal.UnsupportedTemporalTypeException;
|
||||||
|
|
||||||
|
import jdk.internal.javac.PreviewFeature;
|
||||||
import jdk.internal.math.DoubleConsts;
|
import jdk.internal.math.DoubleConsts;
|
||||||
import jdk.internal.math.FormattedFPDecimal;
|
import jdk.internal.math.FormattedFPDecimal;
|
||||||
import sun.util.locale.provider.LocaleProviderAdapter;
|
import sun.util.locale.provider.LocaleProviderAdapter;
|
||||||
@ -2770,8 +2772,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
int lasto = -1;
|
int lasto = -1;
|
||||||
|
|
||||||
List<FormatString> fsa = parse(format);
|
List<FormatString> fsa = parse(format);
|
||||||
for (int i = 0; i < fsa.size(); i++) {
|
for (FormatString fs : fsa) {
|
||||||
var fs = fsa.get(i);
|
|
||||||
int index = fs.index();
|
int index = fs.index();
|
||||||
try {
|
try {
|
||||||
switch (index) {
|
switch (index) {
|
||||||
@ -2789,7 +2790,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
throw new MissingFormatArgumentException(fs.toString());
|
throw new MissingFormatArgumentException(fs.toString());
|
||||||
fs.print(this, (args == null ? null : args[lasto]), l);
|
fs.print(this, (args == null ? null : args[lasto]), l);
|
||||||
}
|
}
|
||||||
default -> { // explicit index
|
default -> { // explicit index
|
||||||
last = index - 1;
|
last = index - 1;
|
||||||
if (args != null && last > args.length - 1)
|
if (args != null && last > args.length - 1)
|
||||||
throw new MissingFormatArgumentException(fs.toString());
|
throw new MissingFormatArgumentException(fs.toString());
|
||||||
@ -2804,15 +2805,15 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// %[argument_index$][flags][width][.precision][t]conversion
|
// %[argument_index$][flags][width][.precision][t]conversion
|
||||||
private static final String formatSpecifier
|
static final String FORMAT_SPECIFIER
|
||||||
= "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
|
= "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
|
||||||
|
|
||||||
private static final Pattern fsPattern = Pattern.compile(formatSpecifier);
|
static final Pattern FORMAT_SPECIFIER_PATTERN = Pattern.compile(FORMAT_SPECIFIER);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds format specifiers in the format string.
|
* Finds format specifiers in the format string.
|
||||||
*/
|
*/
|
||||||
private List<FormatString> parse(String s) {
|
static List<FormatString> parse(String s) {
|
||||||
ArrayList<FormatString> al = new ArrayList<>();
|
ArrayList<FormatString> al = new ArrayList<>();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int max = s.length();
|
int max = s.length();
|
||||||
@ -2840,7 +2841,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
if (m == null) {
|
if (m == null) {
|
||||||
m = fsPattern.matcher(s);
|
m = FORMAT_SPECIFIER_PATTERN.matcher(s);
|
||||||
}
|
}
|
||||||
// We have already parsed a '%' at n, so we either have a
|
// We have already parsed a '%' at n, so we either have a
|
||||||
// match or the specifier at n is invalid
|
// match or the specifier at n is invalid
|
||||||
@ -2855,7 +2856,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
return al;
|
return al;
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface FormatString {
|
interface FormatString {
|
||||||
int index();
|
int index();
|
||||||
void print(Formatter fmt, Object arg, Locale l) throws IOException;
|
void print(Formatter fmt, Object arg, Locale l) throws IOException;
|
||||||
String toString();
|
String toString();
|
||||||
@ -2891,14 +2892,15 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
DECIMAL_FLOAT
|
DECIMAL_FLOAT
|
||||||
};
|
};
|
||||||
|
|
||||||
private static class FormatSpecifier implements FormatString {
|
static class FormatSpecifier implements FormatString {
|
||||||
|
private static final double SCALEUP = Math.scalb(1.0, 54);
|
||||||
|
|
||||||
private int index = 0;
|
int index = 0;
|
||||||
private int flags = Flags.NONE;
|
int flags = Flags.NONE;
|
||||||
private int width = -1;
|
int width = -1;
|
||||||
private int precision = -1;
|
int precision = -1;
|
||||||
private boolean dt = false;
|
boolean dt = false;
|
||||||
private char c;
|
char c;
|
||||||
|
|
||||||
private void index(String s, int start, int end) {
|
private void index(String s, int start, int end) {
|
||||||
if (start >= 0) {
|
if (start >= 0) {
|
||||||
@ -3548,8 +3550,8 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
if (width != -1) {
|
if (width != -1) {
|
||||||
newW = adjustWidth(width - exp.length - 1, flags, neg);
|
newW = adjustWidth(width - exp.length - 1, flags, neg);
|
||||||
}
|
}
|
||||||
localizedMagnitude(fmt, sb, mant, 0, flags, newW, l);
|
|
||||||
|
|
||||||
|
localizedMagnitude(fmt, sb, mant, 0, flags, newW, l);
|
||||||
sb.append(Flags.contains(flags, Flags.UPPERCASE) ? 'E' : 'e');
|
sb.append(Flags.contains(flags, Flags.UPPERCASE) ? 'E' : 'e');
|
||||||
|
|
||||||
char sign = exp[0];
|
char sign = exp[0];
|
||||||
@ -3719,8 +3721,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
// If this is subnormal input so normalize (could be faster to
|
// If this is subnormal input so normalize (could be faster to
|
||||||
// do as integer operation).
|
// do as integer operation).
|
||||||
if (subnormal) {
|
if (subnormal) {
|
||||||
double scaleUp = Math.scalb(1.0, 54);
|
d *= SCALEUP;
|
||||||
d *= scaleUp;
|
|
||||||
// Calculate the exponent. This is not just exponent + 54
|
// Calculate the exponent. This is not just exponent + 54
|
||||||
// since the former is not the normalized exponent.
|
// since the former is not the normalized exponent.
|
||||||
exponent = Math.getExponent(d);
|
exponent = Math.getExponent(d);
|
||||||
@ -4623,7 +4624,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Flags {
|
static class Flags {
|
||||||
|
|
||||||
static final int NONE = 0; // ''
|
static final int NONE = 0; // ''
|
||||||
|
|
||||||
@ -4701,7 +4702,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Conversion {
|
static class Conversion {
|
||||||
// Byte, Short, Integer, Long, BigInteger
|
// Byte, Short, Integer, Long, BigInteger
|
||||||
// (and associated primitives due to autoboxing)
|
// (and associated primitives due to autoboxing)
|
||||||
static final char DECIMAL_INTEGER = 'd';
|
static final char DECIMAL_INTEGER = 'd';
|
||||||
@ -4826,7 +4827,7 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DateTime {
|
static class DateTime {
|
||||||
static final char HOUR_OF_DAY_0 = 'H'; // (00 - 23)
|
static final char HOUR_OF_DAY_0 = 'H'; // (00 - 23)
|
||||||
static final char HOUR_0 = 'I'; // (01 - 12)
|
static final char HOUR_0 = 'I'; // (01 - 12)
|
||||||
static final char HOUR_OF_DAY = 'k'; // (0 - 23) -- like H
|
static final char HOUR_OF_DAY = 'k'; // (0 - 23) -- like H
|
||||||
@ -4877,4 +4878,5 @@ public final class Formatter implements Closeable, Flushable {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
489
src/java.base/share/classes/java/util/FormatterBuilder.java
Normal file
489
src/java.base/share/classes/java/util/FormatterBuilder.java
Normal file
@ -0,0 +1,489 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,6 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import jdk.internal.javac.PreviewFeature;
|
||||||
import jdk.internal.misc.CarrierThreadLocal;
|
import jdk.internal.misc.CarrierThreadLocal;
|
||||||
import jdk.internal.module.ServicesCatalog;
|
import jdk.internal.module.ServicesCatalog;
|
||||||
import jdk.internal.reflect.ConstantPool;
|
import jdk.internal.reflect.ConstantPool;
|
||||||
@ -420,6 +421,24 @@ public interface JavaLangAccess {
|
|||||||
*/
|
*/
|
||||||
long stringConcatMix(long lengthCoder, String constant);
|
long stringConcatMix(long lengthCoder, String constant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the coder for the supplied character.
|
||||||
|
*/
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||||
|
long stringConcatCoder(char value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update lengthCoder for StringBuilder.
|
||||||
|
*/
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||||
|
long stringBuilderConcatMix(long lengthCoder, StringBuilder sb);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepend StringBuilder content.
|
||||||
|
*/
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES)
|
||||||
|
long stringBuilderConcatPrepend(long lengthCoder, byte[] buf, StringBuilder sb);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Join strings
|
* Join strings
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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 jdk.internal.access;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface JavaTemplateAccess {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
StringTemplate of(List<String> fragments, List<?> 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
|
||||||
|
*/
|
||||||
|
String interpolate(List<String> fragments, List<?> values);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
StringTemplate combine(StringTemplate... sts);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -89,6 +89,7 @@ public class SharedSecrets {
|
|||||||
private static JavaSecuritySpecAccess javaSecuritySpecAccess;
|
private static JavaSecuritySpecAccess javaSecuritySpecAccess;
|
||||||
private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess;
|
private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess;
|
||||||
private static JavaxCryptoSpecAccess javaxCryptoSpecAccess;
|
private static JavaxCryptoSpecAccess javaxCryptoSpecAccess;
|
||||||
|
private static JavaTemplateAccess javaTemplateAccess;
|
||||||
|
|
||||||
public static void setJavaUtilCollectionAccess(JavaUtilCollectionAccess juca) {
|
public static void setJavaUtilCollectionAccess(JavaUtilCollectionAccess juca) {
|
||||||
javaUtilCollectionAccess = juca;
|
javaUtilCollectionAccess = juca;
|
||||||
@ -516,6 +517,21 @@ public class SharedSecrets {
|
|||||||
return access;
|
return access;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setJavaTemplateAccess(JavaTemplateAccess jta) {
|
||||||
|
javaTemplateAccess = jta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JavaTemplateAccess getJavaTemplateAccess() {
|
||||||
|
var access = javaTemplateAccess;
|
||||||
|
if (access == null) {
|
||||||
|
try {
|
||||||
|
Class.forName("java.lang.runtime.TemplateSupport", true, null);
|
||||||
|
access = javaTemplateAccess;
|
||||||
|
} catch (ClassNotFoundException e) {}
|
||||||
|
}
|
||||||
|
return access;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ensureClassInitialized(Class<?> c) {
|
private static void ensureClassInitialized(Class<?> c) {
|
||||||
try {
|
try {
|
||||||
MethodHandles.lookup().ensureInitialized(c);
|
MethodHandles.lookup().ensureInitialized(c);
|
||||||
|
@ -72,6 +72,8 @@ public @interface PreviewFeature {
|
|||||||
VIRTUAL_THREADS,
|
VIRTUAL_THREADS,
|
||||||
@JEP(number=442, title="Foreign Function & Memory API", status="Third Preview")
|
@JEP(number=442, title="Foreign Function & Memory API", status="Third Preview")
|
||||||
FOREIGN,
|
FOREIGN,
|
||||||
|
@JEP(number=430, title="String Templates", status="First Preview")
|
||||||
|
STRING_TEMPLATES,
|
||||||
/**
|
/**
|
||||||
* A key for testing.
|
* A key for testing.
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 jdk.internal.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of this class provide information necessary to
|
||||||
|
* assist {@link java.lang.invoke.StringConcatFactory} perform optimal
|
||||||
|
* insertion.
|
||||||
|
*
|
||||||
|
* @since 21
|
||||||
|
*
|
||||||
|
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
||||||
|
* Do not rely on its availability.
|
||||||
|
*/
|
||||||
|
public interface FormatConcatItem {
|
||||||
|
/**
|
||||||
|
* Calculate the length of the insertion.
|
||||||
|
*
|
||||||
|
* @param lengthCoder current value of the length + coder
|
||||||
|
* @return adjusted value of the length + coder
|
||||||
|
*/
|
||||||
|
long mix(long lengthCoder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert content into buffer prior to the current length.
|
||||||
|
*
|
||||||
|
* @param lengthCoder current value of the length + coder
|
||||||
|
* @param buffer buffer to append to
|
||||||
|
*
|
||||||
|
* @return adjusted value of the length + coder
|
||||||
|
*
|
||||||
|
* @throws Throwable if fails to prepend value (unusual).
|
||||||
|
*/
|
||||||
|
long prepend(long lengthCoder, byte[] buffer) throws Throwable;
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
package com.sun.source.tree;
|
package com.sun.source.tree;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.lang.model.element.Name;
|
import javax.lang.model.element.Name;
|
||||||
|
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.sun.source.tree;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jdk.internal.javac.PreviewFeature;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node for a string template expression.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES, reflective=true)
|
||||||
|
public interface StringTemplateTree extends ExpressionTree {
|
||||||
|
/**
|
||||||
|
* Returns templated string processor (may be qualified) or null.
|
||||||
|
*
|
||||||
|
* @return templated string processor
|
||||||
|
*/
|
||||||
|
ExpressionTree getProcessor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns string fragments.
|
||||||
|
*
|
||||||
|
* @return string fragments
|
||||||
|
*/
|
||||||
|
List<String> getFragments();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns list of expressions.
|
||||||
|
*
|
||||||
|
* @return list of expressions
|
||||||
|
*/
|
||||||
|
List<? extends ExpressionTree> getExpressions();
|
||||||
|
}
|
@ -175,6 +175,12 @@ public interface Tree {
|
|||||||
*/
|
*/
|
||||||
INSTANCE_OF(InstanceOfTree.class),
|
INSTANCE_OF(InstanceOfTree.class),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for instances of {@link StringTemplateTree}.
|
||||||
|
*/
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES, reflective=true)
|
||||||
|
TEMPLATE(StringTemplateTree.class),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for instances of {@link LabeledStatementTree}.
|
* Used for instances of {@link LabeledStatementTree}.
|
||||||
*/
|
*/
|
||||||
|
@ -259,6 +259,15 @@ public interface TreeVisitor<R,P> {
|
|||||||
*/
|
*/
|
||||||
R visitLiteral(LiteralTree node, P p);
|
R visitLiteral(LiteralTree node, P p);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits a StringTemplateTree node.
|
||||||
|
* @param node the node being visited
|
||||||
|
* @param p a parameter value
|
||||||
|
* @return a result value
|
||||||
|
*/
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES, reflective=true)
|
||||||
|
R visitStringTemplate(StringTemplateTree node, P p);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Visits a {@code BindingPatternTree} node.
|
* Visits a {@code BindingPatternTree} node.
|
||||||
* @param node the node being visited
|
* @param node the node being visited
|
||||||
|
@ -628,6 +628,19 @@ public class SimpleTreeVisitor <R,P> implements TreeVisitor<R,P> {
|
|||||||
return defaultAction(node, p);
|
return defaultAction(node, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc} This implementation calls {@code defaultAction}.
|
||||||
|
*
|
||||||
|
* @param node {@inheritDoc}
|
||||||
|
* @param p {@inheritDoc}
|
||||||
|
* @return the result of {@code defaultAction}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES, reflective=true)
|
||||||
|
public R visitStringTemplate(StringTemplateTree node, P p) {
|
||||||
|
return defaultAction(node, p);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
@ -759,6 +759,23 @@ public class TreeScanner<R,P> implements TreeVisitor<R,P> {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @implSpec This implementation scans the children in left to right order.
|
||||||
|
*
|
||||||
|
* @param node {@inheritDoc}
|
||||||
|
* @param p {@inheritDoc}
|
||||||
|
* @return the result of scanning
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@PreviewFeature(feature=PreviewFeature.Feature.STRING_TEMPLATES, reflective=true)
|
||||||
|
public R visitStringTemplate(StringTemplateTree node, P p) {
|
||||||
|
R r = scan(node.getProcessor(), p);
|
||||||
|
r = scanAndReduce(node.getExpressions(), p, r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
@ -211,6 +211,7 @@ public class Preview {
|
|||||||
return switch (feature) {
|
return switch (feature) {
|
||||||
case CASE_NULL -> true;
|
case CASE_NULL -> true;
|
||||||
case PATTERN_SWITCH -> true;
|
case PATTERN_SWITCH -> true;
|
||||||
|
case STRING_TEMPLATES -> true;
|
||||||
case UNCONDITIONAL_PATTERN_IN_INSTANCEOF -> true;
|
case UNCONDITIONAL_PATTERN_IN_INSTANCEOF -> true;
|
||||||
case RECORD_PATTERNS -> true;
|
case RECORD_PATTERNS -> true;
|
||||||
|
|
||||||
|
@ -235,6 +235,7 @@ public enum Source {
|
|||||||
CASE_NULL(JDK17, Fragments.FeatureCaseNull, DiagKind.NORMAL),
|
CASE_NULL(JDK17, Fragments.FeatureCaseNull, DiagKind.NORMAL),
|
||||||
PATTERN_SWITCH(JDK17, Fragments.FeaturePatternSwitch, DiagKind.PLURAL),
|
PATTERN_SWITCH(JDK17, Fragments.FeaturePatternSwitch, DiagKind.PLURAL),
|
||||||
REDUNDANT_STRICTFP(JDK17),
|
REDUNDANT_STRICTFP(JDK17),
|
||||||
|
STRING_TEMPLATES(JDK21, Fragments.FeatureStringTemplates, DiagKind.PLURAL),
|
||||||
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK19, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
|
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK19, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
|
||||||
RECORD_PATTERNS(JDK19, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
|
RECORD_PATTERNS(JDK19, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
|
||||||
WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
|
WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
|
||||||
|
@ -174,6 +174,7 @@ public class Symtab {
|
|||||||
public final Type serializedLambdaType;
|
public final Type serializedLambdaType;
|
||||||
public final Type varHandleType;
|
public final Type varHandleType;
|
||||||
public final Type methodHandleType;
|
public final Type methodHandleType;
|
||||||
|
public final Type methodHandlesType;
|
||||||
public final Type methodHandleLookupType;
|
public final Type methodHandleLookupType;
|
||||||
public final Type methodTypeType;
|
public final Type methodTypeType;
|
||||||
public final Type nativeHeaderType;
|
public final Type nativeHeaderType;
|
||||||
@ -234,6 +235,12 @@ public class Symtab {
|
|||||||
public final Type objectStreamExceptionType;
|
public final Type objectStreamExceptionType;
|
||||||
public final Type externalizableType;
|
public final Type externalizableType;
|
||||||
|
|
||||||
|
// For string templates
|
||||||
|
public final Type stringTemplateType;
|
||||||
|
public final Type templateRuntimeType;
|
||||||
|
public final Type processorType;
|
||||||
|
public final Type linkageType;
|
||||||
|
|
||||||
/** The symbol representing the length field of an array.
|
/** The symbol representing the length field of an array.
|
||||||
*/
|
*/
|
||||||
public final VarSymbol lengthVar;
|
public final VarSymbol lengthVar;
|
||||||
@ -543,6 +550,7 @@ public class Symtab {
|
|||||||
serializedLambdaType = enterClass("java.lang.invoke.SerializedLambda");
|
serializedLambdaType = enterClass("java.lang.invoke.SerializedLambda");
|
||||||
varHandleType = enterClass("java.lang.invoke.VarHandle");
|
varHandleType = enterClass("java.lang.invoke.VarHandle");
|
||||||
methodHandleType = enterClass("java.lang.invoke.MethodHandle");
|
methodHandleType = enterClass("java.lang.invoke.MethodHandle");
|
||||||
|
methodHandlesType = enterClass("java.lang.invoke.MethodHandles");
|
||||||
methodHandleLookupType = enterClass("java.lang.invoke.MethodHandles$Lookup");
|
methodHandleLookupType = enterClass("java.lang.invoke.MethodHandles$Lookup");
|
||||||
methodTypeType = enterClass("java.lang.invoke.MethodType");
|
methodTypeType = enterClass("java.lang.invoke.MethodType");
|
||||||
errorType = enterClass("java.lang.Error");
|
errorType = enterClass("java.lang.Error");
|
||||||
@ -610,7 +618,6 @@ public class Symtab {
|
|||||||
ioExceptionType = enterClass("java.io.IOException");
|
ioExceptionType = enterClass("java.io.IOException");
|
||||||
objectStreamExceptionType = enterClass("java.io.ObjectStreamException");
|
objectStreamExceptionType = enterClass("java.io.ObjectStreamException");
|
||||||
externalizableType = enterClass("java.io.Externalizable");
|
externalizableType = enterClass("java.io.Externalizable");
|
||||||
|
|
||||||
synthesizeEmptyInterfaceIfMissing(autoCloseableType);
|
synthesizeEmptyInterfaceIfMissing(autoCloseableType);
|
||||||
synthesizeEmptyInterfaceIfMissing(cloneableType);
|
synthesizeEmptyInterfaceIfMissing(cloneableType);
|
||||||
synthesizeEmptyInterfaceIfMissing(serializableType);
|
synthesizeEmptyInterfaceIfMissing(serializableType);
|
||||||
@ -621,6 +628,12 @@ public class Symtab {
|
|||||||
synthesizeBoxTypeIfMissing(floatType);
|
synthesizeBoxTypeIfMissing(floatType);
|
||||||
synthesizeBoxTypeIfMissing(voidType);
|
synthesizeBoxTypeIfMissing(voidType);
|
||||||
|
|
||||||
|
// For string templates
|
||||||
|
stringTemplateType = enterClass("java.lang.StringTemplate");
|
||||||
|
templateRuntimeType = enterClass("java.lang.runtime.TemplateRuntime");
|
||||||
|
processorType = enterClass("java.lang.StringTemplate$Processor");
|
||||||
|
linkageType = enterClass("java.lang.StringTemplate$Processor$Linkage");
|
||||||
|
|
||||||
// Enter a synthetic class that is used to mark internal
|
// Enter a synthetic class that is used to mark internal
|
||||||
// proprietary classes in ct.sym. This class does not have a
|
// proprietary classes in ct.sym. This class does not have a
|
||||||
// class file.
|
// class file.
|
||||||
|
@ -4984,6 +4984,27 @@ public class Attr extends JCTree.Visitor {
|
|||||||
return (tag == CLASS) ? syms.stringType : syms.typeOfTag[tag.ordinal()];
|
return (tag == CLASS) ? syms.stringType : syms.typeOfTag[tag.ordinal()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void visitStringTemplate(JCStringTemplate tree) {
|
||||||
|
JCExpression processor = tree.processor;
|
||||||
|
Type resultType = syms.stringTemplateType;
|
||||||
|
|
||||||
|
if (processor != null) {
|
||||||
|
resultType = attribTree(processor, env, new ResultInfo(KindSelector.VAL, Type.noType));
|
||||||
|
resultType = chk.checkProcessorType(processor, resultType, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
Env<AttrContext> localEnv = env.dup(tree, env.info.dup());
|
||||||
|
|
||||||
|
for (JCExpression arg : tree.expressions) {
|
||||||
|
chk.checkNonVoid(arg.pos(), attribExpr(arg, localEnv));
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.type = resultType;
|
||||||
|
result = resultType;
|
||||||
|
|
||||||
|
check(tree, resultType, KindSelector.VAL, resultInfo);
|
||||||
|
}
|
||||||
|
|
||||||
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
||||||
result = check(tree, syms.typeOfTag[tree.typetag.ordinal()], KindSelector.TYP, resultInfo);
|
result = check(tree, syms.typeOfTag[tree.typetag.ordinal()], KindSelector.TYP, resultInfo);
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,8 @@ public class Check {
|
|||||||
private final Preview preview;
|
private final Preview preview;
|
||||||
private final boolean warnOnAnyAccessToMembers;
|
private final boolean warnOnAnyAccessToMembers;
|
||||||
|
|
||||||
|
public boolean disablePreviewCheck;
|
||||||
|
|
||||||
// The set of lint options currently in effect. It is initialized
|
// The set of lint options currently in effect. It is initialized
|
||||||
// from the context, and then is set/reset as needed by Attr as it
|
// from the context, and then is set/reset as needed by Attr as it
|
||||||
// visits all the various parts of the trees during attribution.
|
// visits all the various parts of the trees during attribution.
|
||||||
@ -155,6 +157,8 @@ public class Check {
|
|||||||
target = Target.instance(context);
|
target = Target.instance(context);
|
||||||
warnOnAnyAccessToMembers = options.isSet("warnOnAccessToMembers");
|
warnOnAnyAccessToMembers = options.isSet("warnOnAccessToMembers");
|
||||||
|
|
||||||
|
disablePreviewCheck = false;
|
||||||
|
|
||||||
Target target = Target.instance(context);
|
Target target = Target.instance(context);
|
||||||
syntheticNameChar = target.syntheticNameChar();
|
syntheticNameChar = target.syntheticNameChar();
|
||||||
|
|
||||||
@ -3793,7 +3797,7 @@ public class Check {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void checkPreview(DiagnosticPosition pos, Symbol other, Symbol s) {
|
void checkPreview(DiagnosticPosition pos, Symbol other, Symbol s) {
|
||||||
if ((s.flags() & PREVIEW_API) != 0 && !preview.participatesInPreview(syms, other, s)) {
|
if ((s.flags() & PREVIEW_API) != 0 && !preview.participatesInPreview(syms, other, s) && !disablePreviewCheck) {
|
||||||
if ((s.flags() & PREVIEW_REFLECTIVE) == 0) {
|
if ((s.flags() & PREVIEW_REFLECTIVE) == 0) {
|
||||||
if (!preview.isEnabled()) {
|
if (!preview.isEnabled()) {
|
||||||
log.error(pos, Errors.IsPreview(s));
|
log.error(pos, Errors.IsPreview(s));
|
||||||
@ -4311,6 +4315,27 @@ public class Check {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Type checkProcessorType(JCExpression processor, Type resultType, Env<AttrContext> env) {
|
||||||
|
Type processorType = processor.type;
|
||||||
|
Type interfaceType = types.asSuper(processorType, syms.processorType.tsym);
|
||||||
|
|
||||||
|
if (interfaceType != null) {
|
||||||
|
List<Type> typeArguments = interfaceType.getTypeArguments();
|
||||||
|
|
||||||
|
if (typeArguments.size() == 2) {
|
||||||
|
resultType = typeArguments.head;
|
||||||
|
} else {
|
||||||
|
log.error(DiagnosticFlag.RESOLVE_ERROR, processor.pos,
|
||||||
|
Errors.ProcessorTypeCannotBeARawType(processorType.tsym));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error(DiagnosticFlag.RESOLVE_ERROR, processor.pos,
|
||||||
|
Errors.NotAProcessorType(processorType.tsym));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultType;
|
||||||
|
}
|
||||||
|
|
||||||
public void checkLeaksNotAccessible(Env<AttrContext> env, JCClassDecl check) {
|
public void checkLeaksNotAccessible(Env<AttrContext> env, JCClassDecl check) {
|
||||||
JCCompilationUnit toplevel = env.toplevel;
|
JCCompilationUnit toplevel = env.toplevel;
|
||||||
|
|
||||||
|
@ -59,10 +59,11 @@ public class CompileStates extends HashMap<Env<AttrContext>, CompileStates.Compi
|
|||||||
ATTR(4),
|
ATTR(4),
|
||||||
FLOW(5),
|
FLOW(5),
|
||||||
TRANSTYPES(6),
|
TRANSTYPES(6),
|
||||||
TRANSPATTERNS(7),
|
TRANSLITERALS(7),
|
||||||
UNLAMBDA(8),
|
TRANSPATTERNS(8),
|
||||||
LOWER(9),
|
UNLAMBDA(9),
|
||||||
GENERATE(10);
|
LOWER(10),
|
||||||
|
GENERATE(11);
|
||||||
|
|
||||||
CompileState(int value) {
|
CompileState(int value) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
@ -1528,6 +1528,30 @@ public class Flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitStringTemplate(JCStringTemplate tree) {
|
||||||
|
JCExpression processor = tree.processor;
|
||||||
|
|
||||||
|
if (processor != null) {
|
||||||
|
scan(processor);
|
||||||
|
Type interfaceType = types.asSuper(processor.type, syms.processorType.tsym);
|
||||||
|
|
||||||
|
if (interfaceType != null) {
|
||||||
|
List<Type> typeArguments = interfaceType.getTypeArguments();
|
||||||
|
|
||||||
|
if (typeArguments.size() == 2) {
|
||||||
|
Type throwType = typeArguments.tail.head;
|
||||||
|
|
||||||
|
if (throwType != null) {
|
||||||
|
markThrown(tree, throwType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scan(tree.expressions);
|
||||||
|
}
|
||||||
|
|
||||||
void checkCaughtType(DiagnosticPosition pos, Type exc, List<Type> thrownInTry, List<Type> caughtInTry) {
|
void checkCaughtType(DiagnosticPosition pos, Type exc, List<Type> thrownInTry, List<Type> caughtInTry) {
|
||||||
if (chk.subset(exc, caughtInTry)) {
|
if (chk.subset(exc, caughtInTry)) {
|
||||||
log.error(pos, Errors.ExceptAlreadyCaught(exc));
|
log.error(pos, Errors.ExceptAlreadyCaught(exc));
|
||||||
|
@ -0,0 +1,344 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.sun.tools.javac.comp;
|
||||||
|
|
||||||
|
import com.sun.tools.javac.code.Flags;
|
||||||
|
import com.sun.tools.javac.code.Symbol;
|
||||||
|
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||||
|
import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol;
|
||||||
|
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||||
|
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||||
|
import com.sun.tools.javac.code.Symtab;
|
||||||
|
import com.sun.tools.javac.code.Type;
|
||||||
|
import com.sun.tools.javac.code.Type.*;
|
||||||
|
import com.sun.tools.javac.code.Types;
|
||||||
|
import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant;
|
||||||
|
import com.sun.tools.javac.tree.JCTree;
|
||||||
|
import com.sun.tools.javac.tree.JCTree.*;
|
||||||
|
import com.sun.tools.javac.tree.TreeInfo;
|
||||||
|
import com.sun.tools.javac.tree.TreeMaker;
|
||||||
|
import com.sun.tools.javac.tree.TreeTranslator;
|
||||||
|
import com.sun.tools.javac.util.Context;
|
||||||
|
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
|
||||||
|
import com.sun.tools.javac.util.List;
|
||||||
|
import com.sun.tools.javac.util.Name;
|
||||||
|
import com.sun.tools.javac.util.Names;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/** This pass translates constructed literals (string templates, ...) to conventional Java.
|
||||||
|
*
|
||||||
|
* <p><b>This is NOT part of any supported API.
|
||||||
|
* If you write code that depends on this, you do so at your own risk.
|
||||||
|
* This code and its internal interfaces are subject to change or
|
||||||
|
* deletion without notice.</b>
|
||||||
|
*/
|
||||||
|
public final class TransLiterals extends TreeTranslator {
|
||||||
|
/**
|
||||||
|
* The context key for the TransTypes phase.
|
||||||
|
*/
|
||||||
|
protected static final Context.Key<TransLiterals> transLiteralsKey = new Context.Key<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance for this context.
|
||||||
|
*/
|
||||||
|
public static TransLiterals instance(Context context) {
|
||||||
|
TransLiterals instance = context.get(transLiteralsKey);
|
||||||
|
if (instance == null)
|
||||||
|
instance = new TransLiterals(context);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Symtab syms;
|
||||||
|
private final Resolve rs;
|
||||||
|
private final Types types;
|
||||||
|
private final Operators operators;
|
||||||
|
private final Names names;
|
||||||
|
private TreeMaker make = null;
|
||||||
|
private Env<AttrContext> env = null;
|
||||||
|
private ClassSymbol currentClass = null;
|
||||||
|
private MethodSymbol currentMethodSym = null;
|
||||||
|
|
||||||
|
protected TransLiterals(Context context) {
|
||||||
|
context.put(transLiteralsKey, this);
|
||||||
|
syms = Symtab.instance(context);
|
||||||
|
rs = Resolve.instance(context);
|
||||||
|
make = TreeMaker.instance(context);
|
||||||
|
types = Types.instance(context);
|
||||||
|
operators = Operators.instance(context);
|
||||||
|
names = Names.instance(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression makeLit(Type type, Object value) {
|
||||||
|
return make.Literal(type.getTag(), value).setType(type.constType(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression makeString(String string) {
|
||||||
|
return makeLit(syms.stringType, string);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<JCExpression> makeStringList(List<String> strings) {
|
||||||
|
List<JCExpression> exprs = List.nil();
|
||||||
|
for (String string : strings) {
|
||||||
|
exprs = exprs.append(makeString(string));
|
||||||
|
}
|
||||||
|
return exprs;
|
||||||
|
}
|
||||||
|
|
||||||
|
JCBinary makeBinary(JCTree.Tag optag, JCExpression lhs, JCExpression rhs) {
|
||||||
|
JCBinary tree = make.Binary(optag, lhs, rhs);
|
||||||
|
tree.operator = operators.resolveBinary(tree, optag, lhs.type, rhs.type);
|
||||||
|
tree.type = tree.operator.type.getReturnType();
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodSymbol lookupMethod(DiagnosticPosition pos, Name name, Type qual, List<Type> args) {
|
||||||
|
return rs.resolveInternalMethod(pos, env, qual, name, args, List.nil());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitClassDef(JCClassDecl tree) {
|
||||||
|
ClassSymbol prevCurrentClass = currentClass;
|
||||||
|
try {
|
||||||
|
currentClass = tree.sym;
|
||||||
|
super.visitClassDef(tree);
|
||||||
|
} finally {
|
||||||
|
currentClass = prevCurrentClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMethodDef(JCMethodDecl tree) {
|
||||||
|
MethodSymbol prevMethodSym = currentMethodSym;
|
||||||
|
try {
|
||||||
|
currentMethodSym = tree.sym;
|
||||||
|
super.visitMethodDef(tree);
|
||||||
|
} finally {
|
||||||
|
currentMethodSym = prevMethodSym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class TransStringTemplate {
|
||||||
|
final JCStringTemplate tree;
|
||||||
|
final JCExpression processor;
|
||||||
|
final List<String> fragments;
|
||||||
|
final List<JCExpression> expressions;
|
||||||
|
final List<Type> expressionTypes;
|
||||||
|
final boolean useValuesList;
|
||||||
|
|
||||||
|
TransStringTemplate(JCStringTemplate tree) {
|
||||||
|
this.tree = tree;
|
||||||
|
this.processor = tree.processor;
|
||||||
|
this.fragments = tree.fragments;
|
||||||
|
this.expressions = translate(tree.expressions);
|
||||||
|
this.expressionTypes = expressions.stream()
|
||||||
|
.map(arg -> arg.type == syms.botType ? syms.objectType : arg.type)
|
||||||
|
.collect(List.collector());
|
||||||
|
int slots = expressionTypes.stream()
|
||||||
|
.mapToInt(t -> types.isSameType(t, syms.longType) ||
|
||||||
|
types.isSameType(t, syms.doubleType) ? 2 : 1).sum();
|
||||||
|
this.useValuesList = 200 < slots; // StringConcatFactory.MAX_INDY_CONCAT_ARG_SLOTS
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression concatExpression(List<String> fragments, List<JCExpression> expressions) {
|
||||||
|
JCExpression expr = null;
|
||||||
|
Iterator<JCExpression> iterator = expressions.iterator();
|
||||||
|
for (String fragment : fragments) {
|
||||||
|
expr = expr == null ? makeString(fragment)
|
||||||
|
: makeBinary(Tag.PLUS, expr, makeString(fragment));
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
JCExpression expression = iterator.next();
|
||||||
|
Type expressionType = expression.type;
|
||||||
|
expr = makeBinary(Tag.PLUS, expr, expression.setType(expressionType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression bsmCall(Name name, Name bootstrapName, Type type,
|
||||||
|
List<JCExpression> args,
|
||||||
|
List<Type> argTypes,
|
||||||
|
List<LoadableConstant> staticArgValues,
|
||||||
|
List<Type> staticArgsTypes) {
|
||||||
|
Symbol bsm = rs.resolveQualifiedMethod(tree.pos(), env,
|
||||||
|
syms.templateRuntimeType, bootstrapName, staticArgsTypes, List.nil());
|
||||||
|
MethodType indyType = new MethodType(argTypes, type, List.nil(), syms.methodClass);
|
||||||
|
DynamicMethodSymbol dynSym = new DynamicMethodSymbol(
|
||||||
|
name,
|
||||||
|
syms.noSymbol,
|
||||||
|
((MethodSymbol)bsm).asHandle(),
|
||||||
|
indyType,
|
||||||
|
staticArgValues.toArray(new LoadableConstant[0])
|
||||||
|
);
|
||||||
|
JCFieldAccess qualifier = make.Select(make.Type(syms.processorType), dynSym.name);
|
||||||
|
qualifier.sym = dynSym;
|
||||||
|
qualifier.type = type;
|
||||||
|
JCMethodInvocation apply = make.Apply(List.nil(), qualifier, args);
|
||||||
|
apply.type = type;
|
||||||
|
return apply;
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression processCall(JCExpression stringTemplate) {
|
||||||
|
MethodSymbol appyMeth = lookupMethod(tree.pos(), names.process,
|
||||||
|
syms.processorType, List.of(syms.stringTemplateType));
|
||||||
|
JCExpression applySelect = make.Select(processor, appyMeth);
|
||||||
|
JCExpression process = make.Apply(null, applySelect, List.of(stringTemplate))
|
||||||
|
.setType(syms.objectType);
|
||||||
|
JCTypeCast cast = make.TypeCast(tree.type, process);
|
||||||
|
return cast;
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression newStringTemplate() {
|
||||||
|
List<LoadableConstant> staticArgValues = List.nil();
|
||||||
|
List<Type> staticArgsTypes =
|
||||||
|
List.of(syms.methodHandleLookupType, syms.stringType,
|
||||||
|
syms.methodTypeType);
|
||||||
|
if (useValuesList) {
|
||||||
|
JCNewArray fragmentArray = make.NewArray(make.Type(syms.stringType),
|
||||||
|
List.nil(), makeStringList(fragments));
|
||||||
|
fragmentArray.type = new ArrayType(syms.stringType, syms.arrayClass);
|
||||||
|
JCNewArray valuesArray = make.NewArray(make.Type(syms.objectType),
|
||||||
|
List.nil(), expressions);
|
||||||
|
valuesArray.type = new ArrayType(syms.objectType, syms.arrayClass);
|
||||||
|
return bsmCall(names.process, names.newLargeStringTemplate, syms.stringTemplateType,
|
||||||
|
List.of(fragmentArray, valuesArray),
|
||||||
|
List.of(fragmentArray.type, valuesArray.type),
|
||||||
|
staticArgValues, staticArgsTypes);
|
||||||
|
} else {
|
||||||
|
for (String fragment : fragments) {
|
||||||
|
staticArgValues = staticArgValues.append(LoadableConstant.String(fragment));
|
||||||
|
staticArgsTypes = staticArgsTypes.append(syms.stringType);
|
||||||
|
}
|
||||||
|
return bsmCall(names.process, names.newStringTemplate, syms.stringTemplateType,
|
||||||
|
expressions, expressionTypes, staticArgValues, staticArgsTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression bsmProcessCall() {
|
||||||
|
List<JCExpression> args = expressions.prepend(processor);
|
||||||
|
List<Type> argTypes = expressionTypes.prepend(processor.type);
|
||||||
|
VarSymbol processorSym = (VarSymbol)TreeInfo.symbol(processor);
|
||||||
|
List<LoadableConstant> staticArgValues = List.of(processorSym.asMethodHandle(true));
|
||||||
|
List<Type> staticArgsTypes =
|
||||||
|
List.of(syms.methodHandleLookupType, syms.stringType,
|
||||||
|
syms.methodTypeType, syms.methodHandleType);
|
||||||
|
for (String fragment : fragments) {
|
||||||
|
staticArgValues = staticArgValues.append(LoadableConstant.String(fragment));
|
||||||
|
staticArgsTypes = staticArgsTypes.append(syms.stringType);
|
||||||
|
}
|
||||||
|
return bsmCall(names.process, names.processStringTemplate, tree.type,
|
||||||
|
args, argTypes, staticArgValues, staticArgsTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isNamedProcessor(Name name) {
|
||||||
|
if (processor instanceof JCIdent ident && ident.sym instanceof VarSymbol varSym) {
|
||||||
|
if (varSym.flags() == (Flags.PUBLIC | Flags.FINAL | Flags.STATIC) &&
|
||||||
|
varSym.name == name &&
|
||||||
|
types.isSameType(varSym.owner.type, syms.stringTemplateType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isLinkageProcessor() {
|
||||||
|
return processor != null &&
|
||||||
|
!useValuesList &&
|
||||||
|
types.isSubtype(processor.type, syms.linkageType) &&
|
||||||
|
processor.type.isFinal() &&
|
||||||
|
TreeInfo.symbol(processor) instanceof VarSymbol varSymbol &&
|
||||||
|
varSymbol.isStatic() &&
|
||||||
|
varSymbol.isFinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
JCExpression visit() {
|
||||||
|
JCExpression result;
|
||||||
|
make.at(tree.pos);
|
||||||
|
|
||||||
|
if (processor == null || isNamedProcessor(names.RAW)) {
|
||||||
|
result = newStringTemplate();
|
||||||
|
} else if (isNamedProcessor(names.STR)) {
|
||||||
|
result = concatExpression(fragments, expressions);
|
||||||
|
} else if (isLinkageProcessor()) {
|
||||||
|
result = bsmProcessCall();
|
||||||
|
} else {
|
||||||
|
result = processCall(newStringTemplate());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void visitStringTemplate(JCStringTemplate tree) {
|
||||||
|
int prevPos = make.pos;
|
||||||
|
try {
|
||||||
|
tree.processor = translate(tree.processor);
|
||||||
|
tree.expressions = translate(tree.expressions);
|
||||||
|
|
||||||
|
TransStringTemplate transStringTemplate = new TransStringTemplate(tree);
|
||||||
|
|
||||||
|
result = transStringTemplate.visit();
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
throw ex;
|
||||||
|
} finally {
|
||||||
|
make.at(prevPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void visitVarDef(JCVariableDecl tree) {
|
||||||
|
MethodSymbol prevMethodSym = currentMethodSym;
|
||||||
|
try {
|
||||||
|
tree.mods = translate(tree.mods);
|
||||||
|
tree.vartype = translate(tree.vartype);
|
||||||
|
if (currentMethodSym == null) {
|
||||||
|
// A class or instance field initializer.
|
||||||
|
currentMethodSym =
|
||||||
|
new MethodSymbol((tree.mods.flags& Flags.STATIC) | Flags.BLOCK,
|
||||||
|
names.empty, null,
|
||||||
|
currentClass);
|
||||||
|
}
|
||||||
|
if (tree.init != null) tree.init = translate(tree.init);
|
||||||
|
result = tree;
|
||||||
|
} finally {
|
||||||
|
currentMethodSym = prevMethodSym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JCTree translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
|
||||||
|
try {
|
||||||
|
this.make = make;
|
||||||
|
this.env = env;
|
||||||
|
translate(cdef);
|
||||||
|
} finally {
|
||||||
|
this.make = null;
|
||||||
|
this.env = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -838,6 +838,13 @@ public class TransTypes extends TreeTranslator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void visitStringTemplate(JCStringTemplate tree) {
|
||||||
|
tree.expressions = tree.expressions.stream()
|
||||||
|
.map(e -> translate(e, erasure(e.type))).collect(List.collector());
|
||||||
|
tree.type = erasure(tree.type);
|
||||||
|
result = tree;
|
||||||
|
}
|
||||||
|
|
||||||
public void visitSelect(JCFieldAccess tree) {
|
public void visitSelect(JCFieldAccess tree) {
|
||||||
Type t = types.skipTypeVars(tree.selected.type, false);
|
Type t = types.skipTypeVars(tree.selected.type, false);
|
||||||
if (t.isCompound()) {
|
if (t.isCompound()) {
|
||||||
|
@ -25,22 +25,23 @@
|
|||||||
|
|
||||||
package com.sun.tools.javac.comp;
|
package com.sun.tools.javac.comp;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.tools.JavaFileObject;
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
import com.sun.tools.javac.code.*;
|
import com.sun.tools.javac.code.*;
|
||||||
import com.sun.tools.javac.code.Lint.LintCategory;
|
import com.sun.tools.javac.code.Lint.LintCategory;
|
||||||
import com.sun.tools.javac.code.Scope.ImportFilter;
|
import com.sun.tools.javac.code.Scope.ImportFilter;
|
||||||
|
import com.sun.tools.javac.code.Scope.ImportScope;
|
||||||
import com.sun.tools.javac.code.Scope.NamedImportScope;
|
import com.sun.tools.javac.code.Scope.NamedImportScope;
|
||||||
import com.sun.tools.javac.code.Scope.StarImportScope;
|
import com.sun.tools.javac.code.Scope.StarImportScope;
|
||||||
import com.sun.tools.javac.code.Scope.WriteableScope;
|
import com.sun.tools.javac.code.Scope.WriteableScope;
|
||||||
import com.sun.tools.javac.code.Source.Feature;
|
import com.sun.tools.javac.code.Source.Feature;
|
||||||
import com.sun.tools.javac.comp.Annotate.AnnotationTypeMetadata;
|
import com.sun.tools.javac.comp.Annotate.AnnotationTypeMetadata;
|
||||||
|
import com.sun.tools.javac.parser.Parser;
|
||||||
|
import com.sun.tools.javac.parser.ParserFactory;
|
||||||
import com.sun.tools.javac.tree.*;
|
import com.sun.tools.javac.tree.*;
|
||||||
import com.sun.tools.javac.util.*;
|
import com.sun.tools.javac.util.*;
|
||||||
import com.sun.tools.javac.util.DefinedBy.Api;
|
import com.sun.tools.javac.util.DefinedBy.Api;
|
||||||
@ -113,6 +114,7 @@ public class TypeEnter implements Completer {
|
|||||||
private final Lint lint;
|
private final Lint lint;
|
||||||
private final TypeEnvs typeEnvs;
|
private final TypeEnvs typeEnvs;
|
||||||
private final Dependencies dependencies;
|
private final Dependencies dependencies;
|
||||||
|
private final ParserFactory parserFactory;
|
||||||
private final Preview preview;
|
private final Preview preview;
|
||||||
|
|
||||||
public static TypeEnter instance(Context context) {
|
public static TypeEnter instance(Context context) {
|
||||||
@ -141,6 +143,7 @@ public class TypeEnter implements Completer {
|
|||||||
lint = Lint.instance(context);
|
lint = Lint.instance(context);
|
||||||
typeEnvs = TypeEnvs.instance(context);
|
typeEnvs = TypeEnvs.instance(context);
|
||||||
dependencies = Dependencies.instance(context);
|
dependencies = Dependencies.instance(context);
|
||||||
|
parserFactory = ParserFactory.instance(context);
|
||||||
preview = Preview.instance(context);
|
preview = Preview.instance(context);
|
||||||
Source source = Source.instance(context);
|
Source source = Source.instance(context);
|
||||||
allowDeprecationOnImport = Feature.DEPRECATION_ON_IMPORT.allowedInSource(source);
|
allowDeprecationOnImport = Feature.DEPRECATION_ON_IMPORT.allowedInSource(source);
|
||||||
@ -326,6 +329,40 @@ public class TypeEnter implements Completer {
|
|||||||
sym.owner.complete();
|
sym.owner.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void importJavaLang(JCCompilationUnit tree, Env<AttrContext> env, ImportFilter typeImportFilter) {
|
||||||
|
// Import-on-demand java.lang.
|
||||||
|
PackageSymbol javaLang = syms.enterPackage(syms.java_base, names.java_lang);
|
||||||
|
if (javaLang.members().isEmpty() && !javaLang.exists()) {
|
||||||
|
log.error(Errors.NoJavaLang);
|
||||||
|
throw new Abort();
|
||||||
|
}
|
||||||
|
importAll(make.at(tree.pos()).Import(make.Select(make.QualIdent(javaLang.owner), javaLang), false),
|
||||||
|
javaLang, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void staticImports(JCCompilationUnit tree, Env<AttrContext> env, ImportFilter staticImportFilter) {
|
||||||
|
if (preview.isEnabled() && preview.isPreview(Feature.STRING_TEMPLATES)) {
|
||||||
|
Lint prevLint = chk.setLint(lint.suppress(LintCategory.DEPRECATION, LintCategory.REMOVAL, LintCategory.PREVIEW));
|
||||||
|
boolean prevPreviewCheck = chk.disablePreviewCheck;
|
||||||
|
|
||||||
|
try {
|
||||||
|
chk.disablePreviewCheck = true;
|
||||||
|
String autoImports = """
|
||||||
|
import static java.lang.StringTemplate.STR;
|
||||||
|
""";
|
||||||
|
Parser parser = parserFactory.newParser(autoImports, false, false, false, false);
|
||||||
|
JCCompilationUnit importTree = parser.parseCompilationUnit();
|
||||||
|
|
||||||
|
for (JCImport imp : importTree.getImports()) {
|
||||||
|
doImport(imp);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
chk.setLint(prevLint);
|
||||||
|
chk.disablePreviewCheck = prevPreviewCheck;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void resolveImports(JCCompilationUnit tree, Env<AttrContext> env) {
|
private void resolveImports(JCCompilationUnit tree, Env<AttrContext> env) {
|
||||||
if (tree.starImportScope.isFilled()) {
|
if (tree.starImportScope.isFilled()) {
|
||||||
// we must have already processed this toplevel
|
// we must have already processed this toplevel
|
||||||
@ -348,14 +385,8 @@ public class TypeEnter implements Completer {
|
|||||||
(origin, sym) -> sym.kind == TYP &&
|
(origin, sym) -> sym.kind == TYP &&
|
||||||
chk.importAccessible(sym, packge);
|
chk.importAccessible(sym, packge);
|
||||||
|
|
||||||
// Import-on-demand java.lang.
|
importJavaLang(tree, env, typeImportFilter);
|
||||||
PackageSymbol javaLang = syms.enterPackage(syms.java_base, names.java_lang);
|
staticImports(tree, env, staticImportFilter);
|
||||||
if (javaLang.members().isEmpty() && !javaLang.exists()) {
|
|
||||||
log.error(Errors.NoJavaLang);
|
|
||||||
throw new Abort();
|
|
||||||
}
|
|
||||||
importAll(make.at(tree.pos()).Import(make.Select(make.QualIdent(javaLang.owner), javaLang), false),
|
|
||||||
javaLang, env);
|
|
||||||
|
|
||||||
JCModuleDecl decl = tree.getModuleDecl();
|
JCModuleDecl decl = tree.getModuleDecl();
|
||||||
|
|
||||||
|
@ -1594,6 +1594,12 @@ public class JavaCompiler {
|
|||||||
env.tree = transTypes.translateTopLevelClass(env.tree, localMake);
|
env.tree = transTypes.translateTopLevelClass(env.tree, localMake);
|
||||||
compileStates.put(env, CompileState.TRANSTYPES);
|
compileStates.put(env, CompileState.TRANSTYPES);
|
||||||
|
|
||||||
|
if (shouldStop(CompileState.TRANSLITERALS))
|
||||||
|
return;
|
||||||
|
|
||||||
|
env.tree = TransLiterals.instance(context).translateTopLevelClass(env, env.tree, localMake);
|
||||||
|
compileStates.put(env, CompileState.TRANSLITERALS);
|
||||||
|
|
||||||
if (shouldStop(CompileState.TRANSPATTERNS))
|
if (shouldStop(CompileState.TRANSPATTERNS))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -38,8 +38,8 @@ import com.sun.tools.javac.util.*;
|
|||||||
import com.sun.tools.javac.util.JCDiagnostic.*;
|
import com.sun.tools.javac.util.JCDiagnostic.*;
|
||||||
|
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static com.sun.tools.javac.parser.Tokens.*;
|
import static com.sun.tools.javac.parser.Tokens.*;
|
||||||
import static com.sun.tools.javac.util.LayoutCharacters.EOI;
|
import static com.sun.tools.javac.util.LayoutCharacters.EOI;
|
||||||
@ -62,17 +62,17 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
/**
|
/**
|
||||||
* Sentinel for non-value.
|
* Sentinel for non-value.
|
||||||
*/
|
*/
|
||||||
private int NOT_FOUND = -1;
|
private final static int NOT_FOUND = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source language setting. Copied from scanner factory.
|
* The source language setting. Copied from scanner factory.
|
||||||
*/
|
*/
|
||||||
private Source source;
|
private final Source source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The preview language setting. Copied from scanner factory.
|
* The preview language setting. Copied from scanner factory.
|
||||||
*/
|
*/
|
||||||
private Preview preview;
|
private final Preview preview;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The log to be used for error reporting. Copied from scanner factory.
|
* The log to be used for error reporting. Copied from scanner factory.
|
||||||
@ -89,6 +89,26 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
*/
|
*/
|
||||||
private final Names names;
|
private final Names names;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Origin scanner factory.
|
||||||
|
*/
|
||||||
|
protected final ScannerFactory fac;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer for building literals, used by nextToken().
|
||||||
|
*/
|
||||||
|
protected final StringBuilder sb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokens pending to be read from string template embedded expressions.
|
||||||
|
*/
|
||||||
|
protected List<Token> pendingTokens;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String template fragment ranges; end-endPos pairs.
|
||||||
|
*/
|
||||||
|
protected List<Integer> fragmentRanges;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The token kind, set by nextToken().
|
* The token kind, set by nextToken().
|
||||||
*/
|
*/
|
||||||
@ -120,21 +140,21 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
protected boolean hasEscapeSequences;
|
protected boolean hasEscapeSequences;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buffer for building literals, used by nextToken().
|
* true if contains templated string escape sequences, set by nextToken().
|
||||||
*/
|
*/
|
||||||
protected StringBuilder sb;
|
protected boolean isStringTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Origin scanner factory.
|
* true if errors are pending from embedded expressions.
|
||||||
*/
|
*/
|
||||||
protected ScannerFactory fac;
|
protected boolean hasStringTemplateErrors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The set of lint options currently in effect. It is initialized
|
* The set of lint options currently in effect. It is initialized
|
||||||
* from the context, and then is set/reset as needed by Attr as it
|
* from the context, and then is set/reset as needed by Attr as it
|
||||||
* visits all the various parts of the trees during attribution.
|
* visits all the various parts of the trees during attribution.
|
||||||
*/
|
*/
|
||||||
protected Lint lint;
|
protected final Lint lint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a Java token scanner from the input character buffer.
|
* Construct a Java token scanner from the input character buffer.
|
||||||
@ -149,9 +169,9 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
/**
|
/**
|
||||||
* Construct a Java token scanner from the input character array.
|
* Construct a Java token scanner from the input character array.
|
||||||
*
|
*
|
||||||
* @param fac the factory which created this Scanner
|
* @param fac factory which created this Scanner
|
||||||
* @param array the input character array.
|
* @param array input character array
|
||||||
* @param length The length of the meaningful content in the array.
|
* @param length length of the meaningful content in the array
|
||||||
*/
|
*/
|
||||||
protected JavaTokenizer(ScannerFactory fac, char[] array, int length) {
|
protected JavaTokenizer(ScannerFactory fac, char[] array, int length) {
|
||||||
super(fac, array, length);
|
super(fac, array, length);
|
||||||
@ -163,6 +183,8 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
this.preview = fac.preview;
|
this.preview = fac.preview;
|
||||||
this.lint = fac.lint;
|
this.lint = fac.lint;
|
||||||
this.sb = new StringBuilder(256);
|
this.sb = new StringBuilder(256);
|
||||||
|
this.pendingTokens = List.nil();
|
||||||
|
this.fragmentRanges = List.nil();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -318,19 +340,106 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes the current character and places in the literal buffer. If the current
|
* Scan the content of a string template expression.
|
||||||
* character is a backslash then the next character is validated as a proper
|
|
||||||
* escape character. Conversion of escape sequences takes place at end of nextToken().
|
|
||||||
*
|
*
|
||||||
* @param pos position of the first character in literal.
|
* @param pos start of literal
|
||||||
|
* @param endPos start of embedded expression
|
||||||
|
*/
|
||||||
|
private void scanEmbeddedExpression(int pos, int endPos) {
|
||||||
|
// If first embedded expression.
|
||||||
|
if (!isStringTemplate) {
|
||||||
|
checkSourceLevel(pos, Feature.STRING_TEMPLATES);
|
||||||
|
fragmentRanges = fragmentRanges.append(pos);
|
||||||
|
isStringTemplate = true;
|
||||||
|
}
|
||||||
|
// Track end of previous fragment.
|
||||||
|
fragmentRanges = fragmentRanges.append(endPos);
|
||||||
|
// Keep backslash and add rest of placeholder.
|
||||||
|
sb.append("{}");
|
||||||
|
|
||||||
|
// Separate tokenizer for the embedded expression.
|
||||||
|
JavaTokenizer tokenizer = new JavaTokenizer(fac, buffer(), length());
|
||||||
|
tokenizer.reset(position());
|
||||||
|
|
||||||
|
// Track brace depth.
|
||||||
|
int braceCount = 0;
|
||||||
|
|
||||||
|
// Accumulate tokens.
|
||||||
|
List<Token> tokens = List.nil();
|
||||||
|
|
||||||
|
// Stash first left brace.
|
||||||
|
Token token = tokenizer.readToken();
|
||||||
|
tokens = tokens.append(token);
|
||||||
|
|
||||||
|
while (isAvailable()) {
|
||||||
|
// Read and stash next token.
|
||||||
|
token = tokenizer.readToken();
|
||||||
|
tokens = tokens.append(token);
|
||||||
|
|
||||||
|
// Intercept errors
|
||||||
|
if (token.kind == TokenKind.ERROR) {
|
||||||
|
// Track start of next fragment.
|
||||||
|
if (isTextBlock) {
|
||||||
|
reset(length());
|
||||||
|
} else {
|
||||||
|
skipToEOLN();
|
||||||
|
}
|
||||||
|
hasStringTemplateErrors = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.kind == TokenKind.RBRACE) {
|
||||||
|
// Potential closing brace.
|
||||||
|
if (braceCount == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
braceCount--;
|
||||||
|
} else if (token.kind == TokenKind.LBRACE) {
|
||||||
|
// Nesting deeper.
|
||||||
|
braceCount++;
|
||||||
|
} else if (token.kind == TokenKind.STRINGFRAGMENT) {
|
||||||
|
tokens = tokens.appendList(tokenizer.pendingTokens);
|
||||||
|
tokenizer.pendingTokens = List.nil();
|
||||||
|
} else if (token.kind == TokenKind.EOF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no closing brace will be picked up as an unterminated string.
|
||||||
|
|
||||||
|
// Set main tokenizer to continue at next position.
|
||||||
|
int position = tokenizer.position();
|
||||||
|
reset(position);
|
||||||
|
|
||||||
|
// Track start of next fragment.
|
||||||
|
fragmentRanges = fragmentRanges.append(position);
|
||||||
|
|
||||||
|
// Pend the expression tokens after the STRINGFRAGMENT.
|
||||||
|
pendingTokens = pendingTokens.appendList(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the current character and places in the literal buffer. If the current
|
||||||
|
* character is a backslash then the next character is assumed to be a proper
|
||||||
|
* escape character. Actual conversion of escape sequences takes place
|
||||||
|
* during at the end of readToken.
|
||||||
|
*
|
||||||
|
* @param pos position of the first character in literal.
|
||||||
*/
|
*/
|
||||||
private void scanLitChar(int pos) {
|
private void scanLitChar(int pos) {
|
||||||
|
int backslash = position();
|
||||||
if (acceptThenPut('\\')) {
|
if (acceptThenPut('\\')) {
|
||||||
hasEscapeSequences = true;
|
hasEscapeSequences = true;
|
||||||
|
|
||||||
switch (get()) {
|
switch (get()) {
|
||||||
case '0': case '1': case '2': case '3':
|
case '0':
|
||||||
case '4': case '5': case '6': case '7':
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
char leadch = get();
|
char leadch = get();
|
||||||
putThenNext();
|
putThenNext();
|
||||||
|
|
||||||
@ -370,6 +479,13 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
scanEmbeddedExpression(pos, backslash);
|
||||||
|
if (hasStringTemplateErrors) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
lexError(position(), Errors.IllegalEscChar);
|
lexError(position(), Errors.IllegalEscChar);
|
||||||
break;
|
break;
|
||||||
@ -385,10 +501,10 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
* @param pos position of the first character in literal.
|
* @param pos position of the first character in literal.
|
||||||
*/
|
*/
|
||||||
private void scanString(int pos) {
|
private void scanString(int pos) {
|
||||||
// Assume the best.
|
|
||||||
tk = Tokens.TokenKind.STRINGLITERAL;
|
|
||||||
// Track the end of first line for error recovery.
|
// Track the end of first line for error recovery.
|
||||||
int firstEOLN = NOT_FOUND;
|
int firstEOLN = NOT_FOUND;
|
||||||
|
tk = TokenKind.STRINGLITERAL;
|
||||||
|
|
||||||
// Check for text block delimiter.
|
// Check for text block delimiter.
|
||||||
isTextBlock = accept("\"\"\"");
|
isTextBlock = accept("\"\"\"");
|
||||||
|
|
||||||
@ -409,7 +525,13 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
|
|
||||||
// While characters are available.
|
// While characters are available.
|
||||||
while (isAvailable()) {
|
while (isAvailable()) {
|
||||||
if (accept("\"\"\"")) {
|
if (hasStringTemplateErrors) {
|
||||||
|
break;
|
||||||
|
} else if (accept("\"\"\"")) {
|
||||||
|
if (isStringTemplate && tk == TokenKind.STRINGLITERAL) {
|
||||||
|
tk = TokenKind.STRINGFRAGMENT;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,7 +555,12 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
|
|
||||||
// While characters are available.
|
// While characters are available.
|
||||||
while (isAvailable()) {
|
while (isAvailable()) {
|
||||||
if (accept('\"')) {
|
if (hasStringTemplateErrors) {
|
||||||
|
break;
|
||||||
|
} else if (accept('\"')) {
|
||||||
|
if (isStringTemplate && tk == TokenKind.STRINGLITERAL) {
|
||||||
|
tk = TokenKind.STRINGFRAGMENT;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,10 +575,18 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String ended without close delimiter sequence.
|
// String ended without close delimiter sequence or has embedded expression errors.
|
||||||
lexError(pos, isTextBlock ? Errors.UnclosedTextBlock : Errors.UnclosedStrLit);
|
if (isStringTemplate) {
|
||||||
|
lexError(pos, isTextBlock ? Errors.TextBlockTemplateIsNotWellFormed
|
||||||
|
: Errors.StringTemplateIsNotWellFormed);
|
||||||
|
fragmentRanges = List.nil();
|
||||||
|
pendingTokens = List.nil();
|
||||||
|
} else {
|
||||||
|
lexError(pos, isTextBlock ? Errors.UnclosedTextBlock
|
||||||
|
: Errors.UnclosedStrLit);
|
||||||
|
}
|
||||||
|
|
||||||
if (firstEOLN != NOT_FOUND) {
|
if (!hasStringTemplateErrors && firstEOLN != NOT_FOUND) {
|
||||||
// Reset recovery position to point after text block open delimiter sequence.
|
// Reset recovery position to point after text block open delimiter sequence.
|
||||||
reset(firstEOLN);
|
reset(firstEOLN);
|
||||||
}
|
}
|
||||||
@ -772,11 +907,20 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
* Read token (main entrypoint.)
|
* Read token (main entrypoint.)
|
||||||
*/
|
*/
|
||||||
public Token readToken() {
|
public Token readToken() {
|
||||||
|
if (pendingTokens.nonEmpty()) {
|
||||||
|
Token token = pendingTokens.head;
|
||||||
|
pendingTokens = pendingTokens.tail;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
sb.setLength(0);
|
sb.setLength(0);
|
||||||
name = null;
|
name = null;
|
||||||
radix = 0;
|
radix = 0;
|
||||||
isTextBlock = false;
|
isTextBlock = false;
|
||||||
hasEscapeSequences = false;
|
hasEscapeSequences = false;
|
||||||
|
isStringTemplate = false;
|
||||||
|
hasStringTemplateErrors = false;
|
||||||
|
fragmentRanges = List.nil();
|
||||||
|
|
||||||
int pos;
|
int pos;
|
||||||
List<Comment> comments = null;
|
List<Comment> comments = null;
|
||||||
@ -971,6 +1115,7 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
lexError(pos, Errors.IllegalLineEndInCharLit);
|
lexError(pos, Errors.IllegalLineEndInCharLit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int errorPos = position();
|
||||||
scanLitChar(pos);
|
scanLitChar(pos);
|
||||||
|
|
||||||
if (accept('\'')) {
|
if (accept('\'')) {
|
||||||
@ -980,7 +1125,6 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break loop;
|
break loop;
|
||||||
|
|
||||||
case '\"': // (Spec. 3.10)
|
case '\"': // (Spec. 3.10)
|
||||||
scanString(pos);
|
scanString(pos);
|
||||||
break loop;
|
break loop;
|
||||||
@ -1017,8 +1161,8 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
arg = String.format("\\u%04x\\u%04x", (int) hi, (int) lo);
|
arg = String.format("\\u%04x\\u%04x", (int) hi, (int) lo);
|
||||||
} else {
|
} else {
|
||||||
char ch = get();
|
char ch = get();
|
||||||
arg = (32 < ch && ch < 127) ? String.format("%s", ch) :
|
arg = (32 < ch && ch < 127) ? String.valueOf(ch) :
|
||||||
String.format("\\u%04x", (int) ch);
|
"\\u%04x".formatted((int) ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
lexError(pos, Errors.IllegalChar(arg));
|
lexError(pos, Errors.IllegalChar(arg));
|
||||||
@ -1031,6 +1175,11 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
|
|
||||||
int endPos = position();
|
int endPos = position();
|
||||||
|
|
||||||
|
// Track end of final fragment.
|
||||||
|
if (isStringTemplate) {
|
||||||
|
fragmentRanges = fragmentRanges.append(endPos);
|
||||||
|
}
|
||||||
|
|
||||||
if (tk.tag == Token.Tag.DEFAULT) {
|
if (tk.tag == Token.Tag.DEFAULT) {
|
||||||
return new Token(tk, pos, endPos, comments);
|
return new Token(tk, pos, endPos, comments);
|
||||||
} else if (tk.tag == Token.Tag.NAMED) {
|
} else if (tk.tag == Token.Tag.NAMED) {
|
||||||
@ -1062,6 +1211,11 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isStringTemplate) {
|
||||||
|
// Break string into fragments and then return the first of the framents.
|
||||||
|
return getFragments(string, comments);
|
||||||
|
}
|
||||||
|
|
||||||
// Translate escape sequences if present.
|
// Translate escape sequences if present.
|
||||||
if (hasEscapeSequences) {
|
if (hasEscapeSequences) {
|
||||||
try {
|
try {
|
||||||
@ -1091,6 +1245,66 @@ public class JavaTokenizer extends UnicodeReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the string into a list of pending tokens to precede embedded
|
||||||
|
* expressions.
|
||||||
|
*
|
||||||
|
* @param string string to fragment
|
||||||
|
* @param comments comments for first token
|
||||||
|
*
|
||||||
|
* @return first pending token.
|
||||||
|
*/
|
||||||
|
private Token getFragments(String string, List<Comment> comments) {
|
||||||
|
List<Token> tokens = List.nil();
|
||||||
|
Iterator<Integer> rangeIter = fragmentRanges.iterator();
|
||||||
|
for (String fragment : fragment(string)) {
|
||||||
|
fragment = fragment.translateEscapes();
|
||||||
|
int fragmentPos = rangeIter.next();
|
||||||
|
int fragmentEndPos = rangeIter.next();
|
||||||
|
Token token = new StringToken(TokenKind.STRINGFRAGMENT,
|
||||||
|
fragmentPos, fragmentEndPos, fragment, comments);
|
||||||
|
comments = null;
|
||||||
|
tokens = tokens.append(token);
|
||||||
|
}
|
||||||
|
pendingTokens = tokens.appendList(pendingTokens);
|
||||||
|
Token first = pendingTokens.head;
|
||||||
|
pendingTokens = pendingTokens.tail;
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Break string template up into fragments. "\{}" indicates where
|
||||||
|
* embedded expressions occur.
|
||||||
|
*
|
||||||
|
* @param string string template
|
||||||
|
*
|
||||||
|
* @return list of fragment strings
|
||||||
|
*/
|
||||||
|
List<String> fragment(String string) {
|
||||||
|
List<String> fragments = List.nil();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int length = string.length();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
char ch = string.charAt(i);
|
||||||
|
if (ch != '\\') {
|
||||||
|
sb.append(ch);
|
||||||
|
} else if (i + 2 < length && string.charAt(i + 1) == '{'
|
||||||
|
&& string.charAt(i + 2) == '}') {
|
||||||
|
fragments = fragments.append(sb.toString());
|
||||||
|
sb.setLength(0);
|
||||||
|
i += 2;
|
||||||
|
} else if (i + 1 < length){
|
||||||
|
sb.append('\\');
|
||||||
|
sb.append(string.charAt(i + 1));
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
// Error already reported.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragments = fragments.append(sb.toString());
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Appends a comment to the list of comments preceding the current token.
|
* Appends a comment to the list of comments preceding the current token.
|
||||||
*
|
*
|
||||||
|
@ -651,6 +651,59 @@ public class JavacParser implements Parser {
|
|||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StringTemplate =
|
||||||
|
* [STRINGFRAGMENT] [EmbeddedExpression]
|
||||||
|
* | STRINGLITERAL
|
||||||
|
*
|
||||||
|
* EmbeddedExpression =
|
||||||
|
* LBRACE term RBRACE
|
||||||
|
*/
|
||||||
|
JCExpression stringTemplate(JCExpression processor) {
|
||||||
|
checkSourceLevel(Feature.STRING_TEMPLATES);
|
||||||
|
// Disable standalone string templates
|
||||||
|
if (processor == null) {
|
||||||
|
log.error(DiagnosticFlag.SYNTAX, token.pos,
|
||||||
|
Errors.ProcessorMissingFromStringTemplateExpression);
|
||||||
|
}
|
||||||
|
int oldmode = mode;
|
||||||
|
selectExprMode();
|
||||||
|
Token stringToken = token;
|
||||||
|
int pos = stringToken.pos;
|
||||||
|
int endPos = stringToken.endPos;
|
||||||
|
TokenKind kind = stringToken.kind;
|
||||||
|
String string = token.stringVal();
|
||||||
|
List<String> fragments = List.of(string);
|
||||||
|
List<JCExpression> expressions = List.nil();
|
||||||
|
nextToken();
|
||||||
|
if (kind != STRINGLITERAL) {
|
||||||
|
while (token.kind == STRINGFRAGMENT) {
|
||||||
|
stringToken = token;
|
||||||
|
endPos = stringToken.endPos;
|
||||||
|
string = stringToken.stringVal();
|
||||||
|
fragments = fragments.append(string);
|
||||||
|
nextToken();
|
||||||
|
}
|
||||||
|
while (token.pos < endPos && token.kind != DEFAULT && token.kind != ERROR) {
|
||||||
|
accept(LBRACE);
|
||||||
|
JCExpression expression = token.kind == RBRACE ? F.at(pos).Literal(TypeTag.BOT, null)
|
||||||
|
: term(EXPR);
|
||||||
|
expressions = expressions.append(expression);
|
||||||
|
if (token.kind != ERROR) {
|
||||||
|
accept(RBRACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// clean up remaining expression tokens if error
|
||||||
|
while (token.pos < endPos && token.kind != DEFAULT) {
|
||||||
|
nextToken();
|
||||||
|
}
|
||||||
|
S.setPrevToken(stringToken);
|
||||||
|
}
|
||||||
|
JCExpression t = toP(F.at(pos).StringTemplate(processor, fragments, expressions));
|
||||||
|
setMode(oldmode);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
JCExpression literal(Name prefix) {
|
JCExpression literal(Name prefix) {
|
||||||
return literal(prefix, token.pos);
|
return literal(prefix, token.pos);
|
||||||
}
|
}
|
||||||
@ -1279,6 +1332,14 @@ public class JavacParser implements Parser {
|
|||||||
t = literal(names.empty);
|
t = literal(names.empty);
|
||||||
} else return illegal();
|
} else return illegal();
|
||||||
break;
|
break;
|
||||||
|
case STRINGFRAGMENT:
|
||||||
|
if (typeArgs == null && isMode(EXPR)) {
|
||||||
|
selectExprMode();
|
||||||
|
t = stringTemplate(null);
|
||||||
|
} else {
|
||||||
|
return illegal();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case NEW:
|
case NEW:
|
||||||
if (typeArgs != null) return illegal();
|
if (typeArgs != null) return illegal();
|
||||||
if (isMode(EXPR)) {
|
if (isMode(EXPR)) {
|
||||||
@ -1409,6 +1470,12 @@ public class JavacParser implements Parser {
|
|||||||
t = innerCreator(pos1, typeArgs, t);
|
t = innerCreator(pos1, typeArgs, t);
|
||||||
typeArgs = null;
|
typeArgs = null;
|
||||||
break loop;
|
break loop;
|
||||||
|
case STRINGFRAGMENT:
|
||||||
|
case STRINGLITERAL:
|
||||||
|
if (typeArgs != null) return illegal();
|
||||||
|
t = stringTemplate(t);
|
||||||
|
typeArgs = null;
|
||||||
|
break loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1631,6 +1698,12 @@ public class JavacParser implements Parser {
|
|||||||
if (token.kind == LT) typeArgs = typeArguments(false);
|
if (token.kind == LT) typeArgs = typeArguments(false);
|
||||||
t = innerCreator(pos2, typeArgs, t);
|
t = innerCreator(pos2, typeArgs, t);
|
||||||
typeArgs = null;
|
typeArgs = null;
|
||||||
|
} else if (token.kind == TokenKind.STRINGFRAGMENT ||
|
||||||
|
token.kind == TokenKind.STRINGLITERAL) {
|
||||||
|
if (typeArgs != null) {
|
||||||
|
return illegal();
|
||||||
|
}
|
||||||
|
t = stringTemplate(t);
|
||||||
} else {
|
} else {
|
||||||
List<JCAnnotation> tyannos = null;
|
List<JCAnnotation> tyannos = null;
|
||||||
if (isMode(TYPE) && token.kind == MONKEYS_AT) {
|
if (isMode(TYPE) && token.kind == MONKEYS_AT) {
|
||||||
@ -1790,6 +1863,7 @@ public class JavacParser implements Parser {
|
|||||||
case LPAREN: case THIS: case SUPER:
|
case LPAREN: case THIS: case SUPER:
|
||||||
case INTLITERAL: case LONGLITERAL: case FLOATLITERAL:
|
case INTLITERAL: case LONGLITERAL: case FLOATLITERAL:
|
||||||
case DOUBLELITERAL: case CHARLITERAL: case STRINGLITERAL:
|
case DOUBLELITERAL: case CHARLITERAL: case STRINGLITERAL:
|
||||||
|
case STRINGFRAGMENT:
|
||||||
case TRUE: case FALSE: case NULL:
|
case TRUE: case FALSE: case NULL:
|
||||||
case NEW: case IDENTIFIER: case ASSERT: case ENUM: case UNDERSCORE:
|
case NEW: case IDENTIFIER: case ASSERT: case ENUM: case UNDERSCORE:
|
||||||
case SWITCH:
|
case SWITCH:
|
||||||
@ -2707,6 +2781,7 @@ public class JavacParser implements Parser {
|
|||||||
boolean isYieldStatement;
|
boolean isYieldStatement;
|
||||||
switch (next.kind) {
|
switch (next.kind) {
|
||||||
case PLUS: case SUB: case STRINGLITERAL: case CHARLITERAL:
|
case PLUS: case SUB: case STRINGLITERAL: case CHARLITERAL:
|
||||||
|
case STRINGFRAGMENT:
|
||||||
case INTLITERAL: case LONGLITERAL: case FLOATLITERAL: case DOUBLELITERAL:
|
case INTLITERAL: case LONGLITERAL: case FLOATLITERAL: case DOUBLELITERAL:
|
||||||
case NULL: case IDENTIFIER: case TRUE: case FALSE:
|
case NULL: case IDENTIFIER: case TRUE: case FALSE:
|
||||||
case NEW: case SWITCH: case THIS: case SUPER:
|
case NEW: case SWITCH: case THIS: case SUPER:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -59,6 +59,11 @@ public interface Lexer {
|
|||||||
*/
|
*/
|
||||||
Token prevToken();
|
Token prevToken();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the previous token.
|
||||||
|
*/
|
||||||
|
void setPrevToken(Token prevToken);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Splits the current token in two and return the first (split) token.
|
* Splits the current token in two and return the first (split) token.
|
||||||
* For instance {@literal '<<<'} is split into two tokens
|
* For instance {@literal '<<<'} is split into two tokens
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -30,8 +30,6 @@ import java.util.List;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import com.sun.tools.javac.util.Position.LineMap;
|
import com.sun.tools.javac.util.Position.LineMap;
|
||||||
import com.sun.tools.javac.parser.JavaTokenizer.*;
|
|
||||||
|
|
||||||
import static com.sun.tools.javac.parser.Tokens.*;
|
import static com.sun.tools.javac.parser.Tokens.*;
|
||||||
|
|
||||||
/** The lexical analyzer maps an input stream consisting of
|
/** The lexical analyzer maps an input stream consisting of
|
||||||
@ -44,7 +42,7 @@ import static com.sun.tools.javac.parser.Tokens.*;
|
|||||||
*/
|
*/
|
||||||
public class Scanner implements Lexer {
|
public class Scanner implements Lexer {
|
||||||
|
|
||||||
private Tokens tokens;
|
private final Tokens tokens;
|
||||||
|
|
||||||
/** The token, set by nextToken().
|
/** The token, set by nextToken().
|
||||||
*/
|
*/
|
||||||
@ -56,9 +54,9 @@ public class Scanner implements Lexer {
|
|||||||
|
|
||||||
/** Buffer of saved tokens (used during lookahead)
|
/** Buffer of saved tokens (used during lookahead)
|
||||||
*/
|
*/
|
||||||
private List<Token> savedTokens = new ArrayList<>();
|
private final List<Token> savedTokens = new ArrayList<>();
|
||||||
|
|
||||||
private JavaTokenizer tokenizer;
|
private final JavaTokenizer tokenizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a scanner from the input array. This method might
|
* Create a scanner from the input array. This method might
|
||||||
@ -98,7 +96,7 @@ public class Scanner implements Lexer {
|
|||||||
}
|
}
|
||||||
//where
|
//where
|
||||||
private void ensureLookahead(int lookahead) {
|
private void ensureLookahead(int lookahead) {
|
||||||
for (int i = savedTokens.size() ; i < lookahead ; i ++) {
|
for (int i = savedTokens.size() ; i < lookahead ; i++) {
|
||||||
savedTokens.add(tokenizer.readToken());
|
savedTokens.add(tokenizer.readToken());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,6 +105,10 @@ public class Scanner implements Lexer {
|
|||||||
return prevToken;
|
return prevToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPrevToken(Token prevToken) {
|
||||||
|
this.prevToken = prevToken;
|
||||||
|
}
|
||||||
|
|
||||||
public void nextToken() {
|
public void nextToken() {
|
||||||
prevToken = token;
|
prevToken = token;
|
||||||
if (!savedTokens.isEmpty()) {
|
if (!savedTokens.isEmpty()) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -152,6 +152,7 @@ public class Tokens {
|
|||||||
DOUBLELITERAL(Tag.NUMERIC),
|
DOUBLELITERAL(Tag.NUMERIC),
|
||||||
CHARLITERAL(Tag.NUMERIC),
|
CHARLITERAL(Tag.NUMERIC),
|
||||||
STRINGLITERAL(Tag.STRING),
|
STRINGLITERAL(Tag.STRING),
|
||||||
|
STRINGFRAGMENT(Tag.STRING),
|
||||||
TRUE("true", Tag.NAMED),
|
TRUE("true", Tag.NAMED),
|
||||||
FALSE("false", Tag.NAMED),
|
FALSE("false", Tag.NAMED),
|
||||||
NULL("null", Tag.NAMED),
|
NULL("null", Tag.NAMED),
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -147,6 +147,15 @@ public class UnicodeReader {
|
|||||||
nextCodePoint();
|
nextCodePoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character buffer.
|
||||||
|
*
|
||||||
|
* @return character buffer.
|
||||||
|
*/
|
||||||
|
protected char[] buffer() {
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the length of the buffer. This is length of meaningful content in buffer and
|
* Returns the length of the buffer. This is length of meaningful content in buffer and
|
||||||
* not the length of the buffer array.
|
* not the length of the buffer array.
|
||||||
@ -410,6 +419,9 @@ public class UnicodeReader {
|
|||||||
protected boolean isOneOf(char ch1, char ch2, char ch3) {
|
protected boolean isOneOf(char ch1, char ch2, char ch3) {
|
||||||
return is(ch1) || is(ch2) || is(ch3);
|
return is(ch1) || is(ch2) || is(ch3);
|
||||||
}
|
}
|
||||||
|
protected boolean isOneOf(char ch1, char ch2, char ch3, char ch4) {
|
||||||
|
return is(ch1) || is(ch2) || is(ch3) || is(ch4);
|
||||||
|
}
|
||||||
protected boolean isOneOf(char ch1, char ch2, char ch3, char ch4, char ch5, char ch6) {
|
protected boolean isOneOf(char ch1, char ch2, char ch3, char ch4, char ch5, char ch6) {
|
||||||
return is(ch1) || is(ch2) || is(ch3) || is(ch4) || is(ch5) || is(ch6);
|
return is(ch1) || is(ch2) || is(ch3) || is(ch4) || is(ch5) || is(ch6);
|
||||||
}
|
}
|
||||||
|
@ -1322,6 +1322,23 @@ compiler.err.unclosed.str.lit=\
|
|||||||
compiler.err.unclosed.text.block=\
|
compiler.err.unclosed.text.block=\
|
||||||
unclosed text block
|
unclosed text block
|
||||||
|
|
||||||
|
compiler.err.string.template.is.not.well.formed=\
|
||||||
|
string template is not well formed
|
||||||
|
|
||||||
|
compiler.err.text.block.template.is.not.well.formed=\
|
||||||
|
text block template is not well formed
|
||||||
|
|
||||||
|
compiler.err.processor.missing.from.string.template.expression=\
|
||||||
|
processor missing from string template expression
|
||||||
|
|
||||||
|
# 0: symbol
|
||||||
|
compiler.err.processor.type.cannot.be.a.raw.type=\
|
||||||
|
processor type cannot be a raw type: {0}
|
||||||
|
|
||||||
|
# 0: symbol
|
||||||
|
compiler.err.not.a.processor.type=\
|
||||||
|
not a processor type: {0}
|
||||||
|
|
||||||
# 0: string
|
# 0: string
|
||||||
compiler.err.unsupported.encoding=\
|
compiler.err.unsupported.encoding=\
|
||||||
unsupported encoding: {0}
|
unsupported encoding: {0}
|
||||||
@ -3127,6 +3144,9 @@ compiler.misc.feature.case.null=\
|
|||||||
compiler.misc.feature.pattern.switch=\
|
compiler.misc.feature.pattern.switch=\
|
||||||
patterns in switch statements
|
patterns in switch statements
|
||||||
|
|
||||||
|
compiler.misc.feature.string.templates=\
|
||||||
|
string templates
|
||||||
|
|
||||||
compiler.misc.feature.unconditional.patterns.in.instanceof=\
|
compiler.misc.feature.unconditional.patterns.in.instanceof=\
|
||||||
unconditional patterns in instanceof
|
unconditional patterns in instanceof
|
||||||
|
|
||||||
|
@ -269,6 +269,10 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
|
|||||||
*/
|
*/
|
||||||
LITERAL,
|
LITERAL,
|
||||||
|
|
||||||
|
/** String template expression.
|
||||||
|
*/
|
||||||
|
STRING_TEMPLATE,
|
||||||
|
|
||||||
/** Basic type identifiers, of type TypeIdent.
|
/** Basic type identifiers, of type TypeIdent.
|
||||||
*/
|
*/
|
||||||
TYPEIDENT,
|
TYPEIDENT,
|
||||||
@ -2499,6 +2503,58 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String template expression.
|
||||||
|
*/
|
||||||
|
public static class JCStringTemplate extends JCExpression implements StringTemplateTree {
|
||||||
|
public JCExpression processor;
|
||||||
|
public List<String> fragments;
|
||||||
|
public List<JCExpression> expressions;
|
||||||
|
|
||||||
|
protected JCStringTemplate(JCExpression processor,
|
||||||
|
List<String> fragments,
|
||||||
|
List<JCExpression> expressions) {
|
||||||
|
this.processor = processor;
|
||||||
|
this.fragments = fragments;
|
||||||
|
this.expressions = expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExpressionTree getProcessor() {
|
||||||
|
return processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getFragments() {
|
||||||
|
return fragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<? extends ExpressionTree> getExpressions() {
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||||
|
public Kind getKind() {
|
||||||
|
return Kind.TEMPLATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||||
|
public Tag getTag() {
|
||||||
|
return STRING_TEMPLATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||||
|
public void accept(Visitor v) {
|
||||||
|
v.visitStringTemplate(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||||
|
public <R, D> R accept(TreeVisitor<R, D> v, D d) {
|
||||||
|
return v.visitStringTemplate(this, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array selection
|
* An array selection
|
||||||
*/
|
*/
|
||||||
@ -3478,6 +3534,9 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
|
|||||||
JCFieldAccess Select(JCExpression selected, Name selector);
|
JCFieldAccess Select(JCExpression selected, Name selector);
|
||||||
JCIdent Ident(Name idname);
|
JCIdent Ident(Name idname);
|
||||||
JCLiteral Literal(TypeTag tag, Object value);
|
JCLiteral Literal(TypeTag tag, Object value);
|
||||||
|
JCStringTemplate StringTemplate(JCExpression processor,
|
||||||
|
List<String> fragments,
|
||||||
|
List<JCExpression> expressions);
|
||||||
JCPrimitiveTypeTree TypeIdent(TypeTag typetag);
|
JCPrimitiveTypeTree TypeIdent(TypeTag typetag);
|
||||||
JCArrayTypeTree TypeArray(JCExpression elemtype);
|
JCArrayTypeTree TypeArray(JCExpression elemtype);
|
||||||
JCTypeApply TypeApply(JCExpression clazz, List<JCExpression> arguments);
|
JCTypeApply TypeApply(JCExpression clazz, List<JCExpression> arguments);
|
||||||
@ -3549,6 +3608,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
|
|||||||
public void visitReference(JCMemberReference that) { visitTree(that); }
|
public void visitReference(JCMemberReference that) { visitTree(that); }
|
||||||
public void visitIdent(JCIdent that) { visitTree(that); }
|
public void visitIdent(JCIdent that) { visitTree(that); }
|
||||||
public void visitLiteral(JCLiteral that) { visitTree(that); }
|
public void visitLiteral(JCLiteral that) { visitTree(that); }
|
||||||
|
public void visitStringTemplate(JCStringTemplate that) { visitTree(that); }
|
||||||
public void visitTypeIdent(JCPrimitiveTypeTree that) { visitTree(that); }
|
public void visitTypeIdent(JCPrimitiveTypeTree that) { visitTree(that); }
|
||||||
public void visitTypeArray(JCArrayTypeTree that) { visitTree(that); }
|
public void visitTypeArray(JCArrayTypeTree that) { visitTree(that); }
|
||||||
public void visitTypeApply(JCTypeApply that) { visitTree(that); }
|
public void visitTypeApply(JCTypeApply that) { visitTree(that); }
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
package com.sun.tools.javac.tree;
|
package com.sun.tools.javac.tree;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
|
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
|
||||||
import com.sun.source.tree.ModuleTree.ModuleKind;
|
import com.sun.source.tree.ModuleTree.ModuleKind;
|
||||||
@ -1474,6 +1475,23 @@ public class Pretty extends JCTree.Visitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void visitStringTemplate(JCStringTemplate tree) {
|
||||||
|
try {
|
||||||
|
JCExpression processor = tree.processor;
|
||||||
|
print("[");
|
||||||
|
if (processor != null) {
|
||||||
|
printExpr(processor);
|
||||||
|
}
|
||||||
|
print("]");
|
||||||
|
print("\"" + tree.fragments.stream().collect(Collectors.joining("\\{}")) + "\"");
|
||||||
|
print("(");
|
||||||
|
printExprs(tree.expressions);
|
||||||
|
print(")");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
||||||
try {
|
try {
|
||||||
switch(tree.typetag) {
|
switch(tree.typetag) {
|
||||||
|
@ -282,6 +282,14 @@ public class TreeCopier<P> implements TreeVisitor<JCTree,P> {
|
|||||||
return M.at(t.pos).Literal(t.typetag, t.value);
|
return M.at(t.pos).Literal(t.typetag, t.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DefinedBy(Api.COMPILER_TREE)
|
||||||
|
public JCTree visitStringTemplate(StringTemplateTree node, P p) {
|
||||||
|
JCStringTemplate t = (JCStringTemplate) node;
|
||||||
|
JCExpression processor = copy(t.processor, p);
|
||||||
|
List<JCExpression> expressions = copy(t.expressions, p);
|
||||||
|
return M.at(t.pos).StringTemplate(processor, t.fragments, expressions);
|
||||||
|
}
|
||||||
|
|
||||||
@DefinedBy(Api.COMPILER_TREE)
|
@DefinedBy(Api.COMPILER_TREE)
|
||||||
public JCTree visitMethod(MethodTree node, P p) {
|
public JCTree visitMethod(MethodTree node, P p) {
|
||||||
JCMethodDecl t = (JCMethodDecl) node;
|
JCMethodDecl t = (JCMethodDecl) node;
|
||||||
|
@ -330,6 +330,7 @@ public class TreeInfo {
|
|||||||
case PLUS_ASG: case MINUS_ASG:
|
case PLUS_ASG: case MINUS_ASG:
|
||||||
case MUL_ASG: case DIV_ASG: case MOD_ASG:
|
case MUL_ASG: case DIV_ASG: case MOD_ASG:
|
||||||
case APPLY: case NEWCLASS:
|
case APPLY: case NEWCLASS:
|
||||||
|
case STRING_TEMPLATE:
|
||||||
case ERRONEOUS:
|
case ERRONEOUS:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
@ -545,6 +546,14 @@ public class TreeInfo {
|
|||||||
JCBindingPattern node = (JCBindingPattern)tree;
|
JCBindingPattern node = (JCBindingPattern)tree;
|
||||||
return getStartPos(node.var);
|
return getStartPos(node.var);
|
||||||
}
|
}
|
||||||
|
case STRING_TEMPLATE: {
|
||||||
|
JCStringTemplate node = (JCStringTemplate) tree;
|
||||||
|
if (node.processor == null) {
|
||||||
|
return node.pos;
|
||||||
|
} else {
|
||||||
|
return getStartPos(node.processor);
|
||||||
|
}
|
||||||
|
}
|
||||||
case ERRONEOUS: {
|
case ERRONEOUS: {
|
||||||
JCErroneous node = (JCErroneous)tree;
|
JCErroneous node = (JCErroneous)tree;
|
||||||
if (node.errs != null && node.errs.nonEmpty()) {
|
if (node.errs != null && node.errs.nonEmpty()) {
|
||||||
@ -973,6 +982,8 @@ public class TreeInfo {
|
|||||||
return symbol(((JCAnnotatedType) tree).underlyingType);
|
return symbol(((JCAnnotatedType) tree).underlyingType);
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
return ((JCMemberReference) tree).sym;
|
return ((JCMemberReference) tree).sym;
|
||||||
|
case CLASSDEF:
|
||||||
|
return ((JCClassDecl) tree).sym;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -550,6 +550,14 @@ public class TreeMaker implements JCTree.Factory {
|
|||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JCStringTemplate StringTemplate(JCExpression processor,
|
||||||
|
List<String> fragments,
|
||||||
|
List<JCExpression> expressions) {
|
||||||
|
JCStringTemplate tree = new JCStringTemplate(processor, fragments, expressions);
|
||||||
|
tree.pos = pos;
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
public JCPrimitiveTypeTree TypeIdent(TypeTag typetag) {
|
public JCPrimitiveTypeTree TypeIdent(TypeTag typetag) {
|
||||||
JCPrimitiveTypeTree tree = new JCPrimitiveTypeTree(typetag);
|
JCPrimitiveTypeTree tree = new JCPrimitiveTypeTree(typetag);
|
||||||
tree.pos = pos;
|
tree.pos = pos;
|
||||||
|
@ -353,6 +353,11 @@ public class TreeScanner extends Visitor {
|
|||||||
public void visitLiteral(JCLiteral tree) {
|
public void visitLiteral(JCLiteral tree) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void visitStringTemplate(JCStringTemplate tree) {
|
||||||
|
scan(tree.processor);
|
||||||
|
scan(tree.expressions);
|
||||||
|
}
|
||||||
|
|
||||||
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,6 +411,13 @@ public class TreeTranslator extends JCTree.Visitor {
|
|||||||
result = tree;
|
result = tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void visitStringTemplate(JCStringTemplate tree) {
|
||||||
|
tree.processor = translate(tree.processor);
|
||||||
|
tree.expressions = translate(tree.expressions);
|
||||||
|
|
||||||
|
result = tree;
|
||||||
|
}
|
||||||
|
|
||||||
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
public void visitTypeIdent(JCPrimitiveTypeTree tree) {
|
||||||
result = tree;
|
result = tree;
|
||||||
}
|
}
|
||||||
|
@ -222,6 +222,14 @@ public class Names {
|
|||||||
public final Name typeSwitch;
|
public final Name typeSwitch;
|
||||||
public final Name enumSwitch;
|
public final Name enumSwitch;
|
||||||
|
|
||||||
|
// templated string
|
||||||
|
public final Name process;
|
||||||
|
public final Name STR;
|
||||||
|
public final Name RAW;
|
||||||
|
public final Name newStringTemplate;
|
||||||
|
public final Name newLargeStringTemplate;
|
||||||
|
public final Name processStringTemplate;
|
||||||
|
|
||||||
public final Name.Table table;
|
public final Name.Table table;
|
||||||
|
|
||||||
@SuppressWarnings("this-escape")
|
@SuppressWarnings("this-escape")
|
||||||
@ -396,6 +404,14 @@ public class Names {
|
|||||||
permits = fromString("permits");
|
permits = fromString("permits");
|
||||||
sealed = fromString("sealed");
|
sealed = fromString("sealed");
|
||||||
|
|
||||||
|
// templated string
|
||||||
|
process = fromString("process");
|
||||||
|
STR = fromString("STR");
|
||||||
|
RAW = fromString("RAW");
|
||||||
|
newStringTemplate = fromString("newStringTemplate");
|
||||||
|
newLargeStringTemplate = fromString("newLargeStringTemplate");
|
||||||
|
processStringTemplate = fromString("processStringTemplate");
|
||||||
|
|
||||||
// pattern switches
|
// pattern switches
|
||||||
typeSwitch = fromString("typeSwitch");
|
typeSwitch = fromString("typeSwitch");
|
||||||
enumSwitch = fromString("enumSwitch");
|
enumSwitch = fromString("enumSwitch");
|
||||||
|
@ -272,6 +272,7 @@ class CompletenessAnalyzer {
|
|||||||
DOUBLELITERAL(TokenKind.DOUBLELITERAL, XEXPR1|XTERM), //
|
DOUBLELITERAL(TokenKind.DOUBLELITERAL, XEXPR1|XTERM), //
|
||||||
CHARLITERAL(TokenKind.CHARLITERAL, XEXPR1|XTERM), //
|
CHARLITERAL(TokenKind.CHARLITERAL, XEXPR1|XTERM), //
|
||||||
STRINGLITERAL(TokenKind.STRINGLITERAL, XEXPR1|XTERM), //
|
STRINGLITERAL(TokenKind.STRINGLITERAL, XEXPR1|XTERM), //
|
||||||
|
STRINGFRAGMENT(TokenKind.STRINGFRAGMENT, XEXPR1|XTERM),
|
||||||
TRUE(TokenKind.TRUE, XEXPR1|XTERM), // true
|
TRUE(TokenKind.TRUE, XEXPR1|XTERM), // true
|
||||||
FALSE(TokenKind.FALSE, XEXPR1|XTERM), // false
|
FALSE(TokenKind.FALSE, XEXPR1|XTERM), // false
|
||||||
NULL(TokenKind.NULL, XEXPR1|XTERM), // null
|
NULL(TokenKind.NULL, XEXPR1|XTERM), // null
|
||||||
@ -479,8 +480,16 @@ class CompletenessAnalyzer {
|
|||||||
|
|
||||||
private Token advance() {
|
private Token advance() {
|
||||||
Token prev = current;
|
Token prev = current;
|
||||||
scanner.nextToken();
|
if (current != null && current.kind == TokenKind.STRINGFRAGMENT) {
|
||||||
current = scanner.token();
|
int endPos = current.endPos;
|
||||||
|
do {
|
||||||
|
scanner.nextToken();
|
||||||
|
current = scanner.token();
|
||||||
|
} while (current != null && current.endPos <= endPos && current.kind != TokenKind.EOF);
|
||||||
|
} else {
|
||||||
|
scanner.nextToken();
|
||||||
|
current = scanner.token();
|
||||||
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
139
test/jdk/java/lang/String/concat/MakeConcatWithTemplate.java
Normal file
139
test/jdk/java/lang/String/concat/MakeConcatWithTemplate.java
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.StringConcatFactory;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @summary Test StringConcatFactory.makeConcatWithTemplate... methods.
|
||||||
|
* @enablePreview true
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MakeConcatWithTemplate {
|
||||||
|
public static void main(String... args) {
|
||||||
|
makeConcatWithTemplate();
|
||||||
|
makeConcatWithTemplateCluster();
|
||||||
|
makeConcatWithTemplateGetters();
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<String> fragments(int n) {
|
||||||
|
String[] array = new String[n];
|
||||||
|
Arrays.fill(array, "abc");
|
||||||
|
return Arrays.asList(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Class<?>> types(int n) {
|
||||||
|
Class<?>[] array = new Class<?>[n];
|
||||||
|
Arrays.fill(array, int.class);
|
||||||
|
return Arrays.asList(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Integer> values(int n) {
|
||||||
|
Integer[] array = new Integer[n];
|
||||||
|
Arrays.fill(array, 123);
|
||||||
|
return Arrays.asList(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<MethodHandle> getters(int n) {
|
||||||
|
MethodHandle[] array = new MethodHandle[n];
|
||||||
|
MethodHandle m = MethodHandles.dropArguments(MethodHandles.constant(int.class, 123), 0, Object.class);
|
||||||
|
Arrays.fill(array, m);
|
||||||
|
return Arrays.asList(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void makeConcatWithTemplate() {
|
||||||
|
try {
|
||||||
|
int n = StringConcatFactory.MAX_INDY_CONCAT_ARG_SLOTS - 1;
|
||||||
|
MethodHandle m = StringConcatFactory.makeConcatWithTemplate(fragments(n + 1), types(n));
|
||||||
|
m.invokeWithArguments(values(n));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int n = StringConcatFactory.MAX_INDY_CONCAT_ARG_SLOTS;
|
||||||
|
MethodHandle m = StringConcatFactory.makeConcatWithTemplate(fragments(n + 1), types(n));
|
||||||
|
m.invokeWithArguments(values(n));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean threw = false;
|
||||||
|
try {
|
||||||
|
int n = StringConcatFactory.MAX_INDY_CONCAT_ARG_SLOTS + 1;
|
||||||
|
MethodHandle m = StringConcatFactory.makeConcatWithTemplate(fragments(n + 1), types(n));
|
||||||
|
m.invokeWithArguments(values(n));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!threw) {
|
||||||
|
throw new RuntimeException("Exception expected - makeConcatWithTemplate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void makeConcatWithTemplateCluster() {
|
||||||
|
int n = StringConcatFactory.MAX_INDY_CONCAT_ARG_SLOTS;
|
||||||
|
int c = 3;
|
||||||
|
try {
|
||||||
|
List<MethodHandle> ms = StringConcatFactory.makeConcatWithTemplateCluster(fragments(c * n + 1), types(c * n), n);
|
||||||
|
MethodHandle m0 = ms.get(0);
|
||||||
|
MethodHandle m1 = ms.get(1);
|
||||||
|
MethodHandle m2 = ms.get(2);
|
||||||
|
MethodHandle m3 = ms.get(3);
|
||||||
|
|
||||||
|
String s = (String)m0.invokeWithArguments(values(n));
|
||||||
|
List<Object> args = new ArrayList<>();
|
||||||
|
args.add(s);
|
||||||
|
args.addAll(values(n - 1)); // one less for carry over string
|
||||||
|
s = (String)m1.invokeWithArguments(args);
|
||||||
|
args.clear();
|
||||||
|
args.add(s);
|
||||||
|
args.addAll(values(n - 1)); // one less for carry over string
|
||||||
|
s = (String)m2.invokeWithArguments(args);
|
||||||
|
args.clear();
|
||||||
|
args.add(s);
|
||||||
|
args.addAll(values(2)); // two remaining carry overs
|
||||||
|
s = (String)m3.invokeWithArguments(args);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void makeConcatWithTemplateGetters() {
|
||||||
|
int n = StringConcatFactory.MAX_INDY_CONCAT_ARG_SLOTS;
|
||||||
|
int c = 3;
|
||||||
|
try {
|
||||||
|
MethodHandle m = StringConcatFactory.makeConcatWithTemplateGetters(fragments(c * n + 1), getters(c * n), n);
|
||||||
|
String s = (String)m.invoke(null);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
166
test/jdk/java/lang/runtime/CarriersTest.java
Normal file
166
test/jdk/java/lang/runtime/CarriersTest.java
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Test features provided by the Carriers class.
|
||||||
|
* @modules java.base/java.lang.runtime
|
||||||
|
* @enablePreview true
|
||||||
|
* @compile --patch-module java.base=${test.src} CarriersTest.java
|
||||||
|
* @run main/othervm --patch-module java.base=${test.class.path} java.lang.runtime.CarriersTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.lang.runtime;
|
||||||
|
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CarriersTest {
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
primitivesTest();
|
||||||
|
primitivesTestLarge();
|
||||||
|
limitsTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertTrue(boolean test, String message) {
|
||||||
|
if (!test) {
|
||||||
|
throw new RuntimeException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final int MAX_COMPONENTS = 254;
|
||||||
|
|
||||||
|
static void primitivesTest() throws Throwable {
|
||||||
|
MethodType methodType =
|
||||||
|
MethodType.methodType(Object.class, byte.class, short.class,
|
||||||
|
char.class, int.class, long.class,
|
||||||
|
float.class, double.class,
|
||||||
|
boolean.class, String.class);
|
||||||
|
MethodHandle constructor = Carriers.initializingConstructor(methodType);
|
||||||
|
Object object = (Object)constructor.invokeExact((byte)0xFF, (short)0xFFFF,
|
||||||
|
'C', 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFFL,
|
||||||
|
1.0f / 3.0f, 1.0 / 3.0,
|
||||||
|
true, "abcde");
|
||||||
|
List<MethodHandle> components = Carriers.components(methodType);
|
||||||
|
assertTrue((byte)components.get(0).invokeExact(object) == (byte)0xFF,
|
||||||
|
"primitive byte test failure");
|
||||||
|
assertTrue((short)components.get(1).invokeExact(object) == (short)0xFFFF,
|
||||||
|
"primitive short test failure");
|
||||||
|
assertTrue((char)components.get(2).invokeExact(object) == 'C',
|
||||||
|
"primitive char test failure");
|
||||||
|
assertTrue((int)components.get(3).invokeExact(object) == 0xFFFFFFFF,
|
||||||
|
"primitive int test failure");
|
||||||
|
assertTrue((long)components.get(4).invokeExact(object) == 0xFFFFFFFFFFFFFFFFL,
|
||||||
|
"primitive long test failure");
|
||||||
|
assertTrue((float)components.get(5).invokeExact(object) == 1.0f / 3.0f,
|
||||||
|
"primitive float test failure");
|
||||||
|
assertTrue((double)components.get(6).invokeExact(object) == 1.0 / 3.0,
|
||||||
|
"primitive double test failure");
|
||||||
|
assertTrue((boolean)components.get(7).invokeExact(object),
|
||||||
|
"primitive boolean test failure");
|
||||||
|
assertTrue("abcde".equals((String)components.get(8).invokeExact(object)),
|
||||||
|
"primitive String test failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void primitivesTestLarge() throws Throwable {
|
||||||
|
MethodType methodType =
|
||||||
|
MethodType.methodType(Object.class, byte.class, short.class,
|
||||||
|
char.class, int.class, long.class,
|
||||||
|
float.class, double.class,
|
||||||
|
boolean.class, String.class,
|
||||||
|
Object.class, Object.class,Object.class,Object.class,
|
||||||
|
Object.class, Object.class,Object.class,Object.class,
|
||||||
|
Object.class, Object.class,Object.class,Object.class,
|
||||||
|
Object.class, Object.class,Object.class,Object.class,
|
||||||
|
Object.class, Object.class,Object.class,Object.class,
|
||||||
|
Object.class, Object.class,Object.class,Object.class,
|
||||||
|
Object.class, Object.class,Object.class,Object.class
|
||||||
|
);
|
||||||
|
MethodHandle constructor = Carriers.initializingConstructor(methodType);
|
||||||
|
Object object = (Object)constructor.invokeExact((byte)0xFF, (short)0xFFFF,
|
||||||
|
'C', 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFFL,
|
||||||
|
1.0f / 3.0f, 1.0 / 3.0,
|
||||||
|
true, "abcde",
|
||||||
|
(Object)null, (Object)null, (Object)null, (Object)null,
|
||||||
|
(Object)null, (Object)null, (Object)null, (Object)null,
|
||||||
|
(Object)null, (Object)null, (Object)null, (Object)null,
|
||||||
|
(Object)null, (Object)null, (Object)null, (Object)null,
|
||||||
|
(Object)null, (Object)null, (Object)null, (Object)null,
|
||||||
|
(Object)null, (Object)null, (Object)null, (Object)null,
|
||||||
|
(Object)null, (Object)null, (Object)null, (Object)null
|
||||||
|
);
|
||||||
|
List<MethodHandle> components = Carriers.components(methodType);
|
||||||
|
assertTrue((byte)components.get(0).invokeExact(object) == (byte)0xFF,
|
||||||
|
"large primitive byte test failure");
|
||||||
|
assertTrue((short)components.get(1).invokeExact(object) == (short)0xFFFF,
|
||||||
|
"large primitive short test failure");
|
||||||
|
assertTrue((char)components.get(2).invokeExact(object) == 'C',
|
||||||
|
"large primitive char test failure");
|
||||||
|
assertTrue((int)components.get(3).invokeExact(object) == 0xFFFFFFFF,
|
||||||
|
"large primitive int test failure");
|
||||||
|
assertTrue((long)components.get(4).invokeExact(object) == 0xFFFFFFFFFFFFFFFFL,
|
||||||
|
"large primitive long test failure");
|
||||||
|
assertTrue((float)components.get(5).invokeExact(object) == 1.0f / 3.0f,
|
||||||
|
"large primitive float test failure");
|
||||||
|
assertTrue((double)components.get(6).invokeExact(object) == 1.0 / 3.0,
|
||||||
|
"large primitive double test failure");
|
||||||
|
assertTrue((boolean)components.get(7).invokeExact(object),
|
||||||
|
"large primitive boolean test failure");
|
||||||
|
assertTrue("abcde".equals((String)components.get(8).invokeExact(object)),
|
||||||
|
"large primitive String test failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void limitsTest() {
|
||||||
|
boolean passed;
|
||||||
|
|
||||||
|
passed = false;
|
||||||
|
try {
|
||||||
|
Class<?>[] ptypes = new Class<?>[MAX_COMPONENTS + 1];
|
||||||
|
Arrays.fill(ptypes, Object.class);
|
||||||
|
MethodType methodType = MethodType.methodType(Object.class, ptypes);
|
||||||
|
MethodHandle constructor = Carriers.constructor(methodType);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
passed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passed) {
|
||||||
|
throw new RuntimeException("failed to report too many components ");
|
||||||
|
}
|
||||||
|
|
||||||
|
passed = false;
|
||||||
|
try {
|
||||||
|
Class<?>[] ptypes = new Class<?>[MAX_COMPONENTS / 2 + 1];
|
||||||
|
Arrays.fill(ptypes, long.class);
|
||||||
|
MethodType methodType = MethodType.methodType(Object.class, ptypes);
|
||||||
|
MethodHandle constructor = Carriers.constructor(methodType);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
passed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!passed) {
|
||||||
|
throw new RuntimeException("failed to report too many components ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
test/jdk/java/lang/runtime/ReferencedKeyTest.java
Normal file
109
test/jdk/java/lang/runtime/ReferencedKeyTest.java
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Test features provided by the ReferencedKeyMap class.
|
||||||
|
* @modules java.base/java.lang.runtime
|
||||||
|
* @enablePreview
|
||||||
|
* @compile --patch-module java.base=${test.src} ReferencedKeyTest.java
|
||||||
|
* @run main/othervm --patch-module java.base=${test.class.path} java.lang.runtime.ReferencedKeyTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
package java.lang.runtime;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class ReferencedKeyTest {
|
||||||
|
static long BASE_KEY = 10_000_000L;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
mapTest(false, HashMap::new);
|
||||||
|
mapTest(true, HashMap::new);
|
||||||
|
mapTest(false, ConcurrentHashMap::new);
|
||||||
|
mapTest(true, ConcurrentHashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertTrue(boolean test, String message) {
|
||||||
|
if (!test) {
|
||||||
|
throw new RuntimeException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mapTest(boolean isSoft, Supplier<Map<ReferenceKey<Long>, String>> supplier) {
|
||||||
|
Map<Long, String> map = ReferencedKeyMap.create(isSoft, supplier);
|
||||||
|
populate(map);
|
||||||
|
collect();
|
||||||
|
// assertTrue(map.isEmpty() || isSoft, "Weak not collecting");
|
||||||
|
populate(map);
|
||||||
|
methods(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void methods(Map<Long, String> map) {
|
||||||
|
assertTrue(map.size() == 26, "missing key");
|
||||||
|
assertTrue(map.containsKey(BASE_KEY + 'a' -'a'), "missing key");
|
||||||
|
assertTrue(map.get(BASE_KEY + 'b' -'a').equals("b"), "wrong key");
|
||||||
|
assertTrue(map.containsValue("c"), "missing value");
|
||||||
|
map.remove(BASE_KEY + 'd' -'a');
|
||||||
|
assertTrue(map.get(BASE_KEY + 'd' -'a') == null, "not removed");
|
||||||
|
map.putAll(Map.of(1L, "A", 2L, "B"));
|
||||||
|
assertTrue(map.get(2L).equals("B"), "collection not added");
|
||||||
|
assertTrue(map.keySet().contains(1L), "key missing");
|
||||||
|
assertTrue(map.values().contains("A"), "key missing");
|
||||||
|
assertTrue(map.entrySet().contains(Map.entry(1L, "A")), "key missing");
|
||||||
|
map.putIfAbsent(3L, "C");
|
||||||
|
assertTrue(map.get(3L).equals("C"), "key missing");
|
||||||
|
map.putIfAbsent(2L, "D");
|
||||||
|
assertTrue(map.get(2L).equals("B"), "key replaced");
|
||||||
|
map.remove(3L);
|
||||||
|
assertTrue(map.get(3L) == null, "key not removed");
|
||||||
|
map.replace(2L, "D");
|
||||||
|
assertTrue(map.get(2L).equals("D"), "key not replaced");
|
||||||
|
map.replace(2L, "B", "E");
|
||||||
|
assertTrue(map.get(2L).equals("D"), "key replaced");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void collect() {
|
||||||
|
System.gc();
|
||||||
|
sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sleep() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(100L);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populate(Map<Long, String> map) {
|
||||||
|
for (int i = 0; i < 26; i++) {
|
||||||
|
Long key = BASE_KEY + i;
|
||||||
|
String value = String.valueOf((char) ('a' + i));
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
504
test/jdk/java/lang/template/Basic.java
Normal file
504
test/jdk/java/lang/template/Basic.java
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 0000000
|
||||||
|
* @summary Exercise runtime handing of templated strings.
|
||||||
|
* @enablePreview true
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.lang.StringTemplate.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
import static java.lang.StringTemplate.RAW;
|
||||||
|
|
||||||
|
public class Basic {
|
||||||
|
public static void main(String... arg) {
|
||||||
|
equalsHashCode();
|
||||||
|
concatenationTests();
|
||||||
|
componentTests();
|
||||||
|
limitsTests();
|
||||||
|
processorTests();
|
||||||
|
stringTemplateCoverage();
|
||||||
|
simpleProcessorCoverage();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ASSERT(String a, String b) {
|
||||||
|
if (!Objects.equals(a, b)) {
|
||||||
|
System.out.println(a);
|
||||||
|
System.out.println(b);
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ASSERT(Object a, Object b) {
|
||||||
|
if (!Objects.deepEquals(a, b)) {
|
||||||
|
System.out.println(a);
|
||||||
|
System.out.println(b);
|
||||||
|
throw new RuntimeException("Test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* equals and hashCode tests.
|
||||||
|
*/
|
||||||
|
static void equalsHashCode() {
|
||||||
|
int x = 10;
|
||||||
|
int y = 20;
|
||||||
|
int a = 10;
|
||||||
|
int b = 20;
|
||||||
|
|
||||||
|
StringTemplate st0 = RAW."\{x} + \{y} = \{x + y}";
|
||||||
|
StringTemplate st1 = RAW."\{a} + \{b} = \{a + b}";
|
||||||
|
StringTemplate st2 = RAW."\{x} + \{y} = \{x + y}!";
|
||||||
|
x++;
|
||||||
|
StringTemplate st3 = RAW."\{x} + \{y} = \{x + y}";
|
||||||
|
|
||||||
|
if (!st0.equals(st1)) throw new RuntimeException("st0 != st1");
|
||||||
|
if (st0.equals(st2)) throw new RuntimeException("st0 == st2");
|
||||||
|
if (st0.equals(st3)) throw new RuntimeException("st0 == st3");
|
||||||
|
|
||||||
|
if (st0.hashCode() != st1.hashCode()) throw new RuntimeException("st0.hashCode() != st1.hashCode()");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Concatenation tests.
|
||||||
|
*/
|
||||||
|
static void concatenationTests() {
|
||||||
|
int x = 10;
|
||||||
|
int y = 20;
|
||||||
|
|
||||||
|
ASSERT(STR."\{x} \{y}", x + " " + y);
|
||||||
|
ASSERT(STR."\{x + y}", "" + (x + y));
|
||||||
|
ASSERT(STR.process(RAW."\{x} \{y}"), x + " " + y);
|
||||||
|
ASSERT(STR.process(RAW."\{x + y}"), "" + (x + y));
|
||||||
|
ASSERT((RAW."\{x} \{y}").process(STR), x + " " + y);
|
||||||
|
ASSERT((RAW."\{x + y}").process(STR), "" + (x + y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Component tests.
|
||||||
|
*/
|
||||||
|
static void componentTests() {
|
||||||
|
int x = 10;
|
||||||
|
int y = 20;
|
||||||
|
|
||||||
|
StringTemplate st = RAW."\{x} + \{y} = \{x + y}";
|
||||||
|
ASSERT(st.values(), List.of(x, y, x + y));
|
||||||
|
ASSERT(st.fragments(), List.of("", " + ", " = ", ""));
|
||||||
|
ASSERT(st.interpolate(), x + " + " + y + " = " + (x + y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Limits tests.
|
||||||
|
*/
|
||||||
|
static void limitsTests() {
|
||||||
|
int x = 9;
|
||||||
|
|
||||||
|
StringTemplate ts250 = RAW."""
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
""";
|
||||||
|
ASSERT(ts250.values().size(), 250);
|
||||||
|
ASSERT(ts250.interpolate(), """
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999
|
||||||
|
""");
|
||||||
|
|
||||||
|
StringTemplate ts251 = RAW."""
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}
|
||||||
|
""";
|
||||||
|
ASSERT(ts251.values().size(), 251);
|
||||||
|
ASSERT(ts251.interpolate(), """
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9
|
||||||
|
""");
|
||||||
|
|
||||||
|
StringTemplate ts252 = RAW."""
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}
|
||||||
|
""";
|
||||||
|
ASSERT(ts252.values().size(), 252);
|
||||||
|
ASSERT(ts252.interpolate(), """
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 99
|
||||||
|
""");
|
||||||
|
|
||||||
|
StringTemplate ts253 = RAW."""
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}
|
||||||
|
""";
|
||||||
|
ASSERT(ts253.values().size(), 253);
|
||||||
|
ASSERT(ts253.interpolate(), """
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 999
|
||||||
|
""");
|
||||||
|
|
||||||
|
StringTemplate ts254 = RAW."""
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}
|
||||||
|
""";
|
||||||
|
ASSERT(ts254.values().size(), 254);
|
||||||
|
ASSERT(ts254.interpolate(), """
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999
|
||||||
|
""");
|
||||||
|
|
||||||
|
StringTemplate ts255 = RAW."""
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}
|
||||||
|
""";
|
||||||
|
ASSERT(ts255.values().size(), 255);
|
||||||
|
ASSERT(ts255.interpolate(), """
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 99999
|
||||||
|
""");
|
||||||
|
|
||||||
|
StringTemplate ts256 = RAW."""
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x}\{x} \{x}\{x}\{x}\{x}\{x}\{x}
|
||||||
|
""";
|
||||||
|
ASSERT(ts256.values().size(), 256);
|
||||||
|
ASSERT(ts256.interpolate(), """
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 9999999999
|
||||||
|
9999999999 999999
|
||||||
|
""");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Processor tests.
|
||||||
|
*/
|
||||||
|
public static final Processor<StringTemplate, RuntimeException> STRINGIFY = st -> {
|
||||||
|
List<Object> values = st.values()
|
||||||
|
.stream()
|
||||||
|
.map(v -> (Object)String.valueOf(v))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return StringTemplate.of(st.fragments(), values);
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Processor<StringTemplate, RuntimeException> UPPER = st -> {
|
||||||
|
List<String> fragments = st.fragments()
|
||||||
|
.stream()
|
||||||
|
.map(String::toUpperCase)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return StringTemplate.of(fragments, st.values());
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final Processor<String, RuntimeException> CHAIN = st -> {
|
||||||
|
st = STRINGIFY.process(st);
|
||||||
|
st = UPPER.process(st);
|
||||||
|
return STR.process(st);
|
||||||
|
};
|
||||||
|
|
||||||
|
static void processorTests() {
|
||||||
|
String name = "Joan";
|
||||||
|
int age = 25;
|
||||||
|
ASSERT(CHAIN."\{name} is \{age} years old", "Joan IS 25 YEARS OLD");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* StringTemplate coverage
|
||||||
|
*/
|
||||||
|
static void stringTemplateCoverage() {
|
||||||
|
StringTemplate tsNoValues = StringTemplate.of("No Values");
|
||||||
|
|
||||||
|
ASSERT(tsNoValues.values(), List.of());
|
||||||
|
ASSERT(tsNoValues.fragments(), List.of("No Values"));
|
||||||
|
ASSERT(tsNoValues.interpolate(), STR."No Values");
|
||||||
|
|
||||||
|
int x = 10, y = 20;
|
||||||
|
StringTemplate src = RAW."\{x} + \{y} = \{x + y}";
|
||||||
|
StringTemplate tsValues = StringTemplate.of(src.fragments(), src.values());
|
||||||
|
ASSERT(tsValues.fragments(), List.of("", " + ", " = ", ""));
|
||||||
|
ASSERT(tsValues.values(), List.of(x, y, x + y));
|
||||||
|
ASSERT(tsValues.interpolate(), x + " + " + y + " = " + (x + y));
|
||||||
|
ASSERT(StringTemplate.combine(src, src).interpolate(),
|
||||||
|
RAW."\{x} + \{y} = \{x + y}\{x} + \{y} = \{x + y}".interpolate());
|
||||||
|
ASSERT(StringTemplate.combine(src), src);
|
||||||
|
ASSERT(StringTemplate.combine().interpolate(), "");
|
||||||
|
ASSERT(StringTemplate.combine(List.of(src, src)).interpolate(),
|
||||||
|
RAW."\{x} + \{y} = \{x + y}\{x} + \{y} = \{x + y}".interpolate());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SimpleProcessor coverage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static class Processor0 implements Processor<String, IllegalArgumentException> {
|
||||||
|
@Override
|
||||||
|
public String process(StringTemplate stringTemplate) throws IllegalArgumentException {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
Iterator<String> fragmentsIter = stringTemplate.fragments().iterator();
|
||||||
|
|
||||||
|
for (Object value : stringTemplate.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Processor0 processor0 = new Processor0();
|
||||||
|
|
||||||
|
static Processor<String, RuntimeException> processor1 =
|
||||||
|
st -> st.interpolate();
|
||||||
|
|
||||||
|
static Processor<String, RuntimeException> processor2 = st -> st.interpolate();
|
||||||
|
|
||||||
|
static Processor<String, RuntimeException> processor3 = st -> st.interpolate();
|
||||||
|
|
||||||
|
static Processor<String, RuntimeException> processor4 = st ->
|
||||||
|
StringTemplate.interpolate(st.fragments(), st.values());
|
||||||
|
|
||||||
|
|
||||||
|
static void simpleProcessorCoverage() {
|
||||||
|
try {
|
||||||
|
int x = 10;
|
||||||
|
int y = 20;
|
||||||
|
ASSERT(processor0."\{x} + \{y} = \{x + y}", "10 + 20 = 30");
|
||||||
|
ASSERT(processor1."\{x} + \{y} = \{x + y}", "10 + 20 = 30");
|
||||||
|
ASSERT(processor2."\{x} + \{y} = \{x + y}", "10 + 20 = 30");
|
||||||
|
ASSERT(processor3."\{x} + \{y} = \{x + y}", "10 + 20 = 30");
|
||||||
|
ASSERT(processor4."\{x} + \{y} = \{x + y}", "10 + 20 = 30");
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new RuntimeException("processor fail");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String justify(String string, int width) {
|
||||||
|
boolean leftJustify = width < 0;
|
||||||
|
int length = string.length();
|
||||||
|
width = Math.abs(width);
|
||||||
|
int diff = width - length;
|
||||||
|
|
||||||
|
if (diff < 0) {
|
||||||
|
string = "*".repeat(width);
|
||||||
|
} else if (0 < diff) {
|
||||||
|
if (leftJustify) {
|
||||||
|
string += " ".repeat(diff);
|
||||||
|
} else {
|
||||||
|
string = " ".repeat(diff) + string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
915
test/jdk/java/lang/template/FormatterBuilder.java
Normal file
915
test/jdk/java/lang/template/FormatterBuilder.java
Normal file
@ -0,0 +1,915 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 0000000
|
||||||
|
* @summary Exercise format builder.
|
||||||
|
* @enablePreview true
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.FormatProcessor;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static java.util.FormatProcessor.FMT;
|
||||||
|
|
||||||
|
public class FormatterBuilder {
|
||||||
|
public static void main(String... args) {
|
||||||
|
Locale.setDefault(Locale.US);
|
||||||
|
suite(FMT);
|
||||||
|
Locale thai = Locale.forLanguageTag("th-TH-u-nu-thai");
|
||||||
|
FormatProcessor thaiFormat = FormatProcessor.create(thai);
|
||||||
|
Locale.setDefault(thai);
|
||||||
|
suite(thaiFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test(String a, String b) {
|
||||||
|
if (!Objects.equals(a, b)) {
|
||||||
|
throw new RuntimeException("format and FMT do not match: " + a + " : " + b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void suite(FormatProcessor fmt) {
|
||||||
|
Object nullObject = null;
|
||||||
|
test(String.format("%b", false), fmt."%b\{false}");
|
||||||
|
test(String.format("%b", true), fmt."%b\{true}");
|
||||||
|
test(String.format("%10b", false), fmt."%10b\{false}");
|
||||||
|
test(String.format("%10b", true), fmt."%10b\{true}");
|
||||||
|
test(String.format("%-10b", false), fmt."%-10b\{false}");
|
||||||
|
test(String.format("%-10b", true), fmt."%-10b\{true}");
|
||||||
|
test(String.format("%B", false), fmt."%B\{false}");
|
||||||
|
test(String.format("%B", true), fmt."%B\{true}");
|
||||||
|
test(String.format("%10B", false), fmt."%10B\{false}");
|
||||||
|
test(String.format("%10B", true), fmt."%10B\{true}");
|
||||||
|
test(String.format("%-10B", false), fmt."%-10B\{false}");
|
||||||
|
test(String.format("%-10B", true), fmt."%-10B\{true}");
|
||||||
|
|
||||||
|
test(String.format("%h", 12345), fmt."%h\{12345}");
|
||||||
|
test(String.format("%h", 0xABCDE), fmt."%h\{0xABCDE}");
|
||||||
|
test(String.format("%10h", 12345), fmt."%10h\{12345}");
|
||||||
|
test(String.format("%10h", 0xABCDE), fmt."%10h\{0xABCDE}");
|
||||||
|
test(String.format("%-10h", 12345), fmt."%-10h\{12345}");
|
||||||
|
test(String.format("%-10h", 0xABCDE), fmt."%-10h\{0xABCDE}");
|
||||||
|
test(String.format("%H", 12345), fmt."%H\{12345}");
|
||||||
|
test(String.format("%H", 0xABCDE), fmt."%H\{0xABCDE}");
|
||||||
|
test(String.format("%10H", 12345), fmt."%10H\{12345}");
|
||||||
|
test(String.format("%10H", 0xABCDE), fmt."%10H\{0xABCDE}");
|
||||||
|
test(String.format("%-10H", 12345), fmt."%-10H\{12345}");
|
||||||
|
test(String.format("%-10H", 0xABCDE), fmt."%-10H\{0xABCDE}");
|
||||||
|
|
||||||
|
test(String.format("%s", (byte)0xFF), fmt."%s\{(byte)0xFF}");
|
||||||
|
test(String.format("%s", (short)0xFFFF), fmt."%s\{(short)0xFFFF}");
|
||||||
|
test(String.format("%s", 12345), fmt."%s\{12345}");
|
||||||
|
test(String.format("%s", 12345L), fmt."%s\{12345L}");
|
||||||
|
test(String.format("%s", 1.33f), fmt."%s\{1.33f}");
|
||||||
|
test(String.format("%s", 1.33), fmt."%s\{1.33}");
|
||||||
|
test(String.format("%s", "abcde"), fmt."%s\{"abcde"}");
|
||||||
|
test(String.format("%s", nullObject), fmt."%s\{nullObject}");
|
||||||
|
test(String.format("%10s", (byte)0xFF), fmt."%10s\{(byte)0xFF}");
|
||||||
|
test(String.format("%10s", (short)0xFFFF), fmt."%10s\{(short)0xFFFF}");
|
||||||
|
test(String.format("%10s", 12345), fmt."%10s\{12345}");
|
||||||
|
test(String.format("%10s", 12345L), fmt."%10s\{12345L}");
|
||||||
|
test(String.format("%10s", 1.33f), fmt."%10s\{1.33f}");
|
||||||
|
test(String.format("%10s", 1.33), fmt."%10s\{1.33}");
|
||||||
|
test(String.format("%10s", "abcde"), fmt."%10s\{"abcde"}");
|
||||||
|
test(String.format("%10s", nullObject), fmt."%10s\{nullObject}");
|
||||||
|
test(String.format("%-10s", (byte)0xFF), fmt."%-10s\{(byte)0xFF}");
|
||||||
|
test(String.format("%-10s", (short)0xFFFF), fmt."%-10s\{(short)0xFFFF}");
|
||||||
|
test(String.format("%-10s", 12345), fmt."%-10s\{12345}");
|
||||||
|
test(String.format("%-10s", 12345L), fmt."%-10s\{12345L}");
|
||||||
|
test(String.format("%-10s", 1.33f), fmt."%-10s\{1.33f}");
|
||||||
|
test(String.format("%-10s", 1.33), fmt."%-10s\{1.33}");
|
||||||
|
test(String.format("%-10s", "abcde"), fmt."%-10s\{"abcde"}");
|
||||||
|
test(String.format("%-10s", nullObject), fmt."%-10s\{nullObject}");
|
||||||
|
test(String.format("%S", (byte)0xFF), fmt."%S\{(byte)0xFF}");
|
||||||
|
test(String.format("%S", (short)0xFFFF), fmt."%S\{(short)0xFFFF}");
|
||||||
|
test(String.format("%S", 12345), fmt."%S\{12345}");
|
||||||
|
test(String.format("%S", 12345L), fmt."%S\{12345L}");
|
||||||
|
test(String.format("%S", 1.33f), fmt."%S\{1.33f}");
|
||||||
|
test(String.format("%S", 1.33), fmt."%S\{1.33}");
|
||||||
|
test(String.format("%S", "abcde"), fmt."%S\{"abcde"}");
|
||||||
|
test(String.format("%S", nullObject), fmt."%S\{nullObject}");
|
||||||
|
test(String.format("%10S", (byte)0xFF), fmt."%10S\{(byte)0xFF}");
|
||||||
|
test(String.format("%10S", (short)0xFFFF), fmt."%10S\{(short)0xFFFF}");
|
||||||
|
test(String.format("%10S", 12345), fmt."%10S\{12345}");
|
||||||
|
test(String.format("%10S", 12345L), fmt."%10S\{12345L}");
|
||||||
|
test(String.format("%10S", 1.33f), fmt."%10S\{1.33f}");
|
||||||
|
test(String.format("%10S", 1.33), fmt."%10S\{1.33}");
|
||||||
|
test(String.format("%10S", "abcde"), fmt."%10S\{"abcde"}");
|
||||||
|
test(String.format("%10S", nullObject), fmt."%10S\{nullObject}");
|
||||||
|
test(String.format("%-10S", (byte)0xFF), fmt."%-10S\{(byte)0xFF}");
|
||||||
|
test(String.format("%-10S", (short)0xFFFF), fmt."%-10S\{(short)0xFFFF}");
|
||||||
|
test(String.format("%-10S", 12345), fmt."%-10S\{12345}");
|
||||||
|
test(String.format("%-10S", 12345L), fmt."%-10S\{12345L}");
|
||||||
|
test(String.format("%-10S", 1.33f), fmt."%-10S\{1.33f}");
|
||||||
|
test(String.format("%-10S", 1.33), fmt."%-10S\{1.33}");
|
||||||
|
test(String.format("%-10S", "abcde"), fmt."%-10S\{"abcde"}");
|
||||||
|
test(String.format("%-10S", nullObject), fmt."%-10S\{nullObject}");
|
||||||
|
|
||||||
|
test(String.format("%c", 'a'), fmt."%c\{'a'}");
|
||||||
|
test(String.format("%10c", 'a'), fmt."%10c\{'a'}");
|
||||||
|
test(String.format("%-10c", 'a'), fmt."%-10c\{'a'}");
|
||||||
|
test(String.format("%C", 'a'), fmt."%C\{'a'}");
|
||||||
|
test(String.format("%10C", 'a'), fmt."%10C\{'a'}");
|
||||||
|
test(String.format("%-10C", 'a'), fmt."%-10C\{'a'}");
|
||||||
|
|
||||||
|
test(String.format("%d", -12345), fmt."%d\{-12345}");
|
||||||
|
test(String.format("%d", 0), fmt."%d\{0}");
|
||||||
|
test(String.format("%d", 12345), fmt."%d\{12345}");
|
||||||
|
test(String.format("%10d", -12345), fmt."%10d\{-12345}");
|
||||||
|
test(String.format("%10d", 0), fmt."%10d\{0}");
|
||||||
|
test(String.format("%10d", 12345), fmt."%10d\{12345}");
|
||||||
|
test(String.format("%-10d", -12345), fmt."%-10d\{-12345}");
|
||||||
|
test(String.format("%-10d", 0), fmt."%-10d\{0}");
|
||||||
|
test(String.format("%-10d", 12345), fmt."%-10d\{12345}");
|
||||||
|
test(String.format("%,d", -12345), fmt."%,d\{-12345}");
|
||||||
|
test(String.format("%,d", 0), fmt."%,d\{0}");
|
||||||
|
test(String.format("%,d", 12345), fmt."%,d\{12345}");
|
||||||
|
test(String.format("%,10d", -12345), fmt."%,10d\{-12345}");
|
||||||
|
test(String.format("%,10d", 0), fmt."%,10d\{0}");
|
||||||
|
test(String.format("%,10d", 12345), fmt."%,10d\{12345}");
|
||||||
|
test(String.format("%,-10d", -12345), fmt."%,-10d\{-12345}");
|
||||||
|
test(String.format("%,-10d", 0), fmt."%,-10d\{0}");
|
||||||
|
test(String.format("%,-10d", 12345), fmt."%,-10d\{12345}");
|
||||||
|
test(String.format("%010d", -12345), fmt."%010d\{-12345}");
|
||||||
|
test(String.format("%010d", 0), fmt."%010d\{0}");
|
||||||
|
test(String.format("%010d", 12345), fmt."%010d\{12345}");
|
||||||
|
test(String.format("%,010d", -12345), fmt."%,010d\{-12345}");
|
||||||
|
test(String.format("%,010d", 0), fmt."%,010d\{0}");
|
||||||
|
test(String.format("%,010d", 12345), fmt."%,010d\{12345}");
|
||||||
|
|
||||||
|
test(String.format("%d", -12345), fmt."%d\{-12345}");
|
||||||
|
test(String.format("%d", 0), fmt."%d\{0}");
|
||||||
|
test(String.format("%d", 12345), fmt."%d\{12345}");
|
||||||
|
test(String.format("%10d", -12345), fmt."%10d\{-12345}");
|
||||||
|
test(String.format("%10d", 0), fmt."%10d\{0}");
|
||||||
|
test(String.format("%10d", 12345), fmt."%10d\{12345}");
|
||||||
|
test(String.format("%-10d", -12345), fmt."%-10d\{-12345}");
|
||||||
|
test(String.format("%-10d", 0), fmt."%-10d\{0}");
|
||||||
|
test(String.format("%-10d", 12345), fmt."%-10d\{12345}");
|
||||||
|
test(String.format("%,d", -12345), fmt."%,d\{-12345}");
|
||||||
|
test(String.format("%,d", 0), fmt."%,d\{0}");
|
||||||
|
test(String.format("%,d", 12345), fmt."%,d\{12345}");
|
||||||
|
test(String.format("%,10d", -12345), fmt."%,10d\{-12345}");
|
||||||
|
test(String.format("%,10d", 0), fmt."%,10d\{0}");
|
||||||
|
test(String.format("%,10d", 12345), fmt."%,10d\{12345}");
|
||||||
|
test(String.format("%,-10d", -12345), fmt."%,-10d\{-12345}");
|
||||||
|
test(String.format("%,-10d", 0), fmt."%,-10d\{0}");
|
||||||
|
test(String.format("%,-10d", 12345), fmt."%,-10d\{12345}");
|
||||||
|
test(String.format("% d", -12345), fmt."% d\{-12345}");
|
||||||
|
test(String.format("% d", 0), fmt."% d\{0}");
|
||||||
|
test(String.format("% d", 12345), fmt."% d\{12345}");
|
||||||
|
test(String.format("% 10d", -12345), fmt."% 10d\{-12345}");
|
||||||
|
test(String.format("% 10d", 0), fmt."% 10d\{0}");
|
||||||
|
test(String.format("% 10d", 12345), fmt."% 10d\{12345}");
|
||||||
|
test(String.format("% -10d", -12345), fmt."% -10d\{-12345}");
|
||||||
|
test(String.format("% -10d", 0), fmt."% -10d\{0}");
|
||||||
|
test(String.format("% -10d", 12345), fmt."% -10d\{12345}");
|
||||||
|
test(String.format("%, d", -12345), fmt."%, d\{-12345}");
|
||||||
|
test(String.format("%, d", 0), fmt."%, d\{0}");
|
||||||
|
test(String.format("%, d", 12345), fmt."%, d\{12345}");
|
||||||
|
test(String.format("%, 10d", -12345), fmt."%, 10d\{-12345}");
|
||||||
|
test(String.format("%, 10d", 0), fmt."%, 10d\{0}");
|
||||||
|
test(String.format("%, 10d", 12345), fmt."%, 10d\{12345}");
|
||||||
|
test(String.format("%, -10d", -12345), fmt."%, -10d\{-12345}");
|
||||||
|
test(String.format("%, -10d", 0), fmt."%, -10d\{0}");
|
||||||
|
test(String.format("%, -10d", 12345), fmt."%, -10d\{12345}");
|
||||||
|
test(String.format("%010d", -12345), fmt."%010d\{-12345}");
|
||||||
|
test(String.format("%010d", 0), fmt."%010d\{0}");
|
||||||
|
test(String.format("%010d", 12345), fmt."%010d\{12345}");
|
||||||
|
test(String.format("%,010d", -12345), fmt."%,010d\{-12345}");
|
||||||
|
test(String.format("%,010d", 0), fmt."%,010d\{0}");
|
||||||
|
test(String.format("%,010d", 12345), fmt."%,010d\{12345}");
|
||||||
|
test(String.format("% 010d", -12345), fmt."% 010d\{-12345}");
|
||||||
|
test(String.format("% 010d", 0), fmt."% 010d\{0}");
|
||||||
|
test(String.format("% 010d", 12345), fmt."% 010d\{12345}");
|
||||||
|
test(String.format("%, 010d", -12345), fmt."%, 010d\{-12345}");
|
||||||
|
test(String.format("%, 010d", 0), fmt."%, 010d\{0}");
|
||||||
|
test(String.format("%, 010d", 12345), fmt."%, 010d\{12345}");
|
||||||
|
|
||||||
|
test(String.format("%d", -12345), fmt."%d\{-12345}");
|
||||||
|
test(String.format("%d", 0), fmt."%d\{0}");
|
||||||
|
test(String.format("%d", 12345), fmt."%d\{12345}");
|
||||||
|
test(String.format("%10d", -12345), fmt."%10d\{-12345}");
|
||||||
|
test(String.format("%10d", 0), fmt."%10d\{0}");
|
||||||
|
test(String.format("%10d", 12345), fmt."%10d\{12345}");
|
||||||
|
test(String.format("%-10d", -12345), fmt."%-10d\{-12345}");
|
||||||
|
test(String.format("%-10d", 0), fmt."%-10d\{0}");
|
||||||
|
test(String.format("%-10d", 12345), fmt."%-10d\{12345}");
|
||||||
|
test(String.format("%,d", -12345), fmt."%,d\{-12345}");
|
||||||
|
test(String.format("%,d", 0), fmt."%,d\{0}");
|
||||||
|
test(String.format("%,d", 12345), fmt."%,d\{12345}");
|
||||||
|
test(String.format("%,10d", -12345), fmt."%,10d\{-12345}");
|
||||||
|
test(String.format("%,10d", 0), fmt."%,10d\{0}");
|
||||||
|
test(String.format("%,10d", 12345), fmt."%,10d\{12345}");
|
||||||
|
test(String.format("%,-10d", -12345), fmt."%,-10d\{-12345}");
|
||||||
|
test(String.format("%,-10d", 0), fmt."%,-10d\{0}");
|
||||||
|
test(String.format("%,-10d", 12345), fmt."%,-10d\{12345}");
|
||||||
|
test(String.format("%+d", -12345), fmt."%+d\{-12345}");
|
||||||
|
test(String.format("%+d", 0), fmt."%+d\{0}");
|
||||||
|
test(String.format("%+d", 12345), fmt."%+d\{12345}");
|
||||||
|
test(String.format("%+10d", -12345), fmt."%+10d\{-12345}");
|
||||||
|
test(String.format("%+10d", 0), fmt."%+10d\{0}");
|
||||||
|
test(String.format("%+10d", 12345), fmt."%+10d\{12345}");
|
||||||
|
test(String.format("%+-10d", -12345), fmt."%+-10d\{-12345}");
|
||||||
|
test(String.format("%+-10d", 0), fmt."%+-10d\{0}");
|
||||||
|
test(String.format("%+-10d", 12345), fmt."%+-10d\{12345}");
|
||||||
|
test(String.format("%,+d", -12345), fmt."%,+d\{-12345}");
|
||||||
|
test(String.format("%,+d", 0), fmt."%,+d\{0}");
|
||||||
|
test(String.format("%,+d", 12345), fmt."%,+d\{12345}");
|
||||||
|
test(String.format("%,+10d", -12345), fmt."%,+10d\{-12345}");
|
||||||
|
test(String.format("%,+10d", 0), fmt."%,+10d\{0}");
|
||||||
|
test(String.format("%,+10d", 12345), fmt."%,+10d\{12345}");
|
||||||
|
test(String.format("%,+-10d", -12345), fmt."%,+-10d\{-12345}");
|
||||||
|
test(String.format("%,+-10d", 0), fmt."%,+-10d\{0}");
|
||||||
|
test(String.format("%,+-10d", 12345), fmt."%,+-10d\{12345}");
|
||||||
|
test(String.format("%010d", -12345), fmt."%010d\{-12345}");
|
||||||
|
test(String.format("%010d", 0), fmt."%010d\{0}");
|
||||||
|
test(String.format("%010d", 12345), fmt."%010d\{12345}");
|
||||||
|
test(String.format("%,010d", -12345), fmt."%,010d\{-12345}");
|
||||||
|
test(String.format("%,010d", 0), fmt."%,010d\{0}");
|
||||||
|
test(String.format("%,010d", 12345), fmt."%,010d\{12345}");
|
||||||
|
test(String.format("%+010d", -12345), fmt."%+010d\{-12345}");
|
||||||
|
test(String.format("%+010d", 0), fmt."%+010d\{0}");
|
||||||
|
test(String.format("%+010d", 12345), fmt."%+010d\{12345}");
|
||||||
|
test(String.format("%,+010d", -12345), fmt."%,+010d\{-12345}");
|
||||||
|
test(String.format("%,+010d", 0), fmt."%,+010d\{0}");
|
||||||
|
test(String.format("%,+010d", 12345), fmt."%,+010d\{12345}");
|
||||||
|
|
||||||
|
test(String.format("%d", -12345), fmt."%d\{-12345}");
|
||||||
|
test(String.format("%d", 0), fmt."%d\{0}");
|
||||||
|
test(String.format("%d", 12345), fmt."%d\{12345}");
|
||||||
|
test(String.format("%10d", -12345), fmt."%10d\{-12345}");
|
||||||
|
test(String.format("%10d", 0), fmt."%10d\{0}");
|
||||||
|
test(String.format("%10d", 12345), fmt."%10d\{12345}");
|
||||||
|
test(String.format("%-10d", -12345), fmt."%-10d\{-12345}");
|
||||||
|
test(String.format("%-10d", 0), fmt."%-10d\{0}");
|
||||||
|
test(String.format("%-10d", 12345), fmt."%-10d\{12345}");
|
||||||
|
test(String.format("%,d", -12345), fmt."%,d\{-12345}");
|
||||||
|
test(String.format("%,d", 0), fmt."%,d\{0}");
|
||||||
|
test(String.format("%,d", 12345), fmt."%,d\{12345}");
|
||||||
|
test(String.format("%,10d", -12345), fmt."%,10d\{-12345}");
|
||||||
|
test(String.format("%,10d", 0), fmt."%,10d\{0}");
|
||||||
|
test(String.format("%,10d", 12345), fmt."%,10d\{12345}");
|
||||||
|
test(String.format("%,-10d", -12345), fmt."%,-10d\{-12345}");
|
||||||
|
test(String.format("%,-10d", 0), fmt."%,-10d\{0}");
|
||||||
|
test(String.format("%,-10d", 12345), fmt."%,-10d\{12345}");
|
||||||
|
test(String.format("%(d", -12345), fmt."%(d\{-12345}");
|
||||||
|
test(String.format("%(d", 0), fmt."%(d\{0}");
|
||||||
|
test(String.format("%(d", 12345), fmt."%(d\{12345}");
|
||||||
|
test(String.format("%(10d", -12345), fmt."%(10d\{-12345}");
|
||||||
|
test(String.format("%(10d", 0), fmt."%(10d\{0}");
|
||||||
|
test(String.format("%(10d", 12345), fmt."%(10d\{12345}");
|
||||||
|
test(String.format("%(-10d", -12345), fmt."%(-10d\{-12345}");
|
||||||
|
test(String.format("%(-10d", 0), fmt."%(-10d\{0}");
|
||||||
|
test(String.format("%(-10d", 12345), fmt."%(-10d\{12345}");
|
||||||
|
test(String.format("%,(d", -12345), fmt."%,(d\{-12345}");
|
||||||
|
test(String.format("%,(d", 0), fmt."%,(d\{0}");
|
||||||
|
test(String.format("%,(d", 12345), fmt."%,(d\{12345}");
|
||||||
|
test(String.format("%,(10d", -12345), fmt."%,(10d\{-12345}");
|
||||||
|
test(String.format("%,(10d", 0), fmt."%,(10d\{0}");
|
||||||
|
test(String.format("%,(10d", 12345), fmt."%,(10d\{12345}");
|
||||||
|
test(String.format("%,(-10d", -12345), fmt."%,(-10d\{-12345}");
|
||||||
|
test(String.format("%,(-10d", 0), fmt."%,(-10d\{0}");
|
||||||
|
test(String.format("%,(-10d", 12345), fmt."%,(-10d\{12345}");
|
||||||
|
test(String.format("%010d", -12345), fmt."%010d\{-12345}");
|
||||||
|
test(String.format("%010d", 0), fmt."%010d\{0}");
|
||||||
|
test(String.format("%010d", 12345), fmt."%010d\{12345}");
|
||||||
|
test(String.format("%,010d", -12345), fmt."%,010d\{-12345}");
|
||||||
|
test(String.format("%,010d", 0), fmt."%,010d\{0}");
|
||||||
|
test(String.format("%,010d", 12345), fmt."%,010d\{12345}");
|
||||||
|
test(String.format("%(010d", -12345), fmt."%(010d\{-12345}");
|
||||||
|
test(String.format("%(010d", 0), fmt."%(010d\{0}");
|
||||||
|
test(String.format("%(010d", 12345), fmt."%(010d\{12345}");
|
||||||
|
test(String.format("%,(010d", -12345), fmt."%,(010d\{-12345}");
|
||||||
|
test(String.format("%,(010d", 0), fmt."%,(010d\{0}");
|
||||||
|
test(String.format("%,(010d", 12345), fmt."%,(010d\{12345}");
|
||||||
|
|
||||||
|
test(String.format("%o", -12345), fmt."%o\{-12345}");
|
||||||
|
test(String.format("%o", 0), fmt."%o\{0}");
|
||||||
|
test(String.format("%o", 12345), fmt."%o\{12345}");
|
||||||
|
test(String.format("%10o", -12345), fmt."%10o\{-12345}");
|
||||||
|
test(String.format("%10o", 0), fmt."%10o\{0}");
|
||||||
|
test(String.format("%10o", 12345), fmt."%10o\{12345}");
|
||||||
|
test(String.format("%-10o", -12345), fmt."%-10o\{-12345}");
|
||||||
|
test(String.format("%-10o", 0), fmt."%-10o\{0}");
|
||||||
|
test(String.format("%-10o", 12345), fmt."%-10o\{12345}");
|
||||||
|
test(String.format("%#o", -12345), fmt."%#o\{-12345}");
|
||||||
|
test(String.format("%#o", 0), fmt."%#o\{0}");
|
||||||
|
test(String.format("%#o", 12345), fmt."%#o\{12345}");
|
||||||
|
test(String.format("%#10o", -12345), fmt."%#10o\{-12345}");
|
||||||
|
test(String.format("%#10o", 0), fmt."%#10o\{0}");
|
||||||
|
test(String.format("%#10o", 12345), fmt."%#10o\{12345}");
|
||||||
|
test(String.format("%#-10o", -12345), fmt."%#-10o\{-12345}");
|
||||||
|
test(String.format("%#-10o", 0), fmt."%#-10o\{0}");
|
||||||
|
test(String.format("%#-10o", 12345), fmt."%#-10o\{12345}");
|
||||||
|
test(String.format("%010o", -12345), fmt."%010o\{-12345}");
|
||||||
|
test(String.format("%010o", 0), fmt."%010o\{0}");
|
||||||
|
test(String.format("%010o", 12345), fmt."%010o\{12345}");
|
||||||
|
test(String.format("%#010o", -12345), fmt."%#010o\{-12345}");
|
||||||
|
test(String.format("%#010o", 0), fmt."%#010o\{0}");
|
||||||
|
test(String.format("%#010o", 12345), fmt."%#010o\{12345}");
|
||||||
|
|
||||||
|
test(String.format("%x", -12345), fmt."%x\{-12345}");
|
||||||
|
test(String.format("%x", 0), fmt."%x\{0}");
|
||||||
|
test(String.format("%x", 12345), fmt."%x\{12345}");
|
||||||
|
test(String.format("%10x", -12345), fmt."%10x\{-12345}");
|
||||||
|
test(String.format("%10x", 0), fmt."%10x\{0}");
|
||||||
|
test(String.format("%10x", 12345), fmt."%10x\{12345}");
|
||||||
|
test(String.format("%-10x", -12345), fmt."%-10x\{-12345}");
|
||||||
|
test(String.format("%-10x", 0), fmt."%-10x\{0}");
|
||||||
|
test(String.format("%-10x", 12345), fmt."%-10x\{12345}");
|
||||||
|
test(String.format("%X", -12345), fmt."%X\{-12345}");
|
||||||
|
test(String.format("%X", 0), fmt."%X\{0}");
|
||||||
|
test(String.format("%X", 12345), fmt."%X\{12345}");
|
||||||
|
test(String.format("%10X", -12345), fmt."%10X\{-12345}");
|
||||||
|
test(String.format("%10X", 0), fmt."%10X\{0}");
|
||||||
|
test(String.format("%10X", 12345), fmt."%10X\{12345}");
|
||||||
|
test(String.format("%-10X", -12345), fmt."%-10X\{-12345}");
|
||||||
|
test(String.format("%-10X", 0), fmt."%-10X\{0}");
|
||||||
|
test(String.format("%-10X", 12345), fmt."%-10X\{12345}");
|
||||||
|
test(String.format("%#x", -12345), fmt."%#x\{-12345}");
|
||||||
|
test(String.format("%#x", 0), fmt."%#x\{0}");
|
||||||
|
test(String.format("%#x", 12345), fmt."%#x\{12345}");
|
||||||
|
test(String.format("%#10x", -12345), fmt."%#10x\{-12345}");
|
||||||
|
test(String.format("%#10x", 0), fmt."%#10x\{0}");
|
||||||
|
test(String.format("%#10x", 12345), fmt."%#10x\{12345}");
|
||||||
|
test(String.format("%#-10x", -12345), fmt."%#-10x\{-12345}");
|
||||||
|
test(String.format("%#-10x", 0), fmt."%#-10x\{0}");
|
||||||
|
test(String.format("%#-10x", 12345), fmt."%#-10x\{12345}");
|
||||||
|
test(String.format("%#X", -12345), fmt."%#X\{-12345}");
|
||||||
|
test(String.format("%#X", 0), fmt."%#X\{0}");
|
||||||
|
test(String.format("%#X", 12345), fmt."%#X\{12345}");
|
||||||
|
test(String.format("%#10X", -12345), fmt."%#10X\{-12345}");
|
||||||
|
test(String.format("%#10X", 0), fmt."%#10X\{0}");
|
||||||
|
test(String.format("%#10X", 12345), fmt."%#10X\{12345}");
|
||||||
|
test(String.format("%#-10X", -12345), fmt."%#-10X\{-12345}");
|
||||||
|
test(String.format("%#-10X", 0), fmt."%#-10X\{0}");
|
||||||
|
test(String.format("%#-10X", 12345), fmt."%#-10X\{12345}");
|
||||||
|
test(String.format("%010x", -12345), fmt."%010x\{-12345}");
|
||||||
|
test(String.format("%010x", 0), fmt."%010x\{0}");
|
||||||
|
test(String.format("%010x", 12345), fmt."%010x\{12345}");
|
||||||
|
test(String.format("%010X", -12345), fmt."%010X\{-12345}");
|
||||||
|
test(String.format("%010X", 0), fmt."%010X\{0}");
|
||||||
|
test(String.format("%010X", 12345), fmt."%010X\{12345}");
|
||||||
|
test(String.format("%#010x", -12345), fmt."%#010x\{-12345}");
|
||||||
|
test(String.format("%#010x", 0), fmt."%#010x\{0}");
|
||||||
|
test(String.format("%#010x", 12345), fmt."%#010x\{12345}");
|
||||||
|
test(String.format("%#010X", -12345), fmt."%#010X\{-12345}");
|
||||||
|
test(String.format("%#010X", 0), fmt."%#010X\{0}");
|
||||||
|
test(String.format("%#010X", 12345), fmt."%#010X\{12345}");
|
||||||
|
|
||||||
|
test(String.format("%f", -12345.6), fmt."%f\{-12345.6}");
|
||||||
|
test(String.format("%f", 0.0), fmt."%f\{0.0}");
|
||||||
|
test(String.format("%f", 12345.6), fmt."%f\{12345.6}");
|
||||||
|
test(String.format("%10f", -12345.6), fmt."%10f\{-12345.6}");
|
||||||
|
test(String.format("%10f", 0.0), fmt."%10f\{0.0}");
|
||||||
|
test(String.format("%10f", 12345.6), fmt."%10f\{12345.6}");
|
||||||
|
test(String.format("%-10f", -12345.6), fmt."%-10f\{-12345.6}");
|
||||||
|
test(String.format("%-10f", 0.0), fmt."%-10f\{0.0}");
|
||||||
|
test(String.format("%-10f", 12345.6), fmt."%-10f\{12345.6}");
|
||||||
|
test(String.format("%,f", -12345.6), fmt."%,f\{-12345.6}");
|
||||||
|
test(String.format("%,f", 0.0), fmt."%,f\{0.0}");
|
||||||
|
test(String.format("%,f", 12345.6), fmt."%,f\{12345.6}");
|
||||||
|
test(String.format("%,10f", -12345.6), fmt."%,10f\{-12345.6}");
|
||||||
|
test(String.format("%,10f", 0.0), fmt."%,10f\{0.0}");
|
||||||
|
test(String.format("%,10f", 12345.6), fmt."%,10f\{12345.6}");
|
||||||
|
test(String.format("%,-10f", -12345.6), fmt."%,-10f\{-12345.6}");
|
||||||
|
test(String.format("%,-10f", 0.0), fmt."%,-10f\{0.0}");
|
||||||
|
test(String.format("%,-10f", 12345.6), fmt."%,-10f\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%f", -12345.6), fmt."%f\{-12345.6}");
|
||||||
|
test(String.format("%f", 0.0), fmt."%f\{0.0}");
|
||||||
|
test(String.format("%f", 12345.6), fmt."%f\{12345.6}");
|
||||||
|
test(String.format("%10f", -12345.6), fmt."%10f\{-12345.6}");
|
||||||
|
test(String.format("%10f", 0.0), fmt."%10f\{0.0}");
|
||||||
|
test(String.format("%10f", 12345.6), fmt."%10f\{12345.6}");
|
||||||
|
test(String.format("%-10f", -12345.6), fmt."%-10f\{-12345.6}");
|
||||||
|
test(String.format("%-10f", 0.0), fmt."%-10f\{0.0}");
|
||||||
|
test(String.format("%-10f", 12345.6), fmt."%-10f\{12345.6}");
|
||||||
|
test(String.format("%,f", -12345.6), fmt."%,f\{-12345.6}");
|
||||||
|
test(String.format("%,f", 0.0), fmt."%,f\{0.0}");
|
||||||
|
test(String.format("%,f", 12345.6), fmt."%,f\{12345.6}");
|
||||||
|
test(String.format("%,10f", -12345.6), fmt."%,10f\{-12345.6}");
|
||||||
|
test(String.format("%,10f", 0.0), fmt."%,10f\{0.0}");
|
||||||
|
test(String.format("%,10f", 12345.6), fmt."%,10f\{12345.6}");
|
||||||
|
test(String.format("%,-10f", -12345.6), fmt."%,-10f\{-12345.6}");
|
||||||
|
test(String.format("%,-10f", 0.0), fmt."%,-10f\{0.0}");
|
||||||
|
test(String.format("%,-10f", 12345.6), fmt."%,-10f\{12345.6}");
|
||||||
|
test(String.format("% f", -12345.6), fmt."% f\{-12345.6}");
|
||||||
|
test(String.format("% f", 0.0), fmt."% f\{0.0}");
|
||||||
|
test(String.format("% f", 12345.6), fmt."% f\{12345.6}");
|
||||||
|
test(String.format("% 10f", -12345.6), fmt."% 10f\{-12345.6}");
|
||||||
|
test(String.format("% 10f", 0.0), fmt."% 10f\{0.0}");
|
||||||
|
test(String.format("% 10f", 12345.6), fmt."% 10f\{12345.6}");
|
||||||
|
test(String.format("% -10f", -12345.6), fmt."% -10f\{-12345.6}");
|
||||||
|
test(String.format("% -10f", 0.0), fmt."% -10f\{0.0}");
|
||||||
|
test(String.format("% -10f", 12345.6), fmt."% -10f\{12345.6}");
|
||||||
|
test(String.format("%, f", -12345.6), fmt."%, f\{-12345.6}");
|
||||||
|
test(String.format("%, f", 0.0), fmt."%, f\{0.0}");
|
||||||
|
test(String.format("%, f", 12345.6), fmt."%, f\{12345.6}");
|
||||||
|
test(String.format("%, 10f", -12345.6), fmt."%, 10f\{-12345.6}");
|
||||||
|
test(String.format("%, 10f", 0.0), fmt."%, 10f\{0.0}");
|
||||||
|
test(String.format("%, 10f", 12345.6), fmt."%, 10f\{12345.6}");
|
||||||
|
test(String.format("%, -10f", -12345.6), fmt."%, -10f\{-12345.6}");
|
||||||
|
test(String.format("%, -10f", 0.0), fmt."%, -10f\{0.0}");
|
||||||
|
test(String.format("%, -10f", 12345.6), fmt."%, -10f\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%f", -12345.6), fmt."%f\{-12345.6}");
|
||||||
|
test(String.format("%f", 0.0), fmt."%f\{0.0}");
|
||||||
|
test(String.format("%f", 12345.6), fmt."%f\{12345.6}");
|
||||||
|
test(String.format("%10f", -12345.6), fmt."%10f\{-12345.6}");
|
||||||
|
test(String.format("%10f", 0.0), fmt."%10f\{0.0}");
|
||||||
|
test(String.format("%10f", 12345.6), fmt."%10f\{12345.6}");
|
||||||
|
test(String.format("%-10f", -12345.6), fmt."%-10f\{-12345.6}");
|
||||||
|
test(String.format("%-10f", 0.0), fmt."%-10f\{0.0}");
|
||||||
|
test(String.format("%-10f", 12345.6), fmt."%-10f\{12345.6}");
|
||||||
|
test(String.format("%,f", -12345.6), fmt."%,f\{-12345.6}");
|
||||||
|
test(String.format("%,f", 0.0), fmt."%,f\{0.0}");
|
||||||
|
test(String.format("%,f", 12345.6), fmt."%,f\{12345.6}");
|
||||||
|
test(String.format("%,10f", -12345.6), fmt."%,10f\{-12345.6}");
|
||||||
|
test(String.format("%,10f", 0.0), fmt."%,10f\{0.0}");
|
||||||
|
test(String.format("%,10f", 12345.6), fmt."%,10f\{12345.6}");
|
||||||
|
test(String.format("%,-10f", -12345.6), fmt."%,-10f\{-12345.6}");
|
||||||
|
test(String.format("%,-10f", 0.0), fmt."%,-10f\{0.0}");
|
||||||
|
test(String.format("%,-10f", 12345.6), fmt."%,-10f\{12345.6}");
|
||||||
|
test(String.format("%+f", -12345.6), fmt."%+f\{-12345.6}");
|
||||||
|
test(String.format("%+f", 0.0), fmt."%+f\{0.0}");
|
||||||
|
test(String.format("%+f", 12345.6), fmt."%+f\{12345.6}");
|
||||||
|
test(String.format("%+10f", -12345.6), fmt."%+10f\{-12345.6}");
|
||||||
|
test(String.format("%+10f", 0.0), fmt."%+10f\{0.0}");
|
||||||
|
test(String.format("%+10f", 12345.6), fmt."%+10f\{12345.6}");
|
||||||
|
test(String.format("%+-10f", -12345.6), fmt."%+-10f\{-12345.6}");
|
||||||
|
test(String.format("%+-10f", 0.0), fmt."%+-10f\{0.0}");
|
||||||
|
test(String.format("%+-10f", 12345.6), fmt."%+-10f\{12345.6}");
|
||||||
|
test(String.format("%,+f", -12345.6), fmt."%,+f\{-12345.6}");
|
||||||
|
test(String.format("%,+f", 0.0), fmt."%,+f\{0.0}");
|
||||||
|
test(String.format("%,+f", 12345.6), fmt."%,+f\{12345.6}");
|
||||||
|
test(String.format("%,+10f", -12345.6), fmt."%,+10f\{-12345.6}");
|
||||||
|
test(String.format("%,+10f", 0.0), fmt."%,+10f\{0.0}");
|
||||||
|
test(String.format("%,+10f", 12345.6), fmt."%,+10f\{12345.6}");
|
||||||
|
test(String.format("%,+-10f", -12345.6), fmt."%,+-10f\{-12345.6}");
|
||||||
|
test(String.format("%,+-10f", 0.0), fmt."%,+-10f\{0.0}");
|
||||||
|
test(String.format("%,+-10f", 12345.6), fmt."%,+-10f\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%f", -12345.6), fmt."%f\{-12345.6}");
|
||||||
|
test(String.format("%f", 0.0), fmt."%f\{0.0}");
|
||||||
|
test(String.format("%f", 12345.6), fmt."%f\{12345.6}");
|
||||||
|
test(String.format("%10f", -12345.6), fmt."%10f\{-12345.6}");
|
||||||
|
test(String.format("%10f", 0.0), fmt."%10f\{0.0}");
|
||||||
|
test(String.format("%10f", 12345.6), fmt."%10f\{12345.6}");
|
||||||
|
test(String.format("%-10f", -12345.6), fmt."%-10f\{-12345.6}");
|
||||||
|
test(String.format("%-10f", 0.0), fmt."%-10f\{0.0}");
|
||||||
|
test(String.format("%-10f", 12345.6), fmt."%-10f\{12345.6}");
|
||||||
|
test(String.format("%,f", -12345.6), fmt."%,f\{-12345.6}");
|
||||||
|
test(String.format("%,f", 0.0), fmt."%,f\{0.0}");
|
||||||
|
test(String.format("%,f", 12345.6), fmt."%,f\{12345.6}");
|
||||||
|
test(String.format("%,10f", -12345.6), fmt."%,10f\{-12345.6}");
|
||||||
|
test(String.format("%,10f", 0.0), fmt."%,10f\{0.0}");
|
||||||
|
test(String.format("%,10f", 12345.6), fmt."%,10f\{12345.6}");
|
||||||
|
test(String.format("%,-10f", -12345.6), fmt."%,-10f\{-12345.6}");
|
||||||
|
test(String.format("%,-10f", 0.0), fmt."%,-10f\{0.0}");
|
||||||
|
test(String.format("%,-10f", 12345.6), fmt."%,-10f\{12345.6}");
|
||||||
|
test(String.format("%(f", -12345.6), fmt."%(f\{-12345.6}");
|
||||||
|
test(String.format("%(f", 0.0), fmt."%(f\{0.0}");
|
||||||
|
test(String.format("%(f", 12345.6), fmt."%(f\{12345.6}");
|
||||||
|
test(String.format("%(10f", -12345.6), fmt."%(10f\{-12345.6}");
|
||||||
|
test(String.format("%(10f", 0.0), fmt."%(10f\{0.0}");
|
||||||
|
test(String.format("%(10f", 12345.6), fmt."%(10f\{12345.6}");
|
||||||
|
test(String.format("%(-10f", -12345.6), fmt."%(-10f\{-12345.6}");
|
||||||
|
test(String.format("%(-10f", 0.0), fmt."%(-10f\{0.0}");
|
||||||
|
test(String.format("%(-10f", 12345.6), fmt."%(-10f\{12345.6}");
|
||||||
|
test(String.format("%,(f", -12345.6), fmt."%,(f\{-12345.6}");
|
||||||
|
test(String.format("%,(f", 0.0), fmt."%,(f\{0.0}");
|
||||||
|
test(String.format("%,(f", 12345.6), fmt."%,(f\{12345.6}");
|
||||||
|
test(String.format("%,(10f", -12345.6), fmt."%,(10f\{-12345.6}");
|
||||||
|
test(String.format("%,(10f", 0.0), fmt."%,(10f\{0.0}");
|
||||||
|
test(String.format("%,(10f", 12345.6), fmt."%,(10f\{12345.6}");
|
||||||
|
test(String.format("%,(-10f", -12345.6), fmt."%,(-10f\{-12345.6}");
|
||||||
|
test(String.format("%,(-10f", 0.0), fmt."%,(-10f\{0.0}");
|
||||||
|
test(String.format("%,(-10f", 12345.6), fmt."%,(-10f\{12345.6}");
|
||||||
|
test(String.format("%+f", -12345.6), fmt."%+f\{-12345.6}");
|
||||||
|
test(String.format("%+f", 0.0), fmt."%+f\{0.0}");
|
||||||
|
test(String.format("%+f", 12345.6), fmt."%+f\{12345.6}");
|
||||||
|
test(String.format("%+10f", -12345.6), fmt."%+10f\{-12345.6}");
|
||||||
|
test(String.format("%+10f", 0.0), fmt."%+10f\{0.0}");
|
||||||
|
test(String.format("%+10f", 12345.6), fmt."%+10f\{12345.6}");
|
||||||
|
test(String.format("%+-10f", -12345.6), fmt."%+-10f\{-12345.6}");
|
||||||
|
test(String.format("%+-10f", 0.0), fmt."%+-10f\{0.0}");
|
||||||
|
test(String.format("%+-10f", 12345.6), fmt."%+-10f\{12345.6}");
|
||||||
|
test(String.format("%,+f", -12345.6), fmt."%,+f\{-12345.6}");
|
||||||
|
test(String.format("%,+f", 0.0), fmt."%,+f\{0.0}");
|
||||||
|
test(String.format("%,+f", 12345.6), fmt."%,+f\{12345.6}");
|
||||||
|
test(String.format("%,+10f", -12345.6), fmt."%,+10f\{-12345.6}");
|
||||||
|
test(String.format("%,+10f", 0.0), fmt."%,+10f\{0.0}");
|
||||||
|
test(String.format("%,+10f", 12345.6), fmt."%,+10f\{12345.6}");
|
||||||
|
test(String.format("%,+-10f", -12345.6), fmt."%,+-10f\{-12345.6}");
|
||||||
|
test(String.format("%,+-10f", 0.0), fmt."%,+-10f\{0.0}");
|
||||||
|
test(String.format("%,+-10f", 12345.6), fmt."%,+-10f\{12345.6}");
|
||||||
|
test(String.format("%(+f", -12345.6), fmt."%(+f\{-12345.6}");
|
||||||
|
test(String.format("%(+f", 0.0), fmt."%(+f\{0.0}");
|
||||||
|
test(String.format("%(+f", 12345.6), fmt."%(+f\{12345.6}");
|
||||||
|
test(String.format("%(+10f", -12345.6), fmt."%(+10f\{-12345.6}");
|
||||||
|
test(String.format("%(+10f", 0.0), fmt."%(+10f\{0.0}");
|
||||||
|
test(String.format("%(+10f", 12345.6), fmt."%(+10f\{12345.6}");
|
||||||
|
test(String.format("%(+-10f", -12345.6), fmt."%(+-10f\{-12345.6}");
|
||||||
|
test(String.format("%(+-10f", 0.0), fmt."%(+-10f\{0.0}");
|
||||||
|
test(String.format("%(+-10f", 12345.6), fmt."%(+-10f\{12345.6}");
|
||||||
|
test(String.format("%,(+f", -12345.6), fmt."%,(+f\{-12345.6}");
|
||||||
|
test(String.format("%,(+f", 0.0), fmt."%,(+f\{0.0}");
|
||||||
|
test(String.format("%,(+f", 12345.6), fmt."%,(+f\{12345.6}");
|
||||||
|
test(String.format("%,(+10f", -12345.6), fmt."%,(+10f\{-12345.6}");
|
||||||
|
test(String.format("%,(+10f", 0.0), fmt."%,(+10f\{0.0}");
|
||||||
|
test(String.format("%,(+10f", 12345.6), fmt."%,(+10f\{12345.6}");
|
||||||
|
test(String.format("%,(+-10f", -12345.6), fmt."%,(+-10f\{-12345.6}");
|
||||||
|
test(String.format("%,(+-10f", 0.0), fmt."%,(+-10f\{0.0}");
|
||||||
|
test(String.format("%,(+-10f", 12345.6), fmt."%,(+-10f\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%e", -12345.6), fmt."%e\{-12345.6}");
|
||||||
|
test(String.format("%e", 0.0), fmt."%e\{0.0}");
|
||||||
|
test(String.format("%e", 12345.6), fmt."%e\{12345.6}");
|
||||||
|
test(String.format("%10e", -12345.6), fmt."%10e\{-12345.6}");
|
||||||
|
test(String.format("%10e", 0.0), fmt."%10e\{0.0}");
|
||||||
|
test(String.format("%10e", 12345.6), fmt."%10e\{12345.6}");
|
||||||
|
test(String.format("%-10e", -12345.6), fmt."%-10e\{-12345.6}");
|
||||||
|
test(String.format("%-10e", 0.0), fmt."%-10e\{0.0}");
|
||||||
|
test(String.format("%-10e", 12345.6), fmt."%-10e\{12345.6}");
|
||||||
|
test(String.format("%E", -12345.6), fmt."%E\{-12345.6}");
|
||||||
|
test(String.format("%E", 0.0), fmt."%E\{0.0}");
|
||||||
|
test(String.format("%E", 12345.6), fmt."%E\{12345.6}");
|
||||||
|
test(String.format("%10E", -12345.6), fmt."%10E\{-12345.6}");
|
||||||
|
test(String.format("%10E", 0.0), fmt."%10E\{0.0}");
|
||||||
|
test(String.format("%10E", 12345.6), fmt."%10E\{12345.6}");
|
||||||
|
test(String.format("%-10E", -12345.6), fmt."%-10E\{-12345.6}");
|
||||||
|
test(String.format("%-10E", 0.0), fmt."%-10E\{0.0}");
|
||||||
|
test(String.format("%-10E", 12345.6), fmt."%-10E\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%g", -12345.6), fmt."%g\{-12345.6}");
|
||||||
|
test(String.format("%g", 0.0), fmt."%g\{0.0}");
|
||||||
|
test(String.format("%g", 12345.6), fmt."%g\{12345.6}");
|
||||||
|
test(String.format("%10g", -12345.6), fmt."%10g\{-12345.6}");
|
||||||
|
test(String.format("%10g", 0.0), fmt."%10g\{0.0}");
|
||||||
|
test(String.format("%10g", 12345.6), fmt."%10g\{12345.6}");
|
||||||
|
test(String.format("%-10g", -12345.6), fmt."%-10g\{-12345.6}");
|
||||||
|
test(String.format("%-10g", 0.0), fmt."%-10g\{0.0}");
|
||||||
|
test(String.format("%-10g", 12345.6), fmt."%-10g\{12345.6}");
|
||||||
|
test(String.format("%G", -12345.6), fmt."%G\{-12345.6}");
|
||||||
|
test(String.format("%G", 0.0), fmt."%G\{0.0}");
|
||||||
|
test(String.format("%G", 12345.6), fmt."%G\{12345.6}");
|
||||||
|
test(String.format("%10G", -12345.6), fmt."%10G\{-12345.6}");
|
||||||
|
test(String.format("%10G", 0.0), fmt."%10G\{0.0}");
|
||||||
|
test(String.format("%10G", 12345.6), fmt."%10G\{12345.6}");
|
||||||
|
test(String.format("%-10G", -12345.6), fmt."%-10G\{-12345.6}");
|
||||||
|
test(String.format("%-10G", 0.0), fmt."%-10G\{0.0}");
|
||||||
|
test(String.format("%-10G", 12345.6), fmt."%-10G\{12345.6}");
|
||||||
|
test(String.format("%,g", -12345.6), fmt."%,g\{-12345.6}");
|
||||||
|
test(String.format("%,g", 0.0), fmt."%,g\{0.0}");
|
||||||
|
test(String.format("%,g", 12345.6), fmt."%,g\{12345.6}");
|
||||||
|
test(String.format("%,10g", -12345.6), fmt."%,10g\{-12345.6}");
|
||||||
|
test(String.format("%,10g", 0.0), fmt."%,10g\{0.0}");
|
||||||
|
test(String.format("%,10g", 12345.6), fmt."%,10g\{12345.6}");
|
||||||
|
test(String.format("%,-10g", -12345.6), fmt."%,-10g\{-12345.6}");
|
||||||
|
test(String.format("%,-10g", 0.0), fmt."%,-10g\{0.0}");
|
||||||
|
test(String.format("%,-10g", 12345.6), fmt."%,-10g\{12345.6}");
|
||||||
|
test(String.format("%,G", -12345.6), fmt."%,G\{-12345.6}");
|
||||||
|
test(String.format("%,G", 0.0), fmt."%,G\{0.0}");
|
||||||
|
test(String.format("%,G", 12345.6), fmt."%,G\{12345.6}");
|
||||||
|
test(String.format("%,10G", -12345.6), fmt."%,10G\{-12345.6}");
|
||||||
|
test(String.format("%,10G", 0.0), fmt."%,10G\{0.0}");
|
||||||
|
test(String.format("%,10G", 12345.6), fmt."%,10G\{12345.6}");
|
||||||
|
test(String.format("%,-10G", -12345.6), fmt."%,-10G\{-12345.6}");
|
||||||
|
test(String.format("%,-10G", 0.0), fmt."%,-10G\{0.0}");
|
||||||
|
test(String.format("%,-10G", 12345.6), fmt."%,-10G\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%g", -12345.6), fmt."%g\{-12345.6}");
|
||||||
|
test(String.format("%g", 0.0), fmt."%g\{0.0}");
|
||||||
|
test(String.format("%g", 12345.6), fmt."%g\{12345.6}");
|
||||||
|
test(String.format("%10g", -12345.6), fmt."%10g\{-12345.6}");
|
||||||
|
test(String.format("%10g", 0.0), fmt."%10g\{0.0}");
|
||||||
|
test(String.format("%10g", 12345.6), fmt."%10g\{12345.6}");
|
||||||
|
test(String.format("%-10g", -12345.6), fmt."%-10g\{-12345.6}");
|
||||||
|
test(String.format("%-10g", 0.0), fmt."%-10g\{0.0}");
|
||||||
|
test(String.format("%-10g", 12345.6), fmt."%-10g\{12345.6}");
|
||||||
|
test(String.format("%G", -12345.6), fmt."%G\{-12345.6}");
|
||||||
|
test(String.format("%G", 0.0), fmt."%G\{0.0}");
|
||||||
|
test(String.format("%G", 12345.6), fmt."%G\{12345.6}");
|
||||||
|
test(String.format("%10G", -12345.6), fmt."%10G\{-12345.6}");
|
||||||
|
test(String.format("%10G", 0.0), fmt."%10G\{0.0}");
|
||||||
|
test(String.format("%10G", 12345.6), fmt."%10G\{12345.6}");
|
||||||
|
test(String.format("%-10G", -12345.6), fmt."%-10G\{-12345.6}");
|
||||||
|
test(String.format("%-10G", 0.0), fmt."%-10G\{0.0}");
|
||||||
|
test(String.format("%-10G", 12345.6), fmt."%-10G\{12345.6}");
|
||||||
|
test(String.format("%,g", -12345.6), fmt."%,g\{-12345.6}");
|
||||||
|
test(String.format("%,g", 0.0), fmt."%,g\{0.0}");
|
||||||
|
test(String.format("%,g", 12345.6), fmt."%,g\{12345.6}");
|
||||||
|
test(String.format("%,10g", -12345.6), fmt."%,10g\{-12345.6}");
|
||||||
|
test(String.format("%,10g", 0.0), fmt."%,10g\{0.0}");
|
||||||
|
test(String.format("%,10g", 12345.6), fmt."%,10g\{12345.6}");
|
||||||
|
test(String.format("%,-10g", -12345.6), fmt."%,-10g\{-12345.6}");
|
||||||
|
test(String.format("%,-10g", 0.0), fmt."%,-10g\{0.0}");
|
||||||
|
test(String.format("%,-10g", 12345.6), fmt."%,-10g\{12345.6}");
|
||||||
|
test(String.format("%,G", -12345.6), fmt."%,G\{-12345.6}");
|
||||||
|
test(String.format("%,G", 0.0), fmt."%,G\{0.0}");
|
||||||
|
test(String.format("%,G", 12345.6), fmt."%,G\{12345.6}");
|
||||||
|
test(String.format("%,10G", -12345.6), fmt."%,10G\{-12345.6}");
|
||||||
|
test(String.format("%,10G", 0.0), fmt."%,10G\{0.0}");
|
||||||
|
test(String.format("%,10G", 12345.6), fmt."%,10G\{12345.6}");
|
||||||
|
test(String.format("%,-10G", -12345.6), fmt."%,-10G\{-12345.6}");
|
||||||
|
test(String.format("%,-10G", 0.0), fmt."%,-10G\{0.0}");
|
||||||
|
test(String.format("%,-10G", 12345.6), fmt."%,-10G\{12345.6}");
|
||||||
|
test(String.format("% g", -12345.6), fmt."% g\{-12345.6}");
|
||||||
|
test(String.format("% g", 0.0), fmt."% g\{0.0}");
|
||||||
|
test(String.format("% g", 12345.6), fmt."% g\{12345.6}");
|
||||||
|
test(String.format("% 10g", -12345.6), fmt."% 10g\{-12345.6}");
|
||||||
|
test(String.format("% 10g", 0.0), fmt."% 10g\{0.0}");
|
||||||
|
test(String.format("% 10g", 12345.6), fmt."% 10g\{12345.6}");
|
||||||
|
test(String.format("% -10g", -12345.6), fmt."% -10g\{-12345.6}");
|
||||||
|
test(String.format("% -10g", 0.0), fmt."% -10g\{0.0}");
|
||||||
|
test(String.format("% -10g", 12345.6), fmt."% -10g\{12345.6}");
|
||||||
|
test(String.format("% G", -12345.6), fmt."% G\{-12345.6}");
|
||||||
|
test(String.format("% G", 0.0), fmt."% G\{0.0}");
|
||||||
|
test(String.format("% G", 12345.6), fmt."% G\{12345.6}");
|
||||||
|
test(String.format("% 10G", -12345.6), fmt."% 10G\{-12345.6}");
|
||||||
|
test(String.format("% 10G", 0.0), fmt."% 10G\{0.0}");
|
||||||
|
test(String.format("% 10G", 12345.6), fmt."% 10G\{12345.6}");
|
||||||
|
test(String.format("% -10G", -12345.6), fmt."% -10G\{-12345.6}");
|
||||||
|
test(String.format("% -10G", 0.0), fmt."% -10G\{0.0}");
|
||||||
|
test(String.format("% -10G", 12345.6), fmt."% -10G\{12345.6}");
|
||||||
|
test(String.format("%, g", -12345.6), fmt."%, g\{-12345.6}");
|
||||||
|
test(String.format("%, g", 0.0), fmt."%, g\{0.0}");
|
||||||
|
test(String.format("%, g", 12345.6), fmt."%, g\{12345.6}");
|
||||||
|
test(String.format("%, 10g", -12345.6), fmt."%, 10g\{-12345.6}");
|
||||||
|
test(String.format("%, 10g", 0.0), fmt."%, 10g\{0.0}");
|
||||||
|
test(String.format("%, 10g", 12345.6), fmt."%, 10g\{12345.6}");
|
||||||
|
test(String.format("%, -10g", -12345.6), fmt."%, -10g\{-12345.6}");
|
||||||
|
test(String.format("%, -10g", 0.0), fmt."%, -10g\{0.0}");
|
||||||
|
test(String.format("%, -10g", 12345.6), fmt."%, -10g\{12345.6}");
|
||||||
|
test(String.format("%, G", -12345.6), fmt."%, G\{-12345.6}");
|
||||||
|
test(String.format("%, G", 0.0), fmt."%, G\{0.0}");
|
||||||
|
test(String.format("%, G", 12345.6), fmt."%, G\{12345.6}");
|
||||||
|
test(String.format("%, 10G", -12345.6), fmt."%, 10G\{-12345.6}");
|
||||||
|
test(String.format("%, 10G", 0.0), fmt."%, 10G\{0.0}");
|
||||||
|
test(String.format("%, 10G", 12345.6), fmt."%, 10G\{12345.6}");
|
||||||
|
test(String.format("%, -10G", -12345.6), fmt."%, -10G\{-12345.6}");
|
||||||
|
test(String.format("%, -10G", 0.0), fmt."%, -10G\{0.0}");
|
||||||
|
test(String.format("%, -10G", 12345.6), fmt."%, -10G\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%g", -12345.6), fmt."%g\{-12345.6}");
|
||||||
|
test(String.format("%g", 0.0), fmt."%g\{0.0}");
|
||||||
|
test(String.format("%g", 12345.6), fmt."%g\{12345.6}");
|
||||||
|
test(String.format("%10g", -12345.6), fmt."%10g\{-12345.6}");
|
||||||
|
test(String.format("%10g", 0.0), fmt."%10g\{0.0}");
|
||||||
|
test(String.format("%10g", 12345.6), fmt."%10g\{12345.6}");
|
||||||
|
test(String.format("%-10g", -12345.6), fmt."%-10g\{-12345.6}");
|
||||||
|
test(String.format("%-10g", 0.0), fmt."%-10g\{0.0}");
|
||||||
|
test(String.format("%-10g", 12345.6), fmt."%-10g\{12345.6}");
|
||||||
|
test(String.format("%G", -12345.6), fmt."%G\{-12345.6}");
|
||||||
|
test(String.format("%G", 0.0), fmt."%G\{0.0}");
|
||||||
|
test(String.format("%G", 12345.6), fmt."%G\{12345.6}");
|
||||||
|
test(String.format("%10G", -12345.6), fmt."%10G\{-12345.6}");
|
||||||
|
test(String.format("%10G", 0.0), fmt."%10G\{0.0}");
|
||||||
|
test(String.format("%10G", 12345.6), fmt."%10G\{12345.6}");
|
||||||
|
test(String.format("%-10G", -12345.6), fmt."%-10G\{-12345.6}");
|
||||||
|
test(String.format("%-10G", 0.0), fmt."%-10G\{0.0}");
|
||||||
|
test(String.format("%-10G", 12345.6), fmt."%-10G\{12345.6}");
|
||||||
|
test(String.format("%,g", -12345.6), fmt."%,g\{-12345.6}");
|
||||||
|
test(String.format("%,g", 0.0), fmt."%,g\{0.0}");
|
||||||
|
test(String.format("%,g", 12345.6), fmt."%,g\{12345.6}");
|
||||||
|
test(String.format("%,10g", -12345.6), fmt."%,10g\{-12345.6}");
|
||||||
|
test(String.format("%,10g", 0.0), fmt."%,10g\{0.0}");
|
||||||
|
test(String.format("%,10g", 12345.6), fmt."%,10g\{12345.6}");
|
||||||
|
test(String.format("%,-10g", -12345.6), fmt."%,-10g\{-12345.6}");
|
||||||
|
test(String.format("%,-10g", 0.0), fmt."%,-10g\{0.0}");
|
||||||
|
test(String.format("%,-10g", 12345.6), fmt."%,-10g\{12345.6}");
|
||||||
|
test(String.format("%,G", -12345.6), fmt."%,G\{-12345.6}");
|
||||||
|
test(String.format("%,G", 0.0), fmt."%,G\{0.0}");
|
||||||
|
test(String.format("%,G", 12345.6), fmt."%,G\{12345.6}");
|
||||||
|
test(String.format("%,10G", -12345.6), fmt."%,10G\{-12345.6}");
|
||||||
|
test(String.format("%,10G", 0.0), fmt."%,10G\{0.0}");
|
||||||
|
test(String.format("%,10G", 12345.6), fmt."%,10G\{12345.6}");
|
||||||
|
test(String.format("%,-10G", -12345.6), fmt."%,-10G\{-12345.6}");
|
||||||
|
test(String.format("%,-10G", 0.0), fmt."%,-10G\{0.0}");
|
||||||
|
test(String.format("%,-10G", 12345.6), fmt."%,-10G\{12345.6}");
|
||||||
|
test(String.format("%+g", -12345.6), fmt."%+g\{-12345.6}");
|
||||||
|
test(String.format("%+g", 0.0), fmt."%+g\{0.0}");
|
||||||
|
test(String.format("%+g", 12345.6), fmt."%+g\{12345.6}");
|
||||||
|
test(String.format("%+10g", -12345.6), fmt."%+10g\{-12345.6}");
|
||||||
|
test(String.format("%+10g", 0.0), fmt."%+10g\{0.0}");
|
||||||
|
test(String.format("%+10g", 12345.6), fmt."%+10g\{12345.6}");
|
||||||
|
test(String.format("%+-10g", -12345.6), fmt."%+-10g\{-12345.6}");
|
||||||
|
test(String.format("%+-10g", 0.0), fmt."%+-10g\{0.0}");
|
||||||
|
test(String.format("%+-10g", 12345.6), fmt."%+-10g\{12345.6}");
|
||||||
|
test(String.format("%+G", -12345.6), fmt."%+G\{-12345.6}");
|
||||||
|
test(String.format("%+G", 0.0), fmt."%+G\{0.0}");
|
||||||
|
test(String.format("%+G", 12345.6), fmt."%+G\{12345.6}");
|
||||||
|
test(String.format("%+10G", -12345.6), fmt."%+10G\{-12345.6}");
|
||||||
|
test(String.format("%+10G", 0.0), fmt."%+10G\{0.0}");
|
||||||
|
test(String.format("%+10G", 12345.6), fmt."%+10G\{12345.6}");
|
||||||
|
test(String.format("%+-10G", -12345.6), fmt."%+-10G\{-12345.6}");
|
||||||
|
test(String.format("%+-10G", 0.0), fmt."%+-10G\{0.0}");
|
||||||
|
test(String.format("%+-10G", 12345.6), fmt."%+-10G\{12345.6}");
|
||||||
|
test(String.format("%,+g", -12345.6), fmt."%,+g\{-12345.6}");
|
||||||
|
test(String.format("%,+g", 0.0), fmt."%,+g\{0.0}");
|
||||||
|
test(String.format("%,+g", 12345.6), fmt."%,+g\{12345.6}");
|
||||||
|
test(String.format("%,+10g", -12345.6), fmt."%,+10g\{-12345.6}");
|
||||||
|
test(String.format("%,+10g", 0.0), fmt."%,+10g\{0.0}");
|
||||||
|
test(String.format("%,+10g", 12345.6), fmt."%,+10g\{12345.6}");
|
||||||
|
test(String.format("%,+-10g", -12345.6), fmt."%,+-10g\{-12345.6}");
|
||||||
|
test(String.format("%,+-10g", 0.0), fmt."%,+-10g\{0.0}");
|
||||||
|
test(String.format("%,+-10g", 12345.6), fmt."%,+-10g\{12345.6}");
|
||||||
|
test(String.format("%,+G", -12345.6), fmt."%,+G\{-12345.6}");
|
||||||
|
test(String.format("%,+G", 0.0), fmt."%,+G\{0.0}");
|
||||||
|
test(String.format("%,+G", 12345.6), fmt."%,+G\{12345.6}");
|
||||||
|
test(String.format("%,+10G", -12345.6), fmt."%,+10G\{-12345.6}");
|
||||||
|
test(String.format("%,+10G", 0.0), fmt."%,+10G\{0.0}");
|
||||||
|
test(String.format("%,+10G", 12345.6), fmt."%,+10G\{12345.6}");
|
||||||
|
test(String.format("%,+-10G", -12345.6), fmt."%,+-10G\{-12345.6}");
|
||||||
|
test(String.format("%,+-10G", 0.0), fmt."%,+-10G\{0.0}");
|
||||||
|
test(String.format("%,+-10G", 12345.6), fmt."%,+-10G\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%g", -12345.6), fmt."%g\{-12345.6}");
|
||||||
|
test(String.format("%g", 0.0), fmt."%g\{0.0}");
|
||||||
|
test(String.format("%g", 12345.6), fmt."%g\{12345.6}");
|
||||||
|
test(String.format("%10g", -12345.6), fmt."%10g\{-12345.6}");
|
||||||
|
test(String.format("%10g", 0.0), fmt."%10g\{0.0}");
|
||||||
|
test(String.format("%10g", 12345.6), fmt."%10g\{12345.6}");
|
||||||
|
test(String.format("%-10g", -12345.6), fmt."%-10g\{-12345.6}");
|
||||||
|
test(String.format("%-10g", 0.0), fmt."%-10g\{0.0}");
|
||||||
|
test(String.format("%-10g", 12345.6), fmt."%-10g\{12345.6}");
|
||||||
|
test(String.format("%G", -12345.6), fmt."%G\{-12345.6}");
|
||||||
|
test(String.format("%G", 0.0), fmt."%G\{0.0}");
|
||||||
|
test(String.format("%G", 12345.6), fmt."%G\{12345.6}");
|
||||||
|
test(String.format("%10G", -12345.6), fmt."%10G\{-12345.6}");
|
||||||
|
test(String.format("%10G", 0.0), fmt."%10G\{0.0}");
|
||||||
|
test(String.format("%10G", 12345.6), fmt."%10G\{12345.6}");
|
||||||
|
test(String.format("%-10G", -12345.6), fmt."%-10G\{-12345.6}");
|
||||||
|
test(String.format("%-10G", 0.0), fmt."%-10G\{0.0}");
|
||||||
|
test(String.format("%-10G", 12345.6), fmt."%-10G\{12345.6}");
|
||||||
|
test(String.format("%,g", -12345.6), fmt."%,g\{-12345.6}");
|
||||||
|
test(String.format("%,g", 0.0), fmt."%,g\{0.0}");
|
||||||
|
test(String.format("%,g", 12345.6), fmt."%,g\{12345.6}");
|
||||||
|
test(String.format("%,10g", -12345.6), fmt."%,10g\{-12345.6}");
|
||||||
|
test(String.format("%,10g", 0.0), fmt."%,10g\{0.0}");
|
||||||
|
test(String.format("%,10g", 12345.6), fmt."%,10g\{12345.6}");
|
||||||
|
test(String.format("%,-10g", -12345.6), fmt."%,-10g\{-12345.6}");
|
||||||
|
test(String.format("%,-10g", 0.0), fmt."%,-10g\{0.0}");
|
||||||
|
test(String.format("%,-10g", 12345.6), fmt."%,-10g\{12345.6}");
|
||||||
|
test(String.format("%,G", -12345.6), fmt."%,G\{-12345.6}");
|
||||||
|
test(String.format("%,G", 0.0), fmt."%,G\{0.0}");
|
||||||
|
test(String.format("%,G", 12345.6), fmt."%,G\{12345.6}");
|
||||||
|
test(String.format("%,10G", -12345.6), fmt."%,10G\{-12345.6}");
|
||||||
|
test(String.format("%,10G", 0.0), fmt."%,10G\{0.0}");
|
||||||
|
test(String.format("%,10G", 12345.6), fmt."%,10G\{12345.6}");
|
||||||
|
test(String.format("%,-10G", -12345.6), fmt."%,-10G\{-12345.6}");
|
||||||
|
test(String.format("%,-10G", 0.0), fmt."%,-10G\{0.0}");
|
||||||
|
test(String.format("%,-10G", 12345.6), fmt."%,-10G\{12345.6}");
|
||||||
|
test(String.format("%(g", -12345.6), fmt."%(g\{-12345.6}");
|
||||||
|
test(String.format("%(g", 0.0), fmt."%(g\{0.0}");
|
||||||
|
test(String.format("%(g", 12345.6), fmt."%(g\{12345.6}");
|
||||||
|
test(String.format("%(10g", -12345.6), fmt."%(10g\{-12345.6}");
|
||||||
|
test(String.format("%(10g", 0.0), fmt."%(10g\{0.0}");
|
||||||
|
test(String.format("%(10g", 12345.6), fmt."%(10g\{12345.6}");
|
||||||
|
test(String.format("%(-10g", -12345.6), fmt."%(-10g\{-12345.6}");
|
||||||
|
test(String.format("%(-10g", 0.0), fmt."%(-10g\{0.0}");
|
||||||
|
test(String.format("%(-10g", 12345.6), fmt."%(-10g\{12345.6}");
|
||||||
|
test(String.format("%(G", -12345.6), fmt."%(G\{-12345.6}");
|
||||||
|
test(String.format("%(G", 0.0), fmt."%(G\{0.0}");
|
||||||
|
test(String.format("%(G", 12345.6), fmt."%(G\{12345.6}");
|
||||||
|
test(String.format("%(10G", -12345.6), fmt."%(10G\{-12345.6}");
|
||||||
|
test(String.format("%(10G", 0.0), fmt."%(10G\{0.0}");
|
||||||
|
test(String.format("%(10G", 12345.6), fmt."%(10G\{12345.6}");
|
||||||
|
test(String.format("%(-10G", -12345.6), fmt."%(-10G\{-12345.6}");
|
||||||
|
test(String.format("%(-10G", 0.0), fmt."%(-10G\{0.0}");
|
||||||
|
test(String.format("%(-10G", 12345.6), fmt."%(-10G\{12345.6}");
|
||||||
|
test(String.format("%,(g", -12345.6), fmt."%,(g\{-12345.6}");
|
||||||
|
test(String.format("%,(g", 0.0), fmt."%,(g\{0.0}");
|
||||||
|
test(String.format("%,(g", 12345.6), fmt."%,(g\{12345.6}");
|
||||||
|
test(String.format("%,(10g", -12345.6), fmt."%,(10g\{-12345.6}");
|
||||||
|
test(String.format("%,(10g", 0.0), fmt."%,(10g\{0.0}");
|
||||||
|
test(String.format("%,(10g", 12345.6), fmt."%,(10g\{12345.6}");
|
||||||
|
test(String.format("%,(-10g", -12345.6), fmt."%,(-10g\{-12345.6}");
|
||||||
|
test(String.format("%,(-10g", 0.0), fmt."%,(-10g\{0.0}");
|
||||||
|
test(String.format("%,(-10g", 12345.6), fmt."%,(-10g\{12345.6}");
|
||||||
|
test(String.format("%,(G", -12345.6), fmt."%,(G\{-12345.6}");
|
||||||
|
test(String.format("%,(G", 0.0), fmt."%,(G\{0.0}");
|
||||||
|
test(String.format("%,(G", 12345.6), fmt."%,(G\{12345.6}");
|
||||||
|
test(String.format("%,(10G", -12345.6), fmt."%,(10G\{-12345.6}");
|
||||||
|
test(String.format("%,(10G", 0.0), fmt."%,(10G\{0.0}");
|
||||||
|
test(String.format("%,(10G", 12345.6), fmt."%,(10G\{12345.6}");
|
||||||
|
test(String.format("%,(-10G", -12345.6), fmt."%,(-10G\{-12345.6}");
|
||||||
|
test(String.format("%,(-10G", 0.0), fmt."%,(-10G\{0.0}");
|
||||||
|
test(String.format("%,(-10G", 12345.6), fmt."%,(-10G\{12345.6}");
|
||||||
|
test(String.format("%+g", -12345.6), fmt."%+g\{-12345.6}");
|
||||||
|
test(String.format("%+g", 0.0), fmt."%+g\{0.0}");
|
||||||
|
test(String.format("%+g", 12345.6), fmt."%+g\{12345.6}");
|
||||||
|
test(String.format("%+10g", -12345.6), fmt."%+10g\{-12345.6}");
|
||||||
|
test(String.format("%+10g", 0.0), fmt."%+10g\{0.0}");
|
||||||
|
test(String.format("%+10g", 12345.6), fmt."%+10g\{12345.6}");
|
||||||
|
test(String.format("%+-10g", -12345.6), fmt."%+-10g\{-12345.6}");
|
||||||
|
test(String.format("%+-10g", 0.0), fmt."%+-10g\{0.0}");
|
||||||
|
test(String.format("%+-10g", 12345.6), fmt."%+-10g\{12345.6}");
|
||||||
|
test(String.format("%+G", -12345.6), fmt."%+G\{-12345.6}");
|
||||||
|
test(String.format("%+G", 0.0), fmt."%+G\{0.0}");
|
||||||
|
test(String.format("%+G", 12345.6), fmt."%+G\{12345.6}");
|
||||||
|
test(String.format("%+10G", -12345.6), fmt."%+10G\{-12345.6}");
|
||||||
|
test(String.format("%+10G", 0.0), fmt."%+10G\{0.0}");
|
||||||
|
test(String.format("%+10G", 12345.6), fmt."%+10G\{12345.6}");
|
||||||
|
test(String.format("%+-10G", -12345.6), fmt."%+-10G\{-12345.6}");
|
||||||
|
test(String.format("%+-10G", 0.0), fmt."%+-10G\{0.0}");
|
||||||
|
test(String.format("%+-10G", 12345.6), fmt."%+-10G\{12345.6}");
|
||||||
|
test(String.format("%,+g", -12345.6), fmt."%,+g\{-12345.6}");
|
||||||
|
test(String.format("%,+g", 0.0), fmt."%,+g\{0.0}");
|
||||||
|
test(String.format("%,+g", 12345.6), fmt."%,+g\{12345.6}");
|
||||||
|
test(String.format("%,+10g", -12345.6), fmt."%,+10g\{-12345.6}");
|
||||||
|
test(String.format("%,+10g", 0.0), fmt."%,+10g\{0.0}");
|
||||||
|
test(String.format("%,+10g", 12345.6), fmt."%,+10g\{12345.6}");
|
||||||
|
test(String.format("%,+-10g", -12345.6), fmt."%,+-10g\{-12345.6}");
|
||||||
|
test(String.format("%,+-10g", 0.0), fmt."%,+-10g\{0.0}");
|
||||||
|
test(String.format("%,+-10g", 12345.6), fmt."%,+-10g\{12345.6}");
|
||||||
|
test(String.format("%,+G", -12345.6), fmt."%,+G\{-12345.6}");
|
||||||
|
test(String.format("%,+G", 0.0), fmt."%,+G\{0.0}");
|
||||||
|
test(String.format("%,+G", 12345.6), fmt."%,+G\{12345.6}");
|
||||||
|
test(String.format("%,+10G", -12345.6), fmt."%,+10G\{-12345.6}");
|
||||||
|
test(String.format("%,+10G", 0.0), fmt."%,+10G\{0.0}");
|
||||||
|
test(String.format("%,+10G", 12345.6), fmt."%,+10G\{12345.6}");
|
||||||
|
test(String.format("%,+-10G", -12345.6), fmt."%,+-10G\{-12345.6}");
|
||||||
|
test(String.format("%,+-10G", 0.0), fmt."%,+-10G\{0.0}");
|
||||||
|
test(String.format("%,+-10G", 12345.6), fmt."%,+-10G\{12345.6}");
|
||||||
|
test(String.format("%(+g", -12345.6), fmt."%(+g\{-12345.6}");
|
||||||
|
test(String.format("%(+g", 0.0), fmt."%(+g\{0.0}");
|
||||||
|
test(String.format("%(+g", 12345.6), fmt."%(+g\{12345.6}");
|
||||||
|
test(String.format("%(+10g", -12345.6), fmt."%(+10g\{-12345.6}");
|
||||||
|
test(String.format("%(+10g", 0.0), fmt."%(+10g\{0.0}");
|
||||||
|
test(String.format("%(+10g", 12345.6), fmt."%(+10g\{12345.6}");
|
||||||
|
test(String.format("%(+-10g", -12345.6), fmt."%(+-10g\{-12345.6}");
|
||||||
|
test(String.format("%(+-10g", 0.0), fmt."%(+-10g\{0.0}");
|
||||||
|
test(String.format("%(+-10g", 12345.6), fmt."%(+-10g\{12345.6}");
|
||||||
|
test(String.format("%(+G", -12345.6), fmt."%(+G\{-12345.6}");
|
||||||
|
test(String.format("%(+G", 0.0), fmt."%(+G\{0.0}");
|
||||||
|
test(String.format("%(+G", 12345.6), fmt."%(+G\{12345.6}");
|
||||||
|
test(String.format("%(+10G", -12345.6), fmt."%(+10G\{-12345.6}");
|
||||||
|
test(String.format("%(+10G", 0.0), fmt."%(+10G\{0.0}");
|
||||||
|
test(String.format("%(+10G", 12345.6), fmt."%(+10G\{12345.6}");
|
||||||
|
test(String.format("%(+-10G", -12345.6), fmt."%(+-10G\{-12345.6}");
|
||||||
|
test(String.format("%(+-10G", 0.0), fmt."%(+-10G\{0.0}");
|
||||||
|
test(String.format("%(+-10G", 12345.6), fmt."%(+-10G\{12345.6}");
|
||||||
|
test(String.format("%,(+g", -12345.6), fmt."%,(+g\{-12345.6}");
|
||||||
|
test(String.format("%,(+g", 0.0), fmt."%,(+g\{0.0}");
|
||||||
|
test(String.format("%,(+g", 12345.6), fmt."%,(+g\{12345.6}");
|
||||||
|
test(String.format("%,(+10g", -12345.6), fmt."%,(+10g\{-12345.6}");
|
||||||
|
test(String.format("%,(+10g", 0.0), fmt."%,(+10g\{0.0}");
|
||||||
|
test(String.format("%,(+10g", 12345.6), fmt."%,(+10g\{12345.6}");
|
||||||
|
test(String.format("%,(+-10g", -12345.6), fmt."%,(+-10g\{-12345.6}");
|
||||||
|
test(String.format("%,(+-10g", 0.0), fmt."%,(+-10g\{0.0}");
|
||||||
|
test(String.format("%,(+-10g", 12345.6), fmt."%,(+-10g\{12345.6}");
|
||||||
|
test(String.format("%,(+G", -12345.6), fmt."%,(+G\{-12345.6}");
|
||||||
|
test(String.format("%,(+G", 0.0), fmt."%,(+G\{0.0}");
|
||||||
|
test(String.format("%,(+G", 12345.6), fmt."%,(+G\{12345.6}");
|
||||||
|
test(String.format("%,(+10G", -12345.6), fmt."%,(+10G\{-12345.6}");
|
||||||
|
test(String.format("%,(+10G", 0.0), fmt."%,(+10G\{0.0}");
|
||||||
|
test(String.format("%,(+10G", 12345.6), fmt."%,(+10G\{12345.6}");
|
||||||
|
test(String.format("%,(+-10G", -12345.6), fmt."%,(+-10G\{-12345.6}");
|
||||||
|
test(String.format("%,(+-10G", 0.0), fmt."%,(+-10G\{0.0}");
|
||||||
|
test(String.format("%,(+-10G", 12345.6), fmt."%,(+-10G\{12345.6}");
|
||||||
|
|
||||||
|
test(String.format("%a", -12345.6), fmt."%a\{-12345.6}");
|
||||||
|
test(String.format("%a", 0.0), fmt."%a\{0.0}");
|
||||||
|
test(String.format("%a", 12345.6), fmt."%a\{12345.6}");
|
||||||
|
test(String.format("%10a", -12345.6), fmt."%10a\{-12345.6}");
|
||||||
|
test(String.format("%10a", 0.0), fmt."%10a\{0.0}");
|
||||||
|
test(String.format("%10a", 12345.6), fmt."%10a\{12345.6}");
|
||||||
|
test(String.format("%-10a", -12345.6), fmt."%-10a\{-12345.6}");
|
||||||
|
test(String.format("%-10a", 0.0), fmt."%-10a\{0.0}");
|
||||||
|
test(String.format("%-10a", 12345.6), fmt."%-10a\{12345.6}");
|
||||||
|
test(String.format("%A", -12345.6), fmt."%A\{-12345.6}");
|
||||||
|
test(String.format("%A", 0.0), fmt."%A\{0.0}");
|
||||||
|
test(String.format("%A", 12345.6), fmt."%A\{12345.6}");
|
||||||
|
test(String.format("%10A", -12345.6), fmt."%10A\{-12345.6}");
|
||||||
|
test(String.format("%10A", 0.0), fmt."%10A\{0.0}");
|
||||||
|
test(String.format("%10A", 12345.6), fmt."%10A\{12345.6}");
|
||||||
|
test(String.format("%-10A", -12345.6), fmt."%-10A\{-12345.6}");
|
||||||
|
test(String.format("%-10A", 0.0), fmt."%-10A\{0.0}");
|
||||||
|
test(String.format("%-10A", 12345.6), fmt."%-10A\{12345.6}");
|
||||||
|
}
|
||||||
|
}
|
308
test/jdk/java/lang/template/StringTemplateTest.java
Normal file
308
test/jdk/java/lang/template/StringTemplateTest.java
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 0000000
|
||||||
|
* @summary Exercise runtime handing of templated strings.
|
||||||
|
* @enablePreview true
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.FilterOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import javax.tools.FileObject;
|
||||||
|
import javax.tools.ForwardingJavaFileManager;
|
||||||
|
import javax.tools.JavaFileManager;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
import javax.tools.SimpleJavaFileObject;
|
||||||
|
import static javax.tools.StandardLocation.CLASS_OUTPUT;
|
||||||
|
import javax.tools.ToolProvider;
|
||||||
|
|
||||||
|
public class StringTemplateTest {
|
||||||
|
enum Category{GENERAL, CHARACTER, INTEGRAL, BIG_INT, FLOATING, BIG_FLOAT, DATE};
|
||||||
|
|
||||||
|
static final String[] GENERAL = {"true", "false", "(Object)null", "STR", "BO", "BOOL", "(Boolean)null"};
|
||||||
|
static final String[] CHARS = {"C", "CHAR", "(Character)null"};
|
||||||
|
static final String[] INTS = {"L", "LONG", "I", "INT", "S", "SHORT", "BY", "BYTE", "Long.MAX_VALUE", "Long.MIN_VALUE", "(Long)null", "(Integer)null", "(Short)null", "(Byte)null"};
|
||||||
|
static final String[] BIGINTS = {};
|
||||||
|
static final String[] FLOATS = {"F", "FLOAT", "D", "DOUBLE", "Double.NEGATIVE_INFINITY", "Double.NaN", "Double.MAX_VALUE", "(Double)null", "(Float)null"};
|
||||||
|
static final String[] BIGFLOATS = {};
|
||||||
|
static final String[] DATES = {};
|
||||||
|
|
||||||
|
final Random r = new Random(1);
|
||||||
|
|
||||||
|
String randomValue(Category category) {
|
||||||
|
return switch (category) {
|
||||||
|
case GENERAL -> randomChoice(
|
||||||
|
GENERAL,
|
||||||
|
() -> randomValue(Category.CHARACTER),
|
||||||
|
() -> randomValue(Category.INTEGRAL),
|
||||||
|
() -> randomValue(Category.BIG_INT),
|
||||||
|
() -> randomValue(Category.FLOATING),
|
||||||
|
() -> randomValue(Category.BIG_FLOAT),
|
||||||
|
() -> randomValue(Category.DATE),
|
||||||
|
() -> "\"" + randomString(r.nextInt(10)) + "\"");
|
||||||
|
case CHARACTER -> randomChoice(
|
||||||
|
CHARS,
|
||||||
|
() -> "\'" + randomString(1) + "\'");
|
||||||
|
case INTEGRAL -> randomChoice(
|
||||||
|
INTS,
|
||||||
|
() -> "(byte)" + String.valueOf(r.nextInt(Byte.MIN_VALUE, Byte.MAX_VALUE)),
|
||||||
|
() -> "(short)" + String.valueOf(r.nextInt(Short.MIN_VALUE, Short.MAX_VALUE)),
|
||||||
|
() -> String.valueOf(r.nextInt()),
|
||||||
|
() -> r.nextLong() + "l");
|
||||||
|
case BIG_INT -> randomChoice(
|
||||||
|
BIGINTS,
|
||||||
|
() -> "new java.math.BigInteger(\"" + r.nextLong() + "\")");
|
||||||
|
case FLOATING -> randomChoice(
|
||||||
|
FLOATS,
|
||||||
|
() -> String.valueOf(r.nextDouble()),
|
||||||
|
() -> r.nextFloat() + "f");
|
||||||
|
case BIG_FLOAT -> randomChoice(
|
||||||
|
BIGFLOATS,
|
||||||
|
() -> "new java.math.BigDecimal(" + r.nextDouble() + ")");
|
||||||
|
case DATE -> randomChoice(
|
||||||
|
DATES,
|
||||||
|
() -> "new java.util.Date(" + r.nextLong() + "l)",
|
||||||
|
() -> r.nextLong() + "l");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomChoice(Supplier<String>... suppl) {
|
||||||
|
return suppl[r.nextInt(suppl.length)].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomChoice(String... values) {
|
||||||
|
return values[r.nextInt(values.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomChoice(String[] values, Supplier<String>... suppl) {
|
||||||
|
int i = r.nextInt(values.length + suppl.length);
|
||||||
|
return i < values.length ? values[i] : suppl[i - values.length].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomString(int length) {
|
||||||
|
var sb = new StringBuilder(length << 2);
|
||||||
|
while (length-- > 0) {
|
||||||
|
char ch = (char)r.nextInt(9, 128);
|
||||||
|
var s = switch (ch) {
|
||||||
|
case '\t' -> "\\t";
|
||||||
|
case '\'' -> "\\\'";
|
||||||
|
case '"' -> "\\\"";
|
||||||
|
case '\r' -> "\\r";
|
||||||
|
case '\\' -> "\\\\";
|
||||||
|
case '\n' -> "\\n";
|
||||||
|
case '\f' -> "\\f";
|
||||||
|
case '\b' -> "\\b";
|
||||||
|
default -> ch + "";
|
||||||
|
};
|
||||||
|
sb.append(s);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomFormat(Category category) {
|
||||||
|
char c;
|
||||||
|
return "%" + switch (category) {
|
||||||
|
case GENERAL -> randomWidth("-") + randomPrecision() + randomChar("bBhHsS");
|
||||||
|
case CHARACTER -> randomWidth("-") + randomChar("cC");
|
||||||
|
case INTEGRAL -> switch (c = randomChar("doxX")) {
|
||||||
|
case 'd' -> randomFlags("+ ,(");
|
||||||
|
default -> randomFlags("");
|
||||||
|
} + randomWidth("-0") + c;
|
||||||
|
case BIG_INT -> switch (c = randomChar("doxX")) {
|
||||||
|
case 'd' -> randomFlags("+ ,(");
|
||||||
|
default -> randomFlags("+ (");
|
||||||
|
} + randomWidth("-0") + c;
|
||||||
|
case FLOATING -> switch (c = randomChar("eEfaAgG")) {
|
||||||
|
case 'a', 'A' -> randomFlags("+ ") + randomWidth("-0");
|
||||||
|
case 'e', 'E' -> randomFlags("+ (") + randomWidth("-0") + randomPrecision();
|
||||||
|
default -> randomFlags("+ ,(") + randomWidth("-0") + randomPrecision();
|
||||||
|
} + c;
|
||||||
|
case BIG_FLOAT -> switch (c = randomChar("eEfgG")) {
|
||||||
|
case 'e', 'E' -> randomFlags("+ (") + randomWidth("-0") + randomPrecision();
|
||||||
|
default -> randomFlags("+ ,(") + randomWidth("-0") + randomPrecision();
|
||||||
|
} + c;
|
||||||
|
case DATE -> randomWidth("-") + randomChar("tT") + randomChar("BbhAaCYyjmdeRTrDFc");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomFlags(String flags) {
|
||||||
|
var sb = new StringBuilder(flags.length());
|
||||||
|
for (var f : flags.toCharArray()) {
|
||||||
|
if (r.nextBoolean() && (f != ' ' || sb.length() == 0 || sb.charAt(sb.length() - 1) != '+')) sb.append(f);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
char randomChar(String chars) {
|
||||||
|
return chars.charAt(r.nextInt(chars.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomWidth(String flags) {
|
||||||
|
var f = r.nextInt(flags.length() + 1);
|
||||||
|
return r.nextBoolean() ? (r.nextBoolean() ? flags.charAt(r.nextInt(flags.length())) : "") + String.valueOf(r.nextInt(10) + 1) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String randomPrecision() {
|
||||||
|
return r.nextBoolean() ? '.' + String.valueOf(r.nextInt(10) + 1) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<?> compile() throws Exception {
|
||||||
|
var classes = new HashMap<String, byte[]>();
|
||||||
|
var fileManager = new ForwardingJavaFileManager(ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null)) {
|
||||||
|
@Override
|
||||||
|
public ClassLoader getClassLoader(JavaFileManager.Location location) {
|
||||||
|
return new ClassLoader() {
|
||||||
|
@Override
|
||||||
|
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||||
|
try {
|
||||||
|
return super.loadClass(name);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
byte[] classData = classes.get(name);
|
||||||
|
return defineClass(name, classData, 0, classData.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String name, JavaFileObject.Kind kind, FileObject originatingSource) throws UnsupportedOperationException {
|
||||||
|
return new SimpleJavaFileObject(URI.create(name + ".class"), JavaFileObject.Kind.CLASS) {
|
||||||
|
@Override
|
||||||
|
public OutputStream openOutputStream() {
|
||||||
|
return new FilterOutputStream(new ByteArrayOutputStream()) {
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
classes.put(name, ((ByteArrayOutputStream)out).toByteArray());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var source = genSource();
|
||||||
|
// System.out.println(source);
|
||||||
|
if (ToolProvider.getSystemJavaCompiler().getTask(null, fileManager, null,
|
||||||
|
List.of("--enable-preview", "-source", String.valueOf(Runtime.version().feature())), null,
|
||||||
|
List.of(new SimpleJavaFileObject(URI.create("StringTemplateTest$.java"), JavaFileObject.Kind.SOURCE) {
|
||||||
|
@Override
|
||||||
|
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
})).call()) {
|
||||||
|
return fileManager.getClassLoader(CLASS_OUTPUT).loadClass("StringTemplateTest$");
|
||||||
|
} else {
|
||||||
|
throw new AssertionError("compilation failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String genFragments(Category c) {
|
||||||
|
var fragments = new LinkedList<String>();
|
||||||
|
for (int i = 0; i < 1500; i++) {
|
||||||
|
var format = randomFormat(c);
|
||||||
|
var value = randomValue(c);
|
||||||
|
var qValue = value.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||||
|
fragments.add(STR."test(FMT.\"\{format}\\{\{value}}\", \"\{format}\", \"\{qValue}\", \{value}, log);");
|
||||||
|
}
|
||||||
|
return String.join("\n ", fragments);
|
||||||
|
}
|
||||||
|
|
||||||
|
String genSource() {
|
||||||
|
return STR."""
|
||||||
|
import java.util.FormatProcessor;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class StringTemplateTest$ {
|
||||||
|
static final FormatProcessor FMT = FormatProcessor.create(Locale.US);
|
||||||
|
static String STR = "this is static String";
|
||||||
|
static char C = 'c';
|
||||||
|
static Character CHAR = 'C';
|
||||||
|
static long L = -12345678910l;
|
||||||
|
static Long LONG = 9876543210l;
|
||||||
|
static int I = 42;
|
||||||
|
static Integer INT = -49;
|
||||||
|
static boolean BO = true;
|
||||||
|
static Boolean BOOL = false;
|
||||||
|
static short S = 13;
|
||||||
|
static Short SHORT = -17;
|
||||||
|
static byte BY = -3;
|
||||||
|
static Byte BYTE = 12;
|
||||||
|
static float F = 4.789f;
|
||||||
|
static Float FLOAT = -0.000006f;
|
||||||
|
static double D = 6545745.6734654563;
|
||||||
|
static Double DOUBLE = -4323.7645676574;
|
||||||
|
|
||||||
|
public static void run(java.util.List<String> log) {
|
||||||
|
runGeneral(log);
|
||||||
|
runCharacter(log);
|
||||||
|
runIntegral(log);
|
||||||
|
runBigInt(log);
|
||||||
|
runFloating(log);
|
||||||
|
runBigFloat(log);
|
||||||
|
runDate(log);
|
||||||
|
}
|
||||||
|
public static void runGeneral(java.util.List<String> log) {
|
||||||
|
\{genFragments(Category.GENERAL)}
|
||||||
|
}
|
||||||
|
public static void runCharacter(java.util.List<String> log) {
|
||||||
|
\{genFragments(Category.CHARACTER)}
|
||||||
|
}
|
||||||
|
public static void runIntegral(java.util.List<String> log) {
|
||||||
|
\{genFragments(Category.INTEGRAL)}
|
||||||
|
}
|
||||||
|
public static void runBigInt(java.util.List<String> log) {
|
||||||
|
\{genFragments(Category.BIG_INT)}
|
||||||
|
}
|
||||||
|
public static void runFloating(java.util.List<String> log) {
|
||||||
|
\{genFragments(Category.FLOATING)}
|
||||||
|
}
|
||||||
|
public static void runBigFloat(java.util.List<String> log) {
|
||||||
|
\{genFragments(Category.BIG_FLOAT)}
|
||||||
|
}
|
||||||
|
public static void runDate(java.util.List<String> log) {
|
||||||
|
\{genFragments(Category.DATE)}
|
||||||
|
}
|
||||||
|
static void test(String fmt, String format, String expression, Object value, java.util.List<String> log) {
|
||||||
|
var formatted = String.format(java.util.Locale.US, format, value);
|
||||||
|
if (!fmt.equals(formatted)) {
|
||||||
|
log.add(" format: '%s' expression: '%s' value: '%s' expected: '%s' found: '%s'".formatted(format, expression, value, formatted, fmt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
var log = new LinkedList<String>();
|
||||||
|
new StringTemplateTest().compile().getMethod("run", List.class).invoke(null, log);
|
||||||
|
if (!log.isEmpty()) {
|
||||||
|
log.forEach(System.out::println);
|
||||||
|
throw new AssertionError(STR."failed \{log.size()} tests");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -89,7 +89,8 @@ public class CompletenessTest extends KullaTesting {
|
|||||||
"record.any",
|
"record.any",
|
||||||
"record()",
|
"record()",
|
||||||
"record(1)",
|
"record(1)",
|
||||||
"record.length()"
|
"record.length()",
|
||||||
|
"\"\\{0}\""
|
||||||
};
|
};
|
||||||
|
|
||||||
static final String[] complete_with_semi = new String[] {
|
static final String[] complete_with_semi = new String[] {
|
||||||
@ -232,7 +233,12 @@ public class CompletenessTest extends KullaTesting {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static final String[] unknown = new String[] {
|
static final String[] unknown = new String[] {
|
||||||
"new ;"
|
"new ;",
|
||||||
|
"\"",
|
||||||
|
"\"\\",
|
||||||
|
"\"\\{",
|
||||||
|
"\"\\{0",
|
||||||
|
"\"\\{0}",
|
||||||
};
|
};
|
||||||
|
|
||||||
static final Map<Completeness, String[]> statusToCases = new HashMap<>();
|
static final Map<Completeness, String[]> statusToCases = new HashMap<>();
|
||||||
@ -369,6 +375,7 @@ public class CompletenessTest extends KullaTesting {
|
|||||||
public void testTextBlocks() {
|
public void testTextBlocks() {
|
||||||
assertStatus("\"\"\"", DEFINITELY_INCOMPLETE, null);
|
assertStatus("\"\"\"", DEFINITELY_INCOMPLETE, null);
|
||||||
assertStatus("\"\"\"broken", DEFINITELY_INCOMPLETE, null);
|
assertStatus("\"\"\"broken", DEFINITELY_INCOMPLETE, null);
|
||||||
|
assertStatus("\"\"\"\n", DEFINITELY_INCOMPLETE, null);
|
||||||
assertStatus("\"\"\"\ntext", DEFINITELY_INCOMPLETE, null);
|
assertStatus("\"\"\"\ntext", DEFINITELY_INCOMPLETE, null);
|
||||||
assertStatus("\"\"\"\ntext\"\"", DEFINITELY_INCOMPLETE, "\"\"\"\ntext\"\"\"");
|
assertStatus("\"\"\"\ntext\"\"", DEFINITELY_INCOMPLETE, "\"\"\"\ntext\"\"\"");
|
||||||
assertStatus("\"\"\"\ntext\"\"\"", COMPLETE, "\"\"\"\ntext\"\"\"");
|
assertStatus("\"\"\"\ntext\"\"\"", COMPLETE, "\"\"\"\ntext\"\"\"");
|
||||||
@ -376,6 +383,10 @@ public class CompletenessTest extends KullaTesting {
|
|||||||
assertStatus("\"\"\"\ntext\\\"\"\"", DEFINITELY_INCOMPLETE, null);
|
assertStatus("\"\"\"\ntext\\\"\"\"", DEFINITELY_INCOMPLETE, null);
|
||||||
assertStatus("\"\"\"\ntext\\\"\"\"\\\"\"\"", DEFINITELY_INCOMPLETE, null);
|
assertStatus("\"\"\"\ntext\\\"\"\"\\\"\"\"", DEFINITELY_INCOMPLETE, null);
|
||||||
assertStatus("\"\"\"\ntext\\\"\"\"\\\"\"\"\"\"\"", COMPLETE, "\"\"\"\ntext\\\"\"\"\\\"\"\"\"\"\"");
|
assertStatus("\"\"\"\ntext\\\"\"\"\\\"\"\"\"\"\"", COMPLETE, "\"\"\"\ntext\\\"\"\"\\\"\"\"\"\"\"");
|
||||||
|
assertStatus("\"\"\"\n\\", DEFINITELY_INCOMPLETE, null);
|
||||||
|
assertStatus("\"\"\"\n\\{", DEFINITELY_INCOMPLETE, null);
|
||||||
|
assertStatus("\"\"\"\n\\{0", DEFINITELY_INCOMPLETE, null);
|
||||||
|
assertStatus("\"\"\"\n\\{0}", DEFINITELY_INCOMPLETE, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testMiscSource() {
|
public void testMiscSource() {
|
||||||
|
@ -103,7 +103,7 @@ public class TestJavacTaskScanner extends ToolTester {
|
|||||||
|
|
||||||
check(numTokens, "#Tokens", 1054);
|
check(numTokens, "#Tokens", 1054);
|
||||||
check(numParseTypeElements, "#parseTypeElements", 180);
|
check(numParseTypeElements, "#parseTypeElements", 180);
|
||||||
check(numAllMembers, "#allMembers", 52);
|
check(numAllMembers, "#allMembers", 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
void check(int value, String name, int expected) {
|
void check(int value, String name, int expected) {
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// key: compiler.misc.feature.string.templates
|
||||||
|
// key: compiler.warn.preview.feature.use.plural
|
||||||
|
// options: --enable-preview -source 21 -Xlint:preview
|
||||||
|
|
||||||
|
class StringTemplate {
|
||||||
|
String m() {
|
||||||
|
int x = 10, y = 20;
|
||||||
|
return STR."\{x} + \{y} = \{x + y}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// key: compiler.err.processor.missing.from.string.template.expression
|
||||||
|
// key: compiler.misc.feature.string.templates
|
||||||
|
// key: compiler.warn.preview.feature.use.plural
|
||||||
|
// options: --enable-preview -source 21 -Xlint:preview
|
||||||
|
|
||||||
|
class StringTemplateNoProcessor {
|
||||||
|
String m() {
|
||||||
|
int x = 10, y = 20;
|
||||||
|
return "\{x} + \{y} = \{x + y}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// key: compiler.note.preview.filename
|
||||||
|
// key: compiler.note.preview.recompile
|
||||||
|
// key: compiler.err.not.a.processor.type
|
||||||
|
// options: --enable-preview -source 21
|
||||||
|
|
||||||
|
import java.lang.*;
|
||||||
|
|
||||||
|
class StringTemplateNotProcessor {
|
||||||
|
String m() {
|
||||||
|
String processor = "";
|
||||||
|
int x = 10, y = 20;
|
||||||
|
return processor."\{x} + \{y} = \{x + y}";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// key: compiler.note.preview.filename
|
||||||
|
// key: compiler.note.preview.recompile
|
||||||
|
// key: compiler.misc.unexpected.ret.val
|
||||||
|
// key: compiler.err.prob.found.req
|
||||||
|
// key: compiler.err.processor.type.cannot.be.a.raw.type
|
||||||
|
// options: --enable-preview -source 21
|
||||||
|
|
||||||
|
import java.lang.*;
|
||||||
|
import java.lang.StringTemplate.Processor;
|
||||||
|
|
||||||
|
class StringTemplateRawProcessor {
|
||||||
|
void m() {
|
||||||
|
Processor processor = ts -> ts.interpolate();
|
||||||
|
try {
|
||||||
|
int x = 10, y = 20;
|
||||||
|
return processor."\{x} + \{y} = \{x + y}";
|
||||||
|
} catch (Throwable x) {
|
||||||
|
throw new RuntimeException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// key: compiler.note.preview.filename
|
||||||
|
// key: compiler.note.preview.recompile
|
||||||
|
// key: compiler.err.unclosed.str.lit
|
||||||
|
// key: compiler.err.string.template.is.not.well.formed
|
||||||
|
// options: --enable-preview -source 21
|
||||||
|
|
||||||
|
import java.lang.*;
|
||||||
|
|
||||||
|
class StringTemplateUnclosedString {
|
||||||
|
String m() {
|
||||||
|
int x = 10;
|
||||||
|
return STR."\{x";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// key: compiler.note.preview.filename
|
||||||
|
// key: compiler.note.preview.recompile
|
||||||
|
// key: compiler.err.unclosed.text.block
|
||||||
|
// key: compiler.err.text.block.template.is.not.well.formed
|
||||||
|
// key: compiler.err.premature.eof
|
||||||
|
// options: --enable-preview -source 21
|
||||||
|
|
||||||
|
import java.lang.*;
|
||||||
|
|
||||||
|
class StringTemplateUnclosedTextBlock {
|
||||||
|
String m() {
|
||||||
|
int x = 10;
|
||||||
|
return STR."""
|
||||||
|
aaa
|
||||||
|
\{x
|
||||||
|
"""
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
@ -87,6 +87,7 @@ import com.sun.source.tree.DefaultCaseLabelTree;
|
|||||||
import com.sun.source.tree.ModuleTree;
|
import com.sun.source.tree.ModuleTree;
|
||||||
import com.sun.source.util.TreePathScanner;
|
import com.sun.source.util.TreePathScanner;
|
||||||
import com.sun.tools.javac.api.JavacTaskPool;
|
import com.sun.tools.javac.api.JavacTaskPool;
|
||||||
|
import com.sun.tools.javac.api.JavacTaskPool.Worker;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class JavacParserTest extends TestCase {
|
public class JavacParserTest extends TestCase {
|
||||||
@ -1912,6 +1913,52 @@ public class JavacParserTest extends TestCase {
|
|||||||
}.scan(cut, null);
|
}.scan(cut, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringTemplate1() throws IOException {
|
||||||
|
String code = """
|
||||||
|
package test;
|
||||||
|
public class Test {
|
||||||
|
Test(int a) {
|
||||||
|
String s = "prefix \\{a} suffix";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
|
||||||
|
null, null, Arrays.asList(new MyFileObject(code)));
|
||||||
|
CompilationUnitTree cut = ct.parse().iterator().next();
|
||||||
|
ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
|
||||||
|
MethodTree constr = (MethodTree) clazz.getMembers().get(0);
|
||||||
|
VariableTree decl = (VariableTree) constr.getBody().getStatements().get(0);
|
||||||
|
SourcePositions sp = Trees.instance(ct).getSourcePositions();
|
||||||
|
int initStart = (int) sp.getStartPosition(cut, decl.getInitializer());
|
||||||
|
int initEnd = (int) sp.getEndPosition(cut, decl.getInitializer());
|
||||||
|
assertEquals("correct templated String span expected", code.substring(initStart, initEnd), "\"prefix \\{a} suffix\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringTemplate2() throws IOException {
|
||||||
|
String code = """
|
||||||
|
package test;
|
||||||
|
public class Test {
|
||||||
|
Test(int a) {
|
||||||
|
String s = STR."prefix \\{a} suffix";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
JavacTaskImpl ct = (JavacTaskImpl) tool.getTask(null, fm, null,
|
||||||
|
null, null, Arrays.asList(new MyFileObject(code)));
|
||||||
|
CompilationUnitTree cut = ct.parse().iterator().next();
|
||||||
|
ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0);
|
||||||
|
MethodTree constr = (MethodTree) clazz.getMembers().get(0);
|
||||||
|
VariableTree decl = (VariableTree) constr.getBody().getStatements().get(0);
|
||||||
|
SourcePositions sp = Trees.instance(ct).getSourcePositions();
|
||||||
|
int initStart = (int) sp.getStartPosition(cut, decl.getInitializer());
|
||||||
|
int initEnd = (int) sp.getEndPosition(cut, decl.getInitializer());
|
||||||
|
assertEquals("correct templated String span expected", code.substring(initStart, initEnd), "STR.\"prefix \\{a} suffix\"");
|
||||||
|
}
|
||||||
|
|
||||||
@Test //JDK-8293897
|
@Test //JDK-8293897
|
||||||
void testImplicitFinalInTryWithResources() throws IOException {
|
void testImplicitFinalInTryWithResources() throws IOException {
|
||||||
String code = """
|
String code = """
|
||||||
@ -1993,6 +2040,63 @@ public class JavacParserTest extends TestCase {
|
|||||||
}.scan(cut, null);
|
}.scan(cut, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIncompleteStringTemplate() throws IOException {
|
||||||
|
String template = "\"\\{o.toString()}\"";
|
||||||
|
String prefix = """
|
||||||
|
package t;
|
||||||
|
class Test {
|
||||||
|
void test(Object o) {
|
||||||
|
String s = STR.""";
|
||||||
|
|
||||||
|
Worker<Void> verifyParseable = task -> {
|
||||||
|
try {
|
||||||
|
task.parse().iterator().next();
|
||||||
|
return null;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new AssertionError(ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
JavacTaskPool pool = new JavacTaskPool(1);
|
||||||
|
DiagnosticListener<JavaFileObject> dl = d -> {};
|
||||||
|
List<String> options = List.of("--enable-preview",
|
||||||
|
"-source", System.getProperty("java.specification.version"));
|
||||||
|
for (int i = 0; i < template.length(); i++) {
|
||||||
|
pool.getTask(null, fm, dl, options,
|
||||||
|
null, Arrays.asList(new MyFileObject(prefix + template.substring(0, i))),
|
||||||
|
verifyParseable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < template.length() - 1; i++) {
|
||||||
|
pool.getTask(null, fm, dl, options,
|
||||||
|
null, Arrays.asList(new MyFileObject(prefix + template.substring(0, i) + "\"")),
|
||||||
|
verifyParseable);
|
||||||
|
}
|
||||||
|
String incomplete = prefix + "\"\\{o.";
|
||||||
|
pool.getTask(null, fm, dl, options,
|
||||||
|
null, Arrays.asList(new MyFileObject(incomplete)), task -> {
|
||||||
|
try {
|
||||||
|
CompilationUnitTree cut = task.parse().iterator().next();
|
||||||
|
String result = cut.toString().replaceAll("\\R", "\n");
|
||||||
|
System.out.println("RESULT\n" + result);
|
||||||
|
assertEquals("incorrect AST",
|
||||||
|
result,
|
||||||
|
"""
|
||||||
|
package t;
|
||||||
|
\n\
|
||||||
|
class Test {
|
||||||
|
\n\
|
||||||
|
void test(Object o) {
|
||||||
|
String s = STR.<error>;
|
||||||
|
}
|
||||||
|
}""");
|
||||||
|
return null;
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new AssertionError(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test //JDK-8295401
|
@Test //JDK-8295401
|
||||||
void testModuleInfoProvidesRecovery() throws IOException {
|
void testModuleInfoProvidesRecovery() throws IOException {
|
||||||
String code = """
|
String code = """
|
||||||
|
240
test/langtools/tools/javac/template/Basic.java
Normal file
240
test/langtools/tools/javac/template/Basic.java
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 0000000
|
||||||
|
* @summary Exercise javac handing of templated strings.
|
||||||
|
* @library /tools/lib
|
||||||
|
* @modules jdk.compiler/com.sun.tools.javac.api
|
||||||
|
* jdk.compiler/com.sun.tools.javac.main
|
||||||
|
* @build toolbox.ToolBox toolbox.JavacTask
|
||||||
|
* @run main Basic
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import toolbox.JavacTask;
|
||||||
|
import toolbox.JavaTask;
|
||||||
|
import toolbox.Task;
|
||||||
|
import toolbox.ToolBox;
|
||||||
|
|
||||||
|
public class Basic {
|
||||||
|
private static ToolBox TOOLBOX = new ToolBox();
|
||||||
|
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
|
||||||
|
|
||||||
|
public static void main(String... arg) {
|
||||||
|
primitivesTest();
|
||||||
|
missingPartsTest();
|
||||||
|
expressionsTest();
|
||||||
|
invalidExpressionsTest();
|
||||||
|
processorTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Primitive types test.
|
||||||
|
*/
|
||||||
|
static void primitivesTest() {
|
||||||
|
for (String type : new String[] {
|
||||||
|
"byte",
|
||||||
|
"short",
|
||||||
|
"int",
|
||||||
|
"long",
|
||||||
|
"float",
|
||||||
|
"double"
|
||||||
|
}) {
|
||||||
|
compPass(type + " x = 10; " + type + " y = 20; StringTemplate result = RAW.\"\\{x} + \\{y} = \\{x + y}\";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Missing parts test.
|
||||||
|
*/
|
||||||
|
static void missingPartsTest() {
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{x";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{{x}";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{x + }";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{ * x }";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{ (x + x }";
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Expressions test.
|
||||||
|
*/
|
||||||
|
static void expressionsTest() {
|
||||||
|
compPass("""
|
||||||
|
int x = 10;
|
||||||
|
int[] y = new int[] { 10, 20, 30 };
|
||||||
|
StringTemplate result1 = RAW."\\{x + 1}";
|
||||||
|
StringTemplate result2 = RAW."\\{x + x}";
|
||||||
|
StringTemplate result3 = RAW."\\{x - x}";
|
||||||
|
StringTemplate result4 = RAW."\\{x * x}";
|
||||||
|
StringTemplate result5 = RAW."\\{x / x}";
|
||||||
|
StringTemplate result6 = RAW."\\{x % x}";
|
||||||
|
StringTemplate result7 = RAW."\\{x + (x + x)}";
|
||||||
|
StringTemplate result8 = RAW."\\{y[x - 9]}";
|
||||||
|
StringTemplate result9 = RAW."\\{System.out}";
|
||||||
|
StringTemplate result10 = RAW.\"""
|
||||||
|
\\{ "a string" }
|
||||||
|
\""";
|
||||||
|
""");
|
||||||
|
compPass("""
|
||||||
|
StringTemplate result = RAW.\"""
|
||||||
|
\\{
|
||||||
|
new Collection<String>() {
|
||||||
|
@Override public int size() { return 0; }
|
||||||
|
@Override public boolean isEmpty() { return false; }
|
||||||
|
@Override public boolean contains(Object o) { return false; }
|
||||||
|
@Override public Iterator<String> iterator() { return null; }
|
||||||
|
@Override public Object[] toArray() { return new Object[0]; }
|
||||||
|
@Override public <T> T[] toArray(T[] a) { return null; }
|
||||||
|
@Override public boolean add(String s) { return false; }
|
||||||
|
@Override public boolean remove(Object o) { return false; }
|
||||||
|
@Override public boolean containsAll(Collection<?> c) { return false; }
|
||||||
|
@Override public boolean addAll(Collection<? extends String> c) { return false; }
|
||||||
|
@Override public boolean removeAll(Collection<?> c) { return false; }
|
||||||
|
@Override public boolean retainAll(Collection<?> c) { return false; }
|
||||||
|
@Override public void clear() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\""";
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Invalid expressions test.
|
||||||
|
*/
|
||||||
|
static void invalidExpressionsTest() {
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{ (x == x }";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{ true ? : x - 1 }";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
String result = RAW."\\{ 'a }";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{ Math.min(, x - 1) }";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10;
|
||||||
|
StringTemplate result = RAW."\\{ \\tx }";
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Processor test.
|
||||||
|
*/
|
||||||
|
static void processorTest() {
|
||||||
|
compPass("""
|
||||||
|
int x = 10, y = 20;
|
||||||
|
String string = STR."\\{x} + \\{y} = \\{x + y}";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10, y = 20;
|
||||||
|
String processor = "abc";
|
||||||
|
String string = processor."\\{x} + \\{y} = \\{x + y}";
|
||||||
|
""");
|
||||||
|
compFail("""
|
||||||
|
int x = 10, y = 20;
|
||||||
|
long processor = 100;
|
||||||
|
String string = processor."\\{x} + \\{y} = \\{x + y}";
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test source for successful compile.
|
||||||
|
*/
|
||||||
|
static void compPass(String code) {
|
||||||
|
String source = """
|
||||||
|
import java.lang.*;
|
||||||
|
import java.util.*;
|
||||||
|
import static java.lang.StringTemplate.RAW;
|
||||||
|
public class TEST {
|
||||||
|
public static void main(String... arg) {
|
||||||
|
""" +
|
||||||
|
code.indent(8) +
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
String output = new JavacTask(TOOLBOX)
|
||||||
|
.sources(source)
|
||||||
|
.classpath(".")
|
||||||
|
.options("-encoding", "utf8", "--enable-preview", "-source", JAVA_VERSION)
|
||||||
|
.run()
|
||||||
|
.writeAll()
|
||||||
|
.getOutput(Task.OutputKind.DIRECT);
|
||||||
|
|
||||||
|
if (output.contains("compiler.err")) {
|
||||||
|
throw new RuntimeException("Error detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Test source for unsuccessful compile and specific error.
|
||||||
|
*/
|
||||||
|
static void compFail(String code) {
|
||||||
|
String source = """
|
||||||
|
import java.lang.*;
|
||||||
|
import java.util.*;
|
||||||
|
import static java.lang.StringTemplate.RAW;
|
||||||
|
public class TEST {
|
||||||
|
public static void main(String... arg) {
|
||||||
|
""" +
|
||||||
|
code.indent(8) +
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
String errors = new JavacTask(TOOLBOX)
|
||||||
|
.sources(source)
|
||||||
|
.classpath(".")
|
||||||
|
.options("-XDrawDiagnostics", "-encoding", "utf8", "--enable-preview", "-source", JAVA_VERSION)
|
||||||
|
.run(Task.Expect.FAIL)
|
||||||
|
.writeAll()
|
||||||
|
.getOutput(Task.OutputKind.DIRECT);
|
||||||
|
|
||||||
|
if (!errors.contains("compiler.err")) {
|
||||||
|
throw new RuntimeException("No error detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
107
test/langtools/tools/javac/template/TreeScannerTest.java
Normal file
107
test/langtools/tools/javac/template/TreeScannerTest.java
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @summary Verify proper behavior of TreeScanner w.r.t. templated Strings
|
||||||
|
* @modules jdk.compiler
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
import javax.tools.*;
|
||||||
|
import com.sun.source.tree.*;
|
||||||
|
import com.sun.source.util.*;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
|
||||||
|
public class TreeScannerTest {
|
||||||
|
private static final String JAVA_VERSION = System.getProperty("java.specification.version");
|
||||||
|
|
||||||
|
public static void main(String... args) throws Exception {
|
||||||
|
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||||
|
String code = """
|
||||||
|
public class Test {
|
||||||
|
private void test(int a) {
|
||||||
|
String s1 = TEST."p\\{a}s";
|
||||||
|
String s2 = "p\\{a}s";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
JavacTask task = (JavacTask) compiler.getTask(null, null, null,
|
||||||
|
List.of("--enable-preview", "-source", JAVA_VERSION), null, List.of(new TestJFO(code)));
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
TreeScanner<Void,Void> checker = new TreeScanner<Void, Void>() {
|
||||||
|
private boolean log;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitStringTemplate(StringTemplateTree node, Void p) {
|
||||||
|
boolean prevLog = log;
|
||||||
|
try {
|
||||||
|
log = true;
|
||||||
|
return super.visitStringTemplate(node, p);
|
||||||
|
} finally {
|
||||||
|
log = prevLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void scan(Tree tree, Void p) {
|
||||||
|
if (log) {
|
||||||
|
output.append("(");
|
||||||
|
output.append(tree != null ? tree.getKind() : "null");
|
||||||
|
try {
|
||||||
|
return super.scan(tree, p);
|
||||||
|
} finally {
|
||||||
|
output.append(")");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return super.scan(tree, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
checker.scan(task.parse(), null);
|
||||||
|
|
||||||
|
String expected = "(IDENTIFIER)(IDENTIFIER)(null)(IDENTIFIER)";
|
||||||
|
if (!expected.equals(output.toString())) {
|
||||||
|
throw new AssertionError("expected output not found, found: " + output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class TestJFO extends SimpleJavaFileObject {
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
public TestJFO(String code) throws URISyntaxException, IOException {
|
||||||
|
super(new URI("mem://Test.java"), Kind.SOURCE);
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -123,6 +123,10 @@ public class TreeKindTest {
|
|||||||
ok = ok & verify(k, i, i == OpensTree.class);
|
ok = ok & verify(k, i, i == OpensTree.class);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TEMPLATE:
|
||||||
|
ok = ok & verify(k, i, i == StringTemplateTree.class);
|
||||||
|
break;
|
||||||
|
|
||||||
case OTHER:
|
case OTHER:
|
||||||
ok = ok & verify(k, i, i == null);
|
ok = ok & verify(k, i, i == null);
|
||||||
break;
|
break;
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 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 org.openjdk.bench.java.lang;
|
||||||
|
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static java.util.FormatProcessor.FMT;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This benchmark measures StringTemplate.FMT FormatProcessor performance;
|
||||||
|
* exactly mirroring {@link org.openjdk.bench.java.lang.StringFormat} benchmark
|
||||||
|
*/
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
@Warmup(iterations = 5, time = 1)
|
||||||
|
@Measurement(iterations = 5, time = 1)
|
||||||
|
@Fork(value = 3, jvmArgsAppend = "--enable-preview")
|
||||||
|
public class StringTemplateFMT {
|
||||||
|
|
||||||
|
public String s = "str";
|
||||||
|
public int i = 17;
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public String stringFormat() {
|
||||||
|
return FMT."%s\{s}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public String stringIntFormat() {
|
||||||
|
return FMT."%s\{s} %d\{i}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public String widthStringFormat() {
|
||||||
|
return FMT."%3s\{s}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public String widthStringIntFormat() {
|
||||||
|
return FMT."%3s\{s} %d\{i}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public String complexFormat() {
|
||||||
|
return FMT."%3s\{s} %10d\{i} %4S\{s} %04X\{i} %4S\{s} %04X\{i} %4S\{s} %04X\{i}";
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user