From dcba014ad56eae753c25c579fb30bb8ecfab69af Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Tue, 18 Nov 2025 14:44:14 +0000 Subject: [PATCH 001/114] 8371967: Add Visual Studio 2026 to build toolchain for Windows Reviewed-by: erikj --- doc/building.html | 2 +- doc/building.md | 2 +- make/autoconf/toolchain_microsoft.m4 | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/doc/building.html b/doc/building.html index 99eb3e0c473..19313ebf43a 100644 --- a/doc/building.html +++ b/doc/building.html @@ -668,7 +668,7 @@ update.

(Note that this version is often presented as "MSVC 14.28", and reported by cl.exe as 19.28.) Older versions will not be accepted by configure and will not work. The maximum accepted version -of Visual Studio is 2022.

+of Visual Studio is 2026.

If you have multiple versions of Visual Studio installed, configure will by default pick the latest. You can request a specific version to be used by setting diff --git a/doc/building.md b/doc/building.md index 047255d1848..1fbd395a9d1 100644 --- a/doc/building.md +++ b/doc/building.md @@ -468,7 +468,7 @@ available for this update. The minimum accepted version is Visual Studio 2019 version 16.8. (Note that this version is often presented as "MSVC 14.28", and reported by cl.exe as 19.28.) Older versions will not be accepted by `configure` and will not work. -The maximum accepted version of Visual Studio is 2022. +The maximum accepted version of Visual Studio is 2026. If you have multiple versions of Visual Studio installed, `configure` will by default pick the latest. You can request a specific version to be used by diff --git a/make/autoconf/toolchain_microsoft.m4 b/make/autoconf/toolchain_microsoft.m4 index 17ad2666b3a..f577cf1a2a1 100644 --- a/make/autoconf/toolchain_microsoft.m4 +++ b/make/autoconf/toolchain_microsoft.m4 @@ -25,7 +25,7 @@ ################################################################################ # The order of these defines the priority by which we try to find them. -VALID_VS_VERSIONS="2022 2019" +VALID_VS_VERSIONS="2022 2019 2026" VS_DESCRIPTION_2019="Microsoft Visual Studio 2019" VS_VERSION_INTERNAL_2019=142 @@ -57,6 +57,21 @@ VS_SDK_PLATFORM_NAME_2022= VS_SUPPORTED_2022=true VS_TOOLSET_SUPPORTED_2022=true +VS_DESCRIPTION_2026="Microsoft Visual Studio 2026" +VS_VERSION_INTERNAL_2026=145 +VS_MSVCR_2026=vcruntime140.dll +VS_VCRUNTIME_1_2026=vcruntime140_1.dll +VS_MSVCP_2026=msvcp140.dll +VS_ENVVAR_2026="VS180COMNTOOLS" +VS_USE_UCRT_2026="true" +VS_VS_INSTALLDIR_2026="Microsoft Visual Studio/18" +VS_EDITIONS_2026="BuildTools Community Professional Enterprise" +VS_SDK_INSTALLDIR_2026= +VS_VS_PLATFORM_NAME_2026="v145" +VS_SDK_PLATFORM_NAME_2026= +VS_SUPPORTED_2026=true +VS_TOOLSET_SUPPORTED_2026=true + ################################################################################ AC_DEFUN([TOOLCHAIN_CHECK_POSSIBLE_VISUAL_STUDIO_ROOT], From 43040f30a72591a37deb9a54ab7723988c1e4b51 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Tue, 18 Nov 2025 15:11:45 +0000 Subject: [PATCH 002/114] 8372012: java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java should check ability to create links Reviewed-by: alanb, jpai --- .../attribute/BasicFileAttributeView/SetTimesNanos.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java index acdff692601..55a7ca10a6d 100644 --- a/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java +++ b/test/jdk/java/nio/file/attribute/BasicFileAttributeView/SetTimesNanos.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -67,8 +67,10 @@ public class SetTimesNanos { Path file = Files.createFile(dir.resolve("test.dat")); testNanos(file); - testNanosLink(false); - testNanosLink(true); + if (TestUtil.supportsSymbolicLinks(Path.of(""))) { + testNanosLink(false); + testNanosLink(true); + } } private static void testNanos(Path path) throws IOException { From b6d83eda6bfa76da98274aa3ad294759cb56d3a5 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 18 Nov 2025 15:14:20 +0000 Subject: [PATCH 003/114] 8371960: Missing null check in AnnotatedType annotation accessor methods Reviewed-by: alanb --- .../annotation/AnnotatedTypeFactory.java | 6 +- .../AnnotatedElementNullCheckTest.java | 101 ++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java diff --git a/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java index aa69e29831d..b5db897b218 100644 --- a/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java +++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotatedTypeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, 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 @@ -128,7 +128,7 @@ public final class AnnotatedTypeFactory { private final Type type; private final LocationInfo location; private final TypeAnnotation[] allOnSameTargetTypeAnnotations; - private final Map, Annotation> annotations; + private final Map, Annotation> annotations; AnnotatedTypeBaseImpl(Type type, LocationInfo location, TypeAnnotation[] actualTypeAnnotations, TypeAnnotation[] allOnSameTargetTypeAnnotations) { @@ -162,11 +162,13 @@ public final class AnnotatedTypeFactory { @Override @SuppressWarnings("unchecked") public final T getDeclaredAnnotation(Class annotation) { + Objects.requireNonNull(annotation); return (T)annotations.get(annotation); } @Override public final T[] getDeclaredAnnotationsByType(Class annotation) { + Objects.requireNonNull(annotation); return AnnotationSupport.getDirectlyAndIndirectlyPresent(annotations, annotation); } diff --git a/test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java b/test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java new file mode 100644 index 00000000000..632dab252a9 --- /dev/null +++ b/test/jdk/java/lang/reflect/AnnotatedElement/AnnotatedElementNullCheckTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2025, 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 8371953 8371960 + * @summary Null checks for AnnotatedElement APIs. + * @run junit AnnotatedElementNullCheckTest + */ + +import java.lang.reflect.AnnotatedArrayType; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedParameterizedType; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +class AnnotatedElementNullCheckTest { + + // Return a stream of instances, each of a different implementation class + static AnnotatedElement[] implementations() throws Exception { + var objectHashCodeMethod = Object.class.getMethod("hashCode"); + var annotatedParameterizedType = (AnnotatedParameterizedType) Optional.class + .getMethod("ifPresent", Consumer.class) + .getAnnotatedParameterTypes()[0]; + var annotatedGenericArrayType = (AnnotatedArrayType) List.class + .getMethod("of", Object[].class) + .getAnnotatedParameterTypes()[0]; + record Rec(int a) {} + return new AnnotatedElement[] { + Object.class, + objectHashCodeMethod, + System.class.getField("out"), + Object.class.getConstructor(), + Object.class.getPackage(), + Optional.class.getTypeParameters()[0], + // AnnotatedType (direct) + objectHashCodeMethod.getAnnotatedReturnType(), + // AnnotatedParameterizedType + annotatedParameterizedType, + // AnnotatedArrayType + annotatedGenericArrayType, + // AnnotatedTypeVariable + annotatedGenericArrayType.getAnnotatedGenericComponentType(), + // AnnotatedWildcardType + annotatedParameterizedType.getAnnotatedActualTypeArguments()[0], + Rec.class.getRecordComponents()[0], + Object.class.getMethod("equals", Object.class).getParameters()[0], + Object.class.getModule(), + }; + } + + @Test + void ensureImplementationsDistinct() throws Throwable { + var set = new HashSet>(); + for (var impl : implementations()) { + var clazz = impl.getClass(); + if (!set.add(clazz)) { + fail("Duplicate implementation class %s in %s".formatted(clazz, impl)); + } + } + } + + @ParameterizedTest + @MethodSource("implementations") + void nullChecks(AnnotatedElement impl) { + assertThrows(NullPointerException.class, () -> impl.isAnnotationPresent(null)); + assertThrows(NullPointerException.class, () -> impl.getAnnotation(null)); + assertThrows(NullPointerException.class, () -> impl.getAnnotationsByType(null)); + assertThrows(NullPointerException.class, () -> impl.getDeclaredAnnotation(null)); + assertThrows(NullPointerException.class, () -> impl.getDeclaredAnnotationsByType(null)); + } +} From 1f99cf942449728cdeb9918b93fd9a97a51eb0b6 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 18 Nov 2025 15:14:49 +0000 Subject: [PATCH 004/114] 8372002: VarHandle for receiver's superclass instance fields fails describeConstable Reviewed-by: psandoz, jvernee --- .../classes/java/lang/invoke/VarHandles.java | 13 +++--- .../DescribeConstableTest.java | 45 +++++++++++++++---- .../VarHandles/describeConstable/p/C.java | 4 +- .../VarHandles/describeConstable/p/q/Q.java | 4 +- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandles.java b/src/java.base/share/classes/java/lang/invoke/VarHandles.java index c97d44ba5d3..8c3e123f39a 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandles.java @@ -166,12 +166,15 @@ final class VarHandles { static Field getFieldFromReceiverAndOffset(Class receiverType, long offset, Class fieldType) { - for (Field f : receiverType.getDeclaredFields()) { - if (Modifier.isStatic(f.getModifiers())) continue; + // The receiver may be a referenced class different from the declaring class + for (var declaringClass = receiverType; declaringClass != null; declaringClass = declaringClass.getSuperclass()) { + for (Field f : declaringClass.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) continue; - if (offset == UNSAFE.objectFieldOffset(f)) { - assert f.getType() == fieldType; - return f; + if (offset == UNSAFE.objectFieldOffset(f)) { + assert f.getType() == fieldType; + return f; + } } } throw new InternalError("Field not found at offset"); diff --git a/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java b/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java index ecda61cac48..0eecbdc3798 100644 --- a/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java +++ b/test/jdk/java/lang/invoke/VarHandles/describeConstable/DescribeConstableTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -21,18 +21,16 @@ * questions. */ -/** +/* * @test - * @bug 8302260 + * @bug 8302260 8372002 * @build p.C p.D p.I p.q.Q * @run junit DescribeConstableTest * @summary Test VarHandle::describeConstable on static fields */ -import java.lang.constant.ClassDesc; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; -import java.lang.invoke.VarHandle; import java.lang.invoke.VarHandle.VarHandleDesc; import java.util.stream.Stream; @@ -43,7 +41,7 @@ import static org.junit.jupiter.api.Assertions.*; public class DescribeConstableTest { private static final Lookup LOOKUP = MethodHandles.lookup(); - private static Stream testCases() { + private static Stream staticTestCases() { return Stream.of( // static field defined in p.C only Arguments.of(p.C.class, "cString", String.class, p.C.class, "CClass"), @@ -68,8 +66,8 @@ public class DescribeConstableTest { } @ParameterizedTest - @MethodSource("testCases") - void test(Class refc, String name, Class type, Class declaringClass, Object value) throws Throwable { + @MethodSource("staticTestCases") + void testStatic(Class refc, String name, Class type, Class declaringClass, Object value) throws Throwable { var vh = LOOKUP.findStaticVarHandle(refc, name, type); assertEquals(value, vh.get()); @@ -84,6 +82,37 @@ public class DescribeConstableTest { assertEquals(vhd.toString(), varHandleDescString(declaringClass, name, type, true)); } + private static Arguments[] instanceTestCases() { + return new Arguments[] { + // Basic instance field in p.q.Q + Arguments.of(p.q.Q.class, "instanceIntField", int.class, new p.q.Q(), 42), + // p.C.instanceIntField hides the superclass instanceIntField, but it still exists + Arguments.of(p.C.class, "instanceIntField", int.class, new p.C(), 76), + Arguments.of(p.q.Q.class, "instanceIntField", int.class, new p.C(), 42), + // p.D.instanceIntField points to that of p.q.Q + Arguments.of(p.D.class, "instanceIntField", int.class, new p.D(), 42), + }; + } + + @ParameterizedTest + @MethodSource("instanceTestCases") + void testInstance(Class refc, String name, Class type, Object instance, Object value) throws Throwable { + var vh = LOOKUP.findVarHandle(refc, name, type).withInvokeBehavior(); + assertEquals(value, vh.get(instance)); + + var refcDesc = refc.describeConstable().orElseThrow(); + var typeDesc = type.describeConstable().orElseThrow(); + var vhd = vh.describeConstable().orElseThrow(); + var vhd2 = VarHandleDesc.ofField(refcDesc, name, typeDesc); + + assertEquals(value, vhd.resolveConstantDesc(LOOKUP).get(instance)); + assertEquals(value, vhd2.resolveConstantDesc(LOOKUP).get(instance)); + + // The string does not use the declaring class because + // receiver is restricted on the handle + assertEquals(vhd.toString(), varHandleDescString(refc, name, type, false)); + } + static String varHandleDescString(Class declaringClass, String name, Class type, boolean staticField) { return String.format("VarHandleDesc[%s%s.%s:%s]", staticField ? "static " : "", diff --git a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java index 353bd26dcf5..5e56451ea25 100644 --- a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java +++ b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/C.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -25,4 +25,6 @@ package p; public class C extends p.q.Q implements I { public static String cString = "CClass"; + + public int instanceIntField = 76; // Hides the field from Q } diff --git a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java index b3bbb8adafc..0461d43b533 100644 --- a/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java +++ b/test/jdk/java/lang/invoke/VarHandles/describeConstable/p/q/Q.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -29,4 +29,6 @@ public class Q { public static String stringField2 = "QClass2"; public static long longField2 = 102L; + + public int instanceIntField = 42; } From 713de231a61234632e2f9858b222b5f7fd0bdaf1 Mon Sep 17 00:00:00 2001 From: Nityanand Rai Date: Tue, 18 Nov 2025 15:47:54 +0000 Subject: [PATCH 005/114] 8371854: Shenandoah: Simplify WALK_FORWARD_IN_BLOCK_START use Reviewed-by: shade, ysr, xpeng --- .../share/gc/shenandoah/shenandoahScanRemembered.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index 3a99023eca4..34713898fc6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -378,24 +378,20 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con // evacuation phase) of young collections. This is never called // during global collections during marking or update refs.. // 4. Every allocation under TAMS updates the object start array. +#ifdef ASSERT oop obj = cast_to_oop(p); assert(oopDesc::is_oop(obj), "Should be an object"); -#ifdef ASSERT -#define WALK_FORWARD_IN_BLOCK_START true -#else -#define WALK_FORWARD_IN_BLOCK_START false -#endif // ASSERT - while (WALK_FORWARD_IN_BLOCK_START && p + obj->size() < left) { + while (p + obj->size() < left) { p += obj->size(); obj = cast_to_oop(p); assert(oopDesc::is_oop(obj), "Should be an object"); assert(Klass::is_valid(obj->klass()), "Not a valid klass ptr"); // Check assumptions in previous block comment if this assert fires - guarantee(false, "Should never need forward walk in block start"); + fatal("Should never need forward walk in block start"); } -#undef WALK_FORWARD_IN_BLOCK_START assert(p <= left, "p should start at or before left end of card"); assert(p + obj->size() > left, "obj should end after left end of card"); +#endif // ASSERT return p; } From ac6f5e96512a7f003ac536611c53f2564ea912a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Tue, 18 Nov 2025 16:52:12 +0000 Subject: [PATCH 006/114] 8366094: Sealed graph for nested types creates broken links Reviewed-by: liach --- .../src/classes/build/tools/taglet/SealedGraph.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/make/jdk/src/classes/build/tools/taglet/SealedGraph.java b/make/jdk/src/classes/build/tools/taglet/SealedGraph.java index 17867b99595..3e93826c180 100644 --- a/make/jdk/src/classes/build/tools/taglet/SealedGraph.java +++ b/make/jdk/src/classes/build/tools/taglet/SealedGraph.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -219,13 +219,13 @@ public final class SealedGraph implements Taglet { // This implies the module is always the same. private String relativeLink(TypeElement node) { var util = SealedGraph.this.docletEnvironment.getElementUtils(); - var rootPackage = util.getPackageOf(rootNode); var nodePackage = util.getPackageOf(node); - var backNavigator = rootPackage.getQualifiedName().toString().chars() + // Note: SVG files for nested types use the simple names of containing types as parent directories. + // We therefore need to convert all dots in the qualified name to "../" below. + var backNavigator = rootNode.getQualifiedName().toString().chars() .filter(c -> c == '.') .mapToObj(c -> "../") - .collect(joining()) + - "../"; + .collect(joining()); var forwardNavigator = nodePackage.getQualifiedName().toString() .replace(".", "/"); From 0e6c7e8664fdddd8b789851263613852fc2c55f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Tue, 18 Nov 2025 17:55:43 +0000 Subject: [PATCH 007/114] 8371896: Links in snippets can not be highlighted Reviewed-by: liach --- .../formats/html/taglets/SnippetTaglet.java | 12 +-- .../testSnippetTag/TestSnippetMarkup.java | 82 ++++++++++++++++--- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java index d16c47dd59f..38baa7b2826 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SnippetTaglet.java @@ -162,10 +162,11 @@ public class SnippetTaglet extends BaseTaglet { case Style.Markup m -> markupEncountered = true; } } - Content c; if (markupEncountered) { return; - } else if (linkTarget != null) { + } + Content c = Text.of(text); + if (linkTarget != null) { //disable preview tagging inside the snippets: Utils.PreviewFlagProvider prevPreviewProvider = utils.setPreviewFlagProvider(el -> false); try { @@ -174,15 +175,16 @@ public class SnippetTaglet extends BaseTaglet { null, linkTarget, ref, - false, // TODO: for now + true, Text.of(sequence.toString()), (key, args) -> { /* Error has already been reported above */ }, tagletWriter); } finally { utils.setPreviewFlagProvider(prevPreviewProvider); } - } else { - c = HtmlTree.SPAN(Text.of(text)); + } + if (!classes.isEmpty()) { + c = HtmlTree.SPAN(c); classes.forEach(((HtmlTree) c)::addStyle); } code.add(c); diff --git a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java index a3e92584ae2..be60a114040 100644 --- a/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java +++ b/test/langtools/jdk/javadoc/doclet/testSnippetTag/TestSnippetMarkup.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8266666 8281969 8319339 8338833 + * @bug 8266666 8281969 8319339 8338833 8371896 * @summary Implementation for snippets * @library /tools/lib ../../lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -180,7 +180,7 @@ public class TestSnippetMarkup extends SnippetTester { replace(""" link(First) link(line) Second line - """, "link\\((.+?)\\)", r -> link(true, "java.lang.Object#Object", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.Object#Object", r.group(1))) ), new TestCase( """ @@ -190,7 +190,7 @@ public class TestSnippetMarkup extends SnippetTester { replace(""" First line link( )Secondlink( )line - """, "link\\((.+?)\\)", r -> link(true, "java.lang.System#out", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.System#out", r.group(1))) ), new TestCase( """ @@ -200,7 +200,68 @@ public class TestSnippetMarkup extends SnippetTester { replace(""" First line link( )Secondlink( )line - """, "link\\((.+?)\\)", r -> link(true, "java.lang.System#in", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.System#in", r.group(1))) + ) + ); + testPositive(base, testCases); + } + + // Test combinations of @link, @highlight and @replace on the same or overlapping substrings. + @Test + public void testLinkHighlightReplace(Path base) throws Exception { + var testCases = List.of( + new TestCase( + """ + void method(Object o); // @link substring=Object target=Object @highlight substring=Object + """, + replace(""" + void method(link(Object) o); + """, "link\\((.+?)\\)", r -> link("Object", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @replace substring=Object replacement=String \ + @highlight substring=String @link substring=String target=String + """, + replace(""" + void method(link(String) o); + """, "link\\((.+?)\\)", r -> link("String", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @highlight substring="(Object o)" @link substring=Object target=Object + """, + replace(""" + void method(link(Object) o); + """, "link\\((.+?)\\)", r -> link("Object", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @replace substring=Object replacement=String \ + @link substring=String target=String @highlight substring="(String o)" + """, + replace(""" + void method(link(String) o); + """, "link\\((.+?)\\)", r -> link("String", r.group(1))) + ), + new TestCase( + """ + void method(Object o); // @replace substring=Object replacement=String \ + @link substring="String o" target=String @highlight substring=String + """, + replace(""" + void method(link(String)link( o)); + """, "link\\((.+?)\\)", r -> link("String", r.group(1))) + ), + // replacement subtext does not retain links and highlights of the replaced subtext + new TestCase( + """ + void method(Object o); // @link substring=Object target=Object \ + @highlight substring=Object @replace substring=Object replacement=String + """, + """ + void method(String o); + """ ) ); testPositive(base, testCases); @@ -245,7 +306,7 @@ public class TestSnippetMarkup extends SnippetTester { """

invalid reference -
Second
+
Second
"""); checkNoCrashes(); } @@ -646,7 +707,7 @@ First line // @highlight : replace(""" First line link( Third line) - """, "link\\((.+?)\\)", r -> link(true, "java.lang.Object#equals(Object)", r.group(1))) + """, "link\\((.+?)\\)", r -> link("java.lang.Object#equals(Object)", r.group(1))) ), new TestCase(""" First line @@ -910,8 +971,7 @@ First line // @highlight : checkNoCrashes(); } - private static String link(boolean linkPlain, - String targetReference, + private static String link(String targetReference, String content) throws UncheckedIOException { @@ -935,7 +995,7 @@ First line // @highlight : var LABEL_PLACEHOLDER = "label"; var source = """ - /** {@link %s %s} */ + /** {@linkplain %s %s} */ public interface A { } """.formatted(targetReference, LABEL_PLACEHOLDER); @@ -1063,8 +1123,8 @@ First line // @highlight : } String output = fileManager.getFileString(DOCUMENTATION_OUTPUT, "A.html"); // use the [^<>] regex to select HTML elements that immediately enclose "content" - Matcher m = Pattern.compile("(?is)(]*\" title=\"[^<>]*\" class=\"[^<>]*\">)" - + LABEL_PLACEHOLDER + "()").matcher(output); + Matcher m = Pattern.compile("(?is)(]*\" title=\"[^<>]*\" class=\"[^<>]*\">)" + + LABEL_PLACEHOLDER + "()").matcher(output); if (!m.find()) { throw new IOException(output); } From b3e408c07891b58a312a58ffd756d6a1d18c0f6d Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 18 Nov 2025 18:12:07 +0000 Subject: [PATCH 008/114] 8372045: AOT assembly phase asserts with old class if AOT class linking is disabled Reviewed-by: shade, mgronlun --- src/hotspot/share/oops/instanceKlass.cpp | 2 +- .../cds/appcds/aotCache/OldClassSupport2.java | 104 ++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 2d03b69ee92..24358f662bc 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -2870,7 +2870,7 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl } bool InstanceKlass::can_be_verified_at_dumptime() const { - if (AOTMetaspace::in_aot_cache(this)) { + if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(this)) { // This is a class that was dumped into the base archive, so we know // it was verified at dump time. return true; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java new file mode 100644 index 00000000000..16a4d258e4f --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/OldClassSupport2.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025, 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 AOT cache should exclude old classes when AOT class linking is disabled. + * @bug 8372045 + * @requires vm.cds.supports.aot.class.linking + * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build OldClass + * @build OldClassSupport2 + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AppUsesOldClass2 OldClass + * @run driver OldClassSupport2 + */ + +import jdk.jfr.Event; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class OldClassSupport2 { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "AppUsesOldClass2"; + + public static void main(String[] args) throws Exception { + // Explicitly disable + Tester tester1 = new Tester("-XX:-AOTClassLinking"); + tester1.run(new String[] {"AOT", "--two-step-training"} ); + + // Full module graph caching is disabled with -Djdk.module.showModuleResolution=true. + // This will disable AOT class linking. + Tester tester2 = new Tester("-Djdk.module.showModuleResolution=true"); + tester2.run(new String[] {"AOT", "--two-step-training"} ); + + // Heap archiving is disable with -XX:-UseCompressedClassPointers. + // This will disable AOT class linking. + Tester tester3 = new Tester("-XX:-UseCompressedClassPointers"); + tester3.run(new String[] {"AOT", "--two-step-training"} ); + } + + static class Tester extends CDSAppTester { + String extraArg; + public Tester(String extraArg) { + super(mainClass); + this.extraArg = extraArg; + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:aot", + "-Xlog:aot+class=debug", + extraArg, + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] {mainClass}; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + String prefix = "aot,class.* = 0x.* app *"; + if (runMode == RunMode.ASSEMBLY) { + out.shouldMatch(prefix + "AppUsesOldClass2"); + out.shouldNotMatch(prefix + "OldClass"); + } + } + } +} + +class AppUsesOldClass2 { + public static void main(String args[]) { + System.out.println("Old Class Instance: " + new OldClass()); + } +} From 4a975637a144fa8aa449a1419e656721833513b5 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 18 Nov 2025 18:35:01 +0000 Subject: [PATCH 009/114] 8346944: Update Unicode Data Files to 17.0.0 8346947: Update ICU4J to Version 78.1 Reviewed-by: joehw --- .../share/classes/java/lang/Character.java | 459 +++-- .../java/text/CollationElementIterator.java | 6 +- .../icu/impl/CharacterIteratorWrapper.java | 6 +- .../jdk/internal/icu/impl/Norm2AllModes.java | 4 +- .../impl/ReplaceableUCharacterIterator.java | 8 +- .../jdk/internal/icu/impl/UBiDiProps.java | 4 +- .../internal/icu/impl/UCharacterProperty.java | 4 +- .../impl/data/{icudt76b => icudata}/nfc.nrm | Bin 36224 -> 36336 bytes .../impl/data/{icudt76b => icudata}/nfkc.nrm | Bin 56032 -> 56128 bytes .../impl/data/{icudt76b => icudata}/ubidi.icu | Bin 28464 -> 28768 bytes .../internal/icu/impl/data/icudata/uprops.icu | Bin 0 -> 170864 bytes .../icu/impl/data/icudt76b/uprops.icu | Bin 146880 -> 0 bytes .../jdk/internal/icu/text/NormalizerBase.java | 6 +- .../internal/icu/text/UCharacterIterator.java | 6 +- .../jdk/internal/icu/text/UnicodeSet.java | 16 +- .../jdk/internal/icu/util/VersionInfo.java | 4 +- .../jdk/internal/util/regex/Grapheme.java | 7 +- .../regex/IndicConjunctBreak.java.template | 7 +- .../share/data/unicodedata/Blocks.txt | 14 +- .../share/data/unicodedata/CaseFolding.txt | 42 +- .../unicodedata/DerivedCoreProperties.txt | 599 ++++-- .../data/unicodedata/NormalizationTest.txt | 75 +- .../share/data/unicodedata/PropList.txt | 72 +- .../data/unicodedata/PropertyValueAliases.txt | 35 +- .../share/data/unicodedata/ReadMe.txt | 19 +- .../share/data/unicodedata/Scripts.txt | 142 +- .../share/data/unicodedata/SpecialCasing.txt | 14 +- .../share/data/unicodedata/UnicodeData.txt | 489 ++++- .../auxiliary/GraphemeBreakProperty.txt | 28 +- .../auxiliary/GraphemeBreakTest.txt | 1737 +++++++---------- .../data/unicodedata/emoji/emoji-data.txt | 148 +- src/java.base/share/legal/icu.md | 4 +- src/java.base/share/legal/unicode.md | 4 +- 33 files changed, 2327 insertions(+), 1632 deletions(-) rename src/java.base/share/classes/jdk/internal/icu/impl/data/{icudt76b => icudata}/nfc.nrm (64%) rename src/java.base/share/classes/jdk/internal/icu/impl/data/{icudt76b => icudata}/nfkc.nrm (90%) rename src/java.base/share/classes/jdk/internal/icu/impl/data/{icudt76b => icudata}/ubidi.icu (74%) create mode 100644 src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/uprops.icu delete mode 100644 src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/uprops.icu diff --git a/src/java.base/share/classes/java/lang/Character.java b/src/java.base/share/classes/java/lang/Character.java index 72ff33651f9..d866202909c 100644 --- a/src/java.base/share/classes/java/lang/Character.java +++ b/src/java.base/share/classes/java/lang/Character.java @@ -63,7 +63,7 @@ import static java.lang.constant.ConstantDescs.DEFAULT_NAME; * from the Unicode Consortium at * http://www.unicode.org. *

- * Character information is based on the Unicode Standard, version 16.0. + * Character information is based on the Unicode Standard, version 17.0. *

* The Java platform has supported different versions of the Unicode * Standard over time. Upgrades to newer versions of the Unicode Standard @@ -75,6 +75,8 @@ import static java.lang.constant.ConstantDescs.DEFAULT_NAME; * Unicode version * * + * Java SE 26 + * Unicode 17.0 * Java SE 24 * Unicode 16.0 * Java SE 22 @@ -745,7 +747,7 @@ class Character implements java.io.Serializable, Comparable, Constabl * It should be adjusted whenever the Unicode Character Database * is upgraded. */ - private static final int NUM_ENTITIES = 782; + private static final int NUM_ENTITIES = 804; private static Map map = HashMap.newHashMap(NUM_ENTITIES); /** @@ -3715,6 +3717,85 @@ class Character implements java.io.Serializable, Comparable, Constabl "OL ONAL", "OLONAL"); + /** + * Constant for the "Sidetic" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock SIDETIC = + new UnicodeBlock("SIDETIC"); + + /** + * Constant for the "Sharada Supplement" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock SHARADA_SUPPLEMENT = + new UnicodeBlock("SHARADA_SUPPLEMENT", + "SHARADA SUPPLEMENT", + "SHARADASUPPLEMENT"); + + /** + * Constant for the "Tolong Siki" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock TOLONG_SIKI = + new UnicodeBlock("TOLONG_SIKI", + "TOLONG SIKI", + "TOLONGSIKI"); + + /** + * Constant for the "Beria Erfe" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock BERIA_ERFE = + new UnicodeBlock("BERIA_ERFE", + "BERIA ERFE", + "BERIAERFE"); + + /** + * Constant for the "Tangut Components Supplement" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock TANGUT_COMPONENTS_SUPPLEMENT = + new UnicodeBlock("TANGUT_COMPONENTS_SUPPLEMENT", + "TANGUT COMPONENTS SUPPLEMENT", + "TANGUTCOMPONENTSSUPPLEMENT"); + + /** + * Constant for the "Miscellaneous Symbols Supplement" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock MISCELLANEOUS_SYMBOLS_SUPPLEMENT = + new UnicodeBlock("MISCELLANEOUS_SYMBOLS_SUPPLEMENT", + "MISCELLANEOUS SYMBOLS SUPPLEMENT", + "MISCELLANEOUSSYMBOLSSUPPLEMENT"); + + /** + * Constant for the "Tai Yo" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock TAI_YO = + new UnicodeBlock("TAI_YO", + "TAI YO", + "TAIYO"); + + /** + * Constant for the "CJK Unified Ideographs Extension J" Unicode + * character block. + * @since 26 + */ + public static final UnicodeBlock CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J = + new UnicodeBlock("CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J", + "CJK UNIFIED IDEOGRAPHS EXTENSION J", + "CJKUNIFIEDIDEOGRAPHSEXTENSIONJ"); + + private static final int[] blockStarts = { 0x0000, // 0000..007F; Basic Latin 0x0080, // 0080..00FF; Latin-1 Supplement @@ -3916,7 +3997,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x108E0, // 108E0..108FF; Hatran 0x10900, // 10900..1091F; Phoenician 0x10920, // 10920..1093F; Lydian - 0x10940, // unassigned + 0x10940, // 10940..1095F; Sidetic + 0x10960, // unassigned 0x10980, // 10980..1099F; Meroitic Hieroglyphs 0x109A0, // 109A0..109FF; Meroitic Cursive 0x10A00, // 10A00..10A5F; Kharoshthi @@ -3977,14 +4059,16 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x11AB0, // 11AB0..11ABF; Unified Canadian Aboriginal Syllabics Extended-A 0x11AC0, // 11AC0..11AFF; Pau Cin Hau 0x11B00, // 11B00..11B5F; Devanagari Extended-A - 0x11B60, // unassigned + 0x11B60, // 11B60..11B7F; Sharada Supplement + 0x11B80, // unassigned 0x11BC0, // 11BC0..11BFF; Sunuwar 0x11C00, // 11C00..11C6F; Bhaiksuki 0x11C70, // 11C70..11CBF; Marchen 0x11CC0, // unassigned 0x11D00, // 11D00..11D5F; Masaram Gondi 0x11D60, // 11D60..11DAF; Gunjala Gondi - 0x11DB0, // unassigned + 0x11DB0, // 11DB0..11DEF; Tolong Siki + 0x11DF0, // unassigned 0x11EE0, // 11EE0..11EFF; Makasar 0x11F00, // 11F00..11F5F; Kawi 0x11F60, // unassigned @@ -4011,7 +4095,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x16D40, // 16D40..16D7F; Kirat Rai 0x16D80, // unassigned 0x16E40, // 16E40..16E9F; Medefaidrin - 0x16EA0, // unassigned + 0x16EA0, // 16EA0..16EDF; Beria Erfe + 0x16EE0, // unassigned 0x16F00, // 16F00..16F9F; Miao 0x16FA0, // unassigned 0x16FE0, // 16FE0..16FFF; Ideographic Symbols and Punctuation @@ -4019,7 +4104,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x18800, // 18800..18AFF; Tangut Components 0x18B00, // 18B00..18CFF; Khitan Small Script 0x18D00, // 18D00..18D7F; Tangut Supplement - 0x18D80, // unassigned + 0x18D80, // 18D80..18DFF; Tangut Components Supplement + 0x18E00, // unassigned 0x1AFF0, // 1AFF0..1AFFF; Kana Extended-B 0x1B000, // 1B000..1B0FF; Kana Supplement 0x1B100, // 1B100..1B12F; Kana Extended-A @@ -4030,7 +4116,7 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1BCA0, // 1BCA0..1BCAF; Shorthand Format Controls 0x1BCB0, // unassigned 0x1CC00, // 1CC00..1CEBF; Symbols for Legacy Computing Supplement - 0x1CEC0, // unassigned + 0x1CEC0, // 1CEC0..1CEFF; Miscellaneous Symbols Supplement 0x1CF00, // 1CF00..1CFCF; Znamenny Musical Notation 0x1CFD0, // unassigned 0x1D000, // 1D000..1D0FF; Byzantine Musical Symbols @@ -4058,6 +4144,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1E500, // unassigned 0x1E5D0, // 1E5D0..1E5FF; Ol Onal 0x1E600, // unassigned + 0x1E6C0, // 1E6C0..1E6FF; Tai Yo + 0x1E700, // unassigned 0x1E7E0, // 1E7E0..1E7FF; Ethiopic Extended-B 0x1E800, // 1E800..1E8DF; Mende Kikakui 0x1E8E0, // unassigned @@ -4098,7 +4186,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x2FA20, // unassigned 0x30000, // 30000..3134F; CJK Unified Ideographs Extension G 0x31350, // 31350..323AF; CJK Unified Ideographs Extension H - 0x323B0, // unassigned + 0x323B0, // 323B0..3347F; CJK Unified Ideographs Extension J + 0x33480, // unassigned 0xE0000, // E0000..E007F; Tags 0xE0080, // unassigned 0xE0100, // E0100..E01EF; Variation Selectors Supplement @@ -4308,6 +4397,7 @@ class Character implements java.io.Serializable, Comparable, Constabl HATRAN, PHOENICIAN, LYDIAN, + SIDETIC, null, MEROITIC_HIEROGLYPHS, MEROITIC_CURSIVE, @@ -4369,6 +4459,7 @@ class Character implements java.io.Serializable, Comparable, Constabl UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS_EXTENDED_A, PAU_CIN_HAU, DEVANAGARI_EXTENDED_A, + SHARADA_SUPPLEMENT, null, SUNUWAR, BHAIKSUKI, @@ -4376,6 +4467,7 @@ class Character implements java.io.Serializable, Comparable, Constabl null, MASARAM_GONDI, GUNJALA_GONDI, + TOLONG_SIKI, null, MAKASAR, KAWI, @@ -4403,6 +4495,7 @@ class Character implements java.io.Serializable, Comparable, Constabl KIRAT_RAI, null, MEDEFAIDRIN, + BERIA_ERFE, null, MIAO, null, @@ -4411,6 +4504,7 @@ class Character implements java.io.Serializable, Comparable, Constabl TANGUT_COMPONENTS, KHITAN_SMALL_SCRIPT, TANGUT_SUPPLEMENT, + TANGUT_COMPONENTS_SUPPLEMENT, null, KANA_EXTENDED_B, KANA_SUPPLEMENT, @@ -4422,7 +4516,7 @@ class Character implements java.io.Serializable, Comparable, Constabl SHORTHAND_FORMAT_CONTROLS, null, SYMBOLS_FOR_LEGACY_COMPUTING_SUPPLEMENT, - null, + MISCELLANEOUS_SYMBOLS_SUPPLEMENT, ZNAMENNY_MUSICAL_NOTATION, null, BYZANTINE_MUSICAL_SYMBOLS, @@ -4450,6 +4544,8 @@ class Character implements java.io.Serializable, Comparable, Constabl null, OL_ONAL, null, + TAI_YO, + null, ETHIOPIC_EXTENDED_B, MENDE_KIKAKUI, null, @@ -4490,6 +4586,7 @@ class Character implements java.io.Serializable, Comparable, Constabl null, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_G, CJK_UNIFIED_IDEOGRAPHS_EXTENSION_H, + CJK_UNIFIED_IDEOGRAPHS_EXTENSION_J, null, TAGS, null, @@ -5547,6 +5644,30 @@ class Character implements java.io.Serializable, Comparable, Constabl */ OL_ONAL, + /** + * Unicode script "Sidetic". + * @since 26 + */ + SIDETIC, + + /** + * Unicode script "Tolong Siki". + * @since 26 + */ + TOLONG_SIKI, + + /** + * Unicode script "Beria Erfe". + * @since 26 + */ + BERIA_ERFE, + + /** + * Unicode script "Tai Yo". + * @since 26 + */ + TAI_YO, + /** * Unicode script "Unknown". */ @@ -5648,9 +5769,7 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x085F, // 085F ; UNKNOWN 0x0860, // 0860..086A; SYRIAC 0x086B, // 086B..086F; UNKNOWN - 0x0870, // 0870..088E; ARABIC - 0x088F, // 088F ; UNKNOWN - 0x0890, // 0890..0891; ARABIC + 0x0870, // 0870..0891; ARABIC 0x0892, // 0892..0896; UNKNOWN 0x0897, // 0897..08E1; ARABIC 0x08E2, // 08E2 ; COMMON @@ -5825,8 +5944,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x0C55, // 0C55..0C56; TELUGU 0x0C57, // 0C57 ; UNKNOWN 0x0C58, // 0C58..0C5A; TELUGU - 0x0C5B, // 0C5B..0C5C; UNKNOWN - 0x0C5D, // 0C5D ; TELUGU + 0x0C5B, // 0C5B ; UNKNOWN + 0x0C5C, // 0C5C..0C5D; TELUGU 0x0C5E, // 0C5E..0C5F; UNKNOWN 0x0C60, // 0C60..0C63; TELUGU 0x0C64, // 0C64..0C65; UNKNOWN @@ -5850,8 +5969,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x0CCA, // 0CCA..0CCD; KANNADA 0x0CCE, // 0CCE..0CD4; UNKNOWN 0x0CD5, // 0CD5..0CD6; KANNADA - 0x0CD7, // 0CD7..0CDC; UNKNOWN - 0x0CDD, // 0CDD..0CDE; KANNADA + 0x0CD7, // 0CD7..0CDB; UNKNOWN + 0x0CDC, // 0CDC..0CDE; KANNADA 0x0CDF, // 0CDF ; UNKNOWN 0x0CE0, // 0CE0..0CE3; KANNADA 0x0CE4, // 0CE4..0CE5; UNKNOWN @@ -6062,8 +6181,10 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1A9A, // 1A9A..1A9F; UNKNOWN 0x1AA0, // 1AA0..1AAD; TAI_THAM 0x1AAE, // 1AAE..1AAF; UNKNOWN - 0x1AB0, // 1AB0..1ACE; INHERITED - 0x1ACF, // 1ACF..1AFF; UNKNOWN + 0x1AB0, // 1AB0..1ADD; INHERITED + 0x1ADE, // 1ADE..1ADF; UNKNOWN + 0x1AE0, // 1AE0..1AEB; INHERITED + 0x1AEC, // 1AEC..1AFF; UNKNOWN 0x1B00, // 1B00..1B4C; BALINESE 0x1B4D, // 1B4D ; UNKNOWN 0x1B4E, // 1B4E..1B7F; BALINESE @@ -6155,8 +6276,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x208F, // 208F ; UNKNOWN 0x2090, // 2090..209C; LATIN 0x209D, // 209D..209F; UNKNOWN - 0x20A0, // 20A0..20C0; COMMON - 0x20C1, // 20C1..20CF; UNKNOWN + 0x20A0, // 20A0..20C1; COMMON + 0x20C2, // 20C2..20CF; UNKNOWN 0x20D0, // 20D0..20F0; INHERITED 0x20F1, // 20F1..20FF; UNKNOWN 0x2100, // 2100..2125; COMMON @@ -6179,9 +6300,7 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x2800, // 2800..28FF; BRAILLE 0x2900, // 2900..2B73; COMMON 0x2B74, // 2B74..2B75; UNKNOWN - 0x2B76, // 2B76..2B95; COMMON - 0x2B96, // 2B96 ; UNKNOWN - 0x2B97, // 2B97..2BFF; COMMON + 0x2B76, // 2B76..2BFF; COMMON 0x2C00, // 2C00..2C5F; GLAGOLITIC 0x2C60, // 2C60..2C7F; LATIN 0x2C80, // 2C80..2CF3; COPTIC @@ -6282,15 +6401,9 @@ class Character implements java.io.Serializable, Comparable, Constabl 0xA700, // A700..A721; COMMON 0xA722, // A722..A787; LATIN 0xA788, // A788..A78A; COMMON - 0xA78B, // A78B..A7CD; LATIN - 0xA7CE, // A7CE..A7CF; UNKNOWN - 0xA7D0, // A7D0..A7D1; LATIN - 0xA7D2, // A7D2 ; UNKNOWN - 0xA7D3, // A7D3 ; LATIN - 0xA7D4, // A7D4 ; UNKNOWN - 0xA7D5, // A7D5..A7DC; LATIN - 0xA7DD, // A7DD..A7F1; UNKNOWN - 0xA7F2, // A7F2..A7FF; LATIN + 0xA78B, // A78B..A7DC; LATIN + 0xA7DD, // A7DD..A7F0; UNKNOWN + 0xA7F1, // A7F1..A7FF; LATIN 0xA800, // A800..A82C; SYLOTI_NAGRI 0xA82D, // A82D..A82F; UNKNOWN 0xA830, // A830..A839; COMMON @@ -6378,15 +6491,9 @@ class Character implements java.io.Serializable, Comparable, Constabl 0xFB43, // FB43..FB44; HEBREW 0xFB45, // FB45 ; UNKNOWN 0xFB46, // FB46..FB4F; HEBREW - 0xFB50, // FB50..FBC2; ARABIC - 0xFBC3, // FBC3..FBD2; UNKNOWN - 0xFBD3, // FBD3..FD3D; ARABIC + 0xFB50, // FB50..FD3D; ARABIC 0xFD3E, // FD3E..FD3F; COMMON - 0xFD40, // FD40..FD8F; ARABIC - 0xFD90, // FD90..FD91; UNKNOWN - 0xFD92, // FD92..FDC7; ARABIC - 0xFDC8, // FDC8..FDCE; UNKNOWN - 0xFDCF, // FDCF ; ARABIC + 0xFD40, // FD40..FDCF; ARABIC 0xFDD0, // FDD0..FDEF; UNKNOWN 0xFDF0, // FDF0..FDFF; ARABIC 0xFE00, // FE00..FE0F; INHERITED @@ -6555,7 +6662,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x10920, // 10920..10939; LYDIAN 0x1093A, // 1093A..1093E; UNKNOWN 0x1093F, // 1093F ; LYDIAN - 0x10940, // 10940..1097F; UNKNOWN + 0x10940, // 10940..10959; SIDETIC + 0x1095A, // 1095A..1097F; UNKNOWN 0x10980, // 10980..1099F; MEROITIC_HIEROGLYPHS 0x109A0, // 109A0..109B7; MEROITIC_CURSIVE 0x109B8, // 109B8..109BB; UNKNOWN @@ -6625,9 +6733,11 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x10EAE, // 10EAE..10EAF; UNKNOWN 0x10EB0, // 10EB0..10EB1; YEZIDI 0x10EB2, // 10EB2..10EC1; UNKNOWN - 0x10EC2, // 10EC2..10EC4; ARABIC - 0x10EC5, // 10EC5..10EFB; UNKNOWN - 0x10EFC, // 10EFC..10EFF; ARABIC + 0x10EC2, // 10EC2..10EC7; ARABIC + 0x10EC8, // 10EC8..10ECF; UNKNOWN + 0x10ED0, // 10ED0..10ED8; ARABIC + 0x10ED9, // 10ED9..10EF9; UNKNOWN + 0x10EFA, // 10EFA..10EFF; ARABIC 0x10F00, // 10F00..10F27; OLD_SOGDIAN 0x10F28, // 10F28..10F2F; UNKNOWN 0x10F30, // 10F30..10F59; SOGDIAN @@ -6797,7 +6907,9 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x11AC0, // 11AC0..11AF8; PAU_CIN_HAU 0x11AF9, // 11AF9..11AFF; UNKNOWN 0x11B00, // 11B00..11B09; DEVANAGARI - 0x11B0A, // 11B0A..11BBF; UNKNOWN + 0x11B0A, // 11B0A..11B5F; UNKNOWN + 0x11B60, // 11B60..11B67; SHARADA + 0x11B68, // 11B68..11BBF; UNKNOWN 0x11BC0, // 11BC0..11BE1; SUNUWAR 0x11BE2, // 11BE2..11BEF; UNKNOWN 0x11BF0, // 11BF0..11BF9; SUNUWAR @@ -6841,7 +6953,11 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x11D93, // 11D93..11D98; GUNJALA_GONDI 0x11D99, // 11D99..11D9F; UNKNOWN 0x11DA0, // 11DA0..11DA9; GUNJALA_GONDI - 0x11DAA, // 11DAA..11EDF; UNKNOWN + 0x11DAA, // 11DAA..11DAF; UNKNOWN + 0x11DB0, // 11DB0..11DDB; TOLONG_SIKI + 0x11DDC, // 11DDC..11DDF; UNKNOWN + 0x11DE0, // 11DE0..11DE9; TOLONG_SIKI + 0x11DEA, // 11DEA..11EDF; UNKNOWN 0x11EE0, // 11EE0..11EF8; MAKASAR 0x11EF9, // 11EF9..11EFF; UNKNOWN 0x11F00, // 11F00..11F10; KAWI @@ -6901,7 +7017,11 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x16D40, // 16D40..16D79; KIRAT_RAI 0x16D7A, // 16D7A..16E3F; UNKNOWN 0x16E40, // 16E40..16E9A; MEDEFAIDRIN - 0x16E9B, // 16E9B..16EFF; UNKNOWN + 0x16E9B, // 16E9B..16E9F; UNKNOWN + 0x16EA0, // 16EA0..16EB8; BERIA_ERFE + 0x16EB9, // 16EB9..16EBA; UNKNOWN + 0x16EBB, // 16EBB..16ED3; BERIA_ERFE + 0x16ED4, // 16ED4..16EFF; UNKNOWN 0x16F00, // 16F00..16F4A; MIAO 0x16F4B, // 16F4B..16F4E; UNKNOWN 0x16F4F, // 16F4F..16F87; MIAO @@ -6913,16 +7033,16 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x16FE2, // 16FE2..16FE3; HAN 0x16FE4, // 16FE4 ; KHITAN_SMALL_SCRIPT 0x16FE5, // 16FE5..16FEF; UNKNOWN - 0x16FF0, // 16FF0..16FF1; HAN - 0x16FF2, // 16FF2..16FFF; UNKNOWN - 0x17000, // 17000..187F7; TANGUT - 0x187F8, // 187F8..187FF; UNKNOWN - 0x18800, // 18800..18AFF; TANGUT + 0x16FF0, // 16FF0..16FF6; HAN + 0x16FF7, // 16FF7..16FFF; UNKNOWN + 0x17000, // 17000..18AFF; TANGUT 0x18B00, // 18B00..18CD5; KHITAN_SMALL_SCRIPT 0x18CD6, // 18CD6..18CFE; UNKNOWN 0x18CFF, // 18CFF ; KHITAN_SMALL_SCRIPT - 0x18D00, // 18D00..18D08; TANGUT - 0x18D09, // 18D09..1AFEF; UNKNOWN + 0x18D00, // 18D00..18D1E; TANGUT + 0x18D1F, // 18D1F..18D7F; UNKNOWN + 0x18D80, // 18D80..18DF2; TANGUT + 0x18DF3, // 18DF3..1AFEF; UNKNOWN 0x1AFF0, // 1AFF0..1AFF3; KATAKANA 0x1AFF4, // 1AFF4 ; UNKNOWN 0x1AFF5, // 1AFF5..1AFFB; KATAKANA @@ -6954,10 +7074,14 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1BC9C, // 1BC9C..1BC9F; DUPLOYAN 0x1BCA0, // 1BCA0..1BCA3; COMMON 0x1BCA4, // 1BCA4..1CBFF; UNKNOWN - 0x1CC00, // 1CC00..1CCF9; COMMON - 0x1CCFA, // 1CCFA..1CCFF; UNKNOWN + 0x1CC00, // 1CC00..1CCFC; COMMON + 0x1CCFD, // 1CCFD..1CCFF; UNKNOWN 0x1CD00, // 1CD00..1CEB3; COMMON - 0x1CEB4, // 1CEB4..1CEFF; UNKNOWN + 0x1CEB4, // 1CEB4..1CEB9; UNKNOWN + 0x1CEBA, // 1CEBA..1CED0; COMMON + 0x1CED1, // 1CED1..1CEDF; UNKNOWN + 0x1CEE0, // 1CEE0..1CEF0; COMMON + 0x1CEF1, // 1CEF1..1CEFF; UNKNOWN 0x1CF00, // 1CF00..1CF2D; INHERITED 0x1CF2E, // 1CF2E..1CF2F; UNKNOWN 0x1CF30, // 1CF30..1CF46; INHERITED @@ -7072,7 +7196,13 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1E5D0, // 1E5D0..1E5FA; OL_ONAL 0x1E5FB, // 1E5FB..1E5FE; UNKNOWN 0x1E5FF, // 1E5FF ; OL_ONAL - 0x1E600, // 1E600..1E7DF; UNKNOWN + 0x1E600, // 1E600..1E6BF; UNKNOWN + 0x1E6C0, // 1E6C0..1E6DE; TAI_YO + 0x1E6DF, // 1E6DF ; UNKNOWN + 0x1E6E0, // 1E6E0..1E6F5; TAI_YO + 0x1E6F6, // 1E6F6..1E6FD; UNKNOWN + 0x1E6FE, // 1E6FE..1E6FF; TAI_YO + 0x1E700, // 1E700..1E7DF; UNKNOWN 0x1E7E0, // 1E7E0..1E7E6; ETHIOPIC 0x1E7E7, // 1E7E7 ; UNKNOWN 0x1E7E8, // 1E7E8..1E7EB; ETHIOPIC @@ -7189,15 +7319,13 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1F252, // 1F252..1F25F; UNKNOWN 0x1F260, // 1F260..1F265; COMMON 0x1F266, // 1F266..1F2FF; UNKNOWN - 0x1F300, // 1F300..1F6D7; COMMON - 0x1F6D8, // 1F6D8..1F6DB; UNKNOWN + 0x1F300, // 1F300..1F6D8; COMMON + 0x1F6D9, // 1F6D9..1F6DB; UNKNOWN 0x1F6DC, // 1F6DC..1F6EC; COMMON 0x1F6ED, // 1F6ED..1F6EF; UNKNOWN 0x1F6F0, // 1F6F0..1F6FC; COMMON 0x1F6FD, // 1F6FD..1F6FF; UNKNOWN - 0x1F700, // 1F700..1F776; COMMON - 0x1F777, // 1F777..1F77A; UNKNOWN - 0x1F77B, // 1F77B..1F7D9; COMMON + 0x1F700, // 1F700..1F7D9; COMMON 0x1F7DA, // 1F7DA..1F7DF; UNKNOWN 0x1F7E0, // 1F7E0..1F7EB; COMMON 0x1F7EC, // 1F7EC..1F7EF; UNKNOWN @@ -7216,35 +7344,37 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x1F8B0, // 1F8B0..1F8BB; COMMON 0x1F8BC, // 1F8BC..1F8BF; UNKNOWN 0x1F8C0, // 1F8C0..1F8C1; COMMON - 0x1F8C2, // 1F8C2..1F8FF; UNKNOWN - 0x1F900, // 1F900..1FA53; COMMON - 0x1FA54, // 1FA54..1FA5F; UNKNOWN + 0x1F8C2, // 1F8C2..1F8CF; UNKNOWN + 0x1F8D0, // 1F8D0..1F8D8; COMMON + 0x1F8D9, // 1F8D9..1F8FF; UNKNOWN + 0x1F900, // 1F900..1FA57; COMMON + 0x1FA58, // 1FA58..1FA5F; UNKNOWN 0x1FA60, // 1FA60..1FA6D; COMMON 0x1FA6E, // 1FA6E..1FA6F; UNKNOWN 0x1FA70, // 1FA70..1FA7C; COMMON 0x1FA7D, // 1FA7D..1FA7F; UNKNOWN - 0x1FA80, // 1FA80..1FA89; COMMON - 0x1FA8A, // 1FA8A..1FA8E; UNKNOWN - 0x1FA8F, // 1FA8F..1FAC6; COMMON - 0x1FAC7, // 1FAC7..1FACD; UNKNOWN - 0x1FACE, // 1FACE..1FADC; COMMON + 0x1FA80, // 1FA80..1FA8A; COMMON + 0x1FA8B, // 1FA8B..1FA8D; UNKNOWN + 0x1FA8E, // 1FA8E..1FAC6; COMMON + 0x1FAC7, // 1FAC7 ; UNKNOWN + 0x1FAC8, // 1FAC8 ; COMMON + 0x1FAC9, // 1FAC9..1FACC; UNKNOWN + 0x1FACD, // 1FACD..1FADC; COMMON 0x1FADD, // 1FADD..1FADE; UNKNOWN - 0x1FADF, // 1FADF..1FAE9; COMMON - 0x1FAEA, // 1FAEA..1FAEF; UNKNOWN - 0x1FAF0, // 1FAF0..1FAF8; COMMON + 0x1FADF, // 1FADF..1FAEA; COMMON + 0x1FAEB, // 1FAEB..1FAEE; UNKNOWN + 0x1FAEF, // 1FAEF..1FAF8; COMMON 0x1FAF9, // 1FAF9..1FAFF; UNKNOWN 0x1FB00, // 1FB00..1FB92; COMMON 0x1FB93, // 1FB93 ; UNKNOWN - 0x1FB94, // 1FB94..1FBF9; COMMON - 0x1FBFA, // 1FBFA..1FFFF; UNKNOWN + 0x1FB94, // 1FB94..1FBFA; COMMON + 0x1FBFB, // 1FBFB..1FFFF; UNKNOWN 0x20000, // 20000..2A6DF; HAN 0x2A6E0, // 2A6E0..2A6FF; UNKNOWN - 0x2A700, // 2A700..2B739; HAN - 0x2B73A, // 2B73A..2B73F; UNKNOWN - 0x2B740, // 2B740..2B81D; HAN + 0x2A700, // 2A700..2B81D; HAN 0x2B81E, // 2B81E..2B81F; UNKNOWN - 0x2B820, // 2B820..2CEA1; HAN - 0x2CEA2, // 2CEA2..2CEAF; UNKNOWN + 0x2B820, // 2B820..2CEAD; HAN + 0x2CEAE, // 2CEAE..2CEAF; UNKNOWN 0x2CEB0, // 2CEB0..2EBE0; HAN 0x2EBE1, // 2EBE1..2EBEF; UNKNOWN 0x2EBF0, // 2EBF0..2EE5D; HAN @@ -7253,8 +7383,8 @@ class Character implements java.io.Serializable, Comparable, Constabl 0x2FA1E, // 2FA1E..2FFFF; UNKNOWN 0x30000, // 30000..3134A; HAN 0x3134B, // 3134B..3134F; UNKNOWN - 0x31350, // 31350..323AF; HAN - 0x323B0, // 323B0..E0000; UNKNOWN + 0x31350, // 31350..33479; HAN + 0x3347A, // 3347A..E0000; UNKNOWN 0xE0001, // E0001 ; COMMON 0xE0002, // E0002..E001F; UNKNOWN 0xE0020, // E0020..E007F; COMMON @@ -7359,9 +7489,7 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 085F SYRIAC, // 0860..086A UNKNOWN, // 086B..086F - ARABIC, // 0870..088E - UNKNOWN, // 088F - ARABIC, // 0890..0891 + ARABIC, // 0870..0891 UNKNOWN, // 0892..0896 ARABIC, // 0897..08E1 COMMON, // 08E2 @@ -7536,8 +7664,8 @@ class Character implements java.io.Serializable, Comparable, Constabl TELUGU, // 0C55..0C56 UNKNOWN, // 0C57 TELUGU, // 0C58..0C5A - UNKNOWN, // 0C5B..0C5C - TELUGU, // 0C5D + UNKNOWN, // 0C5B + TELUGU, // 0C5C..0C5D UNKNOWN, // 0C5E..0C5F TELUGU, // 0C60..0C63 UNKNOWN, // 0C64..0C65 @@ -7561,8 +7689,8 @@ class Character implements java.io.Serializable, Comparable, Constabl KANNADA, // 0CCA..0CCD UNKNOWN, // 0CCE..0CD4 KANNADA, // 0CD5..0CD6 - UNKNOWN, // 0CD7..0CDC - KANNADA, // 0CDD..0CDE + UNKNOWN, // 0CD7..0CDB + KANNADA, // 0CDC..0CDE UNKNOWN, // 0CDF KANNADA, // 0CE0..0CE3 UNKNOWN, // 0CE4..0CE5 @@ -7773,8 +7901,10 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 1A9A..1A9F TAI_THAM, // 1AA0..1AAD UNKNOWN, // 1AAE..1AAF - INHERITED, // 1AB0..1ACE - UNKNOWN, // 1ACF..1AFF + INHERITED, // 1AB0..1ADD + UNKNOWN, // 1ADE..1ADF + INHERITED, // 1AE0..1AEB + UNKNOWN, // 1AEC..1AFF BALINESE, // 1B00..1B4C UNKNOWN, // 1B4D BALINESE, // 1B4E..1B7F @@ -7866,8 +7996,8 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 208F LATIN, // 2090..209C UNKNOWN, // 209D..209F - COMMON, // 20A0..20C0 - UNKNOWN, // 20C1..20CF + COMMON, // 20A0..20C1 + UNKNOWN, // 20C2..20CF INHERITED, // 20D0..20F0 UNKNOWN, // 20F1..20FF COMMON, // 2100..2125 @@ -7890,9 +8020,7 @@ class Character implements java.io.Serializable, Comparable, Constabl BRAILLE, // 2800..28FF COMMON, // 2900..2B73 UNKNOWN, // 2B74..2B75 - COMMON, // 2B76..2B95 - UNKNOWN, // 2B96 - COMMON, // 2B97..2BFF + COMMON, // 2B76..2BFF GLAGOLITIC, // 2C00..2C5F LATIN, // 2C60..2C7F COPTIC, // 2C80..2CF3 @@ -7993,15 +8121,9 @@ class Character implements java.io.Serializable, Comparable, Constabl COMMON, // A700..A721 LATIN, // A722..A787 COMMON, // A788..A78A - LATIN, // A78B..A7CD - UNKNOWN, // A7CE..A7CF - LATIN, // A7D0..A7D1 - UNKNOWN, // A7D2 - LATIN, // A7D3 - UNKNOWN, // A7D4 - LATIN, // A7D5..A7DC - UNKNOWN, // A7DD..A7F1 - LATIN, // A7F2..A7FF + LATIN, // A78B..A7DC + UNKNOWN, // A7DD..A7F0 + LATIN, // A7F1..A7FF SYLOTI_NAGRI, // A800..A82C UNKNOWN, // A82D..A82F COMMON, // A830..A839 @@ -8089,15 +8211,9 @@ class Character implements java.io.Serializable, Comparable, Constabl HEBREW, // FB43..FB44 UNKNOWN, // FB45 HEBREW, // FB46..FB4F - ARABIC, // FB50..FBC2 - UNKNOWN, // FBC3..FBD2 - ARABIC, // FBD3..FD3D + ARABIC, // FB50..FD3D COMMON, // FD3E..FD3F - ARABIC, // FD40..FD8F - UNKNOWN, // FD90..FD91 - ARABIC, // FD92..FDC7 - UNKNOWN, // FDC8..FDCE - ARABIC, // FDCF + ARABIC, // FD40..FDCF UNKNOWN, // FDD0..FDEF ARABIC, // FDF0..FDFF INHERITED, // FE00..FE0F @@ -8266,7 +8382,8 @@ class Character implements java.io.Serializable, Comparable, Constabl LYDIAN, // 10920..10939 UNKNOWN, // 1093A..1093E LYDIAN, // 1093F - UNKNOWN, // 10940..1097F + SIDETIC, // 10940..10959 + UNKNOWN, // 1095A..1097F MEROITIC_HIEROGLYPHS, // 10980..1099F MEROITIC_CURSIVE, // 109A0..109B7 UNKNOWN, // 109B8..109BB @@ -8336,9 +8453,11 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 10EAE..10EAF YEZIDI, // 10EB0..10EB1 UNKNOWN, // 10EB2..10EC1 - ARABIC, // 10EC2..10EC4 - UNKNOWN, // 10EC5..10EFB - ARABIC, // 10EFC..10EFF + ARABIC, // 10EC2..10EC7 + UNKNOWN, // 10EC8..10ECF + ARABIC, // 10ED0..10ED8 + UNKNOWN, // 10ED9..10EF9 + ARABIC, // 10EFA..10EFF OLD_SOGDIAN, // 10F00..10F27 UNKNOWN, // 10F28..10F2F SOGDIAN, // 10F30..10F59 @@ -8508,7 +8627,9 @@ class Character implements java.io.Serializable, Comparable, Constabl PAU_CIN_HAU, // 11AC0..11AF8 UNKNOWN, // 11AF9..11AFF DEVANAGARI, // 11B00..11B09 - UNKNOWN, // 11B0A..11BBF + UNKNOWN, // 11B0A..11B5F + SHARADA, // 11B60..11B67 + UNKNOWN, // 11B68..11BBF SUNUWAR, // 11BC0..11BE1 UNKNOWN, // 11BE2..11BEF SUNUWAR, // 11BF0..11BF9 @@ -8552,7 +8673,11 @@ class Character implements java.io.Serializable, Comparable, Constabl GUNJALA_GONDI, // 11D93..11D98 UNKNOWN, // 11D99..11D9F GUNJALA_GONDI, // 11DA0..11DA9 - UNKNOWN, // 11DAA..11EDF + UNKNOWN, // 11DAA..11DAF + TOLONG_SIKI, // 11DB0..11DDB + UNKNOWN, // 11DDC..11DDF + TOLONG_SIKI, // 11DE0..11DE9 + UNKNOWN, // 11DEA..11EDF MAKASAR, // 11EE0..11EF8 UNKNOWN, // 11EF9..11EFF KAWI, // 11F00..11F10 @@ -8612,7 +8737,11 @@ class Character implements java.io.Serializable, Comparable, Constabl KIRAT_RAI, // 16D40..16D79 UNKNOWN, // 16D7A..16E3F MEDEFAIDRIN, // 16E40..16E9A - UNKNOWN, // 16E9B..16EFF + UNKNOWN, // 16E9B..16E9F + BERIA_ERFE, // 16EA0..16EB8 + UNKNOWN, // 16EB9..16EBA + BERIA_ERFE, // 16EBB..16ED3 + UNKNOWN, // 16ED4..16EFF MIAO, // 16F00..16F4A UNKNOWN, // 16F4B..16F4E MIAO, // 16F4F..16F87 @@ -8624,16 +8753,16 @@ class Character implements java.io.Serializable, Comparable, Constabl HAN, // 16FE2..16FE3 KHITAN_SMALL_SCRIPT, // 16FE4 UNKNOWN, // 16FE5..16FEF - HAN, // 16FF0..16FF1 - UNKNOWN, // 16FF2..16FFF - TANGUT, // 17000..187F7 - UNKNOWN, // 187F8..187FF - TANGUT, // 18800..18AFF + HAN, // 16FF0..16FF6 + UNKNOWN, // 16FF7..16FFF + TANGUT, // 17000..18AFF KHITAN_SMALL_SCRIPT, // 18B00..18CD5 UNKNOWN, // 18CD6..18CFE KHITAN_SMALL_SCRIPT, // 18CFF - TANGUT, // 18D00..18D08 - UNKNOWN, // 18D09..1AFEF + TANGUT, // 18D00..18D1E + UNKNOWN, // 18D1F..18D7F + TANGUT, // 18D80..18DF2 + UNKNOWN, // 18DF3..1AFEF KATAKANA, // 1AFF0..1AFF3 UNKNOWN, // 1AFF4 KATAKANA, // 1AFF5..1AFFB @@ -8665,10 +8794,14 @@ class Character implements java.io.Serializable, Comparable, Constabl DUPLOYAN, // 1BC9C..1BC9F COMMON, // 1BCA0..1BCA3 UNKNOWN, // 1BCA4..1CBFF - COMMON, // 1CC00..1CCF9 - UNKNOWN, // 1CCFA..1CCFF + COMMON, // 1CC00..1CCFC + UNKNOWN, // 1CCFD..1CCFF COMMON, // 1CD00..1CEB3 - UNKNOWN, // 1CEB4..1CEFF + UNKNOWN, // 1CEB4..1CEB9 + COMMON, // 1CEBA..1CED0 + UNKNOWN, // 1CED1..1CEDF + COMMON, // 1CEE0..1CEF0 + UNKNOWN, // 1CEF1..1CEFF INHERITED, // 1CF00..1CF2D UNKNOWN, // 1CF2E..1CF2F INHERITED, // 1CF30..1CF46 @@ -8783,7 +8916,13 @@ class Character implements java.io.Serializable, Comparable, Constabl OL_ONAL, // 1E5D0..1E5FA UNKNOWN, // 1E5FB..1E5FE OL_ONAL, // 1E5FF - UNKNOWN, // 1E600..1E7DF + UNKNOWN, // 1E600..1E6BF + TAI_YO, // 1E6C0..1E6DE + UNKNOWN, // 1E6DF + TAI_YO, // 1E6E0..1E6F5 + UNKNOWN, // 1E6F6..1E6FD + TAI_YO, // 1E6FE..1E6FF + UNKNOWN, // 1E700..1E7DF ETHIOPIC, // 1E7E0..1E7E6 UNKNOWN, // 1E7E7 ETHIOPIC, // 1E7E8..1E7EB @@ -8900,15 +9039,13 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 1F252..1F25F COMMON, // 1F260..1F265 UNKNOWN, // 1F266..1F2FF - COMMON, // 1F300..1F6D7 - UNKNOWN, // 1F6D8..1F6DB + COMMON, // 1F300..1F6D8 + UNKNOWN, // 1F6D9..1F6DB COMMON, // 1F6DC..1F6EC UNKNOWN, // 1F6ED..1F6EF COMMON, // 1F6F0..1F6FC UNKNOWN, // 1F6FD..1F6FF - COMMON, // 1F700..1F776 - UNKNOWN, // 1F777..1F77A - COMMON, // 1F77B..1F7D9 + COMMON, // 1F700..1F7D9 UNKNOWN, // 1F7DA..1F7DF COMMON, // 1F7E0..1F7EB UNKNOWN, // 1F7EC..1F7EF @@ -8927,35 +9064,37 @@ class Character implements java.io.Serializable, Comparable, Constabl COMMON, // 1F8B0..1F8BB UNKNOWN, // 1F8BC..1F8BF COMMON, // 1F8C0..1F8C1 - UNKNOWN, // 1F8C2..1F8FF - COMMON, // 1F900..1FA53 - UNKNOWN, // 1FA54..1FA5F + UNKNOWN, // 1F8C2..1F8CF + COMMON, // 1F8D0..1F8D8 + UNKNOWN, // 1F8D9..1F8FF + COMMON, // 1F900..1FA57 + UNKNOWN, // 1FA58..1FA5F COMMON, // 1FA60..1FA6D UNKNOWN, // 1FA6E..1FA6F COMMON, // 1FA70..1FA7C UNKNOWN, // 1FA7D..1FA7F - COMMON, // 1FA80..1FA89 - UNKNOWN, // 1FA8A..1FA8E - COMMON, // 1FA8F..1FAC6 - UNKNOWN, // 1FAC7..1FACD - COMMON, // 1FACE..1FADC + COMMON, // 1FA80..1FA8A + UNKNOWN, // 1FA8B..1FA8D + COMMON, // 1FA8E..1FAC6 + UNKNOWN, // 1FAC7 + COMMON, // 1FAC8 + UNKNOWN, // 1FAC9..1FACC + COMMON, // 1FACD..1FADC UNKNOWN, // 1FADD..1FADE - COMMON, // 1FADF..1FAE9 - UNKNOWN, // 1FAEA..1FAEF - COMMON, // 1FAF0..1FAF8 + COMMON, // 1FADF..1FAEA + UNKNOWN, // 1FAEB..1FAEE + COMMON, // 1FAEF..1FAF8 UNKNOWN, // 1FAF9..1FAFF COMMON, // 1FB00..1FB92 UNKNOWN, // 1FB93 - COMMON, // 1FB94..1FBF9 - UNKNOWN, // 1FBFA..1FFFF + COMMON, // 1FB94..1FBFA + UNKNOWN, // 1FBFB..1FFFF HAN, // 20000..2A6DF UNKNOWN, // 2A6E0..2A6FF - HAN, // 2A700..2B739 - UNKNOWN, // 2B73A..2B73F - HAN, // 2B740..2B81D + HAN, // 2A700..2B81D UNKNOWN, // 2B81E..2B81F - HAN, // 2B820..2CEA1 - UNKNOWN, // 2CEA2..2CEAF + HAN, // 2B820..2CEAD + UNKNOWN, // 2CEAE..2CEAF HAN, // 2CEB0..2EBE0 UNKNOWN, // 2EBE1..2EBEF HAN, // 2EBF0..2EE5D @@ -8964,8 +9103,8 @@ class Character implements java.io.Serializable, Comparable, Constabl UNKNOWN, // 2FA1E..2FFFF HAN, // 30000..3134A UNKNOWN, // 3134B..3134F - HAN, // 31350..323AF - UNKNOWN, // 323B0..E0000 + HAN, // 31350..33479 + UNKNOWN, // 3347A..E0000 COMMON, // E0001 UNKNOWN, // E0002..E001F COMMON, // E0020..E007F @@ -8989,6 +9128,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("BASS", BASSA_VAH); aliases.put("BATK", BATAK); aliases.put("BENG", BENGALI); + aliases.put("BERF", BERIA_ERFE); aliases.put("BHKS", BHAIKSUKI); aliases.put("BOPO", BOPOMOFO); aliases.put("BRAH", BRAHMI); @@ -9107,6 +9247,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("SHAW", SHAVIAN); aliases.put("SHRD", SHARADA); aliases.put("SIDD", SIDDHAM); + aliases.put("SIDT", SIDETIC); aliases.put("SIND", KHUDAWADI); aliases.put("SINH", SINHALA); aliases.put("SOGD", SOGDIAN); @@ -9124,6 +9265,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("TAML", TAMIL); aliases.put("TANG", TANGUT); aliases.put("TAVT", TAI_VIET); + aliases.put("TAYO", TAI_YO); aliases.put("TELU", TELUGU); aliases.put("TFNG", TIFINAGH); aliases.put("TGLG", TAGALOG); @@ -9133,6 +9275,7 @@ class Character implements java.io.Serializable, Comparable, Constabl aliases.put("TIRH", TIRHUTA); aliases.put("TNSA", TANGSA); aliases.put("TODR", TODHRI); + aliases.put("TOLS", TOLONG_SIKI); aliases.put("TOTO", TOTO); aliases.put("TUTG", TULU_TIGALARI); aliases.put("UGAR", UGARITIC); diff --git a/src/java.base/share/classes/java/text/CollationElementIterator.java b/src/java.base/share/classes/java/text/CollationElementIterator.java index 5469b95b11e..46fe80e1bda 100644 --- a/src/java.base/share/classes/java/text/CollationElementIterator.java +++ b/src/java.base/share/classes/java/text/CollationElementIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -661,7 +661,7 @@ public final class CollationElementIterator // (the Normalizer is cloned here so that the seeking we do in the next loop // won't affect our real position in the text) - NormalizerBase tempText = (NormalizerBase)text.clone(); + NormalizerBase tempText = text.clone(); // extract the next maxLength characters in the string (we have to do this using the // Normalizer to ensure that our offsets correspond to those the rest of the @@ -732,7 +732,7 @@ public final class CollationElementIterator pair = list.lastElement(); int maxLength = pair.entryName.length(); - NormalizerBase tempText = (NormalizerBase)text.clone(); + NormalizerBase tempText = text.clone(); tempText.next(); key.setLength(0); diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java b/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java index 79a6264d16d..698f28ea2be 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/CharacterIteratorWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -47,7 +47,7 @@ import jdk.internal.icu.text.UCharacterIterator; * @author ram */ -public class CharacterIteratorWrapper extends UCharacterIterator { +public class CharacterIteratorWrapper extends UCharacterIterator implements Cloneable { private CharacterIterator iterator; @@ -135,7 +135,7 @@ public class CharacterIteratorWrapper extends UCharacterIterator { * Creates a clone of this iterator. Clones the underlying character iterator. * @see UCharacterIterator#clone() */ - public Object clone(){ + public CharacterIteratorWrapper clone(){ try { CharacterIteratorWrapper result = (CharacterIteratorWrapper) super.clone(); result.iterator = (CharacterIterator)this.iterator.clone(); diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java b/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java index 7922bb46829..67c9eff6f34 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/Norm2AllModes.java @@ -269,8 +269,8 @@ public final class Norm2AllModes { private Norm2AllModesSingleton(String name) { try { @SuppressWarnings("deprecation") - String DATA_FILE_NAME = "/jdk/internal/icu/impl/data/icudt" + - VersionInfo.ICU_DATA_VERSION_PATH + "/" + name + ".nrm"; + String DATA_FILE_NAME = "/jdk/internal/icu/impl/data/icudata/" + + name + ".nrm"; NormalizerImpl impl=new NormalizerImpl().load(DATA_FILE_NAME); allModes=new Norm2AllModes(impl); } catch (RuntimeException e) { diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java b/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java index cf58f614155..b44a0d7675c 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/ReplaceableUCharacterIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -51,7 +51,7 @@ import jdk.internal.icu.text.UCharacterIterator; * * What are first, last, and getBeginIndex doing here?!?!?! */ -public class ReplaceableUCharacterIterator extends UCharacterIterator { +public class ReplaceableUCharacterIterator extends UCharacterIterator implements Cloneable { // public constructor ------------------------------------------------------ @@ -86,9 +86,9 @@ public class ReplaceableUCharacterIterator extends UCharacterIterator { * Replaceableobject * @return copy of this iterator */ - public Object clone(){ + public ReplaceableUCharacterIterator clone(){ try { - return super.clone(); + return (ReplaceableUCharacterIterator) super.clone(); } catch (CloneNotSupportedException e) { return null; // never invoked } diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java index 287ac38c87b..e7e1e65c2a0 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java @@ -201,9 +201,7 @@ public final class UBiDiProps { // data format constants ----------------------------------------------- *** @SuppressWarnings("deprecation") private static final String DATA_FILE_NAME = - "/jdk/internal/icu/impl/data/icudt" + - VersionInfo.ICU_DATA_VERSION_PATH + - "/ubidi.icu"; + "/jdk/internal/icu/impl/data/icudata/ubidi.icu"; /* format "BiDi" */ private static final int FMT=0x42694469; diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java b/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java index f439ae23d0f..073ffe933df 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/UCharacterProperty.java @@ -330,9 +330,7 @@ public final class UCharacterProperty */ @SuppressWarnings("deprecation") private static final String DATA_FILE_NAME_ = - "/jdk/internal/icu/impl/data/icudt" + - VersionInfo.ICU_DATA_VERSION_PATH + - "/uprops.icu"; + "/jdk/internal/icu/impl/data/icudata/uprops.icu"; /** * Shift value for lead surrogate to form a supplementary character. diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfc.nrm b/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfc.nrm similarity index 64% rename from src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfc.nrm rename to src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfc.nrm index 114da46498df3ff787c0e42d58a4b309863f91c4..fa1d2917ef760977c7474f739ab8016201b9d61c 100644 GIT binary patch delta 564 zcmZpe&Gcb5(*zM6K?W2M!NB16hJm5y1Or1a2A=3(&zvCtXk!yQ2md3sPwZUmn(TI9 z=rmb?$!_vBjyo~Z0ZeKTD4oFS4`N9dz(gQ4hb&mM0Vp#;dam?Jm@JD3P-Khr5vVF5 zn7H&62uu3@R?^Sl5+7w4I5lMWScGI`WOSI+WK3k7IO<`tGC}O8U~HgVl1w2` zyA&HIhfJkR=VU!@F(#SGlc#WLGESI0fm@z&@#K447nwK790c(=FG6(k>jL%YOm^cD znXJI1BPR0>VIYhp^8utpK<2+JzpSjR7L%H+nXHR!$mAJ3j;txNIkHug@A0HFwgMH& zO-|vJVFN0d%6=KhRp6Ih3Nr*m%TC_NEk1b_uLFzxLHUco@U)q%#va&lE7#SG=5wxbEOH=SAbkiDA_C!W*7B-i*}TZ_hb`mF$+LT&a;$;^0K^}d*8l(j delta 452 zcmew`o2g+o(*zM60R|Kh!NB0x!objzz`)RpfhRiHGyBRP+}Om$VM(8#yn#`~O8OdH;-T~hP7UdQEJ89oGBQkRGAc4g9Q80+87KBrFg8#wNG1`e zU5bs9Lnc$Ea5Qd7MRJorZWeKiW7#~(tAb@SkN-DY#>~mgy-xvh$7^~3 diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfkc.nrm b/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfkc.nrm similarity index 90% rename from src/java.base/share/classes/jdk/internal/icu/impl/data/icudt76b/nfkc.nrm rename to src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/nfkc.nrm index 66421d6f29f65ae3e96ca6a5db716eb86da56a29..c78a9d9cac5e80594bcd617998d54e8e47a8f2ab 100644 GIT binary patch delta 832 zcmZXQUr19?9LMjyyY616b2Gt%ZgjCv^4 zkP5Lnfmu)wioW%SyKIEjf+!G?6{56!@F7e^Xb;gJd#H20ZYrH0=bZ2PeSY5`oZtL2 zTYAiP)5FT8Eh{ET@g7OaR!Y+6|Msm&gKd8(_OwgWU5~Zb+N}0N_pF7N?@{WF8QmwJ z3}h8*MPRw8&s2fce8#%gbvwQ_gqK7O^df=3jF$c3%OHSY1aQbyaM5$%wv zgg(yQ?l8}}R8@;>{XFCDG!B-Z_VZM=-=C6XN&Y*zDN+SJElRQB2;K85)x*Zfk;Mu= rMRlG&s_Md%GQ;R<>(Hvq9%h$~)5=(HW3l~&X4J)#=j0{+?bFgffQ0KN delta 730 zcmZXSUr19?9LMjy+wQ&2&0TSvjjxP{`PeMn3F8tk#1)L{UUR z1}0&DIFVpM4@#Cu%Fl>|usjjUI%;)}IHVAnB=dH)l$}!{L0-_jA4n&W|%c zV_SJ{yX|F$w5_@&sXr)5AF`5E{BPcgAFwqww@hEvbk75AN?XuA>2i5EcvNXKm-T?$ zqdrk+WrT~4VM~R-Rh`PJA1}Lq6pEeLhnuR@o)pe*+_rHlQT1_WMd4#SBYXGZ)FwEM zFV^(o>-Ge`haZXXQ#_}?+@yJ3uWG?Q;_o6JXsl_+KZ&gZobc~}rHGrqbrRXh*J^jj zAs(z3ygR}T*B-uDM|j5NQEFbF4OWSYDZ7v;!%QsSz+ z{R&w}w?GF!x`FNkh8P@%ZZv>68iQUqiylG>uEAaKqCqr*CMv|r2vW&xBQOqGSb!z? z46~4fB4l753a|n*@EVq33g#dWzo?x$sY(NMCv`_@oF-_Bo>8cy#cCk`;qH(x3)RSz zbwPvP+}Z;=Dgg4+Njgh&v`9;Io$1WaFpIGc784LN*2PY+^Jst#DwK^_X*RL-4)%h* zVei-%_B*0k|3W205nm+C`#f>6ZRM+;ERqW9s#K5)nyMjfucd@;IP$-0mkH+Ykon2e};Xmi(-tRoVbI+MO zXLg-;UAW*{pO_b?-Sxz3TJvO0duls+vo+21X&O7Kxx^vO<>`g*)6-*?x_hctEFGt5 zo+`eoyK%Q`gxO>6G#bnUMyq*5&ohsaka+^v84_~DykK5|sqWm$KJ!7IJztIV_n3j_etuTYO&;_)fHqmG4O1hQ?>0`8$Zo=pmy3L6r<8}HT?jhLc&PduH z^`htFaQ+wcYq;r73H(5RqPOX{j4;U(n3rX+9F|Y}9X^{7!Aa3B9b^wjaE60AVRP6# zwvdEaLpY<|$%|w+u@?3sd%@v2Ap*YQTaj6WC2 zSjpFtw|S6vMh@R(HOF@7qu;V$-wg6D`(BL9XvGLA{C?QoUf4yZLvvDG?qS_OSomlB z%c#xI@e{hAU$Sj}o!^G_-o$_)yk8^;86`O=Qgy$`61n;w#QQ~&m@1}=nW9Sf8#!XW zSR@+tJh8%PwXaG%PeLMq_DiD8ZcB6+IZk_QhS;d*8Qn&k*n<9d#7?6WalYsY^A3n3 zB!CG~5ub~%!htiGa1n`D!qyFOC&~!h{evwMl5Qy+kBl+eWRgsg8R*4tHz0ECAbq^8 z%ThVnMog7{@iXp^!fIDyykY5ie6QH!@MW!>i#J=U%O!F-Dn&XwC|gl^#*{d&at*3e zl~|4HE8$aLg|S0txHj1CPQsY|NcLjlF`Fb$ zItA@LY@f#?3KQjJa#miKx8+rGO5T(|%6|D5w&_zkQ52U)C8=~|Rwb&D%KmRcb}x=# zbE{p+rr1Y9Y8LO!pPZkchY8W&u z;c<_SIga*S)2I8*pju|XklC(UR4Z#z0dfi(dKp)6pgMg6ys70GojBz-HzI1IdehEU zJ8*TY9(4eGsrRI|sUr$6O`TBZ{$XCSHFXWIVv1YcLRb}0w_;rCcUxlIj_72t#E7W> zf5EIs(f>N!ickMjxGJpJ>;w-4YKFpQUncZr=Ru&_v_=e;y`eLU1QFql`Wptd|70>z`CZ(6cdu%J`#m}Nr2bOsMj@Ib-69{4aP84f@B z4IIwIvc5EkSv&^X>xaR*h7s`4qFz{D&Y-5k3w?_+p}I5{&r^1OmK}%gf)+h@0g`5W64G0 U^1_wINml05rcSk``Ca(@4RK>6)&Kwi delta 1792 zcmcIieN2^A7=JJ4ea|HyHzcNpsELT1UQAQ(mbjUs1Hr+fvP`YeXS?RE>;Uot`qfcoO+2*vS>pAax@5Ddqzusr(`8^-M=bY!9 z_qlS#(>?5I^9EClyFPHAqK2{Q1&9U=1N#kw_8A_2((ojGroPM0POM8f8h)m}*f5g9 zY<17(4?I&XiK135?yW>G&GbNMtt3$;aO#JhBn&W&M(izcE=X_!8N zqI8{88FA}lyVTMK`Xb$;xpWVH2yn=)Ls7pr;DA;%3dsiA2KpfFh^Og0b{9$|%?-H4 zdOIGxgY-RZ({9?U(YgfZ?Q$X~T@F1%&*N2ez{wt=mx22;9Rp@E{S$Nm{J$Bp3Q&|0 zE1xC1RV+P*SuUceT_UcU%Zu1l7BD~jcVQ*0%+0bzYzbSzs@Pgq6N}Wdr%@-{#+(M> zz`b@;O1t@ByS_edVQ=Vnv2x=l#yWT_hFz)~EmlqGsnfd6&_rRq?31|9&au;Gh+WV= z8)74>EyLT)uCY;`!2XJpT;$vg@id;xGXM+t!~79GpD*N}Aj;f#_F8mZf>m~*dl)3zS@GBtc zO|xt>$h!FZvD6B&6kf616jh=cj8c>Kih3{_uxNyuXTdDP9Ih<3?a#0A|=LsZ7zux)oQTx1FAQG4DZ9nGWXUG?^XGxhQk> zUN9h%%K|ebi$P|?{|~5+47wpUwR&)1dU@W2S-5MO<$?(r%?{xfxkO(AN8~cOlGe&K zXaJg653A-*2c%tPWGK z35S0guK#F)>;rhYS@y|6+3(1Zbi_mxMgHJM|0jKa?9l((-cHND#XX63O5mO(d*7^4 zp-ybdRmnBk_MY-zi8>J4uBJbL?fI3jVU-xlQaO1(wKk7hiAJIkQ8I6i%~t+G)a9U0 zt*esuyftM=&0Z$e@o<{@eP)t6U!7!6e{#F2u5S2NrB)WH96z;hZY=Srn;Q$YQnjt$!hSi42W}exRk0R^ZaVxrXa+1Omow~-wdY()p)koo>@17 SJcjZ$%(24_mPy}@eg6PY&nM>q diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/uprops.icu b/src/java.base/share/classes/jdk/internal/icu/impl/data/icudata/uprops.icu new file mode 100644 index 0000000000000000000000000000000000000000..b72fcd903e009ddba8f3c750bd152cd0f3aa3db5 GIT binary patch literal 170864 zcmeFa2b|PY_dh%>nUd)(Gqas-tb!E~6%-3T%3{Z^*s!43zygYj1q%z@j-d0nn6!S$+sF4uLhuw|Q9y#8I+giO2gSeI+e#PMsn zT*-7w4UI^i?`iZ+_s#NM;Jdf3UyKKhodn-`+pmzpsD1 z{|NtX{*(Nt`Dgp5_^nD<68|ee-|;W?f8qbe|4YCX2>9kI zD+2LAF3=e0uN)s38dw9WwIMJ9yfzPv4vYc1D=d2j4h$R)JwHA$IWW!cZ`TB-2WFu* za6#aTz}&#CfqQ^|IIuAAY~WqsJ`Q{m_&V?^aNh>iz^_41FsiHwW`niCmf!&3h6Ohb zjt&kA?j9TyoDe)Lcua6y@but`V3`fe6~POGbAz`A=LPQ%E>K#7i-JpnF9qKWE`e<| z8n&l_;0HzeP4JhHI}{8hLYYuqaCvBOXn3eKG%z$Wv}I_A&>o?qLi>dd2^}Ar9y&jC zNoZzhjs-V{<^@|qcZVJdJrQ~)^aAj&h29H&75WBhZ3!*IHG}5T0->Lae1x<`Ii=ME zTeN0v6|){P6x-9*3eC~h)kcs^6rwiQw$`?%IAi{`YP+%CY5OXz+QG^S?Fj88ZHhKU z8LgeEouggio2$*i2<--nLmGNnyH$HwyGLCUA{OG7)SlH|)!xJ{rM(9u+oFA}eX0GR z{RZ3*VXqiVd{|>!JDjp_^~yBid}V&zD_qa{|8dSEfZHhC79JR0U0D%cCpVt4T zWk#eqva0XZ$oi4BQ6JeP(iwsG>d0P^10#nqmk#q_Il($kxzhr9r-EpSJWSkMN`olr4_Iu+H7v^f71U(;Qz`9L|2Wj z6J6amw{M7U*hl;;`ak?XGy-h)Mz@A@h9{#tMt3X9##w6M8{gdMl<4^A5vYxx6qTM& zMrQ~6gY`mKE{|Rvy&-x>^uB2EtS7oK`mXPS=*Q77qwq;H`fT*oKF*#Eljj3oNu^U` zQZrLa(&6-`=~L5pq*r9t$c)HL%iNv$KHHKVm%TQ7clJ}gQQu3yT7N&6&W*@T%gxWd zl23=Gg&u=XiX*gX+RfVM?zh9y&w9$c*O)sN#Or{FSR&RG%g2VsI$~?YMrgOj zHm4Q>%fPx#Y-d>S6+19CA$BBiQz7nn<@DI8z@HI2J9c&KV&HCo<@VU4vHO5N&GL$C z?WDlBvBj~sVn*L%pT$0meHr^9sK);A&5bMZNIZkbd1XbsH9jz27atzqG(I>!Iz9%h zrziR)R!OXp*gP>bF(NS<y5>zAb89u1O>{<*X{Zi_!)Q7QWoU2NG zoBB+&;w(@7nhvEsY~M{M(|WoNJ<_e^+ppXs@NIh4pvqciQR%hCI{tn>J=9)(dPI6; zdbC}Sn!bNFvi7k!4%?-7O7Cg4RV&l=e@4>>m}OB)AC^8LeY7$8AFVo+pf6I6?TKeNmDpQkb z&h$_JYFL)0zsszV{xGvPSk}*MlIhHBm)Rh*S!S!u4w;QId!*kqw{Mq}(V0VFIWlux zWyneZabw zXhS~Ol067VUArNDX?9}hG#+1s)(r#M0{ zW$(wIXy&m^_J>M+yZ*>yDz;7_zrQK z{I~h=Wk&n(&7U3my81?TZC^Ai>zcH_d0(oiJXVM64=WRT@%y%wnX^>|XgdQ-OuUEcAK_QV_~%^=H83g#SB*HCuQTkL5a+mCKZaG#=RE7>7`baKpWYXcyNgJ1@=_F^cCzPOPM| zcrjNsJnJ1>Q>z$Htvb~r#{D;V9Pi%b^|{KU0DCpU$&>6d%45t{pOMe3lSy#v=RXRX z8(1&$`0^D;8sAgsg|)kTj&pbeE-oyZ%?al<^EBJ5+mYlrTl7ynTB6Xl=5Bqwo)0Z-=3wi$!xMYSbj_6?DxT}=jiSB<8gRK zhp!qy9_W8>|4%_~-N?_^M$^KI-p8t=lz^geeB`e_)di~*J5R|{;`U2>nO#oz~{fn zxnL{xbp*r;oq3#Ff6q8#EAl!N`2;U&oyV~+b=xb!t%#pb5I&=A)%esxo@^Vd#cj>B z=q7GAJ73-CTH8Xo`d*5)IkjILTp;E07c3rl%;7Jp*i7RNTiWa;m+bY>$jkk$T8u{> zZVTDm!L4g=gKJp6KuI6&E$38Mz}Rx|Qy+sf*T}T!ft+=(kY&Vpe98KQ{_yE~o$kTi z8+H%s9@ZV~gn66?ExtG5_GRtZw=`zNXri)~ncZx`DE1;fN;p%^{Y5Qmh@yw}#jL2M zmP8L(e{n2WySR;IFZ95?R2|FAJc$==R^A+mHM1yjjs0c4!|lkn4z@r%QqmGdJHDtr zj+1bcoezu*lzpT$pX0x37!0Tli)(RhCl*w1# zU-Yn#6XnHdY@MA2Qy-90KZuGpk(b*=Ew;t>XBu7VFQd4oK2Tk3t8#V*ql$@=z8Z?z?e9z29v!vV%;I|-%N|&wE!Mg_7imK}7j+KCE7Iai z(oe&TS>^9ZXlDJatT(K+N_#5Hi8_3lbFjSp9f^$ujuJbX)4t90xHiYjB(`U=9JFTb zxQz1<9%Da~b2>*8M-k&$Z+IqazqrN9StGe!RyWpf9XGsE!H)r~hw^hV>^0BNj_CH! z1fm8%aWFs3%ijZPWaoPDjY#LFoufO)bnf0cu5&`?k(~qZor8VyM2g>S;Ue2^Vr9I> zQXKXMU$jkSl)pRD6%n3M=FeuC+)}BQUEvYO$~=Eah!Fx6Q9@X3J(|n!2E?>8z`MX1%;qF*xqC4H0?ap`B;JIcOrhu0`KiAS;BT^R0 z*5dXr$76&zA5cz}t$^1e=kVAmw}{$s1wNub;Bz6SAn(Qy97*UV$PA}2V%+nlVGqU=4b2iooHWIYv$27+A;~jZ$%Jv8P zP64lfus%7Rs}<{|&l_qdJo}~FufbDYXb)i?<|jR8@#AkSXZY9(hlVk-)qpL1Xq@w|)9+}YhI ztfg}2ZgM-uG7GPiaV#rev;NUL9`#nFd_FKO^~Zb+VG{FYM{L%Hy?;!@N1-pAQ+7|! z=OAZ~QWjp5?4AlIoi!u+S;4m#yDJJuiTN-}b}!s_ROqqsEMzCjEo|119x2bwy+k|m z+`_n0cFZT*W%=1>O?H)-Jh$7&*JF$^-2rU3;})QJs)hRR zD#m7u$Mc+VWyiX=F&XbuCO@-BxaS@b{MMH47ZIqZ8)h=R#Ltaz9~S~Y1NmHGDR_ny ze3Bzz+qJGbh^+0Q=6ZBj#1$3lpu4k0FU&x4*3)hOTr1Rv*XEpn-NGx7eFw`VUyDaF zGfzKJSBpP&H(R+R&r4c*e|N(>J73r=se#!;^M&6z>oKk);=Do|+m^WI`wHB1UG%9L z+%*KQaS!Sm+BFy+dpKO%NE_TavU70v@a~b_TXrj5h99=ez&L7^+Q7`LI?PNJ8NSL4 zWw9DkN2xQ^#r}|glz)bQaUc{Jl~`Oq>f+GkQRVEDM_oK>@(iq9U7T2a@r=of74~fz zjSuf1)rd8){&9^n$K$OW#;`orY*yJRJJo^yPBGS2tCg*7(=}kL-btn!xg9fbsanpd zpOxjQy!VymvwMjx*saxRXbx6F<(VsqWImF3Pj^5IbOyQt-9atb8SDynhqO?q{dno` zmK{a?6-Ucjsd|Qp<8GgGlHQqGkj*NYu3R3gy?hSZpCnW^(v|8=y%VeA*86!VyWE_|2ToNg}0*h<1Bc(Y>lt-AadyyW`cR8;^JV|znTajrg zJ5heOy11n);pH<=9%&yjvql~zXzgcc#&rsuiFnN-C7gdllz(w(RCqDy@H-OBLTC6j zs8dGS#u`Th{b*E(rVLHSxF$qfzdXghVP*Kq3iqh;5poawo|DD%(UN)n#%5-HR&gn|$ z3LQbJ>w(!Dx1bYnCRB>P)}mC=7S`mSUTI}=zw-OI7&ooVI80e6dY9rWnXT4%e21tM zeC}}09$W3`*^j;UoTW9YFhbRhJadJxsa$^hucMAKVTc;rz~rC*tcGluGv|> zxVGlA%jAtcW#6~*YwwEj$)nU5;OFvEcCyiZmi+TPS@0@>JqJE#fIVpdt?YRk$dzu7 zU2$OfMBbj?YL(Ss*0GVp`ASy_h;mK2*-Uo&c0ADY`Jnhsx#gDy_{%rClZdxZSxQlw zQ`YTG-`L~4v%WA!w!evf7+yoUDDa*{@%rg<7E&L~0)emdiZ;EDIYm zS^JoUMvK=X=gfU#?>g+B8?o>5SXKx1h)H6l*fTbF?ENaO!CdSeOx`lvR=9oGHpXKt z>SQl51KP{U<{YB8eS5O+==m|XQjS04#vZ|r%v6QlZq~5+*zDzN+;7-uIL!~rPyHj= zO37X=r99?1xz_AsmXfZ%Apw6&*%z3rDN5F>0{jzBsWW2J%v02bm45~{M>2`GDYF_n z9Bpa+CT-3nnf=9h4Rm00jsRptD! z%~C$rn)o=#Ij3BBeuEI-b!0rfUoeVS)y?1J$YMktepix$yNa^5EAw=P9y7DNtPS*~ zyO8+%6}yfv#^BQzQ^jxI(2qT{hDL$+Tb!$0TRDdr*|RFd7}QoKqbIw6CJV(!uWZQ?flGtzJ&F2fGt&ZZmwNr$7w4J<2 zU#U0qE9Z?{hce4(IlC-|>3X*N9ol?ta_6+pGdj=iytwn~&Ko-K=$sF~vYOU)M%US0 z7k6FVbwk%3UGwQTC2Zdn*KBqa^U1z#vHyl^ns$bEwsx_0wRVH%UrY$Y{ZS0{qs|Cj z+ylIWN}Z8dJg9R}=g=;Arl#1siB~!Yk+sw}j7F`477v1Zw=gfXH0omD{LnhPrwiNT z(&QNzg9M-D5sMkr84mkS&$yOlOqDzLNn&B=QO?_zETv0OtKU{QS175{rFQwd;rlB4 zi`p+kYZhDQ_WY2LtW2~imLKy~Bx?tIx?!opj1;)sw$9pS9oQN7QP!%H(IJPER}L_hn-pTwhTq!bb7|n$!e)XB8|Z}hd;@>s}$a?gnO{JPbAM+UmrcZz2e<}(_)pOIz+WHrCo`HQ$V+v#eO z^;g35oB4OTPWyO~;FU1ive==jf zLPbWd(w{D^-b`kh%q_`14+HSXn;*JIXN+r+zQ%e`cEQD5-W zqjdx=G98CXKi$H59D~n1a6BE_1;tTYB4tMnb{{6{d;8)eD1NqJpNXwG?Z4|adx-T_ z#nXPk-dV%&tPkTIz?@w^4%T+W{`Bzl?g+<5f|wg~kgv=x`pWp88mJ!?o*xu*2~Rlb zl8aE-r>O6Y(g-kGtg+M_HszAFfZqk!ztO_{^h9U&yifbs z+OL7h@>@qmw!ezG%{h4Qd5xr#ACqW=*t3tUt(TvtEW5di>LqqBfxUHn4jN^nN~u5W zyBOK{uy0qHKSh_O=B-LC;B4%9=N0d~wOd58h|lcIQ@B0p`FLk4KKZxUQ(M5VW&+Ykla5 zI-*uP$<|SzD6(S)Y=!N4tTn1wq^u9-eS_(Y5{z0X7t{1+&8y-ZR*WxPb8UGJy|D-8 zV-W7k_`)@_6334E?0YDj_v~8>^yRPet&)emW!jc(n`2ev%l9_fe4<*&pI1{oM=d&0 zd(~JcjT7qun=S0Rp6iNU=IYTx z$5yKa2JfORch8yd^O=@!VRDV=eRctX#Qxv{S2g%Jxv!25v_@7o@!wY4ux`MoOgC znoG%8MX8r$|1!C=Ua;0phN2*er%#}z&%BHAn7Q$!bMC?u%q+ zl*w#u@43BAvj!u~`Ro$TBlM#rzHU^m9;(}k^1P)u96cNpoXzGJ8zph?HS@Ci#94uv*}Iri`_+vT z+j2AqSgmZ!n(MYo?487_cZu|@IE*XbSt zPtsY`xncKU4ewLOe>8p8{tH+3d?KgOanQr>zNSm(x`RLWwZ{z_N!H5mc4QHl%X;r5 z&{7A&Rrsrwo~&IV9%7`wq-jt}!g8 zrxg`#hVHQ$3ygbMAe#9wItsFnGM;V}GM;cW3Z8LffVb1r0^Av+8ttP7dpgqS?_ho} z2k%mKT-og|*|)_O%Ej|1e#trWk(c&QIThO_UgnAWHNC6Jjx!XCv{e0Q=lQKiIJB0I z$J{&~IX)ZdctPLkh39&hmXxx$l#}jxXMhym!sB%O|IvSnq{lGVO1r^OSqf#%NX><7C;W zcB_r_r74ec_TxUt$^1~y?%2j}YZy@1n-?8#oTzL+XTGd8r#P!#tj%hZ$>qKQmc^KL zVvBa3eC?LXG_+4DPVDAL)( zY+?TL_dWZFnKYHYZNsKi3N%TmQgxNkQhA(gpXXYt972NoR+Asvn76vw#KOCIHG*Q>m8_-{5BAO2~hI4gEjbz1aWKlyU&JS|4oD6XAJ5bT)YF!Uc1=8a`nl3C1}gt z<13p#DZf>3mCJoTEo1jcpD_-f?mBJf3)YsIOm!ladsLzovzYlx zqGdjm#cm@Dqi8Sn65~txIoZVc%6b0egvw*clT|{JotCy!KT3~^eOpr;QG2`Qj8;3v z5w+T7GWyE&sH2z2lTM`Klf)S>TAlM#UZ?h{0%yD2Vs723edZ<4Ak$(Vddcjf4*f(~ z3>BGw<9I#mLS-cwgX1+qb7t_U0Ll@UCQ_FR}3b>vYP~duo0bE9NQpJl0Zn zbzvoy^!9t>-o^DUiuzG1y5g;8p4V2%mm~|L3x8V3)w&f$@PO z0#gFh0%rtf2QCa;8MrQROW^LnLxCr=j|7$kUJ1MtSQ_{u@O|L-peq-fO|t1WDg8o5}E_+8$>!7eL?Eo(2MEm z=~?Ny*;_(yrXNl}nO>6FKC@e9-^{_8Ntt6aCue4)UrE1{{!jXY#@c=l_FK>rNdG7G zW9rZBbJ>@(uVvp!Uy)s!U7B8=z9;=<`iJx%86^|RWHNP`)_#kc(=A7~KARbsSv#|S zW|Q>sna+MM_Iso8w8qm#^n0g>r9eOL_ievl4f#)8H~V0zZHc#xORMc8+dJF0Z~v|N zvF5v*=eOjVry@chG@lw;-uy;rMd)YErMX&z%}2IONJrCIEzo#T<3(D$@hT22r`79; z>{D7>!z^vJhMODiYIsl^qOIR>Lc?@zMB`b_E40nEZ5p0vSghw8-fDQS;ev)s8s-45 z(L3}t0c$n9)ObDMw#J(cdVS+PMS4@?eT|PcKHb>e_;OK$ewE-3!|QEh{eI)8jVP~Z z{8@Sg^P5ICZHbzu?R)fWWQ?pm zx}wxHuxYoZeGUHu$kudd^X|>N>s#wPG)&NU)A!L2(ht>-(vR0q(Yy4s^z-%0^=tK8 z^j-A3^@sE)^ltrS{cZgteYw6&|3?2M=gtLliCn&60z^;DHRf6yCgcX>2IYq3HqDLB zjnN;^?Vj5=cW`b}Zi>FEzJq?LJ{vsu$xVgz_}nCngNRdeUAgmem*nQ;Zp_Wg&CflW zdph@g?)BV%a-ZbB%Kv6VSEwh54cR4fO+| zj&-5sS^3TKTjf5_@0cG8*8TH`=H>xC3cMy`4f$#L)Ah&mGxO)?FN0oh4Vr5p)7H6p zpuJJQB!7od*>Cyz`N#6lfb|7^pZpv7_WV)#_wvi~U*&%U-JbOidkF4sMuG(^^ ze^$+~nvKD}dCfLZ^Px36)$9q{<@p!#&w=&;eOApu`2jT(^OFlFgFL0?_?qc8Gxgbp zeD2Gd^T2XRuDP&Beu@5h%~dtm*W8Bvthw7*KbZScp9s00fEsf(kLd^3JYDm=zP#oY z@OVc*5bLN}TJr^r+kBW+mw>xb4pTKhEUpRawj_MO_L`i$DoYrn)%f;qh)cPz#r3Il3?0I%Qm zo$I_%$E9@|jND~)@jAWmcEg0a2HogwT^qz5ST_(VUr@-w2p@_w2}bu3*kfRpK_1vQ z@=NR10zYV@Zr%Ivd1(X4gFreR#PrzYh8UI@mh;Wia25(%;m#t~;RaeSLY|r#0_EhDmk%6mrn^ zv2|b9{e=5h-5)h;8!-pgYy$hsrS)EYmcG1Rt550+>I?PtxheS<>f7p9%N<)k1h(80 zI<#NELH%a+TLRsoeh(O*uj}`tY8b7bkiV|}$a**yfX}J5C)UrbKfnHR;IFB_x&F@j z2Vk_;tADKi8U2aE+x5@azgGV)jO74md#Ta(=NN}uqyD@4U-adL%kwXQ##Q%KL!cp^ zdq3ZutE-PfAExO0G(cY(8XNj!gn`u?hCtuP=(F{0^v4S;8a6;J?6I3QK)w3Y4Lj(U zHSCUiSi`thcmJhLCp4Y9GNA3;bhNft>lH1}S+Gbuu;tX2sV%3qUeJ1hc6jrI7FWyc zmUCNnZ~ac2tW9fvMmwWruy%IKvXrWSg!^S~%FN5$pIMMulwBpe zO6J_mrI{DAP1%0gwK8vJ-pee@e3khz^JiAg{5!mg-V|Odyk0h%O=t7rjl-jKw;s%X zpZz7gU3k~;o8E|nwFYzHCNZX zU)xx_SMB+=FTh#GHg#v$EvXOJkB0ZeB@N-ww9sSlNo|BSO}kn9JUb^G4UIsAHw&MZ zIUaCA_^G1i$HC4mig7jW&N&3h1AMtQzcwEmsf{1S!pB8^C^JnJIge$v2cJJ&5i4mZw z3W;&7w%)FzzxOth#r-e-|L@}$n;M(h`!R_Ao>7+z zPwl5Qj|ndee--{V{B!#$5m)=m5kLH6V`>#OQqz906_Mu1s*$xL>qj=k>LdS(jEZd6 zepzJK$UYz)6qy(~HgdAEyzS-4jL5nD*Na>lnG?COb#D84%JTL}k$J7-BlkxZL>5I} zjJ%2Qt-G}D68S(`-ai#t-d~G+(=j;mOUJUPI||o4Iim57o2-3*-(H-yg1@RO5Z(IWZVz&5c&Lh&;yBIOXjFn(qHx{jOUH^uLa z-`Box`)=_^<4?z*cdn)5$N1}_wPRpMYy3a)PvT#}KK@bLNAVxq-j4r?9tpMo^;O~S zj`N(6h$hkr*#8i4iU>$Fh;{t^e4?f8iT>Z4@IzuiV$H*fg<4 zV%xsv?s!?iYl&S1Nwff;*gJ7x;_!~U5|a}rc3g%?z?^ToRzzZU)3u2Uo8~rM-gIlz zT}=-ru53QP`TWFntjfe~iF@%{cH)u5Q&e};7fs(Ko@@Fs@mk`&#ImLpiLVkrCjM;t zq3I8_G=1IlW7D5aUp9T;v?8fAJ<-(N^itECO;0sF*Ys-BJIP4X(*Dh5a9ICU`ybYS ztz;%yM{V|nCR>vO#rWjlVJCj7PD7*DS3BCCV4;lB^M+g7yXlqlP@LT zN`9F9Ecq>1e-UlSr`+vrDL*W+R5}IwY5%SJAJhND{;%}ksbggSJ^R1Yv1P}WsoPTb zrXJ}yCG}M5xsF2^QY}Cyr3R$-?Gs+L*7NtuskK|SORe8Bre#cO!_>c0Tc>tvy{9$N z`f%&Q)>~71LeCCLO-vn~Iw5r$^|EbH+b~!*ZX4B>Y1^)CSK#+)JE-l5wkd6Oz@O08 z+BTr=w6>Y;tF;eq*W1o(yR7Y+wwv1 zj}wvliuC^!?YHXRh?~5hLE{hCW7z2$$k zz8yNcsz-W{jwe#me` zNAw%nZ#Bp?8kY5pjNA3(-PPd6_8Zr4Lcg*74h89men%U+V_|)=!AbXTIvc(yG|rfd6U>I>P z^O?`^{sHNYwg)->$+JtN?dqUmVqn5_YMI!`6|N^(BARB%Y6I!F819j;Cf$WIKp?X?@-@a(8GWF z)ia`QPbK4R^Xc>+1|xrr_b3>VgT4EEcXG}3E^#gKo#kEIJKndCccS+y??}KNu2aGK znRi#GcQA2vt6+JGb2*U^b zJtqTqGE#@-M96?Xhjhxv&9(Bd23S1H!}7;#9^!lEc6)V;JvQ*5&*uc-#1 zUaMc}K8Za&+_RnMM9)E96N{R$KpCch^+fO?X`JDYnuCnqQ2ar~Ok+J`iyXFv zu_t;k2WB|PGa9UW8`4+&(WU4Vc~0|7dS4(v9&wpsY!mD|E|{HqMmm%a zDCPv6{~5sh{&!7ryLoo-RIy;Zu{`5n=zq)qO7P~;G5**6FB%KTsFN+BBN1Acf!W$@ zpll5^d|dd1@TstzU~niM2l7csJ==MofNeAdz}LuaM=07!W}YN5%Du-@6l?>t50_X= zTxPA|b`o{SOLO?U3D{%LbSqpJ0xtJ_YS0T^=K&_TCV0od`acGGr7U3lN{UbF-+ z44zkNA-~9uk!?yX(NCiv#~+VBn!GA`xwya{w0-q3_RbyMn;IAe>wjerc5ky1ux0jO zZ_(c?zS?llPw|J` zL*485U?@lxAhHqQU%2-CD)U$OFeC6&;Mc&fP{|je<>4BR#Ma)79d6y7b|>7?;H=;* zbys&Yw6GB@9Ys`7A9y#)>T3^;xJcnYqNrcrn{y9U z{v*O2a7UF70Y$BQ1NR2V7qy@dtsh$7U9bad037T;IJACXgn{(}W~>A9I`CHxn*@dj zK7^$g2%Ulmd|Cz{2PD9`$>OzjwDkb%7$C|dt|Jem=u`BAnZ>YaL=KpDfz4JTJ`YF` zoM+zC%u@IU32qMe9=C-NKiV)aJTLrjz})bC;k&U0-Itx2of$uvA$Fdbf^;By#m*Km zD|R7BxF9Z!UFdQpB30v%1ORgnqEh(Qfpq8}^bRYLV z>Z{ra)QKZ=;|43tT(D+{;7zg}x3!ZLob?>_}V}GCqUw4C#-v9of}k8GwK` z*YI59xdtBkdb^0XY#Gn0kMFmK)`J!VkX_S~qhfo6PID^Ds32GsuKBA-AC+YO)HxHv5|BDQdC(&<~+QJ*%lTYCW!@ z#Z{q4AgT30VGVo}uM;^nr{=h(uw8C~2Wwi>vu5~{@F!^T4z$DN3Qq`6$Om|&nai$~ z`oS~XGh2VtbEyfipD~%JvOosUY;_ex^bGKWJf7J=&jEC)@5SHE^z4V3H4y45@O%&f z>FJ*HJ?BTyz-Wj&!;Z*V94s<&ZscMPm^m%*^q5vSXB=e1YJoKYf}ZugN3&`VC-~TP zvfQY{Y-aCb{{Juv)_1TrSOZXk1?B$6(jVxX6#p4dfHjV^I;F(@%#s#7sS!2ZsmEw^O^)NdtUCz;PPzZ*#x#NEc%+r0sC;zCTa`N_0$#){W|2a6^Pn`0Q_dK7q$1GCBG%;*0IBg8{rv| zxG`~~zs^9RNbBJ9IMdYmdvs+u@+VpdY#mt`S%|IR%Rdw_*jNVpM*%m=(4cmxKWgYV zg(ofjOI_h#ACP8HS0~h!TQOtl5EXUx$Hwc(_qXAn8 z_+H?*fcKn-0$XU8YL`YHjXWNC2!1zYApTMOBi1uqyKm)yGw*A-h4Jn?O2dD#2<-s> z#n8i|4s#LDD9T)Cw`=1{+s)ayR4%S;yHK|XUZbexe7W|}UI8w^5lHNi*dbi6?d?Ac zp3F6~h|7RK*M>9w5?~IOOB+`PduukZNVfJLTPf0$Fd}_82g-qLAm0n50&$Fn_*yCU zh9`~f%`C~M%wTb}4&LpA+$^%dbG?*MAZIyG!3^MYUwMS!i-J%z_*n2UYL(OA1LYA) zgc3Z$%mp8Ig3HA$a88mArOi=X^54%9xCi;bf*w2=co6tUg5JP<|KotW{Ezx?^<+7?MMNVNra#4^l7djxPz zH3$z34}_=GTpc#fU$rZ=E8usf6(Tk=z6~xS;Ng;2#x@51#<(^kHj=}z>=-uz`w-tg zwxJ!YcWiC9#*EK}{{)>p;9afxx( zYT{ooM7}6udBlY1b!MP7dQ0?1mM8Kx;3EUeA~qC#jLgxL1n^J~LZ-Z`y4&5&EYK>}!m57-=)I8jUcgtN*#4 zz7*rZ+hk`sg#7O^drutpoV8E+?}vK^T*0k^+akihyMoC*7+m!NHVoc|IlLoX8}?u) z*Lc@Xu6jyc;vPhV@{KYk$`m*Iuq&;rM$FA^y4 zfae*st+mm{GFsb& zBl;mc=Kz-Y`tc3o8ze4G7*`irPHhKmJHTe(y#=ff;(&ANE2v%K;g65YA~8QPpT{Am zJsV$c)|jQlZe`B8EobHCXq3$H)ywb<-l_p-+Y~dkha-)gPPJFgG=Zs=!zqWT51a!y zAAnRq{$Rj?`2%65uBQyLErXPGKwHT&2>i_POha0d)(I{R8lM!@=hf$xlL5n(5kcWv zwi4+bqFZMZS(sJIs^tM*vjLYnBf0}a{^z`L9xsk1yz?F${G>FLe!WHLt`TRop?6fd8cN_xqCg&yY zN!$~Eg^sb2L$wDD%-80JhlGb{^R;^za_uZr>^%b?z!O?Nh%E(t5;J}N4L@8V9tb}W zek}Z$wZ7is{$VdCd;o+0L1Irb^44#g| z(H>hHP?TZzeHL5A4ypqEQm^;m=j3Clx2#A$U?*+A*d?*cll3wro=vWuTs!eB;5EPt ziI)M-Ci!)1p_zC$2dcRg@0Q6zsKp!o#b}h1x0ObEDg$`k0DJLpcnbAv@QmG_vmJhw zOU}i1GO%rI+xRZAo^@>3*qHe4SZQo4JH#IN2QY{_k&pgV2`D1^eQ!Y-;NEcoq_tA_ zmIQ9`U#n(34W3pT@}PA9T;K1pAkSoR&Fu!)&%Ks=fU&IZ9xS7wLTPB90)EoI(Z14t z1$?i4tbNY=0Du1dknerpySM~b1XqM!40Zb+E#e0s;v?TOAH2KxzVI#g{Rw#8*X_H% zXL;QBxbJJgx5oNw@cY{Litk0=OM7OL)m zIyN63f^5tNW=_~bSe&xKeXR=mE zSI$m;c8Zbg6}lJjP>*z1s7e&G+#j+)nCDQ`mUjHSB{vgYkoJ%$Jj`s2- z8&1um#I(kR`p^bV zMhb^i4;~L!J3p=t6q~*}eKkfREd}&1Wrr^h)g*)z$;sCK)6JGDDC<33pKll+AMU$Vpf=j@eS(Ssk8Kc=2Z!W!@$ z**T3(s2nURwtsBAfElrhf$_1^EjTq+0fc%_ikXn|ln0MSF8LROS<9v5?;^Nf&c%jU zA@_6Are{|Ir)ZedU6sHbpFV>l+YNXw`@t(0m^vTcRVERI&5N97|^XI}d z{VyvIF(2~z3-ZPtMpnHjN8juV;62J~2WW=?jAqa(wkPBB$07<_07hHwg-r@0%&>Y? zE*H)(oKx`S|AolDWx~7GEZJAeLvN5F`(hEVXN$1rmdUvrD!_^y!_7GZvOG>Y{qwT; zR2Sfs$Et^ab*lo_Xn4OmK)^u>f5IRCnLyt0AIjjjxDC0_;9uV^fq!}XBzFnr;WX`z z9Tz(;_a)bGE_*KW$+F-P$R+-;L-a^CnJ^?-MB+cdtqWlCPD!}iBk_&|ZzeuOWPiw> zl|3uhb3YEXkvg{$q(`J?rp`(kJ)}sY$wifc{LM6VM0vnnERJ6Jy8MpjjMYi$7ev7E z`~_SXI7b>Yb+DU0f5KC8OJma%0RAC{qcz>1_aU{B9oz~P{F>f5rKjMEb*j+}^g<0$ zvz1HMNQx3Y^nU5xQg!KUO2>u4$P2td3#mf77P++W4LV3!)Uj8pE!7ObDvMF+ya?vS zH56y2`9Bc_H(Qtl?^wqDZUr}TVEe5)K-98Azx+q}j|%-b;NGTw*mePagHUJ%fCqS@ z*J^+@^UDjX=D)~)orkNn`STCL^e%wd3sGxq@w%RG>oSj178qcY<& z`@=uGPOwA&Ff$&mMC_~k^>O;g`p4zkC}mHtlR7>d5Q>?zJW9<-q;0f3z zklrG7N9vCB77Q4Na_X+sU8n)e*6Goy>r?jwZUWqD;QsWRCGc9>Kqi<8V!reysX2fP zQdgp6f2A(AL-c}wyLEu*<;*98QjCNj<>P{Dj?U!f`qwK~a^{>>LMNnFZ(}D?k_5 zBiYNSh18Z3j7nKR-d390K35|G?K${v1=nUUja_f{0%@TBJpq1Q^S%C)k!@gZU~cW) zhWf9dfhD(w{)hgR{s-F3FZfSf8$=KNL^IH4)|8c|>WJGJjCkakcB+0S#7}``s@Q7f zJp3QpFIgq4WPZu~p7}9rd#}$nW;f!`k6_Hr{yBXw=HFt?Rg z>@a(jmI@caKeAn90IBiaMCk~#HUE14MYF~%6BpOObM{a%ubnIGkcW>dS~u-23Ys7Seb0BsY*`a-~0qX zG)QbM>Pzw2p&Tr4b|_pA04-Ysw>~TzWH-osfI9Ol^R^wLNA@NT$OjTzb8Q9O_1Ple zm;onskO5>p1xpt5i7)scdmBW1;pnO{1tTa&VUitWtAu9nssMNB@HgMtTLJfG?=@%5 z?*iz^x8*l=0-Pl}+9Z}X$b~r#u(m*hy4~~p;D0mc8>MK@+fWd`hcj2rCH)=F%Q$@t zeUoyK#W9_}S@O$jkv>Pv7k(b$lmo8oE@4PI9P88PFo-#;;d67#;@`Ov_e15kfvLl> zeZB-_b>+|M4S1&X?IO6oQVu@gWi3eMZ$*G5RmkUWvO~F-fDe){S#2mM->?I;FNpvdvs47$ zMz#JtQsnaY8+fRPKAJ~74!A475OA*@Vtn@gs_;;DA^P(Sg@dXE#ug5!6uc!fmsmj1 zcjF)*{YJ1pQVROcW>6%f$$<=sC!LTvJ6oq;q+gmFW{xB&w{C9T!pVRW5v1ibm#G{) zs<&MJhg{N%!WosLe7m_N46x@GIZ+1fB1Gvt`3*|j(W+m8KrX)#uB|WqY6h#`da$ij zraZErou#5=h2xRrXK75weUOv+8G0=pPp49e)I$vBsLG}M5oK^>-iE>;1ZyLIXk`#9 zV)5nHVuYP97hPD-9wTaUuQ~($oUNio%*goM$kMP!RHg~WDu*niIT}480kf{Ols>=; z?5C_HxlTJ;J38Ei(0V>I$UWuS+%J_u9u1$2cBu?b@u6p`fjvw3rSizW-vg-DoFiCY z0yRMaaI38#_3#)k)0flLLq3SbYG}PHK7h{pUakNO7%~y-Kd3HWL1&G zDAp;$vBlCox3t4EVt%%5xDL6}?PysoE34L=Ln5&%Gva84$Aq^va^-?$kv_kOpA33Y z`qK18fb&@ooYv`|2yjN+!^=3Q45ZH;CBbBgPUd$MWkH(PI7EGEK6AMUxB_I7WfJBy z>zOQSd7F7pY2^NrfyMAF<+%K)avu65C$LEU78AJl(YkRw)(^LX+Jwx95bs2DcUJ(( z{a4m}`WX(mM`xI>+rV?^Cv#rT<<}~MJ@X8!=WUQ@G;8zEl}Dkr&;VZDc2KVTGlj$Z z0{Fzl;x9EQTl0*U7DSElOh0NFDaPc+i-7Z?qRcohlip0>ymbHpGp0JMNH>T;AM-0S ztcaU4h`E^?5c=Nam08!09@~)25aOL_ne%}?i*}-x_hX({Z|y5ES7J5;=SWi-WpTJ> z+2nb+mU%L7>I;{U;|o^6BAHgSi9AIxUmnGKAhS>g?nNB;CXJm^?c_@oJw@Ig$uvA( z&LyTNrpql%o7`UxijiBnmi&lv3+Kp&lxrC$+GKXCKg#H*Kd3(_`{YLX&_=wB7S^-E z?1lNugViDPlhr2b^aXa%XHs(|l}r9+e{;kmhs(<#x7xMjZ>8~TO98!0*^}c-+Y_y& z?eS}q<12@?c9b(GIRSn-Rve?g()dliKyzZ%#H!9MS919qGnkQ?K^g5o4j>DoiA@rl zm^EezPXBvgwp1o1`X}Iz-WkL#n0k>(>31@x0-ne`p`Vk3N7@#qvb_1hxrY&A4ChnN zrJiGcq%*RdYSj7>%T%;G9OvxEm+bBgx!=W}>F3BnKDmd9lhaDyW9hn(H49ZGqv%W3 z<<$IO1e0;(Sf-+k4+{nuKHt(97pCUFrFf*wN4kD22grZx@=`2vn9bsCY%|shwx9o& zZAHEYTTlC#1M_m^hvtV`N1%EckB7O93+uS}BhhV~^0FgdfM*lE;0gxA=HLDHPSX6P zQ8w#bR}udG{QKpPLAb(IpeLU47_{OH$rCCARwVU2J@t5UM&^V}CbNW)-7mXO_CQ?p z)$}J3xorWP=Q>TxR#o)Uu|L80WQfC09pVL7%oPiJs|UvQ6^ZqF4{SUAVfw?ubUDzT zO_xLI9_07yJuq`{-QJ^gW#z5L{*Molw1LOw*DnqE$d=af?5-I6XXgIi@$jFTH+YW& zoZ$KqkRb#P@lJHz?Y-PP*Yyc-j{)Yo@ZUA}cdc-J>7DF4$~6+Oc@H{^T+Xd2@3H?JdG~U)fLAB1Pb%8bqXn>`cU^dP99SXpKG#5?+j=H>pEtZtVDX-< zK|0+vgTcS2f5%XxmJRM3+&{R#YZVjPT^oCUbgiO(;@`%<3nI8va3?L!5M3O7Hu`M* z`sA+3!;%w|6VqcePkFbp!?i~6smc&q7~CtkM-lr&MGRwtduWkxCp>dbiN1iq|3Y=G z%~AY!)D2zhx{fQ3=-aM&t{Z}<`ws-b5gZYm5PSlb3Bk#s)kAG?d9XEjl-3G(4VHe| zn;`FM%paUXW#Ia^ZOgGEe(^bX^t?Op;+mI=61_Ko#XBBntI3P|GY+4gXFNX-=G*hQ zn1J~yALW@_%sH2O&S`M694_!R<$2HbstXq(H|!;X5JmeKMVcw*;koEoA5Ry(?sFN4-xa?LG&ci^ zBOja?Jj`kF4za`Bs$JqW*lk!L8&y~rk1&kmvT?-u%=LkIL! zwG=c<0md9AJ*u=gC|)L2EcQ=dV%1O z(!lDxYglT8HiI)lkLSkZ#`HWXi=LQOEft=q1+B1WjUg?UNAwYSiUVEz{?f>eDQs36 zMnyu#VujeAxS)sK%j(0bWb46IZmZQHzZvCcO}W`%&NmqPAOfoQ2EAe2#-#vN;kM#H z30p=Dr_77Vv?e?ErC9Wk@hl6=Tx=1yUr+1iD3+h)D!=CSnp;{CdW&cQQUbuWu0NTxnk3&K&m~0RB{N%new59uc6XuvCuqpE$aHmE8pdky|#CGw8 z%CGJ@?kf@49^m^Tg|->gH=Jnv=az+u;eUB=K1WrT*-wyr~{4sEmSM?675bMJA zqP}8VZ+S?r3z@DImAT%6p8`wb6K|goT;$VzQJ>~Z_|kA$BVa=Tw0(rSNL>WaJKa;j zF22gp49`lf^Zm;=3Z9s{yMP^im4U@?6kOsPpd6qaq#W+M3x4Q&1^iI=8s%!`M&&wX zoU*sEj8%4nZXT{66_=xxsmh7U6s5Iy2)!G6HyrfctGo&CF|b4@MGucoN_~-9&X@G} z>2G|`_}&8i40sNn(Q8|jbIQTDkR$XQgK`mp+j`Go&I*1WRQ?A|DGUXm-J)*Kik)5&w0JyDWv0kaox!LFCLV%3`C%#v6=S%``@mXj<* ztu3MKWaD~SpPl2(ZD1CjiQ8m4=4rK9WuAj^^6Jp$9EldP61C)lkda2Q_jn>g^U5>7?g*t(?3a@?!Za&1(1KmD+9C zhFNQtSPe2Aug-3@Ysgzpd4+PjTw84`<}57Q+H$oyOZi)Ko7*=_@;q{_xW&vKqQrU5 zQJbi-YsqS+RzIuEyqUI?jc1j6ak~>&K2GLg*V#4Z7SYapWLwZFTGWd?<)(}zS4zcP zoadZ1535;jmusyVP_}!D8j-h0%d9e;J(6oge~~8-Q779<tvd$W-rznejrIoT6T$`hLf9!g@hIoH@!YiE*WPiXvKRJ=~vj@^+ZLoR=6&zC=Y^b$n$RL@$vS+p_Advglii zw{A7jw|q{shglLcS@&F-r%cCkI9sR9&#=B zklF29%l-k0d{$%&RhAim_5zH0oq}c`SJnB`+p( zE$7I?PR%}C;+#Fkt|{eR%4W}G)nGI&R+~&N)tcRA*T{0(bz(*49A?QL$ux2=xmKRT z?#ncGy*$2@)_H&7QRIWv-lko{BRMDcl52Ssa+Ug2>SxX=iy|FSb3Jy+*$*vbCCWAS zOy=6;cC%L8BD@9BhqOo;FKT3-q7HfTC%rj~%!51`wZ=PJOM9B*Na8V^BbyVg#IyX! zI%T)(SuUQ@seMr|^7f4Otz!1Uwn}A?*4%I0>RC3BpZLF9Qn)5>jDFRG}V zH^teUW0#)k${<5FlWX$g=`L6j3r@c!)E$0)v7g zh;Ts!4j~xufgp(B2n2a4>NO~W3TV)XNtB18<6LyE?cj_v&Miwb$NfpZz+gPP$`uDY&g?cQ&_;{cBS=FSm`o>)FEb+7gFjB&Kd2q1NA7 zT)O%(!cj*qoy%AE>m6-$q%x<*Dd&1ltw(U@R2`{$-}yD$hBSekKP|OZca+=aN7b{^ zr*bcNsJ-6NIKRNNdp3&L$R1*o>ctPX#x30WxNT)r#?>S7&sv{GC77*T)sd?GnQP;c z)uyhs)Jt8@R+}=;d9EqqD4J&Wl=VF-t=y}$#5wU;3~+cu9>9e1w#A-blh-lw%$ zDMLOkrQ3!hfq1O)c;ncOkA!$JRmOEk>Ra7Wx2^Wl{a_8GDXNdVIF)Bduj-V+vmQ0_ zu8tJ@1rAg%wzJL6%X!cB>IZ)%=E5r&a&zRi)hU|qH%e7$uiSGHX{~?6dDPp^o?$l#+G9)1>RWx@l^!8>r9JEgs?3}A_*jS(+ntBo#$L)SUpN{l z_H@?jZR``X^NVe+HY#m#S*wreeq6dn9f+AT+xfX| z{j=fG+46M}YPa>yvDPcLXkLwTYV zjra73J6^r5ZV5c@eB#p8v&F~jr)p#kDMQ>q;b<*2_iBDLjut#g&8~3pjMF=Ae$;uz z+c>Yerw*xV)6`nx{A$_xyRGwZc~slo@!IX&J>#@YKIyIl|GewE=8p^?CF_=edoU|+s3JRr14SBLyynqRau_K z-r>qL_6f0?`BWaO+zSz6s`iQZV~*2QawxsC$LV6$-zwmbyT1c(=?%KIzyR^OOPObjU#B);*J1bR@ zy62P{|BB&qopcjkilOSYcKJ&`}j>Z<%zxzFZ4n?H#^q0c++yDQh3d(x^q_hcTP@O?7( z_qoolj{J3fclX^bwv%%6Th!`YBOQ}Fq54?=K#lsVeYSJ|Z*nL7S#(}*n^fG78tZk+ zUC^T5pKDEPa(@n8zRgj#JwnUF_NA(f6UT_T(rYezaq5`olD67Mdqbl>QSHrrm!sR(^QpDXHHYR=-BGu#&8NPF zxd+wvnX5I#AqPxs)N3MjRsB;x{#4)AezFW9=MZ~ae2?JIq2V>Pl=rfZ(Z4zvGd`dK_0m7DAaY$G5?vdrS0Ob z3nenQvwbR4&UMd4ovVMfAJbRc;{CNvkH((LYrCjg`&=!T+O&25-fsMx;F~jA-j8b9 z%uAK+sP|2M#ab0cp&ZgTR2;YWO|kMv<&V;c-S3TbT({H8?o{+XyVbq$GuYqK{g&>F ztlr%1^xmE;8t88-?G55L&s~o)=eKm9-J;_2-yXiXyEV;Ism|ARv-}0` zsuVx(snlRk6ZIai>dQOzEN&wHjo4TVdVbcoW8YBUu6@OxoqKj>3+_3(=ctxGr}wm` z3wq|Nb9(COyq?xH*W4TVUDwl!)>QW9Iq!u$KiktpW7qcF*^0)lZWet`#~$c;p!bzM z_=)YSbQ>&gzsYt}PixxR)0&>_X-V7u*W#;E^fv#OU)@~22SUTpFJj?u*x;|&cShgYjX!p!=4a3R-|OF$E$&(P0#)3*u%WP_xc9)t zvG50CR9oDvTRVSS%aXpP>OH^rTzsf=g)Z#9xK}52QGy!?whC5)odXLxj(;_ zLm%TlEjb)lyYo=LT;W@VZ}uJ6x2o>v*!-sa`>DRy5A0T{{F?llz9aiitedV;ygK%Q zTLwNQpHB~bdgwm~c5614%<0&b=+Bi3R~RI3Y+TiMm%ew=x54TQeP765lRp7_x7Mn^ z9WsR@`fl#K8E*c=x&S&yT-ycQweYU}x?`%;c`EAp9o|-Q|ch2ve-xJ!q1u=a+ zy*0mo{;O8|TYg~RuLhP5EX}_a+OJl{tphty#jVAyl5&6Xr1!SVzOP)^u~GwLL;qB* zk)c}UFKJEv&(+oM`l0-jBPSN;7ut&R>xuS4TVds_x*`9Y{%7*PvHJHs^_!vdhAtR6 zbL8d1lIH5~)#LirTmB`gu%y3Z@Q(uz4L&vaRL%QVwQpcGx9rg0nkM?GokT9uPBtd( z{5bozZ8d7sG~@F|kEzh=(WA$Uq9Jr2d#d>7Mc&e!dZe-|AqY*(hh6A7>#X=?sB-c zf6gkL0i9Vmv;SHl?w!$pOKU2e-G58rY^wh={hzVhTgYO6Z~r|l>g)a6ulxHS>i_n3 zYhyp8ouTrldo#Ycb8)c$Mf|MM@TE?5N5|&|P98XU^Z}~=@nGf1=|)hu?SIj!I8s%z zKQ-UCN6#sqGrCywuk`0$KVtO%i9fl;3N?rO!K~gD3T6G8)va9RiLF@a-D8jADK1ns zFKzq&7`|1Z#t)cxc~D`W7IkZ(C5^pE&0D6@J^ti=GJ{&q{ZG( zk2qgPd4$HFv!`$7 z!Sdt7^7EYKJZgT{sg0_b_0v^bHBP8yJ??U?%!OXTv+`KeJ-G_vq4i%pOU1V76|T!E zRcBHYyU}%XSD43#*ERIAS2;Q!N`dwhavvB7T%Vi=xABMG?wF<=z&{s5JuCO!vC8e> zV}p-Lk8sz8b>Xj}ue-SX5!+a^U$Z!RT-*B>TT{7s(8%catN5XTYl|PM(iKH$;A*dH zhu$^xuCe2XcktS27#iI=y0!Q~i~2^f1&y9ie7H3Y99sNs@%PYgi+_OrwfOHtFAW=N z@YKZRpOS&ko%-^!cIB4;(UZNbxsF-I{)b7{6hxO2Ot|i``Coe;IO%A+-VgZUOUG}xM!47R3y z2Devx4N?bhlMWcH))9j(XlTLUsup$JU~4*iu)fY5gobvfEG-+_b7=3u_d;jL&$kbM zdVI&h%bIIwzrl+JFB;k}h+c4kp~EVrHrQW1OX}va zdGz)cb>Co1sy8OmHgvkG(Q&np?ml!@i~7(|Yr1NvHC;2*nywpaO}`)77P-8drExxP z`}?{jTtC#BzBtsnzB2Ts*`f?@tgo@I(QBaV!kxaQpAWAK8p(&_!)qI9q$kqTBNZC@ z8TTbG=4Un5G4{~tbre!I{-O|ZtG^T5=KW?@dJe}b&UxMN+UQK>tcTaJ{GrB#)(-cp zW%Qbg^!;HEaipGtr5=s+3?Bj=*n*h86}@M8u6p+{b$^xSn!1tSM}}L}$bUJF-WD|P zib~Bbm$89H;=MHVU8}8Qe?2xm_S0&OJU@J6eT|e_Qi9H|=q(@2}=7pvQ~-}k+F^qR2_*SE*I*n`=xoyGg> zSm0CnVIzZNBi<^L+v-^+h}Rw8BBP3Gxo^HycT8Kfol)jyj|$7 zX~lMKt%F`Cwy0Z2TGKa2TGO{iTGRJNTGKy{Y_Fagp`@RncXhbAl3VVpCF)QAGV(9j z!%uiz=e?&!Thn2q&2`o2Rq)`aTCW|Y&S_CsjJ78EmR7$j-Tt5BXU-IY>BnO&X}oPVjs2;a#t&#w zr;fL#Ik)sCJ>u-~T77i9HQg{?tDDDFcVT<$EZQ)>-I_NzZ*cr;E$S!Zt?8-pYW-u4 zrP?Ff4{u&sMX-aN|AHtE z+1fKEQ_9%7${5&f8HA;r9jP&e(tPm&7dDp);seHS!Bu{gU2;1@qU#evj-VpIj;OvTs zZJVKg_UvG1z3^OUN4?28lDDk#+}4SG>(jA$qh4$HP1jG7+cL0cN>XOw>^|2SXV30)LtwoY zZLIs3ti98qjV;XN=#O?~a@rn*ZJQA;MRxe}9K{aKMBEv%j|rbHXMZt@1LieC9N#|M zI-f|)M)hge^OCk|xxjN#-bK#0on^LtMEFH*^U712Hj1B#>Ppy-X_<~RF)v;}RYrWr zE67ou2-|uPw&&7GpH%Zz)_il!%Z`mXiK9M_dG5;AUwo#dPQ+e{{K4M86#0X_-JZLo z1qXZN|DyVLNA=JhoqKn5zQVQ*2;1}RjeL|fzOePlV`OyWp<}igSyX}L@QC_wWiruy!B4*QCi1LyO3#} zkL3jR$tYj2Z&$*$T}?*$3fp{zZOlc{xsVH)vb3v3QQKP-wY^1*Y1eg`>3;=$!uD|`-=if=GK$SxG%ui z;u-e~o^fB`8S8e>X#dK(2D3cv;$WN~$k;gGVaM(o(I4}X&zH=x-P&c%Suki1CB(s4 zN5*;>qa~SjuIpjTCDya$nZVg{#H%8n4y@}*O}qJ7EkENwU*;Gt_O>Xmw#jc!{^a5V#b@B%|Mne@WLrmP0v9uC7ovyY4NRC1_WhpZz@R&8;z* zmt)3MmT}B8?K0=;T&%2Ppk?)!aW5z1c|lmmaAh5Pm2r(&2Jr#2Z72_OKJ^@YWKOqs znbVbR86+*(r`2&7K2KmRs%bF}=s(^wbfmrJ`2w%k&}e8^L!+|Jlr}Sjm?(*GN!q{O zFDRo8bZ3n)=VG$V^_!p0C}XbQq&&hgYV*^*vbS6Zoih4n zW{bq1x}AN0^V*a?53+4*oTXAW#3^Qt<(a^U>Fwxufu}w5e%#qRHvh`obDKjP@Dk^P zvJH9OvP&UiZ!d+k=%+qT#@jjOY@V4fX~E^q|8#?}oMZa=!1tzPZ5#iT3H;BVkv3wn zmvyWtv2T`ft&Mq6?$d=g>U~pL$Al95pk*18mGxfIvWx?kr-OfXL*Nb>!^NlL`JZ?@ z`f}OD&%JQ=fsPA0r-*;yg-*6Z;Zi1)r=)YA+O_SGIUc(Bn7u5sU+dgdmOlQ1`Mktj ziTlj#?I)039|W}Lld}d5vWcU7P zpWX)??5iUCwC6UoPv=i|{_IDdqd07<&c6_)Euh_c`<7k258J1+FLM5DKWM9RUX~>< z+g8P2_})~QbAeu$RocdEy-i|D-FPX-ZP>vNuMp01d?-K7d1_U$b7Q+HJS}xDy!mPI z5q{CzvCfgT2mCj+o07AGt9fk(XN7Z_V7JejR^JW(LS~($y+``X+LUp=#&Tw6#Qrsj8MrO7w`EJ-$Hk|= zi!a=(`99c*-m|6Di~3xRvAW9?uI0H{mUAcf+4Ef;tq+y;+IdDDdPW_3#=N=u8*DRr zjaGkC%CBV*wsrE|$S$n*G{ZXAb+59n!&7-yfb}`*T-o|W&k)oOfBHoC&S`XSlDhRI zeWLq|RMz~u{#TZK6O$NIGDCbO25w7BrGn>4Q^Ai|pDT=c%_wSGL6}CQL?^B?UwS!~(u95u|$AfL@lqG-Xv2e6MbM*o*SAm;_1bUZIA&(2XyVH*?d%ejy~w*G}}od{chu+Mig ziYaV;9J8E@=wr(Uo-`9$PT^ob)%xUarq7tvL+)Elt1-z_jvu9uUhCY?0{7k!@uxzZ z0@|2sSC*w+DeE~ZZ@zvKWmeXF$97~|%Tp2W5%FFT?;Y_gBVIw)yvB}*>_~)!XupPW* z(d6mbWYqwD^qQXdAX&?K;u9flnhEVHMIUr+EJgqFjP~pq?bb8en=&53%X)_ED6p<4 zalZiGCBtJF+dY>f#@f%@F|K;X+Rrh^Qpfy`!}0SWP9YQg3z?1T&$R^CqV$*NEw7#` zD`#-+lsP{0IRouvK8JM7G1)Q4Pq5tMBv(s(aKZ3j&J_H;PtL9ZUV1L`u;Z88Gr!0s z2IGv@zp{=$;9c0)*!~^!`YLN*bIkVUnAcHx$}IM5uLWaXJz+LpKl!#UluzpaEMqWB z`fG&sl>E!!J592?Mvi$MmAM|y2(y0h>@G{Y+`FMp$TIhp)PD&oWy@sR274Q0KCgEF z{Rr)h!}GJwmoZ?=UrsB zjhwYhd@WB;RJI=Y6*7}4)5^U$@4FrIz8mbG{W4$N=W085Z0k_$y=5r_*_A;!t7RB< zZIx-+R$uzSRGG~3bX}%t$rF}wtdLnLWs|;U%ciV#B5cbkJQmq)eTtpW4G>Q`_R;zj zJMaCS55Efl@4ybR>0xOWvRpYOl#|EM=2-98Q6p`$Jk;yS-bKC;iwFy zh$o_aCz$Wj>m|N0pTQ!I;}vA4<=Enw>oLbmBes13Ey|$%OL@oz$Ba`}{{={2SUD;6 zupp)X#O2elP0`23@#8J*%4LZuvqH{&^`|o?wei7;%S+e%nr|BQJ7w*6%6uN)YruH+ z^|11M=Cy0Y)<^8%q&G3hN!Q+B^qi9E#kdU~b?Y?cNz?TKXP-31h{q%5`c3?=D4Fjl z@4z_gHRcZO8z)R^c>q5A9vuGj>9ec)3)?tgUAw|(f<9aiJLY_(tn&+a0e#A92Tz(+ zo~N80Yc^ds!e8dNlKKeSnBYm9m##TA-?0AdwQEAoeV!j7r>M76WbFz@`IY-{{bgLj zye4el+aa@Uusu88Tr!De`M!^g_F3Ap&kb2Jt|`Ck3_K;=#cQ3Shr&l0V}X?P z|2?T^JW~bl#4@ZcNf~yE{QX*@FSK)%Ej)j4%=^$jv?NYmyryK_$H`h$S=XiDgmue$ z%fp>@@*TX(WVy6yLYd{slu%%9@ySkmmiLIPy%hOKUD1y6OZ|98{g5Z|;j?tlrHC&L z%ypgf;dd66eO`!D$^?IT|HS$5+C?mBmtf4{+5`4Bo(p2;$gF3M!;ZPe1It>1^QLE9 zN5`BWJ*&N!aX5yH@{TX42ul~ADP=7z4!e1C|sUPgeYVdAM%lQtxd&Em3-Xm~! ze;HR-rtr^RF7+&RtLwTUsq>|B9XI27b>1lBc^z1vb7rJIm9_p?ssD^=&-1yNKQE5A zi786i{Md^+*Jo20hYODRoC++@n!53P$uXYE)5nj=c{~eZnTKZRzxqMcVcQEO`mg>W zSjJ?s&o?m%iX+b}@?h7_EtjNiCuTQ}MI7s32cP12_s9{*&CinQf=SB5uU6o9mvaXHEoYzWt22Mu#rC*SFzU(`e+ai{B4!g8fYZvCTYuZ^4 zoYToE;_$sDaE5laXRx$kuJOQrT=I-|2zJk!$l5;DUs-*GZ5&~K7q9;E-FiOaesDjp z7sm>6R387F3U+zNgYQ3p{W}j}zfJ~Azp(jgf6=mm@!e3w=sPTf*x8T33z#q0MPU5& z^(<`5wikWazu>c!J}XO7=B2z|E3oFIool(WmUC$ob1D1U%6n7#>>b6~hiSQ9g?*pM z{)&i~kyl_n0H1x4udJ8I!KY(_6jEQ)4xO3Y}T$U_VE33!Q|zg1kPmzh-sCz zFUO2=0N=v-KHle;&+y5UNULoxJWGe&J<~=T@Z4Uo&lpQmpW%69A$m5btoOOfdY=oH zXLr(%aGTA3$Mpd0c%J6{F{e)&u5(~tN*}Hz^<%iP~M<+X>aI9^btmXC%bvXv&MJ#*Yr_V ze_{4J*!}%AIV!WTEt|}hIn7s?_dD-k)O;)3Q!BuT2BrtIYCnjG~Y5jIg^OOknrl(}CUC)n2sU<7C8Nzfa-X zuV~U&`EyTnUBPFhMP*r6fZN;{1@3WU6gbayi1n9edVKx{yL^uj^HR0%=hhhTc~vD( z%31~X{oPone2R>9#UzL4x-Kt{dnH-V7iHW7b9_t2PNkh=X)#F=U)MQ9tTTQ}m>Yz3 ztwP4R%UI_b>vm<#xu_qqt8>dTj)DF7;2G`QF~8#mYoAs2^FHj-?>HuS*0f^hT<`3K z$j)`Rv*R5?&o(bf%Xd-W!)Fm@Wm(QwdB$_W+1Ymv{oxJb@A?|kT3$hB+c@;g8q;z@ zJKN{Vl_}F6FL{+DKDlr>)@-!9zDZ_U_Fu=Z@r<_xJa-vqm-rZm;r}Ma=QA`iWjdfBqbJs;r!i ze6oCIMSoduaJ&V}`rnpqQm%#CbsbWYe3i=)Yunf*Y6FMz99PbW&!KLtLwvB_VmC-02x?~3w$SClVU$7jR=_qlxE73F(o z6!T1;FV}?dKQoH|?kGOk*U7u1yxtwfJUj9SJOAwUlPrIk#$~ zm-JqbYo1}ZUMdOuXY8>1_0n*(MjAG$Jg?@nZpVC%4UXDmdbTfrqH8DK(+ykZeVJpf zk(4K7y`-#jhi9x|99w_C?tf%UU;M*PV#9>V_(?8{tdDr;XRGbY!TWY_n>Cq#Mqbsg-dGydU-MRwzZGRmpv z3p-ir_H_B4OUr*c;~%~@W!iJ1v|wKza0cy7(w@UI*g82UD%*Qm29BSILzZb3V(!F%xmlk9{mBw6hFrS4#QMPmM8`KU!e`&<_JTo(CU&X~6RmxuCa%Vh0S*+V%;Xur$8nb%kQUG}Y!&nXd~7O~8O zHqH%`$ct;*5tIE1^Uaime--iX0(T^29dkO+f4j`I^pX5fh_BBo`Q6ZneQwGBLlH5? zBK6TR%5$IE6O$Qc-=Q7vq6}xmKFPA#n8Gs}pUieY;+|2=Ft1feYxwRi(%R< z1NLPD>s$t(i#bnOAF%fadw;y==F1@OyxH=s3FYZ3lew-KNxD`wf-Zz6t2;5^n6F5eV&sP~~GNs78UMd}A%CrkTbU+_E=m~*AGcbv6vN|rWh{e`VR znfH&@r(|Qcv0V+Re;eBg@9E&<@7+9Oo(0R?%J)ymk-vX73%kD;^Q>vb&igNC=RK5T z-Zz2m{ZV!r?Efz$co&v4vr)>w3-jf;0sDcpuPlrG5aE)tn|I)I2>p5A0bW6$wKL*F zE;wf2U4uE4K5o7sGwmA8p`O33?B)yD-^e&CHQzVVpW`j;e(W8y;|#|Q#~5?^ly#hO z%yDMSjvMN)V~Aca%Tj)2mfQJroFPvd9X~x=f7-3TU!TLrug}3cem*TRN2H$de*0K> zN4SH#u7W8JTf^A_UmeJH~S#p!2tVbn_zi2gZ&PCU|TvZYfHHKE7tZQ2I1zVEN91eZ;YiFLw43 z*ya0Bwg>PS^PRX%So#;ASHP~U{=zmbSiggSkAH3tK8|rxEkEXQwBhTg%CNtg_Oi9R zdsW!a=DD!VlOrF$-hr&Z2Q)5rB?@C{Mg8zTQ3BRkl~yfOG>rLNd7#!dEo+Lc3} z9gpUEVf$X$xC!^U;Hcix^B70!U;32ZhmH$R$-E5q_p0DG^F1i`9k9Q@1N(Uve4guH z5x-Sg=U?zAT>nzmITw6mWC!a!t9Iwp^*%SRjVCF};9{nJeuo`pQ2RVwL)R~M0K508 zb}+qPbUfesa6I%}ig+e)HnMYl==|C5JYybo%~`^^yNK zL{9Nxe+SE$z&7BReHq-&^R@9auARW{=UmU|zslNY!SdauO{>pB(cfv8IO;zxtafG1 zSK0gcn7Gf?XN-sg*85yC*VTL<80_C41D6<|V+D93I+uwk&QkiY-NNT}^kF}>Eca_< z*Qdx)Ot8L-OS|iv;A1176C*wq@m{mth zk4E;7M)s>C`_+;Cn&=vVaosVNq72tWJ|BzHek}6;nD}TrRMz&OtnKjQ^uHBjHDZ38 z{_EA}<59i&aT`85R!^q%zbVR>?5@{MQ8{ml%6U^1^X4e+%~6@b{u~|iy4)Paxg|<_ zOJrXk*_CztSs(e_7Wv#39{$TI3KC<5y`GdXB zosk{v&5B^Bx@7K4mD{J}v*hqU6=TlN1Ex+<*5>uGZli`0W<6oXi z`EPY&5$){Dm*RPE;Hfg1F)zh>fE?xZ>8PBaj`F=d=E&cVKZtW@u!nJ@6pb5Te}7uC z|2J8$uZ|VM(R#RK*Taj^u9Wqif6wU4;Imxc1pEF7#<~}|Xj-tgXV`CLTSc8PE%+42 zzWtXHdp~~lo&zc*NU+DcI%k;l3>3k1pBogc$js|a|cUX z<#*X&|GhIg^4TGSy!eg>?01n5FDvu9^PY=#`m?`y)|j86KifI%%9@reZJ2h~ev}hg zCy*I4)pCBuU2E7sn_)~X4_W3f+TEN*j?&&u|CL$>Wi10)o^`|j?&w^=IG5|E%CyUQ zX<j8CI@Lc`l!e&R1E^H}gpxGo0`6bX3M7-gn@CohZ))W`54jXD{GS@>y>BwAjI2 z=2GUHglDZ_%uOY^cT=8lyxfhYu;Y8W3Av90`>`E-S7Zn4vk|o~asHo);(&D=hRQ0?YAC;Ow-r)Rpyd^#=b;ls3b9=9~_@zmEVfaMuO=Mc01F zw6o0w&Q60jFy`8_W7rXB}7DsKvG0z8_W!l#~&2tIk7}!5&17GjjjIy?w#U{1oXWBHBAqj11v3<|E zgz-vwQreR8u8#fp)Ubn3k@*+=Mb{pbb*xj?HUr)e`G8Rp&FdaFPQnK~5!qeZE_vS0 zwb|k%MOtT1;jg^Jgf`}RM&3)3w#PL9?b5DF5tls|*iYfp&U)r^GqB9*Y;RzFZl+vt zZ4&lD`tx@!g7Ga6%AtAbSgLXSdH_Dk7$3m?T^;bNTw4MkPd0{F|Wk0`^A@r@k$+#996SL}yLT4CNl!vEf0j<+jQ z@>imK$JcmnAP@_EY#wijOj1eK2b; zy`luWGXC$jnsD;39x_NwNG*s zAFR)!VSj-BT!)Zd-yugaAEXcaHS7;YF&_*uGd~Uf;s4GojoPharUw@pGh0%Y=WKG0 zIxlqN|G2?tgfaPl+`zs)FU5aLVEf^{Dj3gub)5kIx|_$r54d)vtnCf_p!3-~e5cd5 z3}s@?YpZ{UrH z)3y$IkVE`17A>>amwABwSOoTC5!hcl@Hb_yUr7Hj7J;=4c-FTp3C}vfJGZ4vr0;_# zcuhHyS2YVlXzZQKanXfJ9 zVlwAG*monR%FBf{jXnW@T zrL6CqIJS1&6X_fW`ws5^)`2nqdi!o}-h_RHYs1Rgw!wJTG$Cys{4J(UHQ!Bvb18Wh z><6O0S+^Y1JkJQ{?DwR3*d6zSyTu=U0qlS0HdxvZ+ZEW)eV#G*Ip*9)cGto&pUHsz z^J?%Ta^1B&kGOG+b~lcZ8HZyR*^OOb`8~sI1LTlbxV{HQ*B9)sFIcWG=OD13gAT;V zAbFv!9w=)ky)Iz?{UaE2Q%U9~@DXIzzp`Enu)m)LKjN;JvR+5~@l!I!G1f>~GV73Y8F>;P>m%b2`!ejf)`|3e&*&FmxxO3|JgeP* zcLuxf*I<8r!CO!snMW*_*vH8osrFN{TqBLItmRSGyue*Dwy1xf%`{!J^Hh4&@TCEUY4_^V9!n{XI)i&nBR|Lem{!&Xyo%~ zZ&3`U#O_6*)q$y3ti)s8z3w14nc*Ulq;tFpFv@b{xQV4wE! zD6M0r1^Y69V}BPjguiTcY=Cb&Ro%1;S{oL)C{m3!r zWw6Z4oa@0lFQYv>W}oux?YcgMeUjJOruAz=*q@L#plOvg?GIdAg3k{le$ur)*q@B; z(q^P=p`EPcID_YS{N1g}J^0-emhC$mu={o0$|O9K@r>upV9aZ}W>cP$_5=27Ht-MK z^);RNZ>fFw6%|FiLh=esqbHiTn|GRX%{p_1S)YCx16$5?^2GE}?7O|snMc#dWPdz85XToF^>XoG3g$-Jf#w$TIrw$LG6Ku;^a>l{ z!1P2|2gPd{{7y9M;MZ=`M)q_aeejcckEfr*c065%&!eU%9T(eUlG}+=9_E64#b48n zrwb$kOHb}VS?IIK+>$<;vYfZDgrL0nn8~4BIdeU>%d!0=w&N&OFZSBadeeq7B-96XFt z%riTih3F_(nopRU&E4q(4UXQN-W?rbstHNAKT=*}ZbaJk=8NV_=F8@R^!s%^mzp}h zW-GkA&G~2%UozdvFjDTHo@X-24yKUaZVCyW2&6Ze{$$cDOs_Nxlf@|Y1L@;tI9Y

Fgv7gO1q^L!$`&Roiz3gFwAtDPuR1W#I`&A1T4c^ z6F8bd%5L%QcJ)CIo5H13{|TwNo25kE$cb^TOdn6#wzUPIB&hehP{QTrD06~&dwK@0 z_)+G#^zHB-N19Gt>l4y*;c=AhA?K$T<2oIMRK@g@CW{_4fd67Tf-gTk;|weI%&y4C z9!*D5{HJr53cqb`mnz>~;C&f-ToFD)cp#|d!!>1nyW`D{ z@|aJ@>nW|QDh2Gg9*l@z>(ret#C8DfaDIBU*)M$q>gsrW(7xMtG<%u7(_`i5!)C8^ zZ`eD{1!&b5wC!%LL`yJle9t>xVan}?_RecNtMj)QR}7~-gHaY!T$*fbGi~Lzv~6R% zX)i}QrG4!jwWGUuTIt{8^W_wEedvT7}$>;X=d$)VLsr^Uozij_4 z>Y)dJmt=R;#_KXCWzIm2d?@pGna|C-O*2<#)@L?kHfEm4%w(QLofWdB>{8U*s_b<3 ztT}Jb%BI9`%-|^QS@9May<0Bn6b$p@Y z{*Lc<{G{Vo9l!7F>>TZ!>fE>U@Xpnpr*@v(d3oo@JMZYcw{uhHkF0*#`CpyCt24tiVeJ)OVJU7Y)1?vuH@XVKSl)K=)JI8GzZ z{W|xDuI{d4?eVUOTANzB_J;QDI=pLj*Qs6Sc3s}}ajQGJ?&!MPrRw^6*VeA5x_;65 z+pgbqC*8&FiSE6-SMX@}k=<|VJ{{Zlb$_t?lihcVzTUmH`>F0)E!hww>Az zZaWEMXKT8;D*XnG!m*yfh#bjaXs-~4@(N+-t`HtiHxU0&n^T8z>NK%(SZP!WuemnW zTMflEj#H0`)r*x#GxcF5t4cAerC80VK8AHH^?9pD%p=KYvM*(R7^xqtf>``TT(P(+ zi8X-LPpm<#-(wA7Z4_%5Yp_@&xCe+eYAQ8`{}K`l_aPOUhm~Qh9k8NmrukTxR%=HS zw3BJBow2sw7Ja~c04t5439QWjoLYdpkA;!mX5MB_hE9P_g(j;sg?p7+eZe@r+c=$t zbTeZ;6P zGpa?nM{A~COmi*9D-J=s8frJu?zlU_{fXBeMzyDjwHH>r&9oG&%4XWzsP-}Y;J&+7 z%Wzj6YhSakd1a*iOmi*AolHIbt)WgcubM?~$6ZdWcbH0@Zkp>H(_B~JKEUZZbDddR zrFDk7+Us%?bTjUGuBy`hxNnVhfN3l~W>b*vF1im}?OX@Y|FRRxY)dt-4ZNL+$xoJJ_5}ocbK_4_9G#_uGj~ePr zc!s39)dYPRPh~zWy3J7cLSKpWpXNW!?UC-|)1yc~#!73m=vPMdC*$;0qdL<3W%V`V z^g8qZS4ZKwO{~|OzpUP1{<1pSw5C;fVxsyh)0*Ci=TXO)W1%-ft7~*To~bt0ad=i; zsq;*w{+Cf*Vw~@&&|*AG>|$G`ijK>MTwvZu zCH+q3pG5!Hh>}CR{wfife{HVhRVrW13-RQb`a1Mm=)XjAIB84T@Ko>A=TA@j#bRi|g3uhM@QucR;OGe2`mUQd~qys9H5 z?kaP`EV>cTvLhu`|3b|Al)N&Q`Brl!lcLuqO=Vu5MO8&fUZ<)uhuy8Fxt=jla$u18 zN6~YpS|KJVIZ9Pwqw!YnE#&9xLy+MDSWNu^FqDpY+QmHWwZ z^S$Ou21UCjs$o2}Y9jNCI{JOc6VJPvYjvV3I8;hX z=KYaAiElNl|NXUTs-&a2LbzoAS%gQ_&|yI#Eh*W>snYKDl7mD`5|OjkZj&RZTD!9( zZ=l>>^)@*{Rc(*=ll_wY%)d7goRiL=IIZJkCns-NoLD(a)%Ho#Tz4Dla~1l&3Hr8a zu1)xMx|tp|&GndZ`fo!$UZp4Sty`?8O{M-|nkvc8qVXiwZb`NFNt(+1%`D>O_|7c) zfvKy?htYNR29oA_P100VEk$?VVw$UNA~hy^&Z0w;=6Yk&R5e*o6-l;^$pf1y9>B+gNP7qYmmDi{;MET`8|` zsfi_0H>GY*O-((VdL=b4wIH=9^?mAhAudRQDYOVn3cZAth1G@igpGxv!ZyM#!rsEc z!coFu!g0c>!r8(&;bP$`;U?i0;V$6;;c?*^;RT^vctvDsx%pTED)ZlesB#J90BqGY@BG zWV$o206j0WAhRg*yQoP?NszLc?}0L;o>EC#Lh1u_KWQy#kTeviU8KFGgQabx6H_y! zGo(?{dD2nRHPWRZxf$lE(iG`oX@>NS^n&z;Fj#tD`cV2z`da!B*44qVJ{?FulT=R2 zx?Gfd$jiyS<<+FW#a+0;QmIDq5LdnsUqeUdpP{AZ1NueK#LGl*>~# zksnaDRCb_wnu68t%6`g0%HhP)+5QGA$CKJArwN0V(W0iDr(C04s$41@tV~qyP^P73 zD37APGLtV)UZ~7g-cnu?w*-s%xF(f_%D2joxTcifpk)WCaaB@HwFs1{_TX(Ptu9B_ zwz{%^t=Gm;``4z&wA8g2{T~(H0=SOULF%Szm#C@RsynNDsRyZtp}q=xvXhQbPjlO= zXM^-S^

Iol~zOdG#hYzgkY6SXE<6HI2sP>QrK8p+V;4RfR z`P|~6)pw))sPm%bwMx~GYt=+Ob&(23_xhp!TE9(W7TH*1cA}OQ8ns-!c3GMDQ#V?i ze$`KF^_De)HM6B`kL=Rr*xnD;`S-ZlzKf^q>{`;pUT@j;vKyzq&+eFob1ZBt zdt?vD!nrzoa`vq3*zASb!?RapZvg(S*$1z z)B$DtaGiocFNw+KFfZ@{v*G9{=od?{Pg@U zg+7Jhg{uqi6x)k^ihC9(6yGeBN;{R#Eln@|(y~g+W-aH+BjqQQn6kbyQn_7O6rZPR z^7;sMBlYa~Jjg$t6$vN1GMp_gkk`*n%l@eK*N)YuX+P@y^<(uZsHeYZq~i08K1d^s zohen$9}2vQ#>cSyhH%Vf<@L?|kv1p6Je!pA-}1i}_A4ZX6yUX65xtd(}(NF!184MEPaeV9;gYa z8SwmRqJBNlH|taMNA&xEnhA5aK2LuI(gnmXzf`V~4E+=RC;cn*ZHW3`hGdvhPoo8L zOB%h5Rgo`h#sW1MlZag}is(F4xy7>6-c7;M1x zw=u(b+vql4rja|PdT(8`!kZzD$Gq-7OuiVzTon3kdksP$-;M|e9({rP9Cm<(xdG4y* z1-YrYn{x2%H1}i<>Lv-f*)H3ca-ZhD$i0=DpZg>C14=B#(ygSmoK>`XFilEXy{)ya z^{v$$%}uPGtgZMpQxA>jV#mvY))CeSYfs+R8cEChr&FT?EDE1ej>!z%g)(FyOZZIy7{{? zZ*7ThyQkgDc8=;^c|Cdh#(|{vn(jQGQ)Z%H;b41Hd#kec2Jq&n@@57pd7(7O-Vx^A z?fvcHkpBU>_Aw+P`(*oMCx4cGE{)N?+P=x2Y~N|$Z$D-~ZC`IcZ@+H8XD_g)Ig;x@ zvdF&1{t6^N*?;B5eAfOoAJ0p91MUb)_9gD}{W;;_{PHlboF9;1f|l7#njezioVU*p z%x^D!nBOnI8{vvt{@DDXyau6-j{GSg8|9ZU4(Oc6a&Ttu$Y09m{dE2sww;0ZiFhQ) zPs`uo*No=mAI(3PpM^U4ck{3Dy7@2jKj!}|2!(7R57Li$8Pd|ALI<|GGSl9^u#CL0 zup(Ut@}ks?LjS_R!Uly+3tLk=DkW)QXLmk2R~S^-t88}=rt)WnBe3+s2$)9}Mirp- zf;=cQ3zrtI0iBx*Q>eUfA5!+3G_FR|!Xvl_3gs)kLO0NGh4#-ueZVQdd?)TMx3Hi9 zPdXPT7S~ho!gq@^s&HS0#r&qD*zo&|@ED^c^bR6QXK~9Uo%g82w*dbpikaq0FNVB7 zaeLKo4&VQ&X3?gUJB_jp_gcHeU0RK~aW(4Iux_KSLu;=^u_rvO15gY6iiDxqhw}ed z>A(9ty4WvuBO<0Z2{l%9&{9tASRSYn%$CF1G~fyP(?!#)~vgsd8K6 zIfYIbd%SX-!w-&eCe~z|2wxISx4gbJNw+{!BHGfpYfWi+sE>~!7~-+85bi9svn6M*N#wL3vB!6Dg4!>*%sg< z45xA1`e)h^O9_rc2;RWU-0*g6CT@QuxEAsL1d(#e7M;5ms!7(dSYFqJi)#FO^V9XU zuD2}A*I!FMwy^TcjSERN|E(?!+%oy!plHPI)CgJAZmr;)KOWlh!TMe<+9MCwMbNf` zYu8@};}CzDNFAmfOo_3Kv*t38BIO3*A|EO5UJ>LG?QsgK59-6+_3GXIyVvXP*S$`+ z)CJpd3vlrah3l7;<6qP05v^$&R5FpfC1^z^%EugTsqXqB7dbRV4evyx$fYIm8bS5N zwjkx=Iu4pp1O3wIh-acnXkVWtcTu5bv}MOuh9bb)sd_)LT=x7L^ih-UdteuV1VeEgRP}si%J(^RW}JXoXYI z`h{~NdGWY3}Nhj>N}8ZDI&Dg;(5Fcqp3AZi4EDlX=@{o?B6sETHkDcrasVX)cU4+ zBrOVE@HcC>sE{SJ1Suca0*jAO{`z}0+$(km$#F~&iO-X$nWoJQZMa!V; z8uQWhMGb#DQNL)75s<53;+>Y^PI(zm({|x`bZej<`0bmoFVb|qd+SSNG)__qSw_^C zY~QXu8n1pC&=x&24y9;4XiE9@r0Cs$ULrMS+p)myam0IOt};jI?wYL(=$hTtAD>9` zQ^0=gh|zEGISIvdPpTz;Qd%vVs1wve$ivC)!(l-rbRsQ*t%N+B35PA+_CYzaXA&QD zN&Vw>2!sDhF63U&-2H4Hv?VXc{eazyvv z6Yw0o;~;wImz@LJg3gWbMx<-QuFbl(>l)UzZ`bgyW4ij_nStzS!dygybp&vnyGCpdn$ zc5n_Y$=1VfYYv@M@I7j>JEf$$QsYwHf|Bl*yVP#I%j~wh3f(PTZFsFY4qd<$?4NPz zUc;xvk}Uc4UyVi!etW>YRW$+@OE87U&R_}8Rcp}U^$CqfQ8M{u$Z=X+8Y|6_WuO1D zVWhp=D_NsK*Mx4pupBu8U`+lg%!cQqeb~h7xoNr$ku9=*d$%`I2htLB$&P7+V#hnG z!BVn6(02-W-9hRUcCA*9m)>uPUC{SUcUpnoZ0H_BG~7{Qj$R>z$=wztF0v$FoFGl& z#^+>J;wx`m(a)^hC3MJPWa5vxjncspgmLNmBI|?SW(iIA zE0V(~$@&gnra>B3lAlku3%4iqPq!DhBxoCQYmn#y+LttLd09K%v^-2nS!_w$fTk!xnoP9RTCPy|QUtiZW4WtV=3_ywxmZbe@cI;7t6oq`!xtB%roKB>w`F48L*jbC2g?v(CvkN`I>O9DR8?& z8CjROWcvzyGhNnc>pyM)JfrS6ZjEvMp&Q46$_7gRt|49hy9ag;>E66s80Y9=xdeow zrP4CcGl>p8(})~hVXQDml*OUqSaD8TP7h6wP0xXTJBONcI)_e>M+~hdA2D>o&=F%X zc5#k5XTsPKa|H6Xi?$l8AB_=XAoXLMaEjS`DYPMeyxzR|Fz+-D)DN@qmRfJ#TQ+@$ z>*a^#X^c#c9$2nkDy*Kh`Ki73wfXaF@g?}B^>OGnSW6o0xfV;J6JVd{&M29#%(zUq zq)1)TIH_A!la6c6?NpgZ5BjV_lj1$R*;u)9`2Rl z(qKuD1)e*}8Izn%F-K?0M-Jg(N`O~AZaH5j^+B#MFvq6ywN<|^=PHZ(!xXPj*+gi1 zHx`yMeWdU?Pt@eTr{mwD5dZvhG~`svhebvC{#dZ~c!kNXG6}`<^x6|s^28OFRE{a# zhH1(y+|RivAFq!YT?BiY^QX8L372}ODf>E|U(>bFLEBxJoxjE0oG`lz-+x8tdfsDk z*Xv-Do+A%c=Kv4iiJ%X9N6>~mVW_XIu{BVSwhB!xhm!|C^#JRJ`v~5N;T~0wEw793 zHHkfIEnZ$b?cL&oEuA$=zHt)WUw>8B1GJHUa(_D$~pyN_F2_mo`r8))l==AE$R4iqQrAZgrcA z*=3G1yV)8SM=`oyN|;rz1+QOU$?2fA=Hm%YZK&=D$ca83J_aw{T#e9M+KIsHm*&0x zu?@r_RO(Mpiu6YHPZ*V+Md?P;5xxlOYc(vhwzb*mE6E$Lw}rfNf4+W;m~g@vgY=@dOsY@zIGTfV*CTFq z%1MusLt94}CCtKYy4G!l9zp7>fm<8bU>I;qs1?n|BGge9#*`jaYh=NC)%S7UZe*2q zIAIpA9Yxo&wR-LG3{flCw!n zt~%ZR{a13;{L?#;YII<-hZQ`v)w8 zzsvI0g1jZGbxE%B`K8xiP@d>xP09ON_N-oT?mVK}0PoAA z0n)PoN=g42NJW=No;VPHJni@Im7)z!Bqh%ph#zW8S`N2Y`jmjCj8koErgHi=JyW?q zD8E_v{7Ha+y1DO@sE2AP-KI#?Y)Rkn<96qr&?abq%@TD~{Mc&dT-OX`Pr z<;Z1gg5=zuP?xt2r`>imhp`4vMUc>z_NVx~DXKl|i3?-k<3SpEtS)Q|>yEvkv)2hp&6EEs2BHh~)T4v1Vl3 z@z*PwgTBZaEVyK{u5kU3b&N+?9b>QtrY9i#&1E6%rpwW z+|42Jk*$|4alaw05w?9$|FnKI?@g(^-jw=r+XZvoI__L9)*Q@Ob8tsf&Uv2-usfWn zD`MA{Cy29ZzXx_(k{l~DNb2ygwFBdKaZ9e8Tc5W_n&kPVTfchFz4g-V!J9HICh?wy z!po|DHOIULQ{ldAk>Foc)%393C>?iP?rRjcjO4<6;q?uIKkEe1^n5{5e5&q#lM`ga z^YC4X1z#Z*d{5@-6MFPa{E{+ImwvUx&R66)K5v7)En4L_b*M-BsG)7Z>K)ylOfQT= zkECx3UIV$c>DSe-YmITxhbCXTix#^2QEAj0Mkm&O zbNa!zUg#G}8ae@}G?Y%h0mJfGZp7FLkb`@9#2f-~Y{f%wI@~B6e zr)d$l27FuGIh3ln(rU*z>Zg$u3#xV;eZxf`+ z%Y*2!dIa(5*$+#{Yk2Qjj&Qu(AWv={?6>5ohHve$9~$^O*sW152S;UgTtqKUbd^O# z27gzQGAg1lqFRYW&bJ77B1c!OLp|{e<(RO)nQ}PnI96L6G#Xl)V)zxoi0+ZyW4gz8 zPw2kBdvf>P-P5}VboYldaKCXIL0{KfbmO^tNL}$ebuE{?JNUqSrNt|Mv%0^?K=sZp z{))nVMMJ-Tq+0}dbx-8_g_NEh&~r{L!-cr&hIi;B*B1 zilnji?sy?v5x3o8WaWw5F8LO7-pp$?g%UFFv1^om}W3P`26MN2HPUndmazYyX@(;qSG@Nji2s6 zE=|bPb6rDhsV5Qc%KdMhg5vX*iC^7Tn}Xt^b-}wwZwq?z^f(mVn}zo{2KPL0JY8cP z1V_u_Q*zWG-(e!ZnTfU{`XFUA=8cc9E}HHK4|K3&!+FyC!ZXc}nEJn%;J|@u?B7ahQ zOQ-vgyA(c0yz`t!X!0$AzjW;PjQXRI()y5hF+uG^-mVgTY8{=r*D5Umx5mnum;dtC z|11dliVU8@%H>NWTNGJiw3e9CeI?j)ZQQW(d4Bb^j2cXfQM?yM2g}`8S4=yY;@gL< z4_4=(_0FUu0`!3svqy?dj8|rO5|k5rZ+xhTJeqptH1BN%YDMMf0ZSo!9x07R%%`Ld z?tO#soDiH?s4v24WXYmp6h@2_E*ZBvh1ysH_b~|fWt?zHq?95@eexa(w|jD}jJj;9 zUMkh_mrU0sS?3rP)n$8I(Dp=QVfxh0pZZJ1a%eqC1Y~WKqZ6wcp@Pcib^Imr9Cztx z=P#MnwHAtc$Cz=V`LNU-0cmMcf3kMz*JAGN*m3PFs?|R{X67S`s^&jP&hbL(PwIws zr!~fuY+MRc)P{?dmW5Yt|Pe(rwssIo7`;}OnuEA%M@UaRt2?&wiMkBwdl=4;(wNbSkqiay!agU1|v`oSOR zQKQz$9aV6wULAMdy=Jgn5*^{Yb;EM*y~2NILicKVR%W$hy0lc{iGV-2I*tolCT zx95*^*ve!Zr0wmM5)ChxUTSV7|T<;-ew1Y5QQr zZvP}l_FP<&I+5cJJwCg_t9m?iYx{G2`J`U;)sEO>9Wh?qy4(|qI(WZAZczL4YY9D* zvjQI}PuCYOM#h}sQz-$yln|Zrk%KiI;?%c4d$%80yBe7M78OkUw{6~Sunps~9;1RO_mO~Yp+OX1 zE~SI?aJ!~kd(eJHZ82q$hEu4=%JJ^$D6Q^?*(1faO1j_BcfI&Ge9#nF4`-BwaVIDz zj5|>|G3@DeaJ^U=!E(aIs7S1Vjr?a?Y=`9}6JZJC&1TDcLTX zTT+iCrE8V0z2Fr8SK^QDybp{fY6`AbI5)~yby>BeJ^NyTQKZf!Pxpi9lB$=&`w7;j zE#NJQ@}pB`6|Tp0kP6a6KKa5HpRN-iuaSu!2g1^7`yaJyq-?aITXIlZP0nomrPH{g zQ?!fL@Jrb-p2+z9nyw9+vIq%<7H*#3e>jI)hHJPbv`l~NROQrqTnu&Oj-*=7=cbl> zET)5RpxrJKn5JJobqgU&UuY$^W(r#o;MA;ek44i$<`E7?if)c z+&<{(p11UHds=Q({qXOm;PTnXY|-}N<)dGWA$SA;S`Y8DTjq~YEFT;vFUQ7o#)@=} z-9p=eq_kX?(yiXDM@D=`t*H9rUXI68>Z6gAMG`{!>xA#u==-M;-6wRN-sRVcwUM3? zVl5~eF>XY6uzNZVl=sWITK;LmFPJ0$d1}Kisrj{$?j8?+Esccl%KLToFMqk>kDaey zwfKT-#c`{Bn@G1SeoLafT2f+%OPp{SWW~5t$5AgY*qt!3r#k3~SP~B9dsAvfbeR>4 zBuGNNFfB?kZOZYdEPf*6ZFt&mN%e^bdN%c@)W4VS%~!j>tRhbY_N5GWXI;MYy|-kN zr*UE$tWgV>_u{5&u@2IqUi>mDAt}nECf+_uFHFYU*G{uPzQb)uiH*yHENLx*WQ4~w zC>5M~J$SXea?JCSSI*0j_R53uQF_#$w_M7vH4Q44@}kP8wWYdz%F5=sQ8crNE)#u( zYw%wDrSa0*+8@jE`ym{mx!&&@=@X8gnVPY!wLeT+R&eU>`w?}j_5TU~!W7{?;Sph`Fjsh0ct`k1_(J$W_(M#Is%VStVo$M;*iT$r91K_8+lsr2`-q2# zM~frGk>Y6a0`YS3I`J0qF7W~Jaq(&KdGU4eJ@FIqEAc1sue6xf(uH(q+Y;$z(<`M1 zq}OR5*12x`_4#e{JLmVxccnLkv0k=G?~>j-ePH^C^oaDx^x5h2)0d^MP2ZfJlD;qf zNP1>^Zu*tbRU)rte8wOgN%){{1twv=|1hP50Z?JFHpdPO?A^%vz|kGo)vv7o;~z^GXkwx=S-!2Dhx;azV@d z{G>82bJEGu`}q^31=1o|elPtlC#`F(%dFR7+z-ooEcb`pX5DGsZ#|#CR$kir#QM_u z(LUKe%Rbk>#J<|T$-dp5YW-;ocE*+qzm&Eq?bLFeow4#(NB)WYto*b2=dGvnujgO4 zbJlxy!S1w|wO6tS*z4LG*;|zMEZyF!v<>qKNsyjV zI;V7@lfTqiUIojkEw{A%ZY^W=ZhyV~y^gb6c5LZt*|O!X77HQwZfTQOYdKtAOCBT- zm4~)o-=ef8t<|ixQd|D!E%QR(~A_k|7q z!+$OCUkm)#0{^vurv)}?ozQt)@y6oh#R2)o;+67kZR@n|=fR%xy{&DnR%=IFzqWqz zLoLac%UhRe?bY&q+gb8c@|>1Eg`>!Sb zU)c(jp~}|EcEZ5sPyIVQ8U@4a{R+nv?k;>&T%~wK@$TX` zrBzBtlhqYea`c7M^ZKt*gZEv=h+INEcttXV2vc57>xm{Ug z_f<7{eT2G^dbZWq>Z?AT6$vN1a`y1-1@ijYY1tpO{@StHH2qlZ$J|l1!4Puy8Uu|} za^D$atfLUdW5&1Us@A>Mz2=eDcjhI=xAnukm&DfSQt#iWC9(U@{{Qdor%%&oHQok# z|6Jc@V1Cz(b*``ZU2|u@=B?qkp;wf7!obo3gt9Q?G@>Mk9cx_8I!>OtzE>ajh>sHe35 z;?ZP?KX zyuY$aTPAB|+p6qnB6tIH;o+A?7^LX zW{=9Am_51k@$6YJ^4&$*E3?;TCuZ+xyQJ+hIHEoR$7CZrGdrjKB(^t}0)7M0azJWeO-(25b-&Nm- zX;Ml*L_b;|p^tPlM|ZxVU(oVuLwHNSocJWx`gI+X81!3cdH-~(-`-=H9?R%=wLV(| z=77%2F1^>d+&lUMJ@#w{`r{qXMc`#3)1L`%qaH`-FN8}MzohNjYS7=%-`79wv7)|6 z|6c#4{j2s*;B`{QFv4T$F~TVE($2d&Z!wl2oc3edk1>{SA8xFId1H;vzW*kS_3JEs zKo7X641=+Wv4yb%U-N&SHg;*>uJddcMjLw@2N;KSUJwKW-VZHabchJ!MB_ALbhDm& zY|mjQ;{qA15Be(SiuA|NPo-?&Yjp4e&A#M=jq(g zmW8=(S{Ah|%I%QbJ-2`Eu(s`5C$|l2+qZ4A+;LE|({tzK&dXhzyN1@XZC2ZJFu&3^ zuWeG>g0@9Kf8X|dd!k)#yR&UpyWVzx+oSEp_Kx<)+n;Wq+}^XjPkX=ib=vO+*@n4^ zxjWiUM&zcoKLBn1Xl|C3Dh)+g0zv6_9#)p}|E;vN-am*z))sBY{L`>@Xy1&%+P&ku ze-qaJou@Vf>#!c1R|h=l*y7(sY4vDWC;m_4JEKRoepq9B3|U;T&i|j*ulm|OqwwLs zinssO(xLAE;BhVR82V2i=e*_pn@2e;yUzQH#eWyn)N!_%V>Gd?|M;_*Ef(9ctifYq zQ_m5ac?Lmjg?6DmU=+abV}Hkf1^f{p^NkspS~s;usv{+&%#;SpC7@sMBBU#)mQJ;% z)=t@}L;#irX)gzBfbLKZ8>VW50_7D`{gZ#E`ldGKutBOeD5*ZFOsW^u@ZX*)EgDbM z(%wFuuA~kvZzl85B0^G3{1%&-+&?xq)hqFOQcC`j%q7=Ld=T$}^z|ryY1}RiwORDMz1h(3{sDZpO+yB6XKwfV77rZ%-JBmNHgNteWTzGg~^X zjx`CHm!fUzq!jP~a#tBhx#mP6xhJff|$vFlJykT^f_TXHlUJAR8D1@rLq8_YY^i47M&1-vHC6vTvNd1I`B2T2hxu6+HNQ(E5|uFL0(thP+nK~Q1~#j zskB=d)UoPloD&x&?!^>OGx2McD-p3R%xmarCq5x|c>Mm@i1_^o&~avZ^M3KY<6$$}-xj|) zb42FQ%!lxe{&?vMT*_1B`w`+#$?f8Dx6peaz|Pnb(??Zb9Hj$emyx{_XO;p2EU zATNHC+#-H_{6v@$GzCih#>pKSKO|o3d`$eXK-~j{x8R?VIl`Rm8|lB&e`Vguyd{1w zeu*=*#qs~+c_h>WO2JaSsd5Rl{fW+uag;SHyofyfyYh`o4|L^A<%^J~Tn=B_l;_Or z@Q+BuO!d^0)Dcm0_OI;k`V;!2=7r|@<~VbVGXo8K(B}2S**mw2uj^n#SZYnmpiOZ!C(y`L9@)8X2w!MtR zCFW+u&E#VeSL*l0`^NiM&=+zwK;54raZQlE@S+=;!3c)ILIdp5hWpPVt8;g}bhqu8ln4^T_;ycH8hV#uHZlD!%Xew+J4CtK0 zCBZx?k@7JooFJV}N)nDEaQr$YLpj6Z!^AH6cw)zv!f})q1`C71%V3NM?UA2&(hn>% zz5`&}3V_ajXFq8_mj4v31sT zD37GVHo<_XT@sS#xmc1g3cNp#!14%yOUnknxHalwsZmq>DQTkIOYVg_iD&#Uu9a?d z+goqsOZiTi+YnIZU5V`z+k^G4Za5x+M^j7ab{Al#eSRk#bw!liIm z_72DI_KBUGbf?5l;*LnMshCAQniqBwKvQsH%-&J9-X`J4mU4&w9s{-KQ(;r1IpKOpVF~n2 zG1ml~%2NOo6t|E6LcFRlAt&*oAF-HnjdD%#E0hOK`6Y9oCN@fJRG5|6%nk78aA*qs z8U!G*5%g>T$ld^Y;3om+BI^3V&@LW=m!tVB{6CZjjTlXFTfVG)Uq;VOG6*`c{C84_z{`*cMUNMf=H_ zf#PI{k603)gm$D3-$i8xA}achF61W?K5TbW0e3TG)@O7Yj;!ju^xg9F^7HC@>U?!x z_FV#fzCNGU4chkt2Dsub?;$8Bbo`=dq@kZB2Kg%Jbty*k=!McG#XRHr<<;!n-dL`h zT`lD@Z+whAm(kVALwPO07BIFlwo7=bFD_#1WZ4!vN z*BXWSjQI@gpZyWi%LI(3B_N;4o!lBSpdz13DCteW3jz34stWYLw5VryD*x_>)?0%s zBQkN)zhngJlcmp++F&aG;)hngx{`kcyipI7y_CJ=SLIigy&T^!$uDWU){LF>T{M_^ z#A5nB+V;TT9+z%tJ25ztJ#`l_0)01a8$VD&C%;tNhRQJ2wsc{4ZLczbkMi7`5qP;M z10D;6^A`kqh@O-ul_ynQ&8cn5OdiFP;VIcsrK6OG126}MADgC3hvgjo8wO*T{-v?A zv9taq0#nQZ_As2MXM803=LA`$jLJc)eE51K>jui7%EI#-oU$5#TtEt0AIdt!bKk5V z{j(L=h8Ly%OZyNHxXka?2$-euh#Sl^q4cr(Xnm~qJ}l1zjCbaXAvYc{8dL2$U z_L=sv_Kx7#D52*6Ts^Pz@;RHiX3G)U+RFwe@msTvOQ?K z&0D7Is4 zpV)5j<<>fhj|pPyRIpua|JZi1J!9}hMV>{Wzvc!;2KP;S3HzphPya5hBMkzqN1$w` zCJC~y61}h`d^rS2ud7_IUQD2?*0$ERR3E+&^C9*!-~|HtZ23%QK2tuCA^S`AN1XLR z@R#Zk<09h%>JMsku6z#Q6wn?C%WE0nn)p(tM`$Hp9mXAo^VE^qDk=R_mh)4_4ppw2 zJy+F!4{m#?mCK@fKK*X}fU6y9^+AQAoZ$2Wf8>Od{-Ab*`QZ8K?*KmnFy&C%8n8ua z3mER~rBt_X?oK@q+*;;t&~w`p4&~C^^HNW#Cq^a?5r+sZLQCq^)Em;0EY{R5jUjtv zUd+#fdOTGfuwO0%ST!8k;|WTimIAia($>;)(MUbmDDaX2wxln@Kw1j=8m$6pMWTTz zYh6ldFGeG42cR|EO6{wq+B4D6&(hD*p48@OGadNu{fc`PcP;cPEDJN@@xmiGmxcol z!m?2gOJ_zJXOxwOSBs3vi#s8pN6o3`O~!4;ZTidf7^_|^f9K$9`D^7+TZtTLW zY!Eigdfwbo57`Z%AA<%#eCRuBt8-XO+XztRz*Y61+CV>O>8NMkOOgBBoNi9@VT$QT zMGKy>eeXqfDueo^`X%OQN=s&``V9g!oRlrm2DQ}B{c!l7sy`vL>H6uK^Jal<9gJPD{~eGa!%U^w~jqYDZFD8}+?;^ZIC5 zhY^6*m9Q5tTX+z9JieviT<2j4>?u_+r5)+tK70b)5Wux?MS?WR^Ghaos4wDu z;>$X7ulUMAseX@sPjN7dDwuLvQjdKc?y}&2*?6zYvKP>pBWW-6N9l?VZ$oKfwXD1R z;^52)(U5kDhSCGDlCr$AjIt`Ax6-aG#g~!VA$4Nvc$}qGq*Y*Z$fR~I+aK$txNMsU>fZoZ9kb+b1CMdCB*j8q5IOi<6g?adGlu(2=|lXE=B?@m69?VvIB| z7+@G83F6nPP4ud1W6gp}p}cBxs^?G9l1$LM`uEbWmE1Q{ohW#MAysO82Vyp0Mu*dXabsNsXY?` zqYTslW@Wx#J958LFnj6C#sPc5=g_;Z1L~3HD`~ny!P{v6B+YLe=uLXh555%p;MzcK z>;oDDwa>vLUmlcCQ|Koo5>(Hf=1wi%6a=`Fa`8zXme&U%j8ApFa*`*Jl%6PKMunmr zSL%+N<;-{L0kfl2bJp;!iKl$(x7M_{Z}DJ&Ggb$*OD$t|cyxlSSn1Mi2INY!ajp-< zg7`#x%tPOKH3RPNj?<2IBezO3AhEDzZbdJcESRz$uLW>l0(#7Y^$3OXLml`^aiDFc zZAN@4-$Jjkb%Vu`?PI)KAE>Q$k#!MTVj2b1kCMxuK!3jgc(UOvsei&%_LV;&fG4XC zMN`VJmgYy~N1#vT07S+6!SV-gER+|@AGqbv6IB_xKXQLinfX}-UzuO! zo-knv@I8frE%}VvLA+}xYo~Fz9(tOr5ooVnT#JY%Q?*=FZBE&W+_GqqO;CZJ_$@Gx1k(H zQI0zek*N6NsDhzrac{SBYx=?ax4y83=JXsqiONBDqdZUx^EBmUJngll zR!j$uvYNGdE^lRWI?e=+U!a{dm&;iiQWp0HA95Mwktx@iYXe}6Wh+bJL9`f$+PTxx zYYyIoCC(1uO!OLRmEJ@O_%GB4u$tSFDa|g;ZpkpjVl7VeEecQy05m`owQNAK^m2;{ zqYJ)QdJniym!5XV2{u}b(;{AeZFzflz!ZBDV0Zggd#^I~g|Py6vbVRlv3K>O@RGd~ z{1LfL{`_%>lRF7;c+Pj84;p2H_n5mg3f9`W$+^ka+5~8a z`P}Wf+mQp34Xkx@*X8a3+z6QH;2x_x0?%3mzgxefKdXQ43cz`}%P{Aka^wBrwd|$B zz-xumCj2-afVGUjOqg-WVSRI8VdLT`-kb5eAH2MDcrN49 z2K-nC<_UhGF<5s5;5O?v>p=&%702+PR<)=3i$gFzkNC-l@%caUzvkh;5HjV41Yl6k z1FG#!b6tgnJWyU(NR~Vwd6*|lx5S+*wiFgnfcApIC+Mx%0_ag(3P$=`fOD}OM*DiV z@HNWZQ{e+Yc#Xn=ZlKJ~shZobfS5qw*dtB2`xUMN`@Lay{tDnru8p=ovlqb_KEJ{8 zEBmwjdR{C0{roBn_B-}FBxk>0#_RT5_FG;nmWPpfB9XhgUNGNC6&Ly8)`-rP9xXi( zrBQmw4_@D1-wQZH_;^)01MO>gfDb-D(7ql#3TD*&t75g?W@R_>SLgqL4s;{FJ}U4_;o4&QGiXHx`6~ zkiQNvIX~I$(dn}`S5ixQ7~m=_R2Cpn!B?esT2dBpkT<9_qy)dmm!K=M07%fozZcGz zIPQ48xk9&>m1xzNhg*NlL1$hZUG#8)IK>3 z`S}!ua~Zroitx*1t?=5`pD%q>gzk{tsLxAr8UgHKWs1)O_g+M_&yw%z#jjsD8-e`N z3VtcvbDTrqmF4xuK>g&PV^Ke-6pjZ0Qo>_8NCoLR{G^qzgq)PSkHBqJd#SU$^pqKT>>AG;F^Es;b;b0oz$d4!f2 zU2{)4%p>|I>xS`=iY`aqO5Q3cTDOmY9Eox#zO|Gwiu3F=vukx&XP2?iNzbv)wax)J zXC_jMpry5t0-keL=pZV{n&-hELVoNX!1HSX$K~nUVg)=unzqOBfF~NRd{7Skx%ni| zb6J_i&#K_#Vh|iF=A{}{TBVvsVOSW5W#Lj6nD!opJy4^tvmdlf@Vo=1!_wk4H6Y@- zB=uW3G6dHa2o5XwKy9HGhcG&rD#1Xvk=Ud}Q1VZ~9^Ks1eboU&5eT4lj~~=m>CWOd zJm7kyoF^2^Q@%Te>$jd1=rgyZ7naF`mdjGRcNtgOh)e9t5rv^t!(QHxifkEs8A^wz zRnEsgGD9WiVC3d(Z-h%xwF3}{~v@R@%6q_*uVoA8XjHjsu z(PdVw20;?)foV~SX;Y3rrLj|8nxZC8`z;BF*#}d`NMmHMgmBGmfQhjf#UmXS1j*^?(N6U?+qviV5=JvJ28$0IhBf|kI*;K!=0SqvD z8NI?w)_Yb5dT_%&fqK-J^nyxAYHVO^;O4k<`51#+Qd`bg)>xW?UxT^XBxgNu9}T$I zzPE5<;g`ZM)N84;I0?bqFgo{i?rEY)d5)K(omgMvJ?U4}1{iM{Kuae%Tm~J7f_eE( z2$&c22C)R9Qqm+!1*f&1ql2wuNg7p9iZ+$#ekpY#NFH0Bcr43d%^)0|`K+wc{E9u+ zvaID#i?BYTvk$e?_QMqUt@B&tx5K&6TDSvI90=%FT-`OVRY$9Y)?exjg1#amrpI(; zt@?p>%|c(LRurcYlzQ@I8f!ET^kcnhy~_LZ(s`*GycX_!sjG3Ir_9NX!yQTeIp=r- zgUI;7e3g1egJ}j+Ft($Zlwj<~O_FVZ&e%_Y%Bem5QZg62FS$-~NbFOfo&ZdY&5g}X zim`8EUnWbjV`D=An^e$Mrb=-|I5aNAh2#pcr(-jc%O!gzmrM?bJ(^r4xms*CcvvyH zBBrq=Ys5sO55je{!DG+>!1%{)3H)SSeCdF28VY%3# zh3qE?9J4dF8UyAX-kPzK$}KuCc28__<{5Dw0L}=A%oHh^ znF5$D9WL!JZ!7PSnI`Wc@1bO2IZQDif2Fhi;5JkRu^WABj+{Zt{kzeA-aw0SUM}b8 zxdtRKHYseaE-j`{sF!KKaj?C;fU^tepZcSICYF6pq%~)0W_mDUpzHGUUhK6P&YW-9 zOSoKYV(Qw|wG|FV<(A%@y1~t9JO}G;I;lh0dZE6<sWabRVOZ z?iBqnU-Ve7FIU!jAm%{7UB4YTw*$&8pSd@4SJ(_arug74)t_TA_+=O&X;oM{JySZZ z4;0Hg^yjhpPFmg?(FXGbgpR$8Hk1eHpx8&i`3T@xQ3>6mF<+)hpP1z(dMSIXZ2J|> z3Ws`uDAptvy#KQAmqE5BJ$Cw1>I;spjy{(%?>t4LpedDk^5keuYD3HLr5+ofocfMP zgUVsNH_?(E8~haX{nR)*pa&!h(*IN4%jjOAGACekiB~)^LYbL4(%;3w#le*xmT1pP z^`^9ECU08J59(7Lp5}PiCs!DtMx;M4YRlQVWK^%0!==lWoj$l~Z|z={zPl(@eXUpX zPCb!yW$5XO2h=VxC2<$7qbNXJl|IIt7+D`=v@bHE!T1;#iLqE^WF*eNi%=c6u5~d_Jf_t>vybw1j zV~{uqK3nTiMtf>!A(dW$klLg*r2*+*61OJauMzLSbBAW}L!vqYo-goE9Pn6%KyrTaBj~a5Cl22v zYXh;LC#lJ&Q-8tZuyktk)KsBg>TY2zVRd1BVPNX_)GyBbW9qxqwIIion_qIXP2I5=aDVE4@Iq!>`r(zMMMcxhn%SVI`8?0jCsqwFm4US*dkjJ9 z@#evL%uYR9y$|{-8ndd%!+NB~x^=@)0Jyc!=!4h(0P+M2%6uaAE~yqD1LcvZXl?<-_zCzT_*z&hwJ`OC zw1T%j_2%_8%Sh@j$V&7JpnA)3>&uGJyIDlX2jev!RK`9|daAP=As#E9RKcm@KH}jp zBW?!}iY!kT{ov)4#?c}7appV4qr~CPJX#zrj&Bl|iH!r^mp5Te%Dsa2fL>?XsGalX z!~4{@TAn>Gs&$mZFg^FEj&-F?~oG$D|pKOdpx)k?EnnK#;vT zdoxPa7s@)vQ(ApGeHdyY&;~WLH$!eI;4hQCO`WaIMoz_&l~FGhJT7MK+(*)3crg9A zR~9fv;!<9n2J=s)o5S1bx?uE26#k;9ah#-gL-jtWeVn8~%}>%z!~dN@F^*&JNGsAY zc*4i(3Dx)_Z2TV||7#zocH4?(X8Q_8WkzMjIUxV~jma=?e?M3gj<&-_$MO#DdqYnivK?BD~?;HRbG(oygoPymz*3H&yhDYD4` z9)`nr2mSm$)B)K2j=Wz5hsXy~uf{3r*2XFNo%)?zw4pVVa*|Hx?19<+)E(9BYO$+9 z=dsMg=s5#v)j2f2$a1LX#;m71QAOF#j4a)6IWvRs^K0ciNo@INV-3;6VH@&ncS=9^ zcBGuI4)8-?0nmoqT^M-3%K1F9hh+~V9;JIYTqaSRB|YlkfpSW6Y{~f7N!Ljam2tI1 zagY2+2rh8*L;OdVPNZ1!b^g)S>*VnDZ-lO6bRz#aK)|VT{-`Fer?-JX-74SPM}Lhk zZ{Y2)Ti!eh$~XdbH41s}2<(AhWpWs%Sn42kO?3-(fV{K3Gj73^@pI`oy7i{q__=H( z-8!f{Z3PT(V#B|8ciGcf5Y|1$6zHAbR6HXR5=>3sj{gm@_=`J z@U|~|0W3#m&jg$u2IUOp4E09!y3l#NI$pgjd@f(9RnDH-FUU8XMjw45!dO|KtWP#p zcK?@Sr^=X4&$|BecbT)b8VTiAWfIJYNe(qdxl3_oh#H_7BzEO`-J(MfZ#%z^L6Y8;LC zvrs*!pGMMKW8=%PejXX$y`AM=i@>}*PJ{CK;yd;k#>V=3&K6w$Z#jF2|6k;WINoh{ zHFXuhWObtcrv9>Zr2c|F*SN*F*%=LX5-dG4xAoyQCbun;Et*~@!d=XAv>Q==TUR#$ z3{lr{&0Q+NZt5=CJ+!Zk(e5yh8+VqF?2h?NL!vp7|vTtWEMX1Bg7B{k2Wv@~XQcn%E3-cGiH^-S{ocR*- z66*uACF)pGlk85|pWR?<7%6zh4$sG*Ec<~yUC7>F!Gl@E-s<13(X`!T zYm=OkZ4Sk3FgoC)iKVkkWMS-eK(J0d+^XYNRvFi_f3`lXkwvWSEZ5Dh>x^o?Rkm?# zUKttPO%2QLoPEBGoy+t~=2B)4vqyFqUUogGcot{>&^NW6cAng;%<=|<3> znb`@=K^Fr74|H@XyD&e*Xw~yZYY>nRojrC{#bsJrk7?;L;u<1t9cUer-zERK)?NiY zTyjf8v}-N7FU`AfZ1;dST+7tOTS;pU{WQd?4ydP9%RZ5hGv(Kp60eaqrC4EY7uGGT zQT*A#Z^a*qFzZ_*wDq;Y%|PD{JgiTAIo=!^dEQal>dx<~H3u@@d*jPG#_04$5h0GHs{RLBQzkdJU4dZ#7as?`jA1mGn7B<)s*!rTaLT654( z)u!pEBDBY}$I9t6q#ge0+A~e!MXhl>r@f-R)i7Sy07lUd`o)ydVVJrOOCMMtH-Sf2 z(cIG9lJr^>EN5Ii^3o%4CDrL<*zV}`Ty(xHJIAj!|o$K6*aE{`VXaK$v{ZK~sKqqhvdEmX z790)I{>C8$u0E{TdfCBK6QA#}^+wk)%I)JqwaROmlk@!$a@(&dSB<2sftvh`c5Z8Y zEls@?4=Zgj76F+jhSQD(PtV^wHt!vGGWs?nTvV7OLksa6N=5t2?|1p8*c=s(T^YWwedzpxx97O5WBBSMp zrOtT{YnPi%`#9w5Y9Gz#>Y%T8o>hj$!ig^XILpzNVw@lr#NE$?#_ty zlelGdyJ(Hj@%X~V=?mh!N66!Icu&itxKVM_;*(zFKeES!pghiWVXMMc_J;&)JZdim zqf@VzA7H;?e{6r8A8Wr3pyO0Gn*Lzdfc%>I_44Zw*x%YeSLm`| zFW6B4`wyo%IO_aToP@^%%JJBHjvf`QaDMRb+wbMe@cfYx=kiW}$U*PG>*6*1B!+*2 zQ@pFOQ4Tq)8pm@B9cAX?d~>)b-yH7CH-|^_&EbW7qnMjVI9sUGLq0z)0ORkiYK7@Z|( zToyNvACV=RF8tUe$eQJrbSwjBl!H$9SP5=fX+DgC!5@hlp=%yxHe@u~Rinq{tSYTO zUU#%cQ{zFySke*WmDVJ4Mx2Wrt1f7qx(YA4*Y+BuP4sK;^oU>Mc(0LrLC~194UL1& zjh%jK$&a&21m{6Ix(Y5UH4Yppos56o`0~+Gb9kcU#S9Q30NM03o>m*Y(I#_1j-s8Tf(71f98c_D)g);M>2YTnO};RVKMuVF zqXihj6MZ6I*ptyJr}L~4OHv)6#R|PdEWyz{U*q^XrDyM1(T_j7;ZE@~A8G0*{_M<{ zQsx`8(>B#v;{Pk|T)^zAsyx5Xt#j($dL=hd6%!KVRB|f`AxQC(fB|oXil`t|0}mhH*Mau@uQga3SNtmTzSY8(!Fy1jxeRY&J+Mi(P11D~5UMha7 zqe%|Vc)h~&jlORJdz;MF=V{xjqu}|5&nS6!)OSUj2I>egAne;CyjI&Lyv6W%$=QHm zA2YPiE-dxz7hVhX=r_4W@{Xa%m9!acTE|B9Uc;tl{MU@nJluEKkfoo6%QolfuIMw* zTz66yt=>SJ2p3i zKAU;h=Ey(v_GbJRD4!j%X&5$1<(k}W+t?siQs~oMu1h#O;(8bK*%3FEtoNeAzJK29 zs}3r!7eIo30SK3-6ZuiE%CVU1SAk)8ZTa{d>e`WPwqfejf zYSLF~6PVYi?Cr&-Pp)0`zRiK7{cd9qroP>@zumOC-L$h){A(X7s~=&1Ez^*pLS72Y zd#XGnV`N$tJ?#5-nr}1vTzHmVqcCjPw!q?t_G8*+(fex*9PRyeQO`UU%H}9bo^Gyj zI(_=~*Nwg|b!jXKdz<~JZ=avB&B{k+x$s!xwkz*%`cwGXTw+XF`;Z)R4!NG{m?AR| zFt6^ZD}S+7^v-(O{;&yAnl_~|!#e;=i+kAcAK6JZ|@ zf!S}$#;1=B=p_z0w;o{n1RTd~-~rn3dVL=rWbW@lrYty?4a~9!nX>bZO<>y0w>F;H z{)X`nj{Y0Qet~HZx$ft(1*Yr*bFUY$tiPw=cz+jE$D&UfioI@zAB1^EHir7@ZQT=avY|3k^yVdb@CpA#OZ&+OR- z+R`m^t8$Ob`<)xuo@aB~;Pa`dD@!>ab$+kB_RjB@ZRpys)OWqY@EpVDo^~Nh!+h6; z`xWN0u0@7-vp+E(VQxa7)m*x5fNR}4T86Sv1;!c`v8sAqyGF(uH8R$akrAKDx*qU6 z9Q>f({Zg!fsR#E24)=(9oImw8Ru*Cl1av8>Mfuz3b+AuWqBuK#gu+FEqFXY0H#-LEKP zru!8f;~E>?@EJIl*w%HaGVXPI=7c$ak!5}y+nFk3e%z)!Q^w2APU?jpKZD%2HpS?{ zUMVv6M1kvL>ys_>sAoPI8=os>!OgLs-zY5CSbVwUtp!pCwx#4& z$oOkfJ^<5(GN$iAU=aVIoNX?MK*pItHHi*%GMFLQNLqhv|)S({eoc6_(817 zb$OP$e5{JS@YX_@IbZL~kj9wrw+80M(Y4Ia(1RZx5YCt%cJJhTG&G|++T!XvrO$=O zc8ZPg?x@EaM%I(C-`0Y%V!t2q3ECLd*)8_<+&C0dt^Ypo#b*)@0 zeJkTsS;whxEiLu_9Ao8r*w@qbQuaC-BO?Xly|C&F*^guWhQ3wSb$%iHU#~ZNVb#Ni_GbTBm_0%vYkptWtpAI0-JZC_ zx!iU0Tm@Vy^1L3o;U@5QZ*z@v+wXjH4*N=_=ykc;gByWU>UXOi+z8xm^x&wUZuH=& zuNyts>JJV+pxq94F)cidA{BP4o1HaIc{#`9`$O#%J%9=O83pu_R z^MM%y%6cyXGsXg!HhOJrDe_;6XHw*FF9P#^ zDRZ5f3QM2h`3Uv0CqkbDX8(|7uF19R5_HgxMp1#jLjOFkAcy`0w?L15u9VGCQ>d$z zHqAcK*Tf!YVz3XKM7B0KBZa=hTno9*81s5LBO)^fYF=;J(}CaB(?&si+0WLrUSaNA zp%3SftP6Wajp4w)-$dWplzPdbUg1pZjpr~d%Q$@g>b@qK?P*)5Wyw6V5 z^>k3$DPzjFQ(5~&*tc1DmeKR<5k5rEv&X>PJ4e?3oWsE1_Nc@O&cU1;(e{&DM~0+r zg zgSpg8t_P-lQ|;%We0{JX*FLYHeeYGn(Dl*A`-wRddgZ40j5!MWMW(Jr#{VMY|5Xk6 z@6K5^JA&=edzT%v2g`lTj$v87FWI|bgXh9?F;=<`4Y{;aatp@q5Mya5lQ!=Zo{?5? zAIDkOkTBP`WUgJYuTl2<7w8X$e>bT12TS|a54g<@NMEhIB6WrL2~L84T^mbXiGx03h4CzGIymM- z@M4zbJOVz%@Djs^mYf|ad2FzNefDnYXX#sA7j;XY_se}8!#V0Sr;KwXu%5rA(x1xO z|3hj&?pmhtJSSck^IPI(NZVqrL!ayU3G!Y&FwauJavsu-GmyYzJ3DC;^Wij{SFp|9 z5Xh)Gsl5 z?7u=;?AMsHz;D_&ZfK#KF80H!Y`k0F%BM!mw3wV=)6#?KKf%e5c$ zM;QH)hL0i-V0{alqfxJ{Nyw#5_5#~8BJF8SgqyT)mG@7)URm_zbIiKy+D&`TS#?Os zI(}8}$1={4>ln+l!CIv&Fwc|84d^xA%5z=l!&xitd*qh7(=#!NLvwyr7uNltvhD}L z*kAX`91X@D&!H)-Vmn^$ED2He3%VH$3b43CDOn%Ejw6#bS8<6g+vq(1aRLJ7w;xslBlGBkZq*bE3o*$_o2_(>$ZstGPwnAnel!cLg?FLmkRYWh}L>pIqyRo44*tSNh} z>Hp(sKbT9s$Az4Ox{jkg^BDLwv{_Mz&1*uAqMkO)C&J^AmzlD{IOpL#Jsy4}=a9?x zWUCwS-`S*}?0mx)f?HG7-^a5Hy)L~ido4LFGS=bBndBLA=x5K8ufQ=6L`Iy0ul+KZlZchJ|3o@X^d&$Alv3A~oUrd*5ic;tG}b1W?X>l?*Bj2)KsJV0ig zFTY=XdQPZkybTT(EPIyJm5YCJeFfH6)Q7P~W?7EMz^{*tC(e=ET+oYu|k=azbZCLm?YIPXp*&Ly^$8xlWY#I4T7;PAX= z7<@vAL&Ik)>zo^Si18-&tKIV73!laD!a2GJ*NVNQ>>tUzF2-c!@sgQOg1$Sl#x3<+ z?{V%RH}Dnw2sgE?@LI#;C1=Kl^K9_LxjQiPo@ZHmdzSc*T>6(irt0xFns3_@+^{i9n2Vp zd?pKM40J_n(mIRR%}^cl}rm9wa4|MM&w`nWa#H%uEEwDB>iYld9x zm79h&1{NCo<@7T+E;h?U?t*`??ls7?Z=NXEF1-AQ${}CD=CqKP$YQVe=rm(>YcA6L7uP2}VQrHOXY|SUWT200n;x^C>2dnL zL5=;H=z50d4}rNS3e0tja<8mmlyxqMjI~5yZy(n}(8sf7GUo_i7g+aq(91rK>ku%W znfA(>1RT$VmHXsuSy|77!SdYSm&J3{Sax}sk6?4!MD}w{;!Zbuu+<-`^HJh1piQA| z1M9jAKJlD-b+Eb6_yGrf8|-tLH+z!K_^dxL+dR{ib6jBNsK~=5<9*cDpvU=S3z;#) z{06QtZat^uY*_TVcSHV!jWTj0Smp)J1(8)RpZnn43cY;pqqecHNoHIzX2ANc)cmaE zBeNX(nCHPUmy#Lt%%94dGs*PHoT;quoP+s{rZU(R{UX|MjRbw%zmTct{snvznfW2| zx{}wPD10jWg!vOTWXW%2j!muum33^A>62?eav00tx0t%(x(oXA>3;=$k;8abMmzP| zp(jg!o^RR@#0FTN|mXA$8%ISL}as-QSBVXz%+LoEeUNenoXW+FVILgSqqxImF48^yB;a zO4HAXv}m)=*sL=)SJ9_$|5auCvt!9RrZVi)b&ovLW!Qt@eG2L4O!gq&=Jm$@jfO97 zz(3cbD7%$)rMd7n!&nJK{ZGl7XR_Zj_J(DQbp3PbvyRrlzD_^Nd>`I0@=w$wFGcQB zPF&iv*T$U~O^3c!l&q)z`<$MR$9EEo^ z!QCnRtDpFOAlhH2X$TpSINxzfj21QJs+K4pa<*wUEnxRflm!{fwImi%DUeH zpHCa)1o{U@8{tyFI@nxfe1dg;LD@^G_w5A7HiK6OJ>H4R4M%%Y!n6n62=!iB zwyh0k5#f53deoG}ls@LqR&W;}Df4ZX~F+`mM|d>5GWU}V+D&pbjebD#GU&n=;k`<=*IR`lHC z1U>gDfw?yU`#nfD3_gf$_U+fbz^GiyLeu_bwBeix`_~HR%3)rhp7vbxg9m6cGA=gc zdSH&TD>2{ECggK6%U+53F7o%3Lq3Q8O!^trde5Xi^C9#xH_q~TfO#V@@EzHSk(&rqujjhXb0zJMEMo_Hj-ANt6UIFC z;CyU;nvo0m;;AB0aZu5n{ulWfZ>XT#KoF$=!Y zl)cf|Z!&st^tq|DDc2seUAB^Xu0v*rF+0xdE$3fh|DMup^ZZU(&kVtF4+wrc&k`k` z6BqYT(8oO#IL>{_I`@Gu3FFA{*~-!8hA@tx2kV>&#yh>eGQN~`-c$}YZE_~bz3Kr@ z#~R~&4miB?Gskt_6L_w-VIGT|8y+t?GkUJUf<665#=I7o_eojLHpvZqalAxs8Xk{a zNlU$a7K8Q$zBqot9M^JB19QBCTNsDlzdSeOeTP2Ip^-5j19O~4j(Q!Z(91JB?^DkY z)m~ZcXG?ojudMYdN1Nyq`$|2dgMYB@E6Mc3cSHj-pD4EleS9Yh`keO68{l4Z?Y;Eh zhH(ddKW&&pz$ep&y^5$>Dz8WZHR?Y3EJG=gp?<&8E%Z zc#VO1Uv4&jZZTzVG5XC$udMlMv$46&*xY971;=`Cv-YOVcNqPh;z#|H>myqvw!!+2 zG}?Tp@fn!ca;NoE`cL0c=I8VsOXzcu{ha+}1FX5RwUza9T{PEC20uX9Cn5JJeliEt|wu$D$IT0%Z&bN!|TJI5jNo7Q1;r19DPEs zYgE{O-1zyFu?I(cu+FovzscBuqYe1rP%k*z-)!u`(HcdzAYi>tA2YRjb}FwEpTa!Lh5fDce^nvvzcu6>>N#fD z;;gmgz9yMI*J2GoHg$c$wDSw5-rFr3`G}Oq82R+)_8{gR>ms&gk{021IPH!6TiRS z&=_twjp1}$*O>A%E|%YgoBF-L&s;-7CvnSlBRH-P!EwC@?qPp=8;MUo^MyV>pCucc zIVt?}od@W@NM6xYX1{W8MLq2~Mk1@vyJ*kYhF)3Ak|l1b4>6;hpx^qMWcn=hTJ8$> z7y2)y^r`J3%iKhLn2*S&?A^2<)HWz<8_05=4EwvywSaLg+J?L3y0i^PH;v) zwVk}iGN*y#85($Fh$Ur>rA0{@OW^qKuCmUX%KG_nu&i@9w<@oe`4{}P5D&_l-;_0G zz*~$Bc#-5o)km9qL!N{l+#77b!KMx868bD)i7VqO^0;$>IoFdVmU6?*$g<9aO$+;w zXTo5ayBT9(JqJ^+hd6|Of7>jZGj$xBS`1*eQ#jtj7y zIdfeKmNmcEd!S~aKXK~7!SHW9N*$2aU9rO>Ce%gA; zY(u{*$4g+IOOqS?9s0zxj0>FaEd=kvv z$l7ai=#}xg-N0832*-0{=-(b<1H38RU$Cw{PzyND*Wj49!QU|U-((xO9)SLvA@<0| zKUmL}p?{e6TyK!WxF8#!-=Yo2FZACsKEGA^%-&Gim!I|MH+eoV%Y$doXO=6oJ#BK0 z<=?b{%}n~_-=YD>SnW^TEap${L&38$Dx${s3O@Ec637=0zXpz5{4UEueCkfLyn?8TbG&KW@s59Q0rwC6qg)6#spD7fTwtt)GBW#t^B1{+ zFK;7xCdW8}zQ+FH*pG~{0G9jf^~$Q3XJNeHypwIi7WQmO*O8bAnKJoSbz|{ML5D&2ToaKgl zm9tRS4?6X_3n%dZw3Ho}zIxhR*VAIZQ?5&x@lSi| zuSEPu)@xV&4^#2U_s1G6kRwgGJIqfhAd8G)^yq z^7HIs8Gb(xrI(5YORzTTmbqKpJ+P}nGZUJn#Wm`oSOwkwVsjL%R=M@CYVjq5epRua z7WhBwon736@3V`e@b#qYC}xZ9DXD1{O3%jfq*ZFs5?Ye~XS?;cUGTTS-BLVRuzk0% zWuR&9I#)xBYVOncz7*d#;`?m0rxQQ5xX-%^uBFeN$qsDxV~Z(|HoJHAih6_9&dNkaeUZ=Rvh3Ca`Tatu5~vQH}Cy76ZEw2 zl@iCH%%|KYceC53pFV#L<$Vky%7Vt0sJQXJ~$ z6mKosr4>CWm4e%g8&FS=UMT{%y?9uEFPdJt_M63XJ4(><+T!VgQOUbDn;y|~Uqnqy z-6`%Z?!4l?xM!!hHy7u@dN#^bakt)5Tnvj-d~9D@T#lYT1*K*bpLH2xe?NS1Zzdjb zMTXxAKe>ZZkN<5kv%tq|Ln*w5vT%~^LRhcESeXHvZoKk311$*m^Z@kfr`!#=GdIb- zxefP*{T6;d)o=N`SdRBa;$%_@=n+BmnEV62&&T(FLO;zdPIJc;r=qXU!WSYm`B!(C zJG?kU{{N^utT-I{s=Ewvd0D0Au1$K+{EPP->6$Hvcl^5YuIeu21BaAYxY5KV&7??1 zE3VS46qV5y*U~hyx@F{#lx;2lmcAza@Lr@yNAVZsn?}}TcT|2>d9LzYOIypqXi44W z*z__*54$yJ)ds}*7L1~2+_>9?7S@wI=|?+yw*ihMA*-{y;JX0C3?7|h*Tk@8ET-8v^aLbxKzOQciAg=Lx`MSMj>!196 zTg%_H{G#Pw&<`E>yZwitH%?C9mcAD~^0D-D={@`WUZfk+&FPkOG<_x=Pj{iu>iD-V z`q6Jg*>JYzkN&Q&XxRPU&6sg72k@iJchb#>Ps z@`qa0OLy&ny~^U+;@S~=l-twOBu#C(EmJ!k^3K`?)m^m@)vm5>tlhZ}d9X&=4*9Y5 z^I}~4Y3(<)7uvEtf8W-#r%p9(eUO9Oj%|BG+p4zrv|ZMAt(Q%0o7!#;rP{vQ_DI`P zZ9l5+YWro|@2b1nv-Y0$KK{1-RqZFVpNjAAY`>uW^7iXQZf^f-`y=g7wg0sJH^D>u z3olJNTt|H$GPh%K$C4@J@Q!8sl;b;2fxJ1iy8Pbp=a3JYpGP|W82Mz!^)DiuN?(5( zb$qGg8-H3!E!DB(CCcA+yd-fe+__kNTnqV(6_d)}Qkf{*D;1SbV|7XSvX|{{JN{A7qbcsMjZDXk zZ>5fT&)O!S6ks9N3b&KpJT>~d7`|+O_W)9N#6=~2NUD~tm3T9!D?wL znTxgKWI524@=7<#7{dAEDFI|s5FaxSDXN&2vp z*i*jZf_%UQS%X#28uu|H7rG1GTF6)Tka4VuRQ}3Ml+QVp?>m*xV?U#E6V|6UC2h$Z ziu^|f?>|m_C$4!*; z-PCfCn_8~HDnH2e?s_*eN!H_BU_I8_L9TM8+>HIqb(7>c>@BUl#=W@kF!6XVC%95x z>q=>2#ou%TMwYu1AyfTy}a@Ur?Lfmx-B>- z`r~9X_JBd|b)|gNebjws3i+g?+=r7Lm0MjY_v7T`3nI5U%2vn&Mt1v#`I-9} zPWmXTApb0KDbCeC?uBLF8s#$VdM^`&V46ZaGMV~=x;-#>WymQ(q*`?h-o z@_Ui!&V46}=#q4j9GIxQG4WCs6&l64-ceo#5|w!gWxki!Br1m{l)eQ2K*uC`)CKu3 zj`BBA{->k-t&!*4bMBW>w9PFE#QlAgU$_a9oIXjWB@@K`n~_PE=boJ;|I0;5x{@yU z--0A>qPXXyO!lO0Js|O7e!bg8Ne(L|SR@OK%u0gHOiDSy{Z1trEG3lZ_vAD$wZuwi z;w9K7-A3jnUcztp_B}ZW^14z=8=MjxqT+7chit<6w2@@ef2Gg8Q}Svn?z>Y<(hzw= zGNrf|_92s+lANqENnQT@4qN}r(Q6qN}|>U&F>@Vo!)_7rcB zbU^C+kQvF;(vS1=spQCHf=p=J9zP^Y-4CXgWPg!^6O|sEPE8^1@Ae{pD67S})|3(~ z@r2+-#9O@0O)YPCQ_JNpNT^c2Co3R%lDoe&@>%!U2}bLv?Fr?? zQp&QVWMPnr`tVb7yvUM7Bxv{io}5J4Qy(o%;~pj>q;P|Hhod5s; diff --git a/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java b/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java index f2566d9d419..afa09a3f537 100644 --- a/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java +++ b/src/java.base/share/classes/jdk/internal/icu/text/NormalizerBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -420,10 +420,10 @@ public final class NormalizerBase implements Cloneable { * iterator's {@code clone} method does so. * @stable ICU 2.8 */ - public Object clone() { + public NormalizerBase clone() { try { NormalizerBase copy = (NormalizerBase) super.clone(); - copy.text = (UCharacterIterator) text.clone(); + copy.text = text.clone(); copy.mode = mode; copy.options = options; copy.norm2 = norm2; diff --git a/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java b/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java index 93978372c3a..24c54795508 100644 --- a/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java +++ b/src/java.base/share/classes/jdk/internal/icu/text/UCharacterIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -310,8 +310,8 @@ public abstract class UCharacterIterator * @return copy of this iterator * @stable ICU 2.4 */ - public Object clone() throws CloneNotSupportedException{ - return super.clone(); + public UCharacterIterator clone() throws CloneNotSupportedException{ + return (UCharacterIterator) super.clone(); } } diff --git a/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java b/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java index 6f5919e016b..0bc68080e83 100644 --- a/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java +++ b/src/java.base/share/classes/jdk/internal/icu/text/UnicodeSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -293,7 +293,7 @@ import jdk.internal.icu.util.VersionInfo; * @author Alan Liu * @stable ICU 2.0 */ -public class UnicodeSet { +public class UnicodeSet implements Cloneable { private static final int LOW = 0x000000; // LOW <= all valid values. ZERO for codepoints private static final int HIGH = 0x110000; // HIGH > all valid values. 10000 for code units. @@ -385,6 +385,18 @@ public class UnicodeSet { applyPattern(pattern, null); } + /** + * Return a new set that is equivalent to this one. + * @stable ICU 2.0 + */ + @Override + public UnicodeSet clone() { + if (isFrozen()) { + return this; + } + return new UnicodeSet(this); + } + /** * Make this object represent the same set as other. * @param other a UnicodeSet whose value will be diff --git a/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java b/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java index b7a52b74ae1..fa55b9df5af 100644 --- a/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java +++ b/src/java.base/share/classes/jdk/internal/icu/util/VersionInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, 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 @@ -54,7 +54,7 @@ public final class VersionInfo * @deprecated This API is ICU internal only. */ @Deprecated - public static final String ICU_DATA_VERSION_PATH = "76b"; + public static final String ICU_DATA_VERSION_PATH = "78b"; // public methods ------------------------------------------------------ diff --git a/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java b/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java index 34a69c3045f..303a76cb545 100644 --- a/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java +++ b/src/java.base/share/classes/jdk/internal/util/regex/Grapheme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, 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 @@ -35,9 +35,9 @@ public final class Grapheme { *

* See Unicode Standard Annex #29 Unicode Text Segmentation for the specification * for the extended grapheme cluster boundary rules. The following implementation - * is based on the annex for Unicode version 16.0. + * is based on the annex for Unicode version 17.0. * - * @spec http://www.unicode.org/reports/tr29/tr29-45.html + * @spec http://www.unicode.org/reports/tr29/tr29-47.html * @param src the {@code CharSequence} to be scanned * @param off offset to start looking for the next boundary in the src * @param limit limit offset in the src (exclusive) @@ -283,7 +283,6 @@ public final class Grapheme { case 0x113D1: case 0x1193F: case 0x11941: - case 0x11A3A: case 0x11A84: case 0x11A85: case 0x11A86: diff --git a/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template b/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template index fc0e6605eca..149b1dce660 100644 --- a/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template +++ b/src/java.base/share/classes/jdk/internal/util/regex/IndicConjunctBreak.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -49,8 +49,9 @@ final class IndicConjunctBreak { } static boolean isConsonant(int cp) { - // fast check - Devanagari to Malayalam - if (cp < 0x0900 || cp > 0x0D7F) { + // fast check - return false for code points below + // the Devanagari range (lowest among Indic scripts) + if (cp < 0x0900) { return false; } diff --git a/src/java.base/share/data/unicodedata/Blocks.txt b/src/java.base/share/data/unicodedata/Blocks.txt index 19460657ac9..5c24ab60cb1 100644 --- a/src/java.base/share/data/unicodedata/Blocks.txt +++ b/src/java.base/share/data/unicodedata/Blocks.txt @@ -1,6 +1,6 @@ -# Blocks-16.0.0.txt -# Date: 2024-02-02 -# Copyright (c) 2024 Unicode, Inc. +# Blocks-17.0.0.txt +# Date: 2025-08-01 +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -228,6 +228,7 @@ FFF0..FFFF; Specials 108E0..108FF; Hatran 10900..1091F; Phoenician 10920..1093F; Lydian +10940..1095F; Sidetic 10980..1099F; Meroitic Hieroglyphs 109A0..109FF; Meroitic Cursive 10A00..10A5F; Kharoshthi @@ -279,11 +280,13 @@ FFF0..FFFF; Specials 11AB0..11ABF; Unified Canadian Aboriginal Syllabics Extended-A 11AC0..11AFF; Pau Cin Hau 11B00..11B5F; Devanagari Extended-A +11B60..11B7F; Sharada Supplement 11BC0..11BFF; Sunuwar 11C00..11C6F; Bhaiksuki 11C70..11CBF; Marchen 11D00..11D5F; Masaram Gondi 11D60..11DAF; Gunjala Gondi +11DB0..11DEF; Tolong Siki 11EE0..11EFF; Makasar 11F00..11F5F; Kawi 11FB0..11FBF; Lisu Supplement @@ -304,12 +307,14 @@ FFF0..FFFF; Specials 16B00..16B8F; Pahawh Hmong 16D40..16D7F; Kirat Rai 16E40..16E9F; Medefaidrin +16EA0..16EDF; Beria Erfe 16F00..16F9F; Miao 16FE0..16FFF; Ideographic Symbols and Punctuation 17000..187FF; Tangut 18800..18AFF; Tangut Components 18B00..18CFF; Khitan Small Script 18D00..18D7F; Tangut Supplement +18D80..18DFF; Tangut Components Supplement 1AFF0..1AFFF; Kana Extended-B 1B000..1B0FF; Kana Supplement 1B100..1B12F; Kana Extended-A @@ -318,6 +323,7 @@ FFF0..FFFF; Specials 1BC00..1BC9F; Duployan 1BCA0..1BCAF; Shorthand Format Controls 1CC00..1CEBF; Symbols for Legacy Computing Supplement +1CEC0..1CEFF; Miscellaneous Symbols Supplement 1CF00..1CFCF; Znamenny Musical Notation 1D000..1D0FF; Byzantine Musical Symbols 1D100..1D1FF; Musical Symbols @@ -336,6 +342,7 @@ FFF0..FFFF; Specials 1E2C0..1E2FF; Wancho 1E4D0..1E4FF; Nag Mundari 1E5D0..1E5FF; Ol Onal +1E6C0..1E6FF; Tai Yo 1E7E0..1E7FF; Ethiopic Extended-B 1E800..1E8DF; Mende Kikakui 1E900..1E95F; Adlam @@ -367,6 +374,7 @@ FFF0..FFFF; Specials 2F800..2FA1F; CJK Compatibility Ideographs Supplement 30000..3134F; CJK Unified Ideographs Extension G 31350..323AF; CJK Unified Ideographs Extension H +323B0..3347F; CJK Unified Ideographs Extension J E0000..E007F; Tags E0100..E01EF; Variation Selectors Supplement F0000..FFFFF; Supplementary Private Use Area-A diff --git a/src/java.base/share/data/unicodedata/CaseFolding.txt b/src/java.base/share/data/unicodedata/CaseFolding.txt index 345c4f3dd03..a0b0f07fd64 100644 --- a/src/java.base/share/data/unicodedata/CaseFolding.txt +++ b/src/java.base/share/data/unicodedata/CaseFolding.txt @@ -1,6 +1,6 @@ -# CaseFolding-16.0.0.txt -# Date: 2024-04-30, 21:48:11 GMT -# © 2024 Unicode®, Inc. +# CaseFolding-17.0.0.txt +# Date: 2025-07-30, 23:54:36 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -18,15 +18,15 @@ # The data supports both implementations that require simple case foldings # (where string lengths don't change), and implementations that allow full case folding # (where string lengths may grow). Note that where they can be supported, the -# full case foldings are superior: for example, they allow "MASSE" and "Maße" to match. +# full case foldings are superior: for example, they allow "FUSS" and "Fuß" to match. # # All code points not listed in this file map to themselves. # # NOTE: case folding does not preserve normalization formats! # # For information on case folding, including how to have case folding -# preserve normalization formats, see Section 3.13 Default Case Algorithms in -# The Unicode Standard. +# preserve normalization formats, see the +# "Conformance" / "Default Case Algorithms" section of the core specification. # # ================================================================================ # Format @@ -1243,7 +1243,10 @@ A7C7; C; A7C8; # LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9; C; A7CA; # LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB; C; 0264; # LATIN CAPITAL LETTER RAMS HORN A7CC; C; A7CD; # LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE; C; A7CF; # LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0; C; A7D1; # LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2; C; A7D3; # LATIN CAPITAL LETTER DOUBLE THORN +A7D4; C; A7D5; # LATIN CAPITAL LETTER DOUBLE WYNN A7D6; C; A7D7; # LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8; C; A7D9; # LATIN CAPITAL LETTER SIGMOID S A7DA; C; A7DB; # LATIN CAPITAL LETTER LAMBDA @@ -1616,6 +1619,31 @@ FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z 16E5D; C; 16E7D; # MEDEFAIDRIN CAPITAL LETTER O 16E5E; C; 16E7E; # MEDEFAIDRIN CAPITAL LETTER AI 16E5F; C; 16E7F; # MEDEFAIDRIN CAPITAL LETTER Y +16EA0; C; 16EBB; # BERIA ERFE CAPITAL LETTER ARKAB +16EA1; C; 16EBC; # BERIA ERFE CAPITAL LETTER BASIGNA +16EA2; C; 16EBD; # BERIA ERFE CAPITAL LETTER DARBAI +16EA3; C; 16EBE; # BERIA ERFE CAPITAL LETTER EH +16EA4; C; 16EBF; # BERIA ERFE CAPITAL LETTER FITKO +16EA5; C; 16EC0; # BERIA ERFE CAPITAL LETTER GOWAY +16EA6; C; 16EC1; # BERIA ERFE CAPITAL LETTER HIRDEABO +16EA7; C; 16EC2; # BERIA ERFE CAPITAL LETTER I +16EA8; C; 16EC3; # BERIA ERFE CAPITAL LETTER DJAI +16EA9; C; 16EC4; # BERIA ERFE CAPITAL LETTER KOBO +16EAA; C; 16EC5; # BERIA ERFE CAPITAL LETTER LAKKO +16EAB; C; 16EC6; # BERIA ERFE CAPITAL LETTER MERI +16EAC; C; 16EC7; # BERIA ERFE CAPITAL LETTER NINI +16EAD; C; 16EC8; # BERIA ERFE CAPITAL LETTER GNA +16EAE; C; 16EC9; # BERIA ERFE CAPITAL LETTER NGAY +16EAF; C; 16ECA; # BERIA ERFE CAPITAL LETTER OI +16EB0; C; 16ECB; # BERIA ERFE CAPITAL LETTER PI +16EB1; C; 16ECC; # BERIA ERFE CAPITAL LETTER ERIGO +16EB2; C; 16ECD; # BERIA ERFE CAPITAL LETTER ERIGO TAMURA +16EB3; C; 16ECE; # BERIA ERFE CAPITAL LETTER SERI +16EB4; C; 16ECF; # BERIA ERFE CAPITAL LETTER SHEP +16EB5; C; 16ED0; # BERIA ERFE CAPITAL LETTER TATASOUE +16EB6; C; 16ED1; # BERIA ERFE CAPITAL LETTER UI +16EB7; C; 16ED2; # BERIA ERFE CAPITAL LETTER WASSE +16EB8; C; 16ED3; # BERIA ERFE CAPITAL LETTER AY 1E900; C; 1E922; # ADLAM CAPITAL LETTER ALIF 1E901; C; 1E923; # ADLAM CAPITAL LETTER DAALI 1E902; C; 1E924; # ADLAM CAPITAL LETTER LAAM @@ -1651,4 +1679,4 @@ FF3A; C; FF5A; # FULLWIDTH LATIN CAPITAL LETTER Z 1E920; C; 1E942; # ADLAM CAPITAL LETTER KPO 1E921; C; 1E943; # ADLAM CAPITAL LETTER SHA # -# EOF \ No newline at end of file +# EOF diff --git a/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt b/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt index 43d7af85c29..f327784bf39 100644 --- a/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt +++ b/src/java.base/share/data/unicodedata/DerivedCoreProperties.txt @@ -1,6 +1,6 @@ -# DerivedCoreProperties-16.0.0.txt -# Date: 2024-05-31, 18:09:32 GMT -# Copyright (c) 2024 Unicode, Inc. +# DerivedCoreProperties-17.0.0.txt +# Date: 2025-07-30, 23:55:08 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -178,6 +178,7 @@ FF5E ; Math # Sm FULLWIDTH TILDE FFE2 ; Math # Sm FULLWIDTH NOT SIGN FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS ARROW 10D8E..10D8F ; Math # Sm [2] GARAY PLUS SIGN..GARAY MINUS SIGN +1CEF0 ; Math # Sm MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR 1D400..1D454 ; Math # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G 1D456..1D49C ; Math # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A 1D49E..1D49F ; Math # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D @@ -253,8 +254,9 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 1EEA5..1EEA9 ; Math # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH 1EEAB..1EEBB ; Math # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1EEF0..1EEF1 ; Math # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL +1F8D0..1F8D8 ; Math # Sm [9] LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW..LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE -# Total code points: 2312 +# Total code points: 2322 # ================================================ @@ -273,8 +275,8 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 01BC..01BF ; Alphabetic # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; Alphabetic # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; Alphabetic # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; Alphabetic # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; Alphabetic # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; Alphabetic # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; Alphabetic # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; Alphabetic # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; Alphabetic # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; Alphabetic # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -344,7 +346,7 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 0840..0858 ; Alphabetic # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; Alphabetic # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; Alphabetic # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; Alphabetic # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; Alphabetic # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0897 ; Alphabetic # Mn ARABIC PEPET 08A0..08C8 ; Alphabetic # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; Alphabetic # Lm ARABIC SMALL FARSI YEH @@ -477,7 +479,7 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 0C4A..0C4C ; Alphabetic # Mn [3] TELUGU VOWEL SIGN O..TELUGU VOWEL SIGN AU 0C55..0C56 ; Alphabetic # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; Alphabetic # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; Alphabetic # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; Alphabetic # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; Alphabetic # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; Alphabetic # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C80 ; Alphabetic # Lo KANNADA SIGN SPACING CANDRABINDU @@ -497,7 +499,7 @@ FFE9..FFEC ; Math # Sm [4] HALFWIDTH LEFTWARDS ARROW..HALFWIDTH DOWNWARDS A 0CCA..0CCB ; Alphabetic # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC ; Alphabetic # Mn KANNADA VOWEL SIGN AU 0CD5..0CD6 ; Alphabetic # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; Alphabetic # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; Alphabetic # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; Alphabetic # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; Alphabetic # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CF1..0CF2 ; Alphabetic # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA @@ -833,11 +835,8 @@ A771..A787 ; Alphabetic # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER A788 ; Alphabetic # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; Alphabetic # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; Alphabetic # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; Alphabetic # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Alphabetic # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Alphabetic # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; Alphabetic # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; Alphabetic # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; Alphabetic # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; Alphabetic # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; Alphabetic # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; Alphabetic # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; Alphabetic # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -1020,6 +1019,7 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 108F4..108F5 ; Alphabetic # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; Alphabetic # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; Alphabetic # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; Alphabetic # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; Alphabetic # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; Alphabetic # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; Alphabetic # Lo KHAROSHTHI LETTER A @@ -1053,7 +1053,9 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 10EAB..10EAC ; Alphabetic # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK 10EB0..10EB1 ; Alphabetic # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; Alphabetic # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC ; Alphabetic # Mn ARABIC COMBINING ALEF OVERLAY +10EC5 ; Alphabetic # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; Alphabetic # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10EFA..10EFC ; Alphabetic # Mn [3] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC COMBINING ALEF OVERLAY 10F00..10F1C ; Alphabetic # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; Alphabetic # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; Alphabetic # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -1239,6 +1241,12 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 11A97 ; Alphabetic # Mc SOYOMBO SIGN VISARGA 11A9D ; Alphabetic # Lo SOYOMBO MARK PLUTA 11AB0..11AF8 ; Alphabetic # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11B60 ; Alphabetic # Mn SHARADA VOWEL SIGN OE +11B61 ; Alphabetic # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Alphabetic # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Alphabetic # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Alphabetic # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Alphabetic # Mc SHARADA VOWEL SIGN CANDRA O 11BC0..11BE0 ; Alphabetic # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO 11C00..11C08 ; Alphabetic # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L 11C0A..11C2E ; Alphabetic # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA @@ -1274,6 +1282,9 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 11D95 ; Alphabetic # Mn GUNJALA GONDI SIGN ANUSVARA 11D96 ; Alphabetic # Mc GUNJALA GONDI SIGN VISARGA 11D98 ; Alphabetic # Lo GUNJALA GONDI OM +11DB0..11DD8 ; Alphabetic # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; Alphabetic # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; Alphabetic # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; Alphabetic # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11EF3..11EF4 ; Alphabetic # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6 ; Alphabetic # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O @@ -1311,6 +1322,8 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 16D43..16D6A ; Alphabetic # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; Alphabetic # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; Alphabetic # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; Alphabetic # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Alphabetic # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; Alphabetic # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F4F ; Alphabetic # Mn MIAO SIGN CONSONANT MODIFIER BAR 16F50 ; Alphabetic # Lo MIAO LETTER NASALIZATION @@ -1320,9 +1333,11 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 16FE0..16FE1 ; Alphabetic # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; Alphabetic # Lm OLD CHINESE ITERATION MARK 16FF0..16FF1 ; Alphabetic # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY -17000..187F7 ; Alphabetic # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; Alphabetic # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; Alphabetic # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; Alphabetic # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; Alphabetic # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; Alphabetic # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; Alphabetic # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; Alphabetic # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; Alphabetic # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; Alphabetic # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; Alphabetic # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -1387,6 +1402,17 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 1E4EB ; Alphabetic # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; Alphabetic # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; Alphabetic # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; Alphabetic # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; Alphabetic # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; Alphabetic # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; Alphabetic # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; Alphabetic # Mn TAI YO SIGN AU +1E6E7..1E6ED ; Alphabetic # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; Alphabetic # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; Alphabetic # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; Alphabetic # Mn TAI YO SIGN OM +1E6FE ; Alphabetic # Lo TAI YO SYMBOL MUEANG +1E6FF ; Alphabetic # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; Alphabetic # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; Alphabetic # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; Alphabetic # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -1432,16 +1458,15 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 1F150..1F169 ; Alphabetic # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; Alphabetic # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z 20000..2A6DF ; Alphabetic # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; Alphabetic # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Alphabetic # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Alphabetic # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; Alphabetic # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; Alphabetic # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; Alphabetic # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; Alphabetic # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; Alphabetic # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; Alphabetic # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Alphabetic # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; Alphabetic # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 142759 +# Total code points: 147421 # ================================================ @@ -1595,7 +1620,7 @@ FFDA..FFDC ; Alphabetic # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANG 024B ; Lowercase # L& LATIN SMALL LETTER Q WITH HOOK TAIL 024D ; Lowercase # L& LATIN SMALL LETTER R WITH STROKE 024F..0293 ; Lowercase # L& [69] LATIN SMALL LETTER Y WITH STROKE..LATIN SMALL LETTER EZH WITH CURL -0295..02AF ; Lowercase # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0296..02AF ; Lowercase # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02B8 ; Lowercase # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y 02C0..02C1 ; Lowercase # Lm [2] MODIFIER LETTER GLOTTAL STOP..MODIFIER LETTER REVERSED GLOTTAL STOP 02E0..02E4 ; Lowercase # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -2073,13 +2098,14 @@ A7C3 ; Lowercase # L& LATIN SMALL LETTER ANGLICANA W A7C8 ; Lowercase # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY A7CA ; Lowercase # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY A7CD ; Lowercase # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE +A7CF ; Lowercase # L& LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE A7D1 ; Lowercase # L& LATIN SMALL LETTER CLOSED INSULAR G A7D3 ; Lowercase # L& LATIN SMALL LETTER DOUBLE THORN A7D5 ; Lowercase # L& LATIN SMALL LETTER DOUBLE WYNN A7D7 ; Lowercase # L& LATIN SMALL LETTER MIDDLE SCOTS S A7D9 ; Lowercase # L& LATIN SMALL LETTER SIGMOID S A7DB ; Lowercase # L& LATIN SMALL LETTER LAMBDA -A7F2..A7F4 ; Lowercase # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F1..A7F4 ; Lowercase # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F6 ; Lowercase # L& LATIN SMALL LETTER REVERSED HALF H A7F8..A7F9 ; Lowercase # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A7FA ; Lowercase # L& LATIN LETTER SMALL CAPITAL TURNED M @@ -2105,6 +2131,7 @@ FF41..FF5A ; Lowercase # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L 10D70..10D85 ; Lowercase # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118C0..118DF ; Lowercase # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E60..16E7F ; Lowercase # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EBB..16ED3 ; Lowercase # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1D41A..1D433 ; Lowercase # L& [26] MATHEMATICAL BOLD SMALL A..MATHEMATICAL BOLD SMALL Z 1D44E..1D454 ; Lowercase # L& [7] MATHEMATICAL ITALIC SMALL A..MATHEMATICAL ITALIC SMALL G 1D456..1D467 ; Lowercase # L& [18] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL ITALIC SMALL Z @@ -2139,7 +2166,7 @@ FF41..FF5A ; Lowercase # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH L 1E030..1E06D ; Lowercase # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE 1E922..1E943 ; Lowercase # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 2569 +# Total code points: 2595 # ================================================ @@ -2750,7 +2777,10 @@ A7C2 ; Uppercase # L& LATIN CAPITAL LETTER ANGLICANA W A7C4..A7C7 ; Uppercase # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9 ; Uppercase # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB..A7CC ; Uppercase # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE ; Uppercase # L& LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0 ; Uppercase # L& LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2 ; Uppercase # L& LATIN CAPITAL LETTER DOUBLE THORN +A7D4 ; Uppercase # L& LATIN CAPITAL LETTER DOUBLE WYNN A7D6 ; Uppercase # L& LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8 ; Uppercase # L& LATIN CAPITAL LETTER SIGMOID S A7DA ; Uppercase # L& LATIN CAPITAL LETTER LAMBDA @@ -2767,6 +2797,7 @@ FF21..FF3A ; Uppercase # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH 10D50..10D65 ; Uppercase # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA 118A0..118BF ; Uppercase # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO 16E40..16E5F ; Uppercase # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +16EA0..16EB8 ; Uppercase # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY 1D400..1D419 ; Uppercase # L& [26] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL BOLD CAPITAL Z 1D434..1D44D ; Uppercase # L& [26] MATHEMATICAL ITALIC CAPITAL A..MATHEMATICAL ITALIC CAPITAL Z 1D468..1D481 ; Uppercase # L& [26] MATHEMATICAL BOLD ITALIC CAPITAL A..MATHEMATICAL BOLD ITALIC CAPITAL Z @@ -2803,7 +2834,7 @@ FF21..FF3A ; Uppercase # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH 1F150..1F169 ; Uppercase # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; Uppercase # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z -# Total code points: 1978 +# Total code points: 2006 # ================================================ @@ -2821,7 +2852,7 @@ FF21..FF3A ; Uppercase # L& [26] FULLWIDTH LATIN CAPITAL LETTER A..FULLWIDTH 00F8..01BA ; Cased # L& [195] LATIN SMALL LETTER O WITH STROKE..LATIN SMALL LETTER EZH WITH TAIL 01BC..01BF ; Cased # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C4..0293 ; Cased # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0295..02AF ; Cased # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0296..02AF ; Cased # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02B8 ; Cased # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y 02C0..02C1 ; Cased # Lm [2] MODIFIER LETTER GLOTTAL STOP..MODIFIER LETTER REVERSED GLOTTAL STOP 02E0..02E4 ; Cased # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -2911,11 +2942,8 @@ A722..A76F ; Cased # L& [78] LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF..LATIN A770 ; Cased # Lm MODIFIER LETTER US A771..A787 ; Cased # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T A78B..A78E ; Cased # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT -A790..A7CD ; Cased # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Cased # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Cased # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; Cased # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; Cased # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; Cased # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; Cased # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; Cased # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F8..A7F9 ; Cased # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A7FA ; Cased # L& LATIN LETTER SMALL CAPITAL TURNED M @@ -2949,6 +2977,8 @@ FF41..FF5A ; Cased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 10D70..10D85 ; Cased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118A0..118DF ; Cased # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E40..16E7F ; Cased # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; Cased # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Cased # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1D400..1D454 ; Cased # L& [85] MATHEMATICAL BOLD CAPITAL A..MATHEMATICAL ITALIC SMALL G 1D456..1D49C ; Cased # L& [71] MATHEMATICAL ITALIC SMALL I..MATHEMATICAL SCRIPT CAPITAL A 1D49E..1D49F ; Cased # L& [2] MATHEMATICAL SCRIPT CAPITAL C..MATHEMATICAL SCRIPT CAPITAL D @@ -2988,7 +3018,7 @@ FF41..FF5A ; Cased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 1F150..1F169 ; Cased # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; Cased # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z -# Total code points: 4578 +# Total code points: 4632 # ================================================ @@ -3194,7 +3224,8 @@ FF41..FF5A ; Cased # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 1AA7 ; Case_Ignorable # Lm TAI THAM SIGN MAI YAMOK 1AB0..1ABD ; Case_Ignorable # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Case_Ignorable # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Case_Ignorable # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Case_Ignorable # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Case_Ignorable # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; Case_Ignorable # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B34 ; Case_Ignorable # Mn BALINESE SIGN REREKAN 1B36..1B3A ; Case_Ignorable # Mn [5] BALINESE VOWEL SIGN ULU..BALINESE VOWEL SIGN RA REPA @@ -3274,7 +3305,7 @@ A720..A721 ; Case_Ignorable # Sk [2] MODIFIER LETTER STRESS AND HIGH TONE.. A770 ; Case_Ignorable # Lm MODIFIER LETTER US A788 ; Case_Ignorable # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A789..A78A ; Case_Ignorable # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN -A7F2..A7F4 ; Case_Ignorable # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A7F1..A7F4 ; Case_Ignorable # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F8..A7F9 ; Case_Ignorable # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE A802 ; Case_Ignorable # Mn SYLOTI NAGRI SIGN DVISVARA A806 ; Case_Ignorable # Mn SYLOTI NAGRI SIGN HASANTA @@ -3350,7 +3381,8 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 10D69..10D6D ; Case_Ignorable # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK 10D6F ; Case_Ignorable # Lm GARAY REDUPLICATION MARK 10EAB..10EAC ; Case_Ignorable # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK -10EFC..10EFF ; Case_Ignorable # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; Case_Ignorable # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EFA..10EFF ; Case_Ignorable # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F46..10F50 ; Case_Ignorable # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F82..10F85 ; Case_Ignorable # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW 11001 ; Case_Ignorable # Mn BRAHMI SIGN ANUSVARA @@ -3427,6 +3459,9 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 11A59..11A5B ; Case_Ignorable # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK 11A8A..11A96 ; Case_Ignorable # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA 11A98..11A99 ; Case_Ignorable # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11B60 ; Case_Ignorable # Mn SHARADA VOWEL SIGN OE +11B62..11B64 ; Case_Ignorable # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B66 ; Case_Ignorable # Mn SHARADA VOWEL SIGN CANDRA E 11C30..11C36 ; Case_Ignorable # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L 11C38..11C3D ; Case_Ignorable # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA 11C3F ; Case_Ignorable # Mn BHAIKSUKI SIGN VIRAMA @@ -3442,6 +3477,7 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 11D90..11D91 ; Case_Ignorable # Mn [2] GUNJALA GONDI VOWEL SIGN EE..GUNJALA GONDI VOWEL SIGN AI 11D95 ; Case_Ignorable # Mn GUNJALA GONDI SIGN ANUSVARA 11D97 ; Case_Ignorable # Mn GUNJALA GONDI VIRAMA +11DD9 ; Case_Ignorable # Lm TOLONG SIKI SIGN SELA 11EF3..11EF4 ; Case_Ignorable # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11F00..11F01 ; Case_Ignorable # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA 11F36..11F3A ; Case_Ignorable # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R @@ -3464,6 +3500,7 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 16FE0..16FE1 ; Case_Ignorable # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; Case_Ignorable # Lm OLD CHINESE ITERATION MARK 16FE4 ; Case_Ignorable # Mn KHITAN SMALL SCRIPT FILLER +16FF2..16FF3 ; Case_Ignorable # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER 1AFF0..1AFF3 ; Case_Ignorable # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; Case_Ignorable # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; Case_Ignorable # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -3497,6 +3534,11 @@ FFF9..FFFB ; Case_Ignorable # Cf [3] INTERLINEAR ANNOTATION ANCHOR..INTERLI 1E4EB ; Case_Ignorable # Lm NAG MUNDARI SIGN OJOD 1E4EC..1E4EF ; Case_Ignorable # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH 1E5EE..1E5EF ; Case_Ignorable # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR +1E6E3 ; Case_Ignorable # Mn TAI YO SIGN UE +1E6E6 ; Case_Ignorable # Mn TAI YO SIGN AU +1E6EE..1E6EF ; Case_Ignorable # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F5 ; Case_Ignorable # Mn TAI YO SIGN OM +1E6FF ; Case_Ignorable # Lm TAI YO XAM LAI 1E8D0..1E8D6 ; Case_Ignorable # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS 1E944..1E94A ; Case_Ignorable # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA 1E94B ; Case_Ignorable # Lm ADLAM NASALIZATION MARK @@ -3505,13 +3547,14 @@ E0001 ; Case_Ignorable # Cf LANGUAGE TAG E0020..E007F ; Case_Ignorable # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF ; Case_Ignorable # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 2749 +# Total code points: 2794 # ================================================ # Derived Property: Changes_When_Lowercased (CWL) # Characters whose normalized forms are not stable under a toLowercase mapping. -# For more information, see D139 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isLowercase(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Lowercased(X) is true when toLowercase(toNFD(X)) != toNFD(X) 0041..005A ; Changes_When_Lowercased # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z @@ -4110,7 +4153,10 @@ A7C2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER ANGLICAN A7C4..A7C7 ; Changes_When_Lowercased # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB..A7CC ; Changes_When_Lowercased # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER DOUBLE THORN +A7D4 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER DOUBLE WYNN A7D6 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8 ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER SIGMOID S A7DA ; Changes_When_Lowercased # L& LATIN CAPITAL LETTER LAMBDA @@ -4127,15 +4173,17 @@ FF21..FF3A ; Changes_When_Lowercased # L& [26] FULLWIDTH LATIN CAPITAL LETTE 10D50..10D65 ; Changes_When_Lowercased # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA 118A0..118BF ; Changes_When_Lowercased # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO 16E40..16E5F ; Changes_When_Lowercased # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +16EA0..16EB8 ; Changes_When_Lowercased # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY 1E900..1E921 ; Changes_When_Lowercased # L& [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA -# Total code points: 1460 +# Total code points: 1488 # ================================================ # Derived Property: Changes_When_Uppercased (CWU) # Characters whose normalized forms are not stable under a toUppercase mapping. -# For more information, see D140 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isUppercase(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Uppercased(X) is true when toUppercase(toNFD(X)) != toNFD(X) 0061..007A ; Changes_When_Uppercased # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z @@ -4747,7 +4795,10 @@ A7C3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER ANGLICANA A7C8 ; Changes_When_Uppercased # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY A7CA ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY A7CD ; Changes_When_Uppercased # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE +A7CF ; Changes_When_Uppercased # L& LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE A7D1 ; Changes_When_Uppercased # L& LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; Changes_When_Uppercased # L& LATIN SMALL LETTER DOUBLE THORN +A7D5 ; Changes_When_Uppercased # L& LATIN SMALL LETTER DOUBLE WYNN A7D7 ; Changes_When_Uppercased # L& LATIN SMALL LETTER MIDDLE SCOTS S A7D9 ; Changes_When_Uppercased # L& LATIN SMALL LETTER SIGMOID S A7DB ; Changes_When_Uppercased # L& LATIN SMALL LETTER LAMBDA @@ -4767,15 +4818,17 @@ FF41..FF5A ; Changes_When_Uppercased # L& [26] FULLWIDTH LATIN SMALL LETTER 10D70..10D85 ; Changes_When_Uppercased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118C0..118DF ; Changes_When_Uppercased # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E60..16E7F ; Changes_When_Uppercased # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EBB..16ED3 ; Changes_When_Uppercased # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1E922..1E943 ; Changes_When_Uppercased # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 1552 +# Total code points: 1580 # ================================================ # Derived Property: Changes_When_Titlecased (CWT) # Characters whose normalized forms are not stable under a toTitlecase mapping. -# For more information, see D141 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isTitlecase(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Titlecased(X) is true when toTitlecase(toNFD(X)) != toNFD(X) 0061..007A ; Changes_When_Titlecased # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z @@ -5386,7 +5439,10 @@ A7C3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER ANGLICANA A7C8 ; Changes_When_Titlecased # L& LATIN SMALL LETTER D WITH SHORT STROKE OVERLAY A7CA ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY A7CD ; Changes_When_Titlecased # L& LATIN SMALL LETTER S WITH DIAGONAL STROKE +A7CF ; Changes_When_Titlecased # L& LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE A7D1 ; Changes_When_Titlecased # L& LATIN SMALL LETTER CLOSED INSULAR G +A7D3 ; Changes_When_Titlecased # L& LATIN SMALL LETTER DOUBLE THORN +A7D5 ; Changes_When_Titlecased # L& LATIN SMALL LETTER DOUBLE WYNN A7D7 ; Changes_When_Titlecased # L& LATIN SMALL LETTER MIDDLE SCOTS S A7D9 ; Changes_When_Titlecased # L& LATIN SMALL LETTER SIGMOID S A7DB ; Changes_When_Titlecased # L& LATIN SMALL LETTER LAMBDA @@ -5406,15 +5462,17 @@ FF41..FF5A ; Changes_When_Titlecased # L& [26] FULLWIDTH LATIN SMALL LETTER 10D70..10D85 ; Changes_When_Titlecased # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118C0..118DF ; Changes_When_Titlecased # L& [32] WARANG CITI SMALL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E60..16E7F ; Changes_When_Titlecased # L& [32] MEDEFAIDRIN SMALL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EBB..16ED3 ; Changes_When_Titlecased # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1E922..1E943 ; Changes_When_Titlecased # L& [34] ADLAM SMALL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 1479 +# Total code points: 1507 # ================================================ # Derived Property: Changes_When_Casefolded (CWCF) # Characters whose normalized forms are not stable under case folding. -# For more information, see D142 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isCasefolded(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Casefolded(X) is true when toCasefold(toNFD(X)) != toNFD(X) 0041..005A ; Changes_When_Casefolded # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z @@ -6022,7 +6080,10 @@ A7C2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER ANGLICAN A7C4..A7C7 ; Changes_When_Casefolded # L& [4] LATIN CAPITAL LETTER C WITH PALATAL HOOK..LATIN CAPITAL LETTER D WITH SHORT STROKE OVERLAY A7C9 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER S WITH SHORT STROKE OVERLAY A7CB..A7CC ; Changes_When_Casefolded # L& [2] LATIN CAPITAL LETTER RAMS HORN..LATIN CAPITAL LETTER S WITH DIAGONAL STROKE +A7CE ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE A7D0 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER CLOSED INSULAR G +A7D2 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER DOUBLE THORN +A7D4 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER DOUBLE WYNN A7D6 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER MIDDLE SCOTS S A7D8 ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER SIGMOID S A7DA ; Changes_When_Casefolded # L& LATIN CAPITAL LETTER LAMBDA @@ -6042,15 +6103,17 @@ FF21..FF3A ; Changes_When_Casefolded # L& [26] FULLWIDTH LATIN CAPITAL LETTE 10D50..10D65 ; Changes_When_Casefolded # L& [22] GARAY CAPITAL LETTER A..GARAY CAPITAL LETTER OLD NA 118A0..118BF ; Changes_When_Casefolded # L& [32] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI CAPITAL LETTER VIYO 16E40..16E5F ; Changes_When_Casefolded # L& [32] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN CAPITAL LETTER Y +16EA0..16EB8 ; Changes_When_Casefolded # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY 1E900..1E921 ; Changes_When_Casefolded # L& [34] ADLAM CAPITAL LETTER ALIF..ADLAM CAPITAL LETTER SHA -# Total code points: 1533 +# Total code points: 1561 # ================================================ # Derived Property: Changes_When_Casemapped (CWCM) # Characters whose normalized forms are not stable under case mapping. -# For more information, see D143 in Section 3.13, "Default Case Algorithms". +# For more information, see the definition of "isCased(X)" +# in the "Conformance" / "Default Case Algorithms" section of the core specification. # Changes_When_Casemapped(X) is true when CWL(X), or CWT(X), or CWU(X) 0041..005A ; Changes_When_Casemapped # L& [26] LATIN CAPITAL LETTER A..LATIN CAPITAL LETTER Z @@ -6156,9 +6219,7 @@ A779..A787 ; Changes_When_Casemapped # L& [15] LATIN CAPITAL LETTER INSULAR A78B..A78D ; Changes_When_Casemapped # L& [3] LATIN CAPITAL LETTER SALTILLO..LATIN CAPITAL LETTER TURNED H A790..A794 ; Changes_When_Casemapped # L& [5] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER C WITH PALATAL HOOK A796..A7AE ; Changes_When_Casemapped # L& [25] LATIN CAPITAL LETTER B WITH FLOURISH..LATIN CAPITAL LETTER SMALL CAPITAL I -A7B0..A7CD ; Changes_When_Casemapped # L& [30] LATIN CAPITAL LETTER TURNED K..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D6..A7DC ; Changes_When_Casemapped # L& [7] LATIN CAPITAL LETTER MIDDLE SCOTS S..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7B0..A7DC ; Changes_When_Casemapped # L& [45] LATIN CAPITAL LETTER TURNED K..LATIN CAPITAL LETTER LAMBDA WITH STROKE A7F5..A7F6 ; Changes_When_Casemapped # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H AB53 ; Changes_When_Casemapped # L& LATIN SMALL LETTER CHI AB70..ABBF ; Changes_When_Casemapped # L& [80] CHEROKEE SMALL LETTER A..CHEROKEE SMALL LETTER YA @@ -6183,9 +6244,11 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 10D70..10D85 ; Changes_When_Casemapped # L& [22] GARAY SMALL LETTER A..GARAY SMALL LETTER OLD NA 118A0..118DF ; Changes_When_Casemapped # L& [64] WARANG CITI CAPITAL LETTER NGAA..WARANG CITI SMALL LETTER VIYO 16E40..16E7F ; Changes_When_Casemapped # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; Changes_When_Casemapped # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Changes_When_Casemapped # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 1E900..1E943 ; Changes_When_Casemapped # L& [68] ADLAM CAPITAL LETTER ALIF..ADLAM SMALL LETTER SHA -# Total code points: 2981 +# Total code points: 3037 # ================================================ @@ -6210,8 +6273,8 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 01BC..01BF ; ID_Start # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; ID_Start # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; ID_Start # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; ID_Start # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; ID_Start # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; ID_Start # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; ID_Start # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; ID_Start # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; ID_Start # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; ID_Start # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -6259,7 +6322,7 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 0840..0858 ; ID_Start # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; ID_Start # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; ID_Start # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; ID_Start # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; ID_Start # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 08A0..08C8 ; ID_Start # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; ID_Start # Lm ARABIC SMALL FARSI YEH 0904..0939 ; ID_Start # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA @@ -6327,7 +6390,7 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 0C2A..0C39 ; ID_Start # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA 0C3D ; ID_Start # Lo TELUGU SIGN AVAGRAHA 0C58..0C5A ; ID_Start # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; ID_Start # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; ID_Start # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; ID_Start # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C80 ; ID_Start # Lo KANNADA SIGN SPACING CANDRABINDU 0C85..0C8C ; ID_Start # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L @@ -6336,7 +6399,7 @@ FF41..FF5A ; Changes_When_Casemapped # L& [26] FULLWIDTH LATIN SMALL LETTER 0CAA..0CB3 ; ID_Start # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA 0CB5..0CB9 ; ID_Start # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA 0CBD ; ID_Start # Lo KANNADA SIGN AVAGRAHA -0CDD..0CDE ; ID_Start # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; ID_Start # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; ID_Start # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CF1..0CF2 ; ID_Start # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D04..0D0C ; ID_Start # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L @@ -6561,11 +6624,8 @@ A771..A787 ; ID_Start # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER I A788 ; ID_Start # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; ID_Start # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; ID_Start # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; ID_Start # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; ID_Start # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; ID_Start # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; ID_Start # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; ID_Start # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; ID_Start # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; ID_Start # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; ID_Start # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; ID_Start # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; ID_Start # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -6702,6 +6762,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 108F4..108F5 ; ID_Start # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; ID_Start # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; ID_Start # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; ID_Start # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; ID_Start # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; ID_Start # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; ID_Start # Lo KHAROSHTHI LETTER A @@ -6729,6 +6790,8 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 10E80..10EA9 ; ID_Start # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET 10EB0..10EB1 ; ID_Start # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; ID_Start # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW +10EC5 ; ID_Start # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; ID_Start # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW 10F00..10F1C ; ID_Start # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; ID_Start # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; ID_Start # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -6821,6 +6884,9 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 11D67..11D68 ; ID_Start # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI 11D6A..11D89 ; ID_Start # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA 11D98 ; ID_Start # Lo GUNJALA GONDI OM +11DB0..11DD8 ; ID_Start # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; ID_Start # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; ID_Start # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; ID_Start # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11F02 ; ID_Start # Lo KAWI SIGN REPHA 11F04..11F10 ; ID_Start # Lo [13] KAWI LETTER A..KAWI LETTER O @@ -6847,14 +6913,18 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 16D43..16D6A ; ID_Start # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; ID_Start # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; ID_Start # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; ID_Start # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; ID_Start # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; ID_Start # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F50 ; ID_Start # Lo MIAO LETTER NASALIZATION 16F93..16F9F ; ID_Start # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 16FE0..16FE1 ; ID_Start # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; ID_Start # Lm OLD CHINESE ITERATION MARK -17000..187F7 ; ID_Start # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; ID_Start # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; ID_Start # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; ID_Start # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; ID_Start # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; ID_Start # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; ID_Start # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; ID_Start # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; ID_Start # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; ID_Start # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; ID_Start # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -6912,6 +6982,13 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1E4EB ; ID_Start # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; ID_Start # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; ID_Start # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; ID_Start # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; ID_Start # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E4..1E6E5 ; ID_Start # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E7..1E6ED ; ID_Start # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6F0..1E6F4 ; ID_Start # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6FE ; ID_Start # Lo TAI YO SYMBOL MUEANG +1E6FF ; ID_Start # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; ID_Start # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; ID_Start # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; ID_Start # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -6953,16 +7030,15 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1EEA5..1EEA9 ; ID_Start # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH 1EEAB..1EEBB ; ID_Start # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 20000..2A6DF ; ID_Start # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; ID_Start # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; ID_Start # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; ID_Start # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; ID_Start # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; ID_Start # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; ID_Start # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; ID_Start # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; ID_Start # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; ID_Start # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; ID_Start # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; ID_Start # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 141269 +# Total code points: 145916 # ================================================ @@ -6991,8 +7067,8 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 01BC..01BF ; ID_Continue # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; ID_Continue # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; ID_Continue # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; ID_Continue # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; ID_Continue # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; ID_Continue # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; ID_Continue # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; ID_Continue # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; ID_Continue # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; ID_Continue # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -7068,7 +7144,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 0859..085B ; ID_Continue # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK 0860..086A ; ID_Continue # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; ID_Continue # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; ID_Continue # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; ID_Continue # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0897..089F ; ID_Continue # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA 08A0..08C8 ; ID_Continue # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; ID_Continue # Lm ARABIC SMALL FARSI YEH @@ -7218,7 +7294,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 0C4A..0C4D ; ID_Continue # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA 0C55..0C56 ; ID_Continue # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; ID_Continue # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; ID_Continue # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; ID_Continue # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; ID_Continue # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; ID_Continue # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C66..0C6F ; ID_Continue # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE @@ -7240,7 +7316,7 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 0CCA..0CCB ; ID_Continue # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC..0CCD ; ID_Continue # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA 0CD5..0CD6 ; ID_Continue # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; ID_Continue # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; ID_Continue # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; ID_Continue # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; ID_Continue # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; ID_Continue # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE @@ -7457,7 +7533,8 @@ FFDA..FFDC ; ID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1A90..1A99 ; ID_Continue # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE 1AA7 ; ID_Continue # Lm TAI THAM SIGN MAI YAMOK 1AB0..1ABD ; ID_Continue # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW -1ABF..1ACE ; ID_Continue # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; ID_Continue # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; ID_Continue # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; ID_Continue # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B04 ; ID_Continue # Mc BALINESE SIGN BISAH 1B05..1B33 ; ID_Continue # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA @@ -7646,11 +7723,8 @@ A771..A787 ; ID_Continue # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTE A788 ; ID_Continue # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; ID_Continue # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; ID_Continue # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; ID_Continue # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; ID_Continue # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; ID_Continue # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; ID_Continue # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; ID_Continue # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; ID_Continue # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; ID_Continue # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; ID_Continue # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; ID_Continue # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; ID_Continue # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -7857,6 +7931,7 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 108F4..108F5 ; ID_Continue # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; ID_Continue # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; ID_Continue # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; ID_Continue # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; ID_Continue # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; ID_Continue # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; ID_Continue # Lo KHAROSHTHI LETTER A @@ -7895,7 +7970,9 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 10EAB..10EAC ; ID_Continue # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK 10EB0..10EB1 ; ID_Continue # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; ID_Continue # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC..10EFF ; ID_Continue # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; ID_Continue # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; ID_Continue # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10EFA..10EFF ; ID_Continue # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F00..10F1C ; ID_Continue # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; ID_Continue # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; ID_Continue # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -8122,6 +8199,12 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 11A98..11A99 ; ID_Continue # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER 11A9D ; ID_Continue # Lo SOYOMBO MARK PLUTA 11AB0..11AF8 ; ID_Continue # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11B60 ; ID_Continue # Mn SHARADA VOWEL SIGN OE +11B61 ; ID_Continue # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; ID_Continue # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; ID_Continue # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; ID_Continue # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; ID_Continue # Mc SHARADA VOWEL SIGN CANDRA O 11BC0..11BE0 ; ID_Continue # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO 11BF0..11BF9 ; ID_Continue # Nd [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE 11C00..11C08 ; ID_Continue # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L @@ -8162,6 +8245,10 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 11D97 ; ID_Continue # Mn GUNJALA GONDI VIRAMA 11D98 ; ID_Continue # Lo GUNJALA GONDI OM 11DA0..11DA9 ; ID_Continue # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11DB0..11DD8 ; ID_Continue # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; ID_Continue # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; ID_Continue # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA +11DE0..11DE9 ; ID_Continue # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE 11EE0..11EF2 ; ID_Continue # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11EF3..11EF4 ; ID_Continue # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6 ; ID_Continue # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O @@ -8212,6 +8299,8 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 16D6B..16D6C ; ID_Continue # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16D70..16D79 ; ID_Continue # Nd [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE 16E40..16E7F ; ID_Continue # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; ID_Continue # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; ID_Continue # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; ID_Continue # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F4F ; ID_Continue # Mn MIAO SIGN CONSONANT MODIFIER BAR 16F50 ; ID_Continue # Lo MIAO LETTER NASALIZATION @@ -8222,9 +8311,11 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 16FE3 ; ID_Continue # Lm OLD CHINESE ITERATION MARK 16FE4 ; ID_Continue # Mn KHITAN SMALL SCRIPT FILLER 16FF0..16FF1 ; ID_Continue # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY -17000..187F7 ; ID_Continue # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; ID_Continue # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; ID_Continue # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; ID_Continue # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; ID_Continue # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; ID_Continue # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; ID_Continue # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; ID_Continue # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; ID_Continue # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; ID_Continue # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; ID_Continue # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -8315,6 +8406,17 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 1E5EE..1E5EF ; ID_Continue # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR 1E5F0 ; ID_Continue # Lo OL ONAL SIGN HODDOND 1E5F1..1E5FA ; ID_Continue # Nd [10] OL ONAL DIGIT ZERO..OL ONAL DIGIT NINE +1E6C0..1E6DE ; ID_Continue # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; ID_Continue # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; ID_Continue # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; ID_Continue # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; ID_Continue # Mn TAI YO SIGN AU +1E6E7..1E6ED ; ID_Continue # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; ID_Continue # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; ID_Continue # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; ID_Continue # Mn TAI YO SIGN OM +1E6FE ; ID_Continue # Lo TAI YO SYMBOL MUEANG +1E6FF ; ID_Continue # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; ID_Continue # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; ID_Continue # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; ID_Continue # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -8360,17 +8462,16 @@ FFDA..FFDC ; ID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HAN 1EEAB..1EEBB ; ID_Continue # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1FBF0..1FBF9 ; ID_Continue # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE 20000..2A6DF ; ID_Continue # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; ID_Continue # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; ID_Continue # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; ID_Continue # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; ID_Continue # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; ID_Continue # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; ID_Continue # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; ID_Continue # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; ID_Continue # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; ID_Continue # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; ID_Continue # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; ID_Continue # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 144541 +# Total code points: 149240 # ================================================ @@ -8393,8 +8494,8 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 01BC..01BF ; XID_Start # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; XID_Start # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; XID_Start # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; XID_Start # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; XID_Start # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; XID_Start # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; XID_Start # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; XID_Start # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; XID_Start # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; XID_Start # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -8441,7 +8542,7 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 0840..0858 ; XID_Start # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; XID_Start # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; XID_Start # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; XID_Start # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; XID_Start # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 08A0..08C8 ; XID_Start # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; XID_Start # Lm ARABIC SMALL FARSI YEH 0904..0939 ; XID_Start # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA @@ -8509,7 +8610,7 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 0C2A..0C39 ; XID_Start # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA 0C3D ; XID_Start # Lo TELUGU SIGN AVAGRAHA 0C58..0C5A ; XID_Start # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; XID_Start # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; XID_Start # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; XID_Start # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C80 ; XID_Start # Lo KANNADA SIGN SPACING CANDRABINDU 0C85..0C8C ; XID_Start # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L @@ -8518,7 +8619,7 @@ E0100..E01EF ; ID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR 0CAA..0CB3 ; XID_Start # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA 0CB5..0CB9 ; XID_Start # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA 0CBD ; XID_Start # Lo KANNADA SIGN AVAGRAHA -0CDD..0CDE ; XID_Start # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; XID_Start # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; XID_Start # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CF1..0CF2 ; XID_Start # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D04..0D0C ; XID_Start # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L @@ -8742,11 +8843,8 @@ A771..A787 ; XID_Start # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER A788 ; XID_Start # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; XID_Start # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; XID_Start # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; XID_Start # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; XID_Start # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; XID_Start # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; XID_Start # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; XID_Start # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; XID_Start # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; XID_Start # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; XID_Start # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; XID_Start # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; XID_Start # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -8888,6 +8986,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 108F4..108F5 ; XID_Start # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; XID_Start # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; XID_Start # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; XID_Start # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; XID_Start # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; XID_Start # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; XID_Start # Lo KHAROSHTHI LETTER A @@ -8915,6 +9014,8 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 10E80..10EA9 ; XID_Start # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET 10EB0..10EB1 ; XID_Start # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; XID_Start # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW +10EC5 ; XID_Start # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; XID_Start # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW 10F00..10F1C ; XID_Start # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; XID_Start # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; XID_Start # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -9007,6 +9108,9 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 11D67..11D68 ; XID_Start # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI 11D6A..11D89 ; XID_Start # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA 11D98 ; XID_Start # Lo GUNJALA GONDI OM +11DB0..11DD8 ; XID_Start # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; XID_Start # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; XID_Start # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; XID_Start # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11F02 ; XID_Start # Lo KAWI SIGN REPHA 11F04..11F10 ; XID_Start # Lo [13] KAWI LETTER A..KAWI LETTER O @@ -9033,14 +9137,18 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 16D43..16D6A ; XID_Start # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; XID_Start # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; XID_Start # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; XID_Start # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; XID_Start # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; XID_Start # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F50 ; XID_Start # Lo MIAO LETTER NASALIZATION 16F93..16F9F ; XID_Start # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 16FE0..16FE1 ; XID_Start # Lm [2] TANGUT ITERATION MARK..NUSHU ITERATION MARK 16FE3 ; XID_Start # Lm OLD CHINESE ITERATION MARK -17000..187F7 ; XID_Start # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; XID_Start # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; XID_Start # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; XID_Start # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; XID_Start # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; XID_Start # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; XID_Start # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; XID_Start # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; XID_Start # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; XID_Start # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; XID_Start # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -9098,6 +9206,13 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 1E4EB ; XID_Start # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; XID_Start # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; XID_Start # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; XID_Start # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; XID_Start # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E4..1E6E5 ; XID_Start # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E7..1E6ED ; XID_Start # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6F0..1E6F4 ; XID_Start # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6FE ; XID_Start # Lo TAI YO SYMBOL MUEANG +1E6FF ; XID_Start # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; XID_Start # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; XID_Start # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; XID_Start # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -9139,16 +9254,15 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 1EEA5..1EEA9 ; XID_Start # Lo [5] ARABIC MATHEMATICAL DOUBLE-STRUCK WAW..ARABIC MATHEMATICAL DOUBLE-STRUCK YEH 1EEAB..1EEBB ; XID_Start # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 20000..2A6DF ; XID_Start # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; XID_Start # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; XID_Start # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; XID_Start # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; XID_Start # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; XID_Start # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; XID_Start # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; XID_Start # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; XID_Start # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; XID_Start # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; XID_Start # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; XID_Start # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 141246 +# Total code points: 145893 # ================================================ @@ -9174,8 +9288,8 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 01BC..01BF ; XID_Continue # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; XID_Continue # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; XID_Continue # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; XID_Continue # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; XID_Continue # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; XID_Continue # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; XID_Continue # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; XID_Continue # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C6..02D1 ; XID_Continue # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON 02E0..02E4 ; XID_Continue # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP @@ -9250,7 +9364,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 0859..085B ; XID_Continue # Mn [3] MANDAIC AFFRICATION MARK..MANDAIC GEMINATION MARK 0860..086A ; XID_Continue # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; XID_Continue # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; XID_Continue # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; XID_Continue # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0897..089F ; XID_Continue # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA 08A0..08C8 ; XID_Continue # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; XID_Continue # Lm ARABIC SMALL FARSI YEH @@ -9400,7 +9514,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 0C4A..0C4D ; XID_Continue # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA 0C55..0C56 ; XID_Continue # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; XID_Continue # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; XID_Continue # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; XID_Continue # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; XID_Continue # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; XID_Continue # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C66..0C6F ; XID_Continue # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE @@ -9422,7 +9536,7 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 0CCA..0CCB ; XID_Continue # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC..0CCD ; XID_Continue # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA 0CD5..0CD6 ; XID_Continue # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; XID_Continue # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; XID_Continue # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; XID_Continue # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; XID_Continue # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; XID_Continue # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE @@ -9639,7 +9753,8 @@ FFDA..FFDC ; XID_Start # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGU 1A90..1A99 ; XID_Continue # Nd [10] TAI THAM THAM DIGIT ZERO..TAI THAM THAM DIGIT NINE 1AA7 ; XID_Continue # Lm TAI THAM SIGN MAI YAMOK 1AB0..1ABD ; XID_Continue # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW -1ABF..1ACE ; XID_Continue # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; XID_Continue # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; XID_Continue # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; XID_Continue # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B04 ; XID_Continue # Mc BALINESE SIGN BISAH 1B05..1B33 ; XID_Continue # Lo [47] BALINESE LETTER AKARA..BALINESE LETTER HA @@ -9827,11 +9942,8 @@ A771..A787 ; XID_Continue # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETT A788 ; XID_Continue # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A78B..A78E ; XID_Continue # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; XID_Continue # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; XID_Continue # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; XID_Continue # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; XID_Continue # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; XID_Continue # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; XID_Continue # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; XID_Continue # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; XID_Continue # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; XID_Continue # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; XID_Continue # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; XID_Continue # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -10044,6 +10156,7 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 108F4..108F5 ; XID_Continue # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; XID_Continue # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; XID_Continue # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; XID_Continue # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; XID_Continue # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; XID_Continue # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; XID_Continue # Lo KHAROSHTHI LETTER A @@ -10082,7 +10195,9 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 10EAB..10EAC ; XID_Continue # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK 10EB0..10EB1 ; XID_Continue # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; XID_Continue # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC..10EFF ; XID_Continue # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; XID_Continue # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; XID_Continue # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10EFA..10EFF ; XID_Continue # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F00..10F1C ; XID_Continue # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; XID_Continue # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; XID_Continue # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -10309,6 +10424,12 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 11A98..11A99 ; XID_Continue # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER 11A9D ; XID_Continue # Lo SOYOMBO MARK PLUTA 11AB0..11AF8 ; XID_Continue # Lo [73] CANADIAN SYLLABICS NATTILIK HI..PAU CIN HAU GLOTTAL STOP FINAL +11B60 ; XID_Continue # Mn SHARADA VOWEL SIGN OE +11B61 ; XID_Continue # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; XID_Continue # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; XID_Continue # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; XID_Continue # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; XID_Continue # Mc SHARADA VOWEL SIGN CANDRA O 11BC0..11BE0 ; XID_Continue # Lo [33] SUNUWAR LETTER DEVI..SUNUWAR LETTER KLOKO 11BF0..11BF9 ; XID_Continue # Nd [10] SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE 11C00..11C08 ; XID_Continue # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L @@ -10349,6 +10470,10 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 11D97 ; XID_Continue # Mn GUNJALA GONDI VIRAMA 11D98 ; XID_Continue # Lo GUNJALA GONDI OM 11DA0..11DA9 ; XID_Continue # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11DB0..11DD8 ; XID_Continue # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; XID_Continue # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; XID_Continue # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA +11DE0..11DE9 ; XID_Continue # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE 11EE0..11EF2 ; XID_Continue # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11EF3..11EF4 ; XID_Continue # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6 ; XID_Continue # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O @@ -10399,6 +10524,8 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 16D6B..16D6C ; XID_Continue # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16D70..16D79 ; XID_Continue # Nd [10] KIRAT RAI DIGIT ZERO..KIRAT RAI DIGIT NINE 16E40..16E7F ; XID_Continue # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; XID_Continue # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; XID_Continue # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; XID_Continue # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F4F ; XID_Continue # Mn MIAO SIGN CONSONANT MODIFIER BAR 16F50 ; XID_Continue # Lo MIAO LETTER NASALIZATION @@ -10409,9 +10536,11 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 16FE3 ; XID_Continue # Lm OLD CHINESE ITERATION MARK 16FE4 ; XID_Continue # Mn KHITAN SMALL SCRIPT FILLER 16FF0..16FF1 ; XID_Continue # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY -17000..187F7 ; XID_Continue # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18CD5 ; XID_Continue # Lo [1238] TANGUT COMPONENT-001..KHITAN SMALL SCRIPT CHARACTER-18CD5 -18CFF..18D08 ; XID_Continue # Lo [10] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D08 +16FF2..16FF3 ; XID_Continue # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; XID_Continue # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS +17000..18CD5 ; XID_Continue # Lo [7382] TANGUT IDEOGRAPH-17000..KHITAN SMALL SCRIPT CHARACTER-18CD5 +18CFF..18D1E ; XID_Continue # Lo [32] KHITAN SMALL SCRIPT CHARACTER-18CFF..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; XID_Continue # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 1AFF0..1AFF3 ; XID_Continue # Lm [4] KATAKANA LETTER MINNAN TONE-2..KATAKANA LETTER MINNAN TONE-5 1AFF5..1AFFB ; XID_Continue # Lm [7] KATAKANA LETTER MINNAN TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-5 1AFFD..1AFFE ; XID_Continue # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 @@ -10502,6 +10631,17 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 1E5EE..1E5EF ; XID_Continue # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR 1E5F0 ; XID_Continue # Lo OL ONAL SIGN HODDOND 1E5F1..1E5FA ; XID_Continue # Nd [10] OL ONAL DIGIT ZERO..OL ONAL DIGIT NINE +1E6C0..1E6DE ; XID_Continue # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; XID_Continue # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; XID_Continue # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; XID_Continue # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; XID_Continue # Mn TAI YO SIGN AU +1E6E7..1E6ED ; XID_Continue # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; XID_Continue # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; XID_Continue # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; XID_Continue # Mn TAI YO SIGN OM +1E6FE ; XID_Continue # Lo TAI YO SYMBOL MUEANG +1E6FF ; XID_Continue # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; XID_Continue # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; XID_Continue # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; XID_Continue # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -10547,17 +10687,16 @@ FFDA..FFDC ; XID_Continue # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HA 1EEAB..1EEBB ; XID_Continue # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1FBF0..1FBF9 ; XID_Continue # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE 20000..2A6DF ; XID_Continue # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; XID_Continue # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; XID_Continue # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; XID_Continue # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; XID_Continue # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; XID_Continue # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; XID_Continue # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; XID_Continue # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; XID_Continue # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; XID_Continue # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; XID_Continue # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; XID_Continue # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 E0100..E01EF ; XID_Continue # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 144522 +# Total code points: 149221 # ================================================ @@ -10778,7 +10917,8 @@ E01F0..E0FFF ; Default_Ignorable_Code_Point # Cn [3600] .... -2B76..2B95 ; Pattern_Syntax # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW -2B96 ; Pattern_Syntax # Cn -2B97..2BFF ; Pattern_Syntax # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2B76..2BFF ; Pattern_Syntax # So [138] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..HELLSCHREIBER PAUSE SYMBOL 2E00..2E01 ; Pattern_Syntax # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER 2E02 ; Pattern_Syntax # Pi LEFT SUBSTITUTION BRACKET 2E03 ; Pattern_Syntax # Pf RIGHT SUBSTITUTION BRACKET diff --git a/src/java.base/share/data/unicodedata/PropertyValueAliases.txt b/src/java.base/share/data/unicodedata/PropertyValueAliases.txt index 97980e2d902..b92662eda28 100644 --- a/src/java.base/share/data/unicodedata/PropertyValueAliases.txt +++ b/src/java.base/share/data/unicodedata/PropertyValueAliases.txt @@ -1,6 +1,6 @@ -# PropertyValueAliases-16.0.0.txt -# Date: 2024-07-30, 19:59:00 GMT -# Copyright (c) 2024 Unicode, Inc. +# PropertyValueAliases-17.0.0.txt +# Date: 2025-06-30, 06:16:21 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -93,6 +93,7 @@ age; 14.0 ; V14_0 age; 15.0 ; V15_0 age; 15.1 ; V15_1 age; 16.0 ; V16_0 +age; 17.0 ; V17_0 age; NA ; Unassigned # Alphabetic (Alpha) @@ -179,6 +180,7 @@ blk; Bamum_Sup ; Bamum_Supplement blk; Bassa_Vah ; Bassa_Vah blk; Batak ; Batak blk; Bengali ; Bengali +blk; Beria_Erfe ; Beria_Erfe blk; Bhaiksuki ; Bhaiksuki blk; Block_Elements ; Block_Elements blk; Bopomofo ; Bopomofo @@ -211,6 +213,7 @@ blk; CJK_Ext_F ; CJK_Unified_Ideographs_Extension_F blk; CJK_Ext_G ; CJK_Unified_Ideographs_Extension_G blk; CJK_Ext_H ; CJK_Unified_Ideographs_Extension_H blk; CJK_Ext_I ; CJK_Unified_Ideographs_Extension_I +blk; CJK_Ext_J ; CJK_Unified_Ideographs_Extension_J blk; CJK_Radicals_Sup ; CJK_Radicals_Supplement blk; CJK_Strokes ; CJK_Strokes blk; CJK_Symbols ; CJK_Symbols_And_Punctuation @@ -360,6 +363,7 @@ blk; Misc_Math_Symbols_A ; Miscellaneous_Mathematical_Symbols_A blk; Misc_Math_Symbols_B ; Miscellaneous_Mathematical_Symbols_B blk; Misc_Pictographs ; Miscellaneous_Symbols_And_Pictographs blk; Misc_Symbols ; Miscellaneous_Symbols +blk; Misc_Symbols_Sup ; Miscellaneous_Symbols_Supplement blk; Misc_Technical ; Miscellaneous_Technical blk; Modi ; Modi blk; Modifier_Letters ; Spacing_Modifier_Letters @@ -419,9 +423,11 @@ blk; Runic ; Runic blk; Samaritan ; Samaritan blk; Saurashtra ; Saurashtra blk; Sharada ; Sharada +blk; Sharada_Sup ; Sharada_Supplement blk; Shavian ; Shavian blk; Shorthand_Format_Controls ; Shorthand_Format_Controls blk; Siddham ; Siddham +blk; Sidetic ; Sidetic blk; Sinhala ; Sinhala blk; Sinhala_Archaic_Numbers ; Sinhala_Archaic_Numbers blk; Small_Forms ; Small_Form_Variants @@ -456,12 +462,14 @@ blk; Tai_Le ; Tai_Le blk; Tai_Tham ; Tai_Tham blk; Tai_Viet ; Tai_Viet blk; Tai_Xuan_Jing ; Tai_Xuan_Jing_Symbols +blk; Tai_Yo ; Tai_Yo blk; Takri ; Takri blk; Tamil ; Tamil blk; Tamil_Sup ; Tamil_Supplement blk; Tangsa ; Tangsa blk; Tangut ; Tangut blk; Tangut_Components ; Tangut_Components +blk; Tangut_Components_Sup ; Tangut_Components_Supplement blk; Tangut_Sup ; Tangut_Supplement blk; Telugu ; Telugu blk; Thaana ; Thaana @@ -470,6 +478,7 @@ blk; Tibetan ; Tibetan blk; Tifinagh ; Tifinagh blk; Tirhuta ; Tirhuta blk; Todhri ; Todhri +blk; Tolong_Siki ; Tolong_Siki blk; Toto ; Toto blk; Transport_And_Map ; Transport_And_Map_Symbols blk; Tulu_Tigalari ; Tulu_Tigalari @@ -878,7 +887,7 @@ InPC; Bottom_And_Left ; Bottom_And_Left InPC; Bottom_And_Right ; Bottom_And_Right InPC; Left ; Left InPC; Left_And_Right ; Left_And_Right -InPC; NA ; NA +InPC; NA ; Not_Applicable InPC; Overstruck ; Overstruck InPC; Right ; Right InPC; Top ; Top @@ -1088,6 +1097,7 @@ jg ; Taw ; Taw jg ; Teh_Marbuta ; Teh_Marbuta jg ; Teh_Marbuta_Goal ; Teh_Marbuta_Goal ; Hamza_On_Heh_Goal jg ; Teth ; Teth +jg ; Thin_Noon ; Thin_Noon jg ; Thin_Yeh ; Thin_Yeh jg ; Vertical_Tail ; Vertical_Tail jg ; Waw ; Waw @@ -1131,6 +1141,7 @@ lb ; EX ; Exclamation lb ; GL ; Glue lb ; H2 ; H2 lb ; H3 ; H3 +lb ; HH ; Unambiguous_Hyphen lb ; HL ; Hebrew_Letter lb ; HY ; Hyphen lb ; ID ; Ideographic @@ -1319,6 +1330,7 @@ sc ; Bamu ; Bamum sc ; Bass ; Bassa_Vah sc ; Batk ; Batak sc ; Beng ; Bengali +sc ; Berf ; Beria_Erfe sc ; Bhks ; Bhaiksuki sc ; Bopo ; Bopomofo sc ; Brah ; Brahmi @@ -1438,6 +1450,7 @@ sc ; Sgnw ; SignWriting sc ; Shaw ; Shavian sc ; Shrd ; Sharada sc ; Sidd ; Siddham +sc ; Sidt ; Sidetic sc ; Sind ; Khudawadi sc ; Sinh ; Sinhala sc ; Sogd ; Sogdian @@ -1455,6 +1468,7 @@ sc ; Talu ; New_Tai_Lue sc ; Taml ; Tamil sc ; Tang ; Tangut sc ; Tavt ; Tai_Viet +sc ; Tayo ; Tai_Yo sc ; Telu ; Telugu sc ; Tfng ; Tifinagh sc ; Tglg ; Tagalog @@ -1464,6 +1478,7 @@ sc ; Tibt ; Tibetan sc ; Tirh ; Tirhuta sc ; Tnsa ; Tangsa sc ; Todr ; Todhri +sc ; Tols ; Tolong_Siki sc ; Toto ; Toto sc ; Tutg ; Tulu_Tigalari sc ; Ugar ; Ugaritic @@ -1705,4 +1720,16 @@ kEH_NoMirror; Y ; Yes ; T kEH_NoRotate; N ; No ; F ; False kEH_NoRotate; Y ; Yes ; T ; True +# kMandarin (cjkMandarin) + +# @missing: 0000..10FFFF; kMandarin; + +# kTotalStrokes (cjkTotalStrokes) + +# @missing: 0000..10FFFF; kTotalStrokes; + +# kUnihanCore2020 (cjkUnihanCore2020) + +# @missing: 0000..10FFFF; kUnihanCore2020; + # EOF diff --git a/src/java.base/share/data/unicodedata/ReadMe.txt b/src/java.base/share/data/unicodedata/ReadMe.txt index 81d489a0575..268f62b8a93 100644 --- a/src/java.base/share/data/unicodedata/ReadMe.txt +++ b/src/java.base/share/data/unicodedata/ReadMe.txt @@ -1,17 +1,24 @@ # Unicode Character Database -# Date: 2024-08-25 -# Copyright (c) 2024 Unicode, Inc. +# Date: 2025-08-15 +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # For documentation, see the following: -# NamesList.html +# ucd/NamesList.html # UAX #38, "Unicode Han Database (Unihan)" +# UAX #42, "Unicode Character Database in XML" # UAX #44, "Unicode Character Database" # UTS #51, "Unicode Emoji" # UAX #57, "Unicode Egyptian Hieroglyph Database" # -# The UAXes and UTS #51 can be accessed at https://www.unicode.org/versions/Unicode16.0.0/ +# The UAXes and UTS #51 can be accessed at https://www.unicode.org/versions/Unicode17.0.0/ -This directory contains final data files -for the Unicode Character Database, for Version 16.0.0 of the Unicode Standard. +This directory contains the final data files +for Version 17.0.0 of the Unicode Standard. + +The "charts" subdirectory contains an archival set of +pdf code charts corresponding exactly to Version 17.0.0. + +The other subdirectories contain the data files for the +Unicode Character Database and for the synchronized Unicode Technical Standards. diff --git a/src/java.base/share/data/unicodedata/Scripts.txt b/src/java.base/share/data/unicodedata/Scripts.txt index 14d48d94162..5574fdd6ae3 100644 --- a/src/java.base/share/data/unicodedata/Scripts.txt +++ b/src/java.base/share/data/unicodedata/Scripts.txt @@ -1,6 +1,6 @@ -# Scripts-16.0.0.txt -# Date: 2024-04-30, 21:48:40 GMT -# Copyright (c) 2024 Unicode, Inc. +# Scripts-17.0.0.txt +# Date: 2025-07-24, 13:28:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -154,7 +154,7 @@ 208A..208C ; Common # Sm [3] SUBSCRIPT PLUS SIGN..SUBSCRIPT EQUALS SIGN 208D ; Common # Ps SUBSCRIPT LEFT PARENTHESIS 208E ; Common # Pe SUBSCRIPT RIGHT PARENTHESIS -20A0..20C0 ; Common # Sc [33] EURO-CURRENCY SIGN..SOM SIGN +20A0..20C1 ; Common # Sc [34] EURO-CURRENCY SIGN..SAUDI RIYAL SIGN 2100..2101 ; Common # So [2] ACCOUNT OF..ADDRESSED TO THE SUBJECT 2102 ; Common # L& DOUBLE-STRUCK CAPITAL C 2103..2106 ; Common # So [4] DEGREE CELSIUS..CADA UNA @@ -306,8 +306,7 @@ 2B45..2B46 ; Common # So [2] LEFTWARDS QUADRUPLE ARROW..RIGHTWARDS QUADRUPLE ARROW 2B47..2B4C ; Common # Sm [6] REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW..RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR 2B4D..2B73 ; Common # So [39] DOWNWARDS TRIANGLE-HEADED ZIGZAG ARROW..DOWNWARDS TRIANGLE-HEADED ARROW TO BAR -2B76..2B95 ; Common # So [32] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..RIGHTWARDS BLACK ARROW -2B97..2BFF ; Common # So [105] SYMBOL FOR TYPE A ELECTRONICS..HELLSCHREIBER PAUSE SYMBOL +2B76..2BFF ; Common # So [138] NORTH WEST TRIANGLE-HEADED ARROW TO BAR..HELLSCHREIBER PAUSE SYMBOL 2E00..2E01 ; Common # Po [2] RIGHT ANGLE SUBSTITUTION MARKER..RIGHT ANGLE DOTTED SUBSTITUTION MARKER 2E02 ; Common # Pi LEFT SUBSTITUTION BRACKET 2E03 ; Common # Pf RIGHT SUBSTITUTION BRACKET @@ -524,7 +523,11 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1BCA0..1BCA3 ; Common # Cf [4] SHORTHAND FORMAT LETTER OVERLAP..SHORTHAND FORMAT UP STEP 1CC00..1CCEF ; Common # So [240] UP-POINTING GO-KART..OUTLINED LATIN CAPITAL LETTER Z 1CCF0..1CCF9 ; Common # Nd [10] OUTLINED DIGIT ZERO..OUTLINED DIGIT NINE +1CCFA..1CCFC ; Common # So [3] SNAKE SYMBOL..NOSE SYMBOL 1CD00..1CEB3 ; Common # So [436] BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET +1CEBA..1CED0 ; Common # So [23] FRAGILE SYMBOL..LEUKOTHEA +1CEE0..1CEEF ; Common # So [16] GEOMANTIC FIGURE POPULUS..GEOMANTIC FIGURE VIA +1CEF0 ; Common # Sm MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR 1CF50..1CFC3 ; Common # So [116] ZNAMENNY NEUME KRYUK..ZNAMENNY NEUME PAUK 1D000..1D0F5 ; Common # So [246] BYZANTINE MUSICAL SYMBOL PSILI..BYZANTINE MUSICAL SYMBOL GORGON NEO KATO 1D100..1D126 ; Common # So [39] MUSICAL SYMBOL SINGLE BARLINE..MUSICAL SYMBOL DRUM CLEF-2 @@ -605,11 +608,10 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1F260..1F265 ; Common # So [6] ROUNDED SYMBOL FOR FU..ROUNDED SYMBOL FOR CAI 1F300..1F3FA ; Common # So [251] CYCLONE..AMPHORA 1F3FB..1F3FF ; Common # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 -1F400..1F6D7 ; Common # So [728] RAT..ELEVATOR +1F400..1F6D8 ; Common # So [729] RAT..LANDSLIDE 1F6DC..1F6EC ; Common # So [17] WIRELESS..AIRPLANE ARRIVING 1F6F0..1F6FC ; Common # So [13] SATELLITE..ROLLER SKATE -1F700..1F776 ; Common # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE -1F77B..1F7D9 ; Common # So [95] HAUMEA..NINE POINTED WHITE STAR +1F700..1F7D9 ; Common # So [218] ALCHEMICAL SYMBOL FOR QUINTESSENCE..NINE POINTED WHITE STAR 1F7E0..1F7EB ; Common # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE 1F7F0 ; Common # So HEAVY EQUALS SIGN 1F800..1F80B ; Common # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD @@ -619,21 +621,24 @@ FFFC..FFFD ; Common # So [2] OBJECT REPLACEMENT CHARACTER..REPLACEMENT CHAR 1F890..1F8AD ; Common # So [30] LEFTWARDS TRIANGLE ARROWHEAD..WHITE ARROW SHAFT WIDTH TWO THIRDS 1F8B0..1F8BB ; Common # So [12] ARROW POINTING UPWARDS THEN NORTH WEST..SOUTH WEST ARROW FROM BAR 1F8C0..1F8C1 ; Common # So [2] LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW -1F900..1FA53 ; Common # So [340] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS KNIGHT-BISHOP +1F8D0..1F8D8 ; Common # Sm [9] LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW..LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE +1F900..1FA57 ; Common # So [344] CIRCLED CROSS FORMEE WITH FOUR DOTS..BLACK CHESS ALFIL 1FA60..1FA6D ; Common # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER 1FA70..1FA7C ; Common # So [13] BALLET SHOES..CRUTCH -1FA80..1FA89 ; Common # So [10] YO-YO..HARP -1FA8F..1FAC6 ; Common # So [56] SHOVEL..FINGERPRINT -1FACE..1FADC ; Common # So [15] MOOSE..ROOT VEGETABLE -1FADF..1FAE9 ; Common # So [11] SPLATTER..FACE WITH BAGS UNDER EYES -1FAF0..1FAF8 ; Common # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND +1FA80..1FA8A ; Common # So [11] YO-YO..TROMBONE +1FA8E..1FAC6 ; Common # So [57] TREASURE CHEST..FINGERPRINT +1FAC8 ; Common # So HAIRY CREATURE +1FACD..1FADC ; Common # So [16] ORCA..ROOT VEGETABLE +1FADF..1FAEA ; Common # So [12] SPLATTER..DISTORTED FACE +1FAEF..1FAF8 ; Common # So [10] FIGHT CLOUD..RIGHTWARDS PUSHING HAND 1FB00..1FB92 ; Common # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK 1FB94..1FBEF ; Common # So [92] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE 1FBF0..1FBF9 ; Common # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE +1FBFA ; Common # So ALARM BELL SYMBOL E0001 ; Common # Cf LANGUAGE TAG E0020..E007F ; Common # Cf [96] TAG SPACE..CANCEL TAG -# Total code points: 9053 +# Total code points: 9123 # ================================================ @@ -648,8 +653,8 @@ E0020..E007F ; Common # Cf [96] TAG SPACE..CANCEL TAG 01BC..01BF ; Latin # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; Latin # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; Latin # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; Latin # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; Latin # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; Latin # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; Latin # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02B8 ; Latin # Lm [9] MODIFIER LETTER SMALL H..MODIFIER LETTER SMALL Y 02E0..02E4 ; Latin # Lm [5] MODIFIER LETTER SMALL GAMMA..MODIFIER LETTER SMALL REVERSED GLOTTAL STOP 1D00..1D25 ; Latin # L& [38] LATIN LETTER SMALL CAPITAL A..LATIN LETTER AIN @@ -676,11 +681,8 @@ A770 ; Latin # Lm MODIFIER LETTER US A771..A787 ; Latin # L& [23] LATIN SMALL LETTER DUM..LATIN SMALL LETTER INSULAR T A78B..A78E ; Latin # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; Latin # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; Latin # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; Latin # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; Latin # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; Latin # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; Latin # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; Latin # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; Latin # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; Latin # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; Latin # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; Latin # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -702,7 +704,7 @@ FF41..FF5A ; Latin # L& [26] FULLWIDTH LATIN SMALL LETTER A..FULLWIDTH LATIN 1DF0B..1DF1E ; Latin # L& [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL 1DF25..1DF2A ; Latin # L& [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK -# Total code points: 1487 +# Total code points: 1492 # ================================================ @@ -869,7 +871,7 @@ FB46..FB4F ; Hebrew # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATU 0750..077F ; Arabic # Lo [48] ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS ABOVE 0870..0887 ; Arabic # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT 0888 ; Arabic # Sk ARABIC RAISED ROUND DOT -0889..088E ; Arabic # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; Arabic # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 0890..0891 ; Arabic # Cf [2] ARABIC POUND MARK ABOVE..ARABIC PIASTRE MARK ABOVE 0897..089F ; Arabic # Mn [9] ARABIC PEPET..ARABIC HALF MADDA OVER MADDA 08A0..08C8 ; Arabic # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF @@ -878,11 +880,13 @@ FB46..FB4F ; Hebrew # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW LIGATU 08E3..08FF ; Arabic # Mn [29] ARABIC TURNED DAMMA BELOW..ARABIC MARK SIDEWAYS NOON GHUNNA FB50..FBB1 ; Arabic # Lo [98] ARABIC LETTER ALEF WASLA ISOLATED FORM..ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM FBB2..FBC2 ; Arabic # Sk [17] ARABIC SYMBOL DOT ABOVE..ARABIC SYMBOL WASLA ABOVE +FBC3..FBD2 ; Arabic # So [16] ARABIC LIGATURE JALLA WA-ALAA..ARABIC LIGATURE ALAYHI AR-RAHMAH FBD3..FD3D ; Arabic # Lo [363] ARABIC LETTER NG ISOLATED FORM..ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM FD40..FD4F ; Arabic # So [16] ARABIC LIGATURE RAHIMAHU ALLAAH..ARABIC LIGATURE RAHIMAHUM ALLAAH FD50..FD8F ; Arabic # Lo [64] ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM..ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM +FD90..FD91 ; Arabic # So [2] ARABIC LIGATURE RAHMATU ALLAAHI ALAYH..ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA FD92..FDC7 ; Arabic # Lo [54] ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM..ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM -FDCF ; Arabic # So ARABIC LIGATURE SALAAMUHU ALAYNAA +FDC8..FDCF ; Arabic # So [8] ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA..ARABIC LIGATURE SALAAMUHU ALAYNAA FDF0..FDFB ; Arabic # Lo [12] ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM..ARABIC LIGATURE JALLAJALALOUHOU FDFC ; Arabic # Sc RIAL SIGN FDFD..FDFF ; Arabic # So [3] ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM..ARABIC LIGATURE AZZA WA JALL @@ -890,7 +894,11 @@ FE70..FE74 ; Arabic # Lo [5] ARABIC FATHATAN ISOLATED FORM..ARABIC KASRATAN FE76..FEFC ; Arabic # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LAM WITH ALEF FINAL FORM 10E60..10E7E ; Arabic # No [31] RUMI DIGIT ONE..RUMI FRACTION TWO THIRDS 10EC2..10EC4 ; Arabic # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW -10EFC..10EFF ; Arabic # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EC5 ; Arabic # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; Arabic # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW +10ED0 ; Arabic # Po ARABIC BIBLICAL END OF VERSE +10ED1..10ED8 ; Arabic # So [8] ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM..ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH +10EFA..10EFF ; Arabic # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 1EE00..1EE03 ; Arabic # Lo [4] ARABIC MATHEMATICAL ALEF..ARABIC MATHEMATICAL DAL 1EE05..1EE1F ; Arabic # Lo [27] ARABIC MATHEMATICAL WAW..ARABIC MATHEMATICAL DOTLESS QAF 1EE21..1EE22 ; Arabic # Lo [2] ARABIC MATHEMATICAL INITIAL BEH..ARABIC MATHEMATICAL INITIAL JEEM @@ -926,7 +934,7 @@ FE76..FEFC ; Arabic # Lo [135] ARABIC FATHA ISOLATED FORM..ARABIC LIGATURE LA 1EEAB..1EEBB ; Arabic # Lo [17] ARABIC MATHEMATICAL DOUBLE-STRUCK LAM..ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN 1EEF0..1EEF1 ; Arabic # Sm [2] ARABIC MATHEMATICAL OPERATOR MEEM WITH HAH WITH TATWEEL..ARABIC MATHEMATICAL OPERATOR HAH WITH DAL -# Total code points: 1373 +# Total code points: 1413 # ================================================ @@ -1155,7 +1163,7 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0C4A..0C4D ; Telugu # Mn [4] TELUGU VOWEL SIGN O..TELUGU SIGN VIRAMA 0C55..0C56 ; Telugu # Mn [2] TELUGU LENGTH MARK..TELUGU AI LENGTH MARK 0C58..0C5A ; Telugu # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; Telugu # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; Telugu # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; Telugu # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C62..0C63 ; Telugu # Mn [2] TELUGU VOWEL SIGN VOCALIC L..TELUGU VOWEL SIGN VOCALIC LL 0C66..0C6F ; Telugu # Nd [10] TELUGU DIGIT ZERO..TELUGU DIGIT NINE @@ -1163,7 +1171,7 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0C78..0C7E ; Telugu # No [7] TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR..TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR 0C7F ; Telugu # So TELUGU SIGN TUUMU -# Total code points: 100 +# Total code points: 101 # ================================================ @@ -1186,14 +1194,14 @@ A8FF ; Devanagari # Mn DEVANAGARI VOWEL SIGN AY 0CCA..0CCB ; Kannada # Mc [2] KANNADA VOWEL SIGN O..KANNADA VOWEL SIGN OO 0CCC..0CCD ; Kannada # Mn [2] KANNADA VOWEL SIGN AU..KANNADA SIGN VIRAMA 0CD5..0CD6 ; Kannada # Mc [2] KANNADA LENGTH MARK..KANNADA AI LENGTH MARK -0CDD..0CDE ; Kannada # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; Kannada # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; Kannada # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CE2..0CE3 ; Kannada # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF ; Kannada # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE 0CF1..0CF2 ; Kannada # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0CF3 ; Kannada # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT -# Total code points: 91 +# Total code points: 92 # ================================================ @@ -1594,17 +1602,18 @@ FA70..FAD9 ; Han # Lo [106] CJK COMPATIBILITY IDEOGRAPH-FA70..CJK COMPATIBILI 16FE2 ; Han # Po OLD CHINESE HOOK MARK 16FE3 ; Han # Lm OLD CHINESE ITERATION MARK 16FF0..16FF1 ; Han # Mc [2] VIETNAMESE ALTERNATE READING MARK CA..VIETNAMESE ALTERNATE READING MARK NHAY +16FF2..16FF3 ; Han # Lm [2] CHINESE SMALL SIMPLIFIED ER..CHINESE SMALL TRADITIONAL ER +16FF4..16FF6 ; Han # Nl [3] YANGQIN SIGN SLOW ONE BEAT..YANGQIN SIGN SLOW TWO BEATS 20000..2A6DF ; Han # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF -2A700..2B739 ; Han # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 -2B740..2B81D ; Han # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D -2B820..2CEA1 ; Han # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 +2A700..2B81D ; Han # Lo [4382] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B81D +2B820..2CEAD ; Han # Lo [5774] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEAD 2CEB0..2EBE0 ; Han # Lo [7473] CJK UNIFIED IDEOGRAPH-2CEB0..CJK UNIFIED IDEOGRAPH-2EBE0 2EBF0..2EE5D ; Han # Lo [622] CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D 2F800..2FA1D ; Han # Lo [542] CJK COMPATIBILITY IDEOGRAPH-2F800..CJK COMPATIBILITY IDEOGRAPH-2FA1D 30000..3134A ; Han # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -31350..323AF ; Han # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +31350..33479 ; Han # Lo [8490] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-33479 -# Total code points: 99030 +# Total code points: 103351 # ================================================ @@ -1647,7 +1656,8 @@ A490..A4C6 ; Yi # So [55] YI RADICAL QOT..YI RADICAL KE 0951..0954 ; Inherited # Mn [4] DEVANAGARI STRESS SIGN UDATTA..DEVANAGARI ACUTE ACCENT 1AB0..1ABD ; Inherited # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Inherited # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Inherited # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Inherited # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Inherited # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1CD0..1CD2 ; Inherited # Mn [3] VEDIC TONE KARSHANA..VEDIC TONE PRENKHA 1CD4..1CE0 ; Inherited # Mn [13] VEDIC SIGN YAJURVEDIC MIDLINE SVARITA..VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA 1CE2..1CE8 ; Inherited # Mn [7] VEDIC SIGN VISARGA SVARITA..VEDIC SIGN VISARGA ANUDATTA WITH TAIL @@ -1676,7 +1686,7 @@ FE20..FE2D ; Inherited # Mn [14] COMBINING LIGATURE LEFT HALF..COMBINING CON 1D1AA..1D1AD ; Inherited # Mn [4] MUSICAL SYMBOL COMBINING DOWN BOW..MUSICAL SYMBOL COMBINING SNAP PIZZICATO E0100..E01EF ; Inherited # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 657 +# Total code points: 684 # ================================================ @@ -2347,8 +2357,14 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI 111DB ; Sharada # Po SHARADA SIGN SIDDHAM 111DC ; Sharada # Lo SHARADA HEADSTROKE 111DD..111DF ; Sharada # Po [3] SHARADA CONTINUATION SIGN..SHARADA SECTION MARK-2 +11B60 ; Sharada # Mn SHARADA VOWEL SIGN OE +11B61 ; Sharada # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Sharada # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Sharada # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Sharada # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Sharada # Mc SHARADA VOWEL SIGN CANDRA O -# Total code points: 96 +# Total code points: 104 # ================================================ @@ -2756,11 +2772,11 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI # ================================================ 16FE0 ; Tangut # Lm TANGUT ITERATION MARK -17000..187F7 ; Tangut # Lo [6136] TANGUT IDEOGRAPH-17000..TANGUT IDEOGRAPH-187F7 -18800..18AFF ; Tangut # Lo [768] TANGUT COMPONENT-001..TANGUT COMPONENT-768 -18D00..18D08 ; Tangut # Lo [9] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D08 +17000..18AFF ; Tangut # Lo [6912] TANGUT IDEOGRAPH-17000..TANGUT COMPONENT-768 +18D00..18D1E ; Tangut # Lo [31] TANGUT IDEOGRAPH-18D00..TANGUT IDEOGRAPH-18D1E +18D80..18DF2 ; Tangut # Lo [115] TANGUT COMPONENT-769..TANGUT COMPONENT-883 -# Total code points: 6914 +# Total code points: 7059 # ================================================ @@ -3125,4 +3141,42 @@ ABF0..ABF9 ; Meetei_Mayek # Nd [10] MEETEI MAYEK DIGIT ZERO..MEETEI MAYEK DI # Total code points: 80 +# ================================================ + +10940..10959 ; Sidetic # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 + +# Total code points: 26 + +# ================================================ + +1E6C0..1E6DE ; Tai_Yo # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; Tai_Yo # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E3 ; Tai_Yo # Mn TAI YO SIGN UE +1E6E4..1E6E5 ; Tai_Yo # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E6 ; Tai_Yo # Mn TAI YO SIGN AU +1E6E7..1E6ED ; Tai_Yo # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6EE..1E6EF ; Tai_Yo # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F0..1E6F4 ; Tai_Yo # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6F5 ; Tai_Yo # Mn TAI YO SIGN OM +1E6FE ; Tai_Yo # Lo TAI YO SYMBOL MUEANG +1E6FF ; Tai_Yo # Lm TAI YO XAM LAI + +# Total code points: 55 + +# ================================================ + +11DB0..11DD8 ; Tolong_Siki # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; Tolong_Siki # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; Tolong_Siki # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA +11DE0..11DE9 ; Tolong_Siki # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE + +# Total code points: 54 + +# ================================================ + +16EA0..16EB8 ; Beria_Erfe # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; Beria_Erfe # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY + +# Total code points: 50 + # EOF diff --git a/src/java.base/share/data/unicodedata/SpecialCasing.txt b/src/java.base/share/data/unicodedata/SpecialCasing.txt index 74700b5d321..1013344a6f2 100644 --- a/src/java.base/share/data/unicodedata/SpecialCasing.txt +++ b/src/java.base/share/data/unicodedata/SpecialCasing.txt @@ -1,6 +1,6 @@ -# SpecialCasing-16.0.0.txt -# Date: 2024-05-10, 22:49:00 GMT -# Copyright (c) 2024 Unicode, Inc. +# SpecialCasing-17.0.0.txt +# Date: 2025-07-31, 22:11:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -47,8 +47,8 @@ # # A language ID is defined by BCP 47, with '-' and '_' treated equivalently. # -# A casing context for a character is defined by Section 3.13 Default Case Algorithms -# of The Unicode Standard. +# A casing context for a character is defined in the +# "Conformance" / "Default Case Algorithms" section of the core specification. # # Parsers of this file must be prepared to deal with future additions to this format: # * Additional contexts @@ -57,6 +57,10 @@ # ================================================================================ # Unconditional mappings +# The mappings in this section are not language-sensitive nor context-sensitive. +# +# Note that comments provide additional information but +# do not modify the case mapping algorithms in the core specification, chapter 3. # ================================================================================ # The German es-zed is special--the normal mapping is to SS. diff --git a/src/java.base/share/data/unicodedata/UnicodeData.txt b/src/java.base/share/data/unicodedata/UnicodeData.txt index 64258a37395..fca68e3e154 100644 --- a/src/java.base/share/data/unicodedata/UnicodeData.txt +++ b/src/java.base/share/data/unicodedata/UnicodeData.txt @@ -659,7 +659,7 @@ 0292;LATIN SMALL LETTER EZH;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH;;01B7;;01B7 0293;LATIN SMALL LETTER EZH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH CURL;;;; 0294;LATIN LETTER GLOTTAL STOP;Lo;0;L;;;;;N;;;;; -0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; +0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Lo;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; 0296;LATIN LETTER INVERTED GLOTTAL STOP;Ll;0;L;;;;;N;;;;; 0297;LATIN LETTER STRETCHED C;Ll;0;L;;;;;N;;;;; 0298;LATIN LETTER BILABIAL CLICK;Ll;0;L;;;;;N;LATIN LETTER BULLSEYE;;;; @@ -2121,6 +2121,7 @@ 088C;ARABIC LETTER TAH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 088D;ARABIC LETTER KEHEH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 088E;ARABIC VERTICAL TAIL;Lo;0;AL;;;;;N;;;;; +088F;ARABIC LETTER NOON WITH RING ABOVE;Lo;0;AL;;;;;N;;;;; 0890;ARABIC POUND MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0891;ARABIC PIASTRE MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0897;ARABIC PEPET;Mn;230;NSM;;;;;N;;;;; @@ -2862,6 +2863,7 @@ 0C58;TELUGU LETTER TSA;Lo;0;L;;;;;N;;;;; 0C59;TELUGU LETTER DZA;Lo;0;L;;;;;N;;;;; 0C5A;TELUGU LETTER RRRA;Lo;0;L;;;;;N;;;;; +0C5C;TELUGU ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0C5D;TELUGU LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0C60;TELUGU LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0C61;TELUGU LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; @@ -2958,6 +2960,7 @@ 0CCD;KANNADA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0CD5;KANNADA LENGTH MARK;Mc;0;L;;;;;N;;;;; 0CD6;KANNADA AI LENGTH MARK;Mc;0;L;;;;;N;;;;; +0CDC;KANNADA ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0CDD;KANNADA LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0CDE;KANNADA LETTER FA;Lo;0;L;;;;;N;;;;; 0CE0;KANNADA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; @@ -6137,6 +6140,33 @@ 1ACC;COMBINING LATIN SMALL LETTER INSULAR G;Mn;230;NSM;;;;;N;;;;; 1ACD;COMBINING LATIN SMALL LETTER INSULAR R;Mn;230;NSM;;;;;N;;;;; 1ACE;COMBINING LATIN SMALL LETTER INSULAR T;Mn;230;NSM;;;;;N;;;;; +1ACF;COMBINING DOUBLE CARON;Mn;230;NSM;;;;;N;;;;; +1AD0;COMBINING VERTICAL-LINE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD1;COMBINING GRAVE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD2;COMBINING VERTICAL-LINE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD3;COMBINING ACUTE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD4;COMBINING VERTICAL-LINE-MACRON;Mn;230;NSM;;;;;N;;;;; +1AD5;COMBINING MACRON-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD6;COMBINING VERTICAL-LINE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD7;COMBINING VERTICAL-LINE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD8;COMBINING MACRON-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD9;COMBINING SHARP SIGN;Mn;230;NSM;;;;;N;;;;; +1ADA;COMBINING FLAT SIGN;Mn;230;NSM;;;;;N;;;;; +1ADB;COMBINING DOWN TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1ADC;COMBINING DIAERESIS WITH RAISED LEFT DOT;Mn;230;NSM;;;;;N;;;;; +1ADD;COMBINING DOT-AND-RING BELOW;Mn;220;NSM;;;;;N;;;;; +1AE0;COMBINING LEFT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE1;COMBINING RIGHT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE2;COMBINING MINUS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE3;COMBINING INVERTED BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE4;COMBINING SQUARE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE5;COMBINING SEAGULL ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE6;COMBINING DOUBLE ARCH BELOW;Mn;220;NSM;;;;;N;;;;; +1AE7;COMBINING DOUBLE ARCH ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE8;COMBINING EQUALS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE9;COMBINING LEFT ANGLE CENTRED ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEA;COMBINING UPWARDS ARROW ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEB;COMBINING DOUBLE RIGHTWARDS ARROW ABOVE;Mn;234;NSM;;;;;N;;;;; 1B00;BALINESE SIGN ULU RICEM;Mn;0;NSM;;;;;N;;;;; 1B01;BALINESE SIGN ULU CANDRA;Mn;0;NSM;;;;;N;;;;; 1B02;BALINESE SIGN CECEK;Mn;0;NSM;;;;;N;;;;; @@ -7545,6 +7575,7 @@ 20BE;LARI SIGN;Sc;0;ET;;;;;N;;;;; 20BF;BITCOIN SIGN;Sc;0;ET;;;;;N;;;;; 20C0;SOM SIGN;Sc;0;ET;;;;;N;;;;; +20C1;SAUDI RIYAL SIGN;Sc;0;ET;;;;;N;;;;; 20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;; 20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;; 20D2;COMBINING LONG VERTICAL LINE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG VERTICAL BAR OVERLAY;;;; @@ -10239,6 +10270,7 @@ 2B93;NEWLINE RIGHT;So;0;ON;;;;;N;;;;; 2B94;FOUR CORNER ARROWS CIRCLING ANTICLOCKWISE;So;0;ON;;;;;N;;;;; 2B95;RIGHTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; +2B96;EQUALS SIGN WITH INFINITY ABOVE;So;0;ON;;;;;N;;;;; 2B97;SYMBOL FOR TYPE A ELECTRONICS;So;0;ON;;;;;N;;;;; 2B98;THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B99;THREE-D RIGHT-LIGHTED UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; @@ -14274,10 +14306,14 @@ A7CA;LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY;Ll;0;L;;;;;N;;;A7C9;;A7C9 A7CB;LATIN CAPITAL LETTER RAMS HORN;Lu;0;L;;;;;N;;;;0264; A7CC;LATIN CAPITAL LETTER S WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A7CD; A7CD;LATIN SMALL LETTER S WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A7CC;;A7CC +A7CE;LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE;Lu;0;L;;;;;N;;;;A7CF; +A7CF;LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;;;A7CE;;A7CE A7D0;LATIN CAPITAL LETTER CLOSED INSULAR G;Lu;0;L;;;;;N;;;;A7D1; A7D1;LATIN SMALL LETTER CLOSED INSULAR G;Ll;0;L;;;;;N;;;A7D0;;A7D0 -A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;;; -A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;;; +A7D2;LATIN CAPITAL LETTER DOUBLE THORN;Lu;0;L;;;;;N;;;;A7D3; +A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;A7D2;;A7D2 +A7D4;LATIN CAPITAL LETTER DOUBLE WYNN;Lu;0;L;;;;;N;;;;A7D5; +A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;A7D4;;A7D4 A7D6;LATIN CAPITAL LETTER MIDDLE SCOTS S;Lu;0;L;;;;;N;;;;A7D7; A7D7;LATIN SMALL LETTER MIDDLE SCOTS S;Ll;0;L;;;;;N;;;A7D6;;A7D6 A7D8;LATIN CAPITAL LETTER SIGMOID S;Lu;0;L;;;;;N;;;;A7D9; @@ -14285,6 +14321,7 @@ A7D9;LATIN SMALL LETTER SIGMOID S;Ll;0;L;;;;;N;;;A7D8;;A7D8 A7DA;LATIN CAPITAL LETTER LAMBDA;Lu;0;L;;;;;N;;;;A7DB; A7DB;LATIN SMALL LETTER LAMBDA;Ll;0;L;;;;;N;;;A7DA;;A7DA A7DC;LATIN CAPITAL LETTER LAMBDA WITH STROKE;Lu;0;L;;;;;N;;;;019B; +A7F1;MODIFIER LETTER CAPITAL S;Lm;0;L; 0053;;;;N;;;;; A7F2;MODIFIER LETTER CAPITAL C;Lm;0;L; 0043;;;;N;;;;; A7F3;MODIFIER LETTER CAPITAL F;Lm;0;L; 0046;;;;N;;;;; A7F4;MODIFIER LETTER CAPITAL Q;Lm;0;L; 0051;;;;N;;;;; @@ -15925,6 +15962,22 @@ FBBF;ARABIC SYMBOL RING;Sk;0;AL;;;;;N;;;;; FBC0;ARABIC SYMBOL SMALL TAH ABOVE;Sk;0;AL;;;;;N;;;;; FBC1;ARABIC SYMBOL SMALL TAH BELOW;Sk;0;AL;;;;;N;;;;; FBC2;ARABIC SYMBOL WASLA ABOVE;Sk;0;AL;;;;;N;;;;; +FBC3;ARABIC LIGATURE JALLA WA-ALAA;So;0;ON;;;;;N;;;;; +FBC4;ARABIC LIGATURE DAAMAT BARAKAATUHUM;So;0;ON;;;;;N;;;;; +FBC5;ARABIC LIGATURE RAHMATU ALLAAHI TAAALAA ALAYH;So;0;ON;;;;;N;;;;; +FBC6;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIM;So;0;ON;;;;;N;;;;; +FBC7;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIMAA;So;0;ON;;;;;N;;;;; +FBC8;ARABIC LIGATURE RAHIMAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBC9;ARABIC LIGATURE RAHIMAHUMAA ALLAAH;So;0;ON;;;;;N;;;;; +FBCA;ARABIC LIGATURE RAHIMAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUM;So;0;ON;;;;;N;;;;; +FBCC;ARABIC LIGATURE HAFIZAHU ALLAAH;So;0;ON;;;;;N;;;;; +FBCD;ARABIC LIGATURE HAFIZAHU ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCE;ARABIC LIGATURE HAFIZAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCF;ARABIC LIGATURE HAFIZAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBD0;ARABIC LIGATURE SALLALLAAHU TAAALAA ALAYHI WA-SALLAM;So;0;ON;;;;;N;;;;; +FBD1;ARABIC LIGATURE AJJAL ALLAAHU FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FBD2;ARABIC LIGATURE ALAYHI AR-RAHMAH;So;0;ON;;;;;N;;;;; FBD3;ARABIC LETTER NG ISOLATED FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD4;ARABIC LETTER NG FINAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD5;ARABIC LETTER NG INITIAL FORM;Lo;0;AL; 06AD;;;;N;;;;; @@ -16370,6 +16423,8 @@ FD8C;ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0645 FD8D;ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062C 0645;;;;N;;;;; FD8E;ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062E 062C;;;;N;;;;; FD8F;ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062E 0645;;;;N;;;;; +FD90;ARABIC LIGATURE RAHMATU ALLAAHI ALAYH;So;0;ON;;;;;N;;;;; +FD91;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA;So;0;ON;;;;;N;;;;; FD92;ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0645 062C 062E;;;;N;;;;; FD93;ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0647 0645 062C;;;;N;;;;; FD94;ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0647 0645 0645;;;;N;;;;; @@ -16424,6 +16479,13 @@ FDC4;ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0639 FDC5;ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0635 0645 0645;;;;N;;;;; FDC6;ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0633 062E 064A;;;;N;;;;; FDC7;ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0646 062C 064A;;;;N;;;;; +FDC8;ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA;So;0;ON;;;;;N;;;;; +FDC9;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANH;So;0;ON;;;;;N;;;;; +FDCA;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHAA;So;0;ON;;;;;N;;;;; +FDCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUMAA;So;0;ON;;;;;N;;;;; +FDCC;ARABIC LIGATURE SALLALLAHU ALAYHI WA-ALAA AALIHEE WA-SALLAM;So;0;ON;;;;;N;;;;; +FDCD;ARABIC LIGATURE AJJAL ALLAAHU TAAALAA FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FDCE;ARABIC LIGATURE KARRAMA ALLAAHU WAJHAH;So;0;ON;;;;;N;;;;; FDCF;ARABIC LIGATURE SALAAMUHU ALAYNAA;So;0;ON;;;;;N;;;;; FDF0;ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0635 0644 06D2;;;;N;;;;; FDF1;ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0642 0644 06D2;;;;N;;;;; @@ -18708,6 +18770,32 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10938;LYDIAN LETTER NN;Lo;0;R;;;;;N;;;;; 10939;LYDIAN LETTER C;Lo;0;R;;;;;N;;;;; 1093F;LYDIAN TRIANGULAR MARK;Po;0;R;;;;;N;;;;; +10940;SIDETIC LETTER N01;Lo;0;R;;;;;N;;;;; +10941;SIDETIC LETTER N02;Lo;0;R;;;;;N;;;;; +10942;SIDETIC LETTER N03;Lo;0;R;;;;;N;;;;; +10943;SIDETIC LETTER N04;Lo;0;R;;;;;N;;;;; +10944;SIDETIC LETTER N05;Lo;0;R;;;;;N;;;;; +10945;SIDETIC LETTER N06;Lo;0;R;;;;;N;;;;; +10946;SIDETIC LETTER N07;Lo;0;R;;;;;N;;;;; +10947;SIDETIC LETTER N08;Lo;0;R;;;;;N;;;;; +10948;SIDETIC LETTER N09;Lo;0;R;;;;;N;;;;; +10949;SIDETIC LETTER N10;Lo;0;R;;;;;N;;;;; +1094A;SIDETIC LETTER N11;Lo;0;R;;;;;N;;;;; +1094B;SIDETIC LETTER N12;Lo;0;R;;;;;N;;;;; +1094C;SIDETIC LETTER N13;Lo;0;R;;;;;N;;;;; +1094D;SIDETIC LETTER N14;Lo;0;R;;;;;N;;;;; +1094E;SIDETIC LETTER N15;Lo;0;R;;;;;N;;;;; +1094F;SIDETIC LETTER N16;Lo;0;R;;;;;N;;;;; +10950;SIDETIC LETTER N17;Lo;0;R;;;;;N;;;;; +10951;SIDETIC LETTER N18;Lo;0;R;;;;;N;;;;; +10952;SIDETIC LETTER N19;Lo;0;R;;;;;N;;;;; +10953;SIDETIC LETTER N20;Lo;0;R;;;;;N;;;;; +10954;SIDETIC LETTER N21;Lo;0;R;;;;;N;;;;; +10955;SIDETIC LETTER N22;Lo;0;R;;;;;N;;;;; +10956;SIDETIC LETTER N23;Lo;0;R;;;;;N;;;;; +10957;SIDETIC LETTER N24;Lo;0;R;;;;;N;;;;; +10958;SIDETIC LETTER N25;Lo;0;R;;;;;N;;;;; +10959;SIDETIC LETTER N26;Lo;0;R;;;;;N;;;;; 10980;MEROITIC HIEROGLYPHIC LETTER A;Lo;0;R;;;;;N;;;;; 10981;MEROITIC HIEROGLYPHIC LETTER E;Lo;0;R;;;;;N;;;;; 10982;MEROITIC HIEROGLYPHIC LETTER I;Lo;0;R;;;;;N;;;;; @@ -19541,6 +19629,20 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10EC2;ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC3;ARABIC LETTER TAH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC4;ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; +10EC5;ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW;Lm;0;AL;;;;;N;;;;; +10EC6;ARABIC LETTER THIN NOON;Lo;0;AL;;;;;N;;;;; +10EC7;ARABIC LETTER YEH WITH FOUR DOTS BELOW;Lo;0;AL;;;;;N;;;;; +10ED0;ARABIC BIBLICAL END OF VERSE;Po;0;ON;;;;;N;;;;; +10ED1;ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED2;ARABIC LIGATURE ALAYHIM AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED3;ARABIC LIGATURE ALAYHIMAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED4;ARABIC LIGATURE QADDASA ALLAAHU SIRRAH;So;0;ON;;;;;N;;;;; +10ED5;ARABIC LIGATURE QUDDISA SIRRUHUM;So;0;ON;;;;;N;;;;; +10ED6;ARABIC LIGATURE QUDDISA SIRRUHUMAA;So;0;ON;;;;;N;;;;; +10ED7;ARABIC LIGATURE QUDDISAT ASRAARUHUM;So;0;ON;;;;;N;;;;; +10ED8;ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH;So;0;ON;;;;;N;;;;; +10EFA;ARABIC DOUBLE VERTICAL BAR BELOW;Mn;220;NSM;;;;;N;;;;; +10EFB;ARABIC SMALL LOW NOON;Mn;220;NSM;;;;;N;;;;; 10EFC;ARABIC COMBINING ALEF OVERLAY;Mn;0;NSM;;;;;N;;;;; 10EFD;ARABIC SMALL LOW WORD SAKTA;Mn;220;NSM;;;;;N;;;;; 10EFE;ARABIC SMALL LOW WORD QASR;Mn;220;NSM;;;;;N;;;;; @@ -21521,6 +21623,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11B07;DEVANAGARI SIGN WESTERN NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B08;DEVANAGARI SIGN REVERSED NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B09;DEVANAGARI SIGN MINDU;Po;0;L;;;;;N;;;;; +11B60;SHARADA VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;; +11B61;SHARADA VOWEL SIGN OOE;Mc;0;L;;;;;N;;;;; +11B62;SHARADA VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; +11B63;SHARADA VOWEL SIGN UUE;Mn;0;NSM;;;;;N;;;;; +11B64;SHARADA VOWEL SIGN SHORT E;Mn;0;NSM;;;;;N;;;;; +11B65;SHARADA VOWEL SIGN SHORT O;Mc;0;L;;;;;N;;;;; +11B66;SHARADA VOWEL SIGN CANDRA E;Mn;0;NSM;;;;;N;;;;; +11B67;SHARADA VOWEL SIGN CANDRA O;Mc;0;L;;;;;N;;;;; 11BC0;SUNUWAR LETTER DEVI;Lo;0;L;;;;;N;;;;; 11BC1;SUNUWAR LETTER TASLA;Lo;0;L;;;;;N;;;;; 11BC2;SUNUWAR LETTER EKO;Lo;0;L;;;;;N;;;;; @@ -21868,6 +21978,60 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11DA7;GUNJALA GONDI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11DA8;GUNJALA GONDI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11DA9;GUNJALA GONDI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; +11DB0;TOLONG SIKI LETTER I;Lo;0;L;;;;;N;;;;; +11DB1;TOLONG SIKI LETTER E;Lo;0;L;;;;;N;;;;; +11DB2;TOLONG SIKI LETTER U;Lo;0;L;;;;;N;;;;; +11DB3;TOLONG SIKI LETTER O;Lo;0;L;;;;;N;;;;; +11DB4;TOLONG SIKI LETTER A;Lo;0;L;;;;;N;;;;; +11DB5;TOLONG SIKI LETTER AA;Lo;0;L;;;;;N;;;;; +11DB6;TOLONG SIKI LETTER P;Lo;0;L;;;;;N;;;;; +11DB7;TOLONG SIKI LETTER PH;Lo;0;L;;;;;N;;;;; +11DB8;TOLONG SIKI LETTER B;Lo;0;L;;;;;N;;;;; +11DB9;TOLONG SIKI LETTER BH;Lo;0;L;;;;;N;;;;; +11DBA;TOLONG SIKI LETTER M;Lo;0;L;;;;;N;;;;; +11DBB;TOLONG SIKI LETTER T;Lo;0;L;;;;;N;;;;; +11DBC;TOLONG SIKI LETTER TH;Lo;0;L;;;;;N;;;;; +11DBD;TOLONG SIKI LETTER D;Lo;0;L;;;;;N;;;;; +11DBE;TOLONG SIKI LETTER DH;Lo;0;L;;;;;N;;;;; +11DBF;TOLONG SIKI LETTER N;Lo;0;L;;;;;N;;;;; +11DC0;TOLONG SIKI LETTER TT;Lo;0;L;;;;;N;;;;; +11DC1;TOLONG SIKI LETTER TTH;Lo;0;L;;;;;N;;;;; +11DC2;TOLONG SIKI LETTER DD;Lo;0;L;;;;;N;;;;; +11DC3;TOLONG SIKI LETTER DDH;Lo;0;L;;;;;N;;;;; +11DC4;TOLONG SIKI LETTER NN;Lo;0;L;;;;;N;;;;; +11DC5;TOLONG SIKI LETTER C;Lo;0;L;;;;;N;;;;; +11DC6;TOLONG SIKI LETTER CH;Lo;0;L;;;;;N;;;;; +11DC7;TOLONG SIKI LETTER J;Lo;0;L;;;;;N;;;;; +11DC8;TOLONG SIKI LETTER JH;Lo;0;L;;;;;N;;;;; +11DC9;TOLONG SIKI LETTER NY;Lo;0;L;;;;;N;;;;; +11DCA;TOLONG SIKI LETTER K;Lo;0;L;;;;;N;;;;; +11DCB;TOLONG SIKI LETTER KH;Lo;0;L;;;;;N;;;;; +11DCC;TOLONG SIKI LETTER G;Lo;0;L;;;;;N;;;;; +11DCD;TOLONG SIKI LETTER GH;Lo;0;L;;;;;N;;;;; +11DCE;TOLONG SIKI LETTER NG;Lo;0;L;;;;;N;;;;; +11DCF;TOLONG SIKI LETTER Y;Lo;0;L;;;;;N;;;;; +11DD0;TOLONG SIKI LETTER R;Lo;0;L;;;;;N;;;;; +11DD1;TOLONG SIKI LETTER L;Lo;0;L;;;;;N;;;;; +11DD2;TOLONG SIKI LETTER V;Lo;0;L;;;;;N;;;;; +11DD3;TOLONG SIKI LETTER NNY;Lo;0;L;;;;;N;;;;; +11DD4;TOLONG SIKI LETTER S;Lo;0;L;;;;;N;;;;; +11DD5;TOLONG SIKI LETTER H;Lo;0;L;;;;;N;;;;; +11DD6;TOLONG SIKI LETTER X;Lo;0;L;;;;;N;;;;; +11DD7;TOLONG SIKI LETTER RR;Lo;0;L;;;;;N;;;;; +11DD8;TOLONG SIKI LETTER RRH;Lo;0;L;;;;;N;;;;; +11DD9;TOLONG SIKI SIGN SELA;Lm;0;L;;;;;N;;;;; +11DDA;TOLONG SIKI SIGN HECAKA;Lo;0;L;;;;;N;;;;; +11DDB;TOLONG SIKI UNGGA;Lo;0;L;;;;;N;;;;; +11DE0;TOLONG SIKI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +11DE1;TOLONG SIKI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +11DE2;TOLONG SIKI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +11DE3;TOLONG SIKI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +11DE4;TOLONG SIKI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +11DE5;TOLONG SIKI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +11DE6;TOLONG SIKI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +11DE7;TOLONG SIKI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +11DE8;TOLONG SIKI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +11DE9;TOLONG SIKI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11EE0;MAKASAR LETTER KA;Lo;0;L;;;;;N;;;;; 11EE1;MAKASAR LETTER GA;Lo;0;L;;;;;N;;;;; 11EE2;MAKASAR LETTER NGA;Lo;0;L;;;;;N;;;;; @@ -22088,8 +22252,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12035;CUNEIFORM SIGN ARAD TIMES KUR;Lo;0;L;;;;;N;;;;; 12036;CUNEIFORM SIGN ARKAB;Lo;0;L;;;;;N;;;;; 12037;CUNEIFORM SIGN ASAL2;Lo;0;L;;;;;N;;;;; -12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;;N;;;;; -12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;;N;;;;; +12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;1;N;;;;; +12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;1;N;;;;; 1203A;CUNEIFORM SIGN ASH KABA TENU;Lo;0;L;;;;;N;;;;; 1203B;CUNEIFORM SIGN ASH OVER ASH TUG2 OVER TUG2 TUG2 OVER TUG2 PAP;Lo;0;L;;;;;N;;;;; 1203C;CUNEIFORM SIGN ASH OVER ASH OVER ASH;Lo;0;L;;;;;N;;;;; @@ -22153,7 +22317,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12076;CUNEIFORM SIGN DIM2;Lo;0;L;;;;;N;;;;; 12077;CUNEIFORM SIGN DIN;Lo;0;L;;;;;N;;;;; 12078;CUNEIFORM SIGN DIN KASKAL U GUNU DISH;Lo;0;L;;;;;N;;;;; -12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;;N;;;;; +12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;1;N;;;;; 1207A;CUNEIFORM SIGN DU;Lo;0;L;;;;;N;;;;; 1207B;CUNEIFORM SIGN DU OVER DU;Lo;0;L;;;;;N;;;;; 1207C;CUNEIFORM SIGN DU GUNU;Lo;0;L;;;;;N;;;;; @@ -22582,12 +22746,12 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12223;CUNEIFORM SIGN MA2;Lo;0;L;;;;;N;;;;; 12224;CUNEIFORM SIGN MAH;Lo;0;L;;;;;N;;;;; 12225;CUNEIFORM SIGN MAR;Lo;0;L;;;;;N;;;;; -12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;;N;;;;; +12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;1/2;N;;;;; 12227;CUNEIFORM SIGN MASH2;Lo;0;L;;;;;N;;;;; 12228;CUNEIFORM SIGN ME;Lo;0;L;;;;;N;;;;; 12229;CUNEIFORM SIGN MES;Lo;0;L;;;;;N;;;;; 1222A;CUNEIFORM SIGN MI;Lo;0;L;;;;;N;;;;; -1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;;N;;;;; +1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;2;N;;;;; 1222C;CUNEIFORM SIGN MU;Lo;0;L;;;;;N;;;;; 1222D;CUNEIFORM SIGN MU OVER MU;Lo;0;L;;;;;N;;;;; 1222E;CUNEIFORM SIGN MUG;Lo;0;L;;;;;N;;;;; @@ -22811,9 +22975,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12308;CUNEIFORM SIGN TUM;Lo;0;L;;;;;N;;;;; 12309;CUNEIFORM SIGN TUR;Lo;0;L;;;;;N;;;;; 1230A;CUNEIFORM SIGN TUR OVER TUR ZA OVER ZA;Lo;0;L;;;;;N;;;;; -1230B;CUNEIFORM SIGN U;Lo;0;L;;;;;N;;;;; +1230B;CUNEIFORM SIGN U;Lo;0;L;;;;1;N;;;;; 1230C;CUNEIFORM SIGN U GUD;Lo;0;L;;;;;N;;;;; -1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;;N;;;;; +1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;3;N;;;;; 1230E;CUNEIFORM SIGN U OVER U PA OVER PA GAR OVER GAR;Lo;0;L;;;;;N;;;;; 1230F;CUNEIFORM SIGN U OVER U SUR OVER SUR;Lo;0;L;;;;;N;;;;; 12310;CUNEIFORM SIGN U OVER U U REVERSED OVER U REVERSED;Lo;0;L;;;;;N;;;;; @@ -22953,7 +23117,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12396;CUNEIFORM SIGN SAG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12397;CUNEIFORM SIGN TI2;Lo;0;L;;;;;N;;;;; 12398;CUNEIFORM SIGN UM TIMES ME;Lo;0;L;;;;;N;;;;; -12399;CUNEIFORM SIGN U U;Lo;0;L;;;;;N;;;;; +12399;CUNEIFORM SIGN U U;Lo;0;L;;;;2;N;;;;; 12400;CUNEIFORM NUMERIC SIGN TWO ASH;Nl;0;L;;;;2;N;;;;; 12401;CUNEIFORM NUMERIC SIGN THREE ASH;Nl;0;L;;;;3;N;;;;; 12402;CUNEIFORM NUMERIC SIGN FOUR ASH;Nl;0;L;;;;4;N;;;;; @@ -30124,6 +30288,56 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16E98;MEDEFAIDRIN FULL STOP;Po;0;L;;;;;N;;;;; 16E99;MEDEFAIDRIN SYMBOL AIVA;Po;0;L;;;;;N;;;;; 16E9A;MEDEFAIDRIN EXCLAMATION OH;Po;0;L;;;;;N;;;;; +16EA0;BERIA ERFE CAPITAL LETTER ARKAB;Lu;0;L;;;;;N;;;;16EBB; +16EA1;BERIA ERFE CAPITAL LETTER BASIGNA;Lu;0;L;;;;;N;;;;16EBC; +16EA2;BERIA ERFE CAPITAL LETTER DARBAI;Lu;0;L;;;;;N;;;;16EBD; +16EA3;BERIA ERFE CAPITAL LETTER EH;Lu;0;L;;;;;N;;;;16EBE; +16EA4;BERIA ERFE CAPITAL LETTER FITKO;Lu;0;L;;;;;N;;;;16EBF; +16EA5;BERIA ERFE CAPITAL LETTER GOWAY;Lu;0;L;;;;;N;;;;16EC0; +16EA6;BERIA ERFE CAPITAL LETTER HIRDEABO;Lu;0;L;;;;;N;;;;16EC1; +16EA7;BERIA ERFE CAPITAL LETTER I;Lu;0;L;;;;;N;;;;16EC2; +16EA8;BERIA ERFE CAPITAL LETTER DJAI;Lu;0;L;;;;;N;;;;16EC3; +16EA9;BERIA ERFE CAPITAL LETTER KOBO;Lu;0;L;;;;;N;;;;16EC4; +16EAA;BERIA ERFE CAPITAL LETTER LAKKO;Lu;0;L;;;;;N;;;;16EC5; +16EAB;BERIA ERFE CAPITAL LETTER MERI;Lu;0;L;;;;;N;;;;16EC6; +16EAC;BERIA ERFE CAPITAL LETTER NINI;Lu;0;L;;;;;N;;;;16EC7; +16EAD;BERIA ERFE CAPITAL LETTER GNA;Lu;0;L;;;;;N;;;;16EC8; +16EAE;BERIA ERFE CAPITAL LETTER NGAY;Lu;0;L;;;;;N;;;;16EC9; +16EAF;BERIA ERFE CAPITAL LETTER OI;Lu;0;L;;;;;N;;;;16ECA; +16EB0;BERIA ERFE CAPITAL LETTER PI;Lu;0;L;;;;;N;;;;16ECB; +16EB1;BERIA ERFE CAPITAL LETTER ERIGO;Lu;0;L;;;;;N;;;;16ECC; +16EB2;BERIA ERFE CAPITAL LETTER ERIGO TAMURA;Lu;0;L;;;;;N;;;;16ECD; +16EB3;BERIA ERFE CAPITAL LETTER SERI;Lu;0;L;;;;;N;;;;16ECE; +16EB4;BERIA ERFE CAPITAL LETTER SHEP;Lu;0;L;;;;;N;;;;16ECF; +16EB5;BERIA ERFE CAPITAL LETTER TATASOUE;Lu;0;L;;;;;N;;;;16ED0; +16EB6;BERIA ERFE CAPITAL LETTER UI;Lu;0;L;;;;;N;;;;16ED1; +16EB7;BERIA ERFE CAPITAL LETTER WASSE;Lu;0;L;;;;;N;;;;16ED2; +16EB8;BERIA ERFE CAPITAL LETTER AY;Lu;0;L;;;;;N;;;;16ED3; +16EBB;BERIA ERFE SMALL LETTER ARKAB;Ll;0;L;;;;;N;;;16EA0;;16EA0 +16EBC;BERIA ERFE SMALL LETTER BASIGNA;Ll;0;L;;;;;N;;;16EA1;;16EA1 +16EBD;BERIA ERFE SMALL LETTER DARBAI;Ll;0;L;;;;;N;;;16EA2;;16EA2 +16EBE;BERIA ERFE SMALL LETTER EH;Ll;0;L;;;;;N;;;16EA3;;16EA3 +16EBF;BERIA ERFE SMALL LETTER FITKO;Ll;0;L;;;;;N;;;16EA4;;16EA4 +16EC0;BERIA ERFE SMALL LETTER GOWAY;Ll;0;L;;;;;N;;;16EA5;;16EA5 +16EC1;BERIA ERFE SMALL LETTER HIRDEABO;Ll;0;L;;;;;N;;;16EA6;;16EA6 +16EC2;BERIA ERFE SMALL LETTER I;Ll;0;L;;;;;N;;;16EA7;;16EA7 +16EC3;BERIA ERFE SMALL LETTER DJAI;Ll;0;L;;;;;N;;;16EA8;;16EA8 +16EC4;BERIA ERFE SMALL LETTER KOBO;Ll;0;L;;;;;N;;;16EA9;;16EA9 +16EC5;BERIA ERFE SMALL LETTER LAKKO;Ll;0;L;;;;;N;;;16EAA;;16EAA +16EC6;BERIA ERFE SMALL LETTER MERI;Ll;0;L;;;;;N;;;16EAB;;16EAB +16EC7;BERIA ERFE SMALL LETTER NINI;Ll;0;L;;;;;N;;;16EAC;;16EAC +16EC8;BERIA ERFE SMALL LETTER GNA;Ll;0;L;;;;;N;;;16EAD;;16EAD +16EC9;BERIA ERFE SMALL LETTER NGAY;Ll;0;L;;;;;N;;;16EAE;;16EAE +16ECA;BERIA ERFE SMALL LETTER OI;Ll;0;L;;;;;N;;;16EAF;;16EAF +16ECB;BERIA ERFE SMALL LETTER PI;Ll;0;L;;;;;N;;;16EB0;;16EB0 +16ECC;BERIA ERFE SMALL LETTER ERIGO;Ll;0;L;;;;;N;;;16EB1;;16EB1 +16ECD;BERIA ERFE SMALL LETTER ERIGO TAMURA;Ll;0;L;;;;;N;;;16EB2;;16EB2 +16ECE;BERIA ERFE SMALL LETTER SERI;Ll;0;L;;;;;N;;;16EB3;;16EB3 +16ECF;BERIA ERFE SMALL LETTER SHEP;Ll;0;L;;;;;N;;;16EB4;;16EB4 +16ED0;BERIA ERFE SMALL LETTER TATASOUE;Ll;0;L;;;;;N;;;16EB5;;16EB5 +16ED1;BERIA ERFE SMALL LETTER UI;Ll;0;L;;;;;N;;;16EB6;;16EB6 +16ED2;BERIA ERFE SMALL LETTER WASSE;Ll;0;L;;;;;N;;;16EB7;;16EB7 +16ED3;BERIA ERFE SMALL LETTER AY;Ll;0;L;;;;;N;;;16EB8;;16EB8 16F00;MIAO LETTER PA;Lo;0;L;;;;;N;;;;; 16F01;MIAO LETTER BA;Lo;0;L;;;;;N;;;;; 16F02;MIAO LETTER YI PA;Lo;0;L;;;;;N;;;;; @@ -30280,8 +30494,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16FE4;KHITAN SMALL SCRIPT FILLER;Mn;0;NSM;;;;;N;;;;; 16FF0;VIETNAMESE ALTERNATE READING MARK CA;Mc;6;L;;;;;N;;;;; 16FF1;VIETNAMESE ALTERNATE READING MARK NHAY;Mc;6;L;;;;;N;;;;; +16FF2;CHINESE SMALL SIMPLIFIED ER;Lm;0;L;;;;;N;;;;; +16FF3;CHINESE SMALL TRADITIONAL ER;Lm;0;L;;;;;N;;;;; +16FF4;YANGQIN SIGN SLOW ONE BEAT;Nl;0;L;;;;1;N;;;;; +16FF5;YANGQIN SIGN SLOW THREE HALF BEATS;Nl;0;L;;;;3/2;N;;;;; +16FF6;YANGQIN SIGN SLOW TWO BEATS;Nl;0;L;;;;2;N;;;;; 17000;;Lo;0;L;;;;;N;;;;; -187F7;;Lo;0;L;;;;;N;;;;; +187FF;;Lo;0;L;;;;;N;;;;; 18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;; 18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;; 18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;; @@ -31522,7 +31741,122 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 18CD5;KHITAN SMALL SCRIPT CHARACTER-18CD5;Lo;0;L;;;;;N;;;;; 18CFF;KHITAN SMALL SCRIPT CHARACTER-18CFF;Lo;0;L;;;;;N;;;;; 18D00;;Lo;0;L;;;;;N;;;;; -18D08;;Lo;0;L;;;;;N;;;;; +18D1E;;Lo;0;L;;;;;N;;;;; +18D80;TANGUT COMPONENT-769;Lo;0;L;;;;;N;;;;; +18D81;TANGUT COMPONENT-770;Lo;0;L;;;;;N;;;;; +18D82;TANGUT COMPONENT-771;Lo;0;L;;;;;N;;;;; +18D83;TANGUT COMPONENT-772;Lo;0;L;;;;;N;;;;; +18D84;TANGUT COMPONENT-773;Lo;0;L;;;;;N;;;;; +18D85;TANGUT COMPONENT-774;Lo;0;L;;;;;N;;;;; +18D86;TANGUT COMPONENT-775;Lo;0;L;;;;;N;;;;; +18D87;TANGUT COMPONENT-776;Lo;0;L;;;;;N;;;;; +18D88;TANGUT COMPONENT-777;Lo;0;L;;;;;N;;;;; +18D89;TANGUT COMPONENT-778;Lo;0;L;;;;;N;;;;; +18D8A;TANGUT COMPONENT-779;Lo;0;L;;;;;N;;;;; +18D8B;TANGUT COMPONENT-780;Lo;0;L;;;;;N;;;;; +18D8C;TANGUT COMPONENT-781;Lo;0;L;;;;;N;;;;; +18D8D;TANGUT COMPONENT-782;Lo;0;L;;;;;N;;;;; +18D8E;TANGUT COMPONENT-783;Lo;0;L;;;;;N;;;;; +18D8F;TANGUT COMPONENT-784;Lo;0;L;;;;;N;;;;; +18D90;TANGUT COMPONENT-785;Lo;0;L;;;;;N;;;;; +18D91;TANGUT COMPONENT-786;Lo;0;L;;;;;N;;;;; +18D92;TANGUT COMPONENT-787;Lo;0;L;;;;;N;;;;; +18D93;TANGUT COMPONENT-788;Lo;0;L;;;;;N;;;;; +18D94;TANGUT COMPONENT-789;Lo;0;L;;;;;N;;;;; +18D95;TANGUT COMPONENT-790;Lo;0;L;;;;;N;;;;; +18D96;TANGUT COMPONENT-791;Lo;0;L;;;;;N;;;;; +18D97;TANGUT COMPONENT-792;Lo;0;L;;;;;N;;;;; +18D98;TANGUT COMPONENT-793;Lo;0;L;;;;;N;;;;; +18D99;TANGUT COMPONENT-794;Lo;0;L;;;;;N;;;;; +18D9A;TANGUT COMPONENT-795;Lo;0;L;;;;;N;;;;; +18D9B;TANGUT COMPONENT-796;Lo;0;L;;;;;N;;;;; +18D9C;TANGUT COMPONENT-797;Lo;0;L;;;;;N;;;;; +18D9D;TANGUT COMPONENT-798;Lo;0;L;;;;;N;;;;; +18D9E;TANGUT COMPONENT-799;Lo;0;L;;;;;N;;;;; +18D9F;TANGUT COMPONENT-800;Lo;0;L;;;;;N;;;;; +18DA0;TANGUT COMPONENT-801;Lo;0;L;;;;;N;;;;; +18DA1;TANGUT COMPONENT-802;Lo;0;L;;;;;N;;;;; +18DA2;TANGUT COMPONENT-803;Lo;0;L;;;;;N;;;;; +18DA3;TANGUT COMPONENT-804;Lo;0;L;;;;;N;;;;; +18DA4;TANGUT COMPONENT-805;Lo;0;L;;;;;N;;;;; +18DA5;TANGUT COMPONENT-806;Lo;0;L;;;;;N;;;;; +18DA6;TANGUT COMPONENT-807;Lo;0;L;;;;;N;;;;; +18DA7;TANGUT COMPONENT-808;Lo;0;L;;;;;N;;;;; +18DA8;TANGUT COMPONENT-809;Lo;0;L;;;;;N;;;;; +18DA9;TANGUT COMPONENT-810;Lo;0;L;;;;;N;;;;; +18DAA;TANGUT COMPONENT-811;Lo;0;L;;;;;N;;;;; +18DAB;TANGUT COMPONENT-812;Lo;0;L;;;;;N;;;;; +18DAC;TANGUT COMPONENT-813;Lo;0;L;;;;;N;;;;; +18DAD;TANGUT COMPONENT-814;Lo;0;L;;;;;N;;;;; +18DAE;TANGUT COMPONENT-815;Lo;0;L;;;;;N;;;;; +18DAF;TANGUT COMPONENT-816;Lo;0;L;;;;;N;;;;; +18DB0;TANGUT COMPONENT-817;Lo;0;L;;;;;N;;;;; +18DB1;TANGUT COMPONENT-818;Lo;0;L;;;;;N;;;;; +18DB2;TANGUT COMPONENT-819;Lo;0;L;;;;;N;;;;; +18DB3;TANGUT COMPONENT-820;Lo;0;L;;;;;N;;;;; +18DB4;TANGUT COMPONENT-821;Lo;0;L;;;;;N;;;;; +18DB5;TANGUT COMPONENT-822;Lo;0;L;;;;;N;;;;; +18DB6;TANGUT COMPONENT-823;Lo;0;L;;;;;N;;;;; +18DB7;TANGUT COMPONENT-824;Lo;0;L;;;;;N;;;;; +18DB8;TANGUT COMPONENT-825;Lo;0;L;;;;;N;;;;; +18DB9;TANGUT COMPONENT-826;Lo;0;L;;;;;N;;;;; +18DBA;TANGUT COMPONENT-827;Lo;0;L;;;;;N;;;;; +18DBB;TANGUT COMPONENT-828;Lo;0;L;;;;;N;;;;; +18DBC;TANGUT COMPONENT-829;Lo;0;L;;;;;N;;;;; +18DBD;TANGUT COMPONENT-830;Lo;0;L;;;;;N;;;;; +18DBE;TANGUT COMPONENT-831;Lo;0;L;;;;;N;;;;; +18DBF;TANGUT COMPONENT-832;Lo;0;L;;;;;N;;;;; +18DC0;TANGUT COMPONENT-833;Lo;0;L;;;;;N;;;;; +18DC1;TANGUT COMPONENT-834;Lo;0;L;;;;;N;;;;; +18DC2;TANGUT COMPONENT-835;Lo;0;L;;;;;N;;;;; +18DC3;TANGUT COMPONENT-836;Lo;0;L;;;;;N;;;;; +18DC4;TANGUT COMPONENT-837;Lo;0;L;;;;;N;;;;; +18DC5;TANGUT COMPONENT-838;Lo;0;L;;;;;N;;;;; +18DC6;TANGUT COMPONENT-839;Lo;0;L;;;;;N;;;;; +18DC7;TANGUT COMPONENT-840;Lo;0;L;;;;;N;;;;; +18DC8;TANGUT COMPONENT-841;Lo;0;L;;;;;N;;;;; +18DC9;TANGUT COMPONENT-842;Lo;0;L;;;;;N;;;;; +18DCA;TANGUT COMPONENT-843;Lo;0;L;;;;;N;;;;; +18DCB;TANGUT COMPONENT-844;Lo;0;L;;;;;N;;;;; +18DCC;TANGUT COMPONENT-845;Lo;0;L;;;;;N;;;;; +18DCD;TANGUT COMPONENT-846;Lo;0;L;;;;;N;;;;; +18DCE;TANGUT COMPONENT-847;Lo;0;L;;;;;N;;;;; +18DCF;TANGUT COMPONENT-848;Lo;0;L;;;;;N;;;;; +18DD0;TANGUT COMPONENT-849;Lo;0;L;;;;;N;;;;; +18DD1;TANGUT COMPONENT-850;Lo;0;L;;;;;N;;;;; +18DD2;TANGUT COMPONENT-851;Lo;0;L;;;;;N;;;;; +18DD3;TANGUT COMPONENT-852;Lo;0;L;;;;;N;;;;; +18DD4;TANGUT COMPONENT-853;Lo;0;L;;;;;N;;;;; +18DD5;TANGUT COMPONENT-854;Lo;0;L;;;;;N;;;;; +18DD6;TANGUT COMPONENT-855;Lo;0;L;;;;;N;;;;; +18DD7;TANGUT COMPONENT-856;Lo;0;L;;;;;N;;;;; +18DD8;TANGUT COMPONENT-857;Lo;0;L;;;;;N;;;;; +18DD9;TANGUT COMPONENT-858;Lo;0;L;;;;;N;;;;; +18DDA;TANGUT COMPONENT-859;Lo;0;L;;;;;N;;;;; +18DDB;TANGUT COMPONENT-860;Lo;0;L;;;;;N;;;;; +18DDC;TANGUT COMPONENT-861;Lo;0;L;;;;;N;;;;; +18DDD;TANGUT COMPONENT-862;Lo;0;L;;;;;N;;;;; +18DDE;TANGUT COMPONENT-863;Lo;0;L;;;;;N;;;;; +18DDF;TANGUT COMPONENT-864;Lo;0;L;;;;;N;;;;; +18DE0;TANGUT COMPONENT-865;Lo;0;L;;;;;N;;;;; +18DE1;TANGUT COMPONENT-866;Lo;0;L;;;;;N;;;;; +18DE2;TANGUT COMPONENT-867;Lo;0;L;;;;;N;;;;; +18DE3;TANGUT COMPONENT-868;Lo;0;L;;;;;N;;;;; +18DE4;TANGUT COMPONENT-869;Lo;0;L;;;;;N;;;;; +18DE5;TANGUT COMPONENT-870;Lo;0;L;;;;;N;;;;; +18DE6;TANGUT COMPONENT-871;Lo;0;L;;;;;N;;;;; +18DE7;TANGUT COMPONENT-872;Lo;0;L;;;;;N;;;;; +18DE8;TANGUT COMPONENT-873;Lo;0;L;;;;;N;;;;; +18DE9;TANGUT COMPONENT-874;Lo;0;L;;;;;N;;;;; +18DEA;TANGUT COMPONENT-875;Lo;0;L;;;;;N;;;;; +18DEB;TANGUT COMPONENT-876;Lo;0;L;;;;;N;;;;; +18DEC;TANGUT COMPONENT-877;Lo;0;L;;;;;N;;;;; +18DED;TANGUT COMPONENT-878;Lo;0;L;;;;;N;;;;; +18DEE;TANGUT COMPONENT-879;Lo;0;L;;;;;N;;;;; +18DEF;TANGUT COMPONENT-880;Lo;0;L;;;;;N;;;;; +18DF0;TANGUT COMPONENT-881;Lo;0;L;;;;;N;;;;; +18DF1;TANGUT COMPONENT-882;Lo;0;L;;;;;N;;;;; +18DF2;TANGUT COMPONENT-883;Lo;0;L;;;;;N;;;;; 1AFF0;KATAKANA LETTER MINNAN TONE-2;Lm;0;L;;;;;N;;;;; 1AFF1;KATAKANA LETTER MINNAN TONE-3;Lm;0;L;;;;;N;;;;; 1AFF2;KATAKANA LETTER MINNAN TONE-4;Lm;0;L;;;;;N;;;;; @@ -32629,6 +32963,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CCF7;OUTLINED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1CCF8;OUTLINED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1CCF9;OUTLINED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1CCFA;SNAKE SYMBOL;So;0;ON;;;;;N;;;;; +1CCFB;FLYING SAUCER SYMBOL;So;0;ON;;;;;N;;;;; +1CCFC;NOSE SYMBOL;So;0;ON;;;;;N;;;;; 1CD00;BLOCK OCTANT-3;So;0;ON;;;;;N;;;;; 1CD01;BLOCK OCTANT-23;So;0;ON;;;;;N;;;;; 1CD02;BLOCK OCTANT-123;So;0;ON;;;;;N;;;;; @@ -33065,6 +33402,46 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CEB1;KEYHOLE;So;0;ON;;;;;N;;;;; 1CEB2;OLD PERSONAL COMPUTER WITH MONITOR IN PORTRAIT ORIENTATION;So;0;ON;;;;;N;;;;; 1CEB3;BLACK RIGHT TRIANGLE CARET;So;0;ON;;;;;N;;;;; +1CEBA;FRAGILE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBB;OFFICE BUILDING SYMBOL;So;0;ON;;;;;N;;;;; +1CEBC;TREE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBD;APPLE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBE;CHERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEBF;STRAWBERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEC0;HEBE;So;0;ON;;;;;N;;;;; +1CEC1;IRIS;So;0;ON;;;;;N;;;;; +1CEC2;FLORA;So;0;ON;;;;;N;;;;; +1CEC3;METIS;So;0;ON;;;;;N;;;;; +1CEC4;PARTHENOPE;So;0;ON;;;;;N;;;;; +1CEC5;VICTORIA;So;0;ON;;;;;N;;;;; +1CEC6;EGERIA;So;0;ON;;;;;N;;;;; +1CEC7;IRENE;So;0;ON;;;;;N;;;;; +1CEC8;EUNOMIA;So;0;ON;;;;;N;;;;; +1CEC9;PSYCHE;So;0;ON;;;;;N;;;;; +1CECA;THETIS;So;0;ON;;;;;N;;;;; +1CECB;MELPOMENE;So;0;ON;;;;;N;;;;; +1CECC;FORTUNA;So;0;ON;;;;;N;;;;; +1CECD;ASTRONOMICAL SYMBOL FOR ASTEROID PROSERPINA;So;0;ON;;;;;N;;;;; +1CECE;BELLONA;So;0;ON;;;;;N;;;;; +1CECF;AMPHITRITE;So;0;ON;;;;;N;;;;; +1CED0;LEUKOTHEA;So;0;ON;;;;;N;;;;; +1CEE0;GEOMANTIC FIGURE POPULUS;So;0;ON;;;;;N;;;;; +1CEE1;GEOMANTIC FIGURE TRISTITIA;So;0;ON;;;;;N;;;;; +1CEE2;GEOMANTIC FIGURE ALBUS;So;0;ON;;;;;N;;;;; +1CEE3;GEOMANTIC FIGURE FORTUNA MAJOR;So;0;ON;;;;;N;;;;; +1CEE4;GEOMANTIC FIGURE RUBEUS;So;0;ON;;;;;N;;;;; +1CEE5;GEOMANTIC FIGURE ACQUISITIO;So;0;ON;;;;;N;;;;; +1CEE6;GEOMANTIC FIGURE CONJUNCTIO;So;0;ON;;;;;N;;;;; +1CEE7;GEOMANTIC FIGURE CAPUT DRACONIS;So;0;ON;;;;;N;;;;; +1CEE8;GEOMANTIC FIGURE LAETITIA;So;0;ON;;;;;N;;;;; +1CEE9;GEOMANTIC FIGURE CARCER;So;0;ON;;;;;N;;;;; +1CEEA;GEOMANTIC FIGURE AMISSIO;So;0;ON;;;;;N;;;;; +1CEEB;GEOMANTIC FIGURE PUELLA;So;0;ON;;;;;N;;;;; +1CEEC;GEOMANTIC FIGURE FORTUNA MINOR;So;0;ON;;;;;N;;;;; +1CEED;GEOMANTIC FIGURE PUER;So;0;ON;;;;;N;;;;; +1CEEE;GEOMANTIC FIGURE CAUDA DRACONIS;So;0;ON;;;;;N;;;;; +1CEEF;GEOMANTIC FIGURE VIA;So;0;ON;;;;;N;;;;; +1CEF0;MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR;Sm;0;ON;;;;;N;;;;; 1CF00;ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF01;ZNAMENNY COMBINING MARK NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF02;ZNAMENNY COMBINING MARK TSATA ON LEFT;Mn;0;NSM;;;;;N;;;;; @@ -36004,6 +36381,61 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1E5F9;OL ONAL DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1E5FA;OL ONAL DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1E5FF;OL ONAL ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; +1E6C0;TAI YO LETTER LOW KO;Lo;0;L;;;;;N;;;;; +1E6C1;TAI YO LETTER HIGH KO;Lo;0;L;;;;;N;;;;; +1E6C2;TAI YO LETTER LOW KHO;Lo;0;L;;;;;N;;;;; +1E6C3;TAI YO LETTER HIGH KHO;Lo;0;L;;;;;N;;;;; +1E6C4;TAI YO LETTER GO;Lo;0;L;;;;;N;;;;; +1E6C5;TAI YO LETTER NGO;Lo;0;L;;;;;N;;;;; +1E6C6;TAI YO LETTER CO;Lo;0;L;;;;;N;;;;; +1E6C7;TAI YO LETTER LOW XO;Lo;0;L;;;;;N;;;;; +1E6C8;TAI YO LETTER HIGH XO;Lo;0;L;;;;;N;;;;; +1E6C9;TAI YO LETTER LOW NYO;Lo;0;L;;;;;N;;;;; +1E6CA;TAI YO LETTER HIGH NYO;Lo;0;L;;;;;N;;;;; +1E6CB;TAI YO LETTER DO;Lo;0;L;;;;;N;;;;; +1E6CC;TAI YO LETTER LOW TO;Lo;0;L;;;;;N;;;;; +1E6CD;TAI YO LETTER HIGH TO;Lo;0;L;;;;;N;;;;; +1E6CE;TAI YO LETTER THO;Lo;0;L;;;;;N;;;;; +1E6CF;TAI YO LETTER NO;Lo;0;L;;;;;N;;;;; +1E6D0;TAI YO LETTER BO;Lo;0;L;;;;;N;;;;; +1E6D1;TAI YO LETTER LOW PO;Lo;0;L;;;;;N;;;;; +1E6D2;TAI YO LETTER HIGH PO;Lo;0;L;;;;;N;;;;; +1E6D3;TAI YO LETTER PHO;Lo;0;L;;;;;N;;;;; +1E6D4;TAI YO LETTER LOW FO;Lo;0;L;;;;;N;;;;; +1E6D5;TAI YO LETTER HIGH FO;Lo;0;L;;;;;N;;;;; +1E6D6;TAI YO LETTER MO;Lo;0;L;;;;;N;;;;; +1E6D7;TAI YO LETTER YO;Lo;0;L;;;;;N;;;;; +1E6D8;TAI YO LETTER LO;Lo;0;L;;;;;N;;;;; +1E6D9;TAI YO LETTER VO;Lo;0;L;;;;;N;;;;; +1E6DA;TAI YO LETTER LOW HO;Lo;0;L;;;;;N;;;;; +1E6DB;TAI YO LETTER HIGH HO;Lo;0;L;;;;;N;;;;; +1E6DC;TAI YO LETTER QO;Lo;0;L;;;;;N;;;;; +1E6DD;TAI YO LETTER LOW KVO;Lo;0;L;;;;;N;;;;; +1E6DE;TAI YO LETTER HIGH KVO;Lo;0;L;;;;;N;;;;; +1E6E0;TAI YO LETTER AA;Lo;0;L;;;;;N;;;;; +1E6E1;TAI YO LETTER I;Lo;0;L;;;;;N;;;;; +1E6E2;TAI YO LETTER UE;Lo;0;L;;;;;N;;;;; +1E6E3;TAI YO SIGN UE;Mn;230;NSM;;;;;N;;;;; +1E6E4;TAI YO LETTER U;Lo;0;L;;;;;N;;;;; +1E6E5;TAI YO LETTER AE;Lo;0;L;;;;;N;;;;; +1E6E6;TAI YO SIGN AU;Mn;230;NSM;;;;;N;;;;; +1E6E7;TAI YO LETTER O;Lo;0;L;;;;;N;;;;; +1E6E8;TAI YO LETTER E;Lo;0;L;;;;;N;;;;; +1E6E9;TAI YO LETTER IA;Lo;0;L;;;;;N;;;;; +1E6EA;TAI YO LETTER UEA;Lo;0;L;;;;;N;;;;; +1E6EB;TAI YO LETTER UA;Lo;0;L;;;;;N;;;;; +1E6EC;TAI YO LETTER OO;Lo;0;L;;;;;N;;;;; +1E6ED;TAI YO LETTER AUE;Lo;0;L;;;;;N;;;;; +1E6EE;TAI YO SIGN AY;Mn;230;NSM;;;;;N;;;;; +1E6EF;TAI YO SIGN ANG;Mn;230;NSM;;;;;N;;;;; +1E6F0;TAI YO LETTER AN;Lo;0;L;;;;;N;;;;; +1E6F1;TAI YO LETTER AM;Lo;0;L;;;;;N;;;;; +1E6F2;TAI YO LETTER AK;Lo;0;L;;;;;N;;;;; +1E6F3;TAI YO LETTER AT;Lo;0;L;;;;;N;;;;; +1E6F4;TAI YO LETTER AP;Lo;0;L;;;;;N;;;;; +1E6F5;TAI YO SIGN OM;Mn;230;NSM;;;;;N;;;;; +1E6FE;TAI YO SYMBOL MUEANG;Lo;0;L;;;;;N;;;;; +1E6FF;TAI YO XAM LAI;Lm;0;L;;;;;N;;;;; 1E7E0;ETHIOPIC SYLLABLE HHYA;Lo;0;L;;;;;N;;;;; 1E7E1;ETHIOPIC SYLLABLE HHYU;Lo;0;L;;;;;N;;;;; 1E7E2;ETHIOPIC SYLLABLE HHYI;Lo;0;L;;;;;N;;;;; @@ -38079,6 +38511,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;; 1F6D6;HUT;So;0;ON;;;;;N;;;;; 1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;; +1F6D8;LANDSLIDE;So;0;ON;;;;;N;;;;; 1F6DC;WIRELESS;So;0;ON;;;;;N;;;;; 1F6DD;PLAYGROUND SLIDE;So;0;ON;;;;;N;;;;; 1F6DE;WHEEL;So;0;ON;;;;;N;;;;; @@ -38228,6 +38661,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F774;LOT OF FORTUNE;So;0;ON;;;;;N;;;;; 1F775;OCCULTATION;So;0;ON;;;;;N;;;;; 1F776;LUNAR ECLIPSE;So;0;ON;;;;;N;;;;; +1F777;VESTA FORM TWO;So;0;ON;;;;;N;;;;; +1F778;ASTRAEA FORM TWO;So;0;ON;;;;;N;;;;; +1F779;HYGIEA FORM TWO;So;0;ON;;;;;N;;;;; +1F77A;PARTHENOPE FORM TWO;So;0;ON;;;;;N;;;;; 1F77B;HAUMEA;So;0;ON;;;;;N;;;;; 1F77C;MAKEMAKE;So;0;ON;;;;;N;;;;; 1F77D;GONGGONG;So;0;ON;;;;;N;;;;; @@ -38498,6 +38935,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F8BB;SOUTH WEST ARROW FROM BAR;So;0;ON;;;;;N;;;;; 1F8C0;LEFTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; 1F8C1;RIGHTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; +1F8D0;LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; +1F8D1;LONG RIGHTWARDS HARPOON OVER LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D2;LONG RIGHTWARDS HARPOON ABOVE SHORT LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D3;SHORT RIGHTWARDS HARPOON ABOVE LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D4;LONG LEFTWARDS HARPOON ABOVE SHORT RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D5;SHORT LEFTWARDS HARPOON ABOVE LONG RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D6;LONG RIGHTWARDS ARROW THROUGH X;Sm;0;ON;;;;;N;;;;; +1F8D7;LONG RIGHTWARDS ARROW WITH DOUBLE SLASH;Sm;0;ON;;;;;N;;;;; +1F8D8;LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE;Sm;0;ON;;;;;N;;;;; 1F900;CIRCLED CROSS FORMEE WITH FOUR DOTS;So;0;ON;;;;;N;;;;; 1F901;CIRCLED CROSS FORMEE WITH TWO DOTS;So;0;ON;;;;;N;;;;; 1F902;CIRCLED CROSS FORMEE;So;0;ON;;;;;N;;;;; @@ -38838,6 +39284,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA51;BLACK CHESS KNIGHT-QUEEN;So;0;ON;;;;;N;;;;; 1FA52;BLACK CHESS KNIGHT-ROOK;So;0;ON;;;;;N;;;;; 1FA53;BLACK CHESS KNIGHT-BISHOP;So;0;ON;;;;;N;;;;; +1FA54;WHITE CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA55;WHITE CHESS ALFIL;So;0;ON;;;;;N;;;;; +1FA56;BLACK CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA57;BLACK CHESS ALFIL;So;0;ON;;;;;N;;;;; 1FA60;XIANGQI RED GENERAL;So;0;ON;;;;;N;;;;; 1FA61;XIANGQI RED MANDARIN;So;0;ON;;;;;N;;;;; 1FA62;XIANGQI RED ELEPHANT;So;0;ON;;;;;N;;;;; @@ -38875,6 +39325,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA87;MARACAS;So;0;ON;;;;;N;;;;; 1FA88;FLUTE;So;0;ON;;;;;N;;;;; 1FA89;HARP;So;0;ON;;;;;N;;;;; +1FA8A;TROMBONE;So;0;ON;;;;;N;;;;; +1FA8E;TREASURE CHEST;So;0;ON;;;;;N;;;;; 1FA8F;SHOVEL;So;0;ON;;;;;N;;;;; 1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;; 1FA91;CHAIR;So;0;ON;;;;;N;;;;; @@ -38931,6 +39383,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAC4;PREGNANT PERSON;So;0;ON;;;;;N;;;;; 1FAC5;PERSON WITH CROWN;So;0;ON;;;;;N;;;;; 1FAC6;FINGERPRINT;So;0;ON;;;;;N;;;;; +1FAC8;HAIRY CREATURE;So;0;ON;;;;;N;;;;; +1FACD;ORCA;So;0;ON;;;;;N;;;;; 1FACE;MOOSE;So;0;ON;;;;;N;;;;; 1FACF;DONKEY;So;0;ON;;;;;N;;;;; 1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;; @@ -38957,6 +39411,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAE7;BUBBLES;So;0;ON;;;;;N;;;;; 1FAE8;SHAKING FACE;So;0;ON;;;;;N;;;;; 1FAE9;FACE WITH BAGS UNDER EYES;So;0;ON;;;;;N;;;;; +1FAEA;DISTORTED FACE;So;0;ON;;;;;N;;;;; +1FAEF;FIGHT CLOUD;So;0;ON;;;;;N;;;;; 1FAF0;HAND WITH INDEX FINGER AND THUMB CROSSED;So;0;ON;;;;;N;;;;; 1FAF1;RIGHTWARDS HAND;So;0;ON;;;;;N;;;;; 1FAF2;LEFTWARDS HAND;So;0;ON;;;;;N;;;;; @@ -39215,14 +39671,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FBF7;SEGMENTED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1FBF8;SEGMENTED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1FBF9;SEGMENTED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1FBFA;ALARM BELL SYMBOL;So;0;ON;;;;;N;;;;; 20000;;Lo;0;L;;;;;N;;;;; 2A6DF;;Lo;0;L;;;;;N;;;;; 2A700;;Lo;0;L;;;;;N;;;;; -2B739;;Lo;0;L;;;;;N;;;;; +2B73F;;Lo;0;L;;;;;N;;;;; 2B740;;Lo;0;L;;;;;N;;;;; 2B81D;;Lo;0;L;;;;;N;;;;; 2B820;;Lo;0;L;;;;;N;;;;; -2CEA1;;Lo;0;L;;;;;N;;;;; +2CEAD;;Lo;0;L;;;;;N;;;;; 2CEB0;;Lo;0;L;;;;;N;;;;; 2EBE0;;Lo;0;L;;;;;N;;;;; 2EBF0;;Lo;0;L;;;;;N;;;;; @@ -39773,6 +40230,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 3134A;;Lo;0;L;;;;;N;;;;; 31350;;Lo;0;L;;;;;N;;;;; 323AF;;Lo;0;L;;;;;N;;;;; +323B0;;Lo;0;L;;;;;N;;;;; +33479;;Lo;0;L;;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; diff --git a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt index a4b7b6fbc3c..19b13571f34 100644 --- a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt +++ b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakProperty.txt @@ -1,6 +1,6 @@ -# GraphemeBreakProperty-16.0.0.txt -# Date: 2024-05-31, 18:09:38 GMT -# Copyright (c) 2024 Unicode, Inc. +# GraphemeBreakProperty-17.0.0.txt +# Date: 2025-06-30, 06:20:23 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -30,12 +30,11 @@ 113D1 ; Prepend # Lo TULU-TIGALARI REPHA 1193F ; Prepend # Lo DIVES AKURU PREFIXED NASAL SIGN 11941 ; Prepend # Lo DIVES AKURU INITIAL RA -11A3A ; Prepend # Lo ZANABAZAR SQUARE CLUSTER-INITIAL LETTER RA 11A84..11A89 ; Prepend # Lo [6] SOYOMBO SIGN JIHVAMULIYA..SOYOMBO CLUSTER-INITIAL LETTER SA 11D46 ; Prepend # Lo MASARAM GONDI REPHA 11F02 ; Prepend # Lo KAWI SIGN REPHA -# Total code points: 28 +# Total code points: 27 # ================================================ @@ -243,7 +242,8 @@ E01F0..E0FFF ; Control # Cn [3600] .. 1A7F ; Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT 1AB0..1ABD ; Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Extend # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Extend # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Extend # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B34 ; Extend # Mn BALINESE SIGN REREKAN 1B35 ; Extend # Mc BALINESE VOWEL SIGN TEDUNG @@ -339,7 +339,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 10D24..10D27 ; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI 10D69..10D6D ; Extend # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK 10EAB..10EAC ; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK -10EFC..10EFF ; Extend # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EFA..10EFF ; Extend # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F82..10F85 ; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW 11001 ; Extend # Mn BRAHMI SIGN ANUSVARA @@ -430,6 +430,9 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 11A59..11A5B ; Extend # Mn [3] SOYOMBO VOWEL SIGN VOCALIC R..SOYOMBO VOWEL LENGTH MARK 11A8A..11A96 ; Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA 11A98..11A99 ; Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11B60 ; Extend # Mn SHARADA VOWEL SIGN OE +11B62..11B64 ; Extend # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B66 ; Extend # Mn SHARADA VOWEL SIGN CANDRA E 11C30..11C36 ; Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L 11C38..11C3D ; Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA 11C3F ; Extend # Mn BHAIKSUKI SIGN VIRAMA @@ -489,13 +492,17 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI 1E4EC..1E4EF ; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH 1E5EE..1E5EF ; Extend # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR +1E6E3 ; Extend # Mn TAI YO SIGN UE +1E6E6 ; Extend # Mn TAI YO SIGN AU +1E6EE..1E6EF ; Extend # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F5 ; Extend # Mn TAI YO SIGN OM 1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS 1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA 1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 2198 +# Total code points: 2237 # ================================================ @@ -646,6 +653,9 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK 11A39 ; SpacingMark # Mc ZANABAZAR SQUARE SIGN VISARGA 11A57..11A58 ; SpacingMark # Mc [2] SOYOMBO VOWEL SIGN AI..SOYOMBO VOWEL SIGN AU 11A97 ; SpacingMark # Mc SOYOMBO SIGN VISARGA +11B61 ; SpacingMark # Mc SHARADA VOWEL SIGN OOE +11B65 ; SpacingMark # Mc SHARADA VOWEL SIGN SHORT O +11B67 ; SpacingMark # Mc SHARADA VOWEL SIGN CANDRA O 11C2F ; SpacingMark # Mc BHAIKSUKI VOWEL SIGN AA 11C3E ; SpacingMark # Mc BHAIKSUKI SIGN VISARGA 11CA9 ; SpacingMark # Mc MARCHEN SUBJOINED LETTER YA @@ -661,7 +671,7 @@ ABEC ; SpacingMark # Mc MEETEI MAYEK LUM IYEK 1612A..1612C ; SpacingMark # Mc [3] GURUNG KHEMA CONSONANT SIGN MEDIAL YA..GURUNG KHEMA CONSONANT SIGN MEDIAL HA 16F51..16F87 ; SpacingMark # Mc [55] MIAO SIGN ASPIRATION..MIAO VOWEL SIGN UI -# Total code points: 378 +# Total code points: 381 # ================================================ diff --git a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt index 3eb4b307e8e..e1215547c58 100644 --- a/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt +++ b/src/java.base/share/data/unicodedata/auxiliary/GraphemeBreakTest.txt @@ -1,6 +1,6 @@ -# GraphemeBreakTest-16.0.0.txt -# Date: 2024-05-02, 15:02:48 GMT -# Copyright (c) 2024 Unicode, Inc. +# GraphemeBreakTest-17.0.0.txt +# Date: 2025-03-24, 14:45:55 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -16,1106 +16,781 @@ # × wherever there is not. # the format can change, but currently it shows: # - the sample character name -# - (x) the Grapheme_Cluster_Break property value for the sample character +# - (x) the Grapheme_Cluster_Break property value for the sample character and +# any other properties relevant to the algorithm, as described in +# GraphemeBreakTest.html # - [x] the rule that determines whether there is a break or not, # as listed in the Rules section of GraphemeBreakTest.html # # These samples may be extended or changed in the future. # -÷ 0020 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0020 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] (CR) ÷ [0.3] -÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0020 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] (LF) ÷ [0.3] -÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0020 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) ÷ [5.0] (Control) ÷ [0.3] -÷ 0020 × 0308 ÷ 0001 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0020 × 200C ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0020 × 0308 × 200C ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0020 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0020 × 0308 ÷ 0600 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0020 × 0A03 ÷ # ÷ [0.2] SPACE (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0020 × 0308 × 0A03 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0020 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0020 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0020 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0020 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0020 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0020 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 ÷ 0904 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 × 0308 ÷ 0904 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 ÷ 0D4E ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 × 0308 ÷ 0D4E ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0020 ÷ 0915 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0020 × 0308 ÷ 0915 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0020 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0020 × 0308 ÷ 231A ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0900 ÷ # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 0900 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0020 × 094D ÷ # ÷ [0.2] SPACE (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 094D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) ÷ [999.0] (Other) ÷ [0.3] -÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 000D ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] SPACE (Other) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] ÷ 000D ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] (CR) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 000D × 000A ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 000D ÷ 0001 ÷ # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 000D ÷ 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 000D ÷ 0308 × 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 000D ÷ 0000 ÷ # ÷ [0.2] (CR) ÷ [4.0] (Control) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 000D ÷ 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0308 × 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000D ÷ 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000D ÷ 0308 × 200C ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000D ÷ 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 000D ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000D ÷ 0600 ÷ # ÷ [0.2] (CR) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000D ÷ 0A03 ÷ # ÷ [0.2] (CR) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 000D ÷ 0308 × 0A03 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000D ÷ 06DD ÷ # ÷ [0.2] (CR) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000D ÷ 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 000D ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 000D ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 000D ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 000D ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000D ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 000D ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000D ÷ 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0308 × 0903 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0904 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0D4E ÷ # ÷ [0.2] (CR) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000D ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000D ÷ 231A ÷ # ÷ [0.2] (CR) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 231A ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 000D ÷ 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 0300 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0900 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 0900 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 094D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0308 × 200D ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000D ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] (Other) ÷ [0.3] -÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 000A ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] SPACE (Other) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 000D ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000D ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000D ÷ 00A9 ÷ # ÷ [0.2] (CR) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000D ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (CR) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 000A ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] (CR) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 000A ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] (LF) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 000A ÷ 0001 ÷ # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 000A ÷ 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 000A ÷ 0308 × 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 000A ÷ 0000 ÷ # ÷ [0.2] (LF) ÷ [4.0] (Control) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 000A ÷ 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0308 × 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 000A ÷ 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000A ÷ 0308 × 200C ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 000A ÷ 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 000A ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 000A ÷ 0600 ÷ # ÷ [0.2] (LF) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 000A ÷ 0A03 ÷ # ÷ [0.2] (LF) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 000A ÷ 0308 × 0A03 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 000A ÷ 06DD ÷ # ÷ [0.2] (LF) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 000A ÷ 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 000A ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 000A ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 000A ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 000A ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 000A ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 000A ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 000A ÷ 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0308 × 0903 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0904 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0D4E ÷ # ÷ [0.2] (LF) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 000A ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 000A ÷ 231A ÷ # ÷ [0.2] (LF) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 231A ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 000A ÷ 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 0300 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0900 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 0900 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 094D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0308 × 200D ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 000A ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] (Other) ÷ [0.3] -÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0001 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] SPACE (Other) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0001 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] (CR) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0001 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] (LF) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0001 ÷ 0001 ÷ # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0001 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0001 ÷ 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0001 ÷ 0308 × 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0001 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0001 ÷ 0600 ÷ # ÷ [0.2] (Control) ÷ [4.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0600 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0001 ÷ 0A03 ÷ # ÷ [0.2] (Control) ÷ [4.0] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0001 ÷ 0308 × 0A03 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0001 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0001 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0001 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0001 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0001 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0001 ÷ 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0308 × 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0904 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0904 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0D4E ÷ # ÷ [0.2] (Control) ÷ [4.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0D4E ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0001 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0001 ÷ 231A ÷ # ÷ [0.2] (Control) ÷ [4.0] WATCH (ExtPict) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 231A ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0001 ÷ 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0900 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 0900 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0308 × 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0001 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] (Other) ÷ [0.3] -÷ 0001 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 200C ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200C × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200C ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (CR) ÷ [0.3] -÷ 200C × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 200C ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (LF) ÷ [0.3] -÷ 200C × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 200C ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [5.0] (Control) ÷ [0.3] -÷ 200C × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 200C × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200C × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200C ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200C × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200C ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200C × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200C × 0A03 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200C × 0308 × 0A03 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200C ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200C × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200C ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200C × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200C ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200C × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200C ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200C × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200C ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200C × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200C × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200C × 0308 ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200C ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C × 0308 ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200C ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200C × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200C ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200C × 0308 ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200C × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200C × 0900 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 0900 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200C × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200C × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200C × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200C ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) ÷ [999.0] (Other) ÷ [0.3] -÷ 200C × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (Extend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 000A ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 000A ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 000A ÷ 00A9 ÷ # ÷ [0.2] (LF) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 000A ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000A ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] (CR) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 000D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0000 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] (LF) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 000A ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0000 ÷ 0000 ÷ # ÷ [0.2] (Control) ÷ [4.0] (Control) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0000 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0000 ÷ 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0308 × 094D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 0308 × 0300 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0000 ÷ 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0000 ÷ 0308 × 200C ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0000 ÷ 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0000 ÷ 0308 × 200D ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0000 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0000 ÷ 06DD ÷ # ÷ [0.2] (Control) ÷ [4.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 06DD ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0000 ÷ 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0000 ÷ 0308 × 0903 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0000 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1100 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0000 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 1160 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0000 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 11A8 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0000 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ AC00 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0000 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ AC01 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0000 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0915 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0000 ÷ 00A9 ÷ # ÷ [0.2] (Control) ÷ [4.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 00A9 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0000 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0020 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0000 ÷ 0308 ÷ 0378 ÷ # ÷ [0.2] (Control) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 094D × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 094D ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 094D × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 094D ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 094D × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 094D × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 094D × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 094D × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 094D × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 094D × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 094D × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 094D × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 094D × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 094D ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 094D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 094D ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 094D × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 094D × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 094D × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 094D ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 094D × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 094D ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 094D × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 094D ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 094D × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 094D ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 094D × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 094D ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 094D × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 094D ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 094D × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 094D ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 094D ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 094D × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0300 ÷ 0000 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0300 × 0308 ÷ 0000 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0300 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0300 × 0308 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0300 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0300 × 0308 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0300 ÷ 06DD ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0300 × 0308 ÷ 06DD ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0300 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0300 × 0308 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0300 ÷ 00A9 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 00A9 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (CR) ÷ [0.3] +÷ 200C × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 200C ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (LF) ÷ [0.3] +÷ 200C × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 200C ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [5.0] (Control) ÷ [0.3] +÷ 200C × 0308 ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 200C × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200C × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200C × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200C × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200C × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200C × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200C × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200C × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200C ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200C × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200C ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200C × 0308 ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200C × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200C × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200C ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200C × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200C ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200C × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200C ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200C × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200C ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200C × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200C ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200C × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200C ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200C × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200C ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200C ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200C × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (CR) ÷ [0.3] +÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (LF) ÷ [0.3] +÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 200D ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [5.0] (Control) ÷ [0.3] +÷ 200D × 0308 ÷ 0000 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 200D × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200D × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 200D × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200D × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 200D ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200D × 0308 ÷ 06DD ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 200D ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200D × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 200D ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 00A9 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1F1E6 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (CR) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 000D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 1F1E6 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (LF) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 1F1E6 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0001 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 1F1E6 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 1F1E6 × 0308 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 000A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1F1E6 ÷ 0000 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [5.0] (Control) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0000 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1F1E6 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0308 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F1E6 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1F1E6 × 0308 × 200C ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 1F1E6 × 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1F1E6 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0600 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1F1E6 × 0A03 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 1F1E6 × 0308 × 0A03 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1F1E6 ÷ 06DD ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 06DD ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 1F1E6 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1100 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 1F1E6 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 1160 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 1F1E6 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 11A8 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 1F1E6 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ AC00 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 1F1E6 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1F1E6 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 × 0308 × 0903 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 ÷ 0904 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0904 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 ÷ 0D4E ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0D4E ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1F1E6 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1F1E6 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 231A ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1F1E6 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 0300 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0900 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 0900 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 094D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 × 0308 × 200D ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] (Other) ÷ [0.3] -÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0600 × 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] SPACE (Other) ÷ [0.3] -÷ 0600 × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0600 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (CR) ÷ [0.3] -÷ 0600 × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0600 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (LF) ÷ [0.3] -÷ 0600 × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0600 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) ÷ [5.0] (Control) ÷ [0.3] -÷ 0600 × 0308 ÷ 0001 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0600 × 200C ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0600 × 0308 × 200C ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0600 × 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0600 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0600 × 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0600 × 0308 ÷ 0600 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0600 × 0A03 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0600 × 0308 × 0A03 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0600 × 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0600 × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0600 × 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0600 × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0600 × 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0600 × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0600 × AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0600 × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0600 × AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0600 × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0600 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0308 × 0903 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0904 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0308 ÷ 0904 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0D4E ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0308 ÷ 0D4E ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0600 × 0915 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0600 × 0308 ÷ 0915 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0600 × 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] WATCH (ExtPict) ÷ [0.3] -÷ 0600 × 0308 ÷ 231A ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0600 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 0300 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0900 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 0900 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0600 × 094D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 094D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0600 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0308 × 200D ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0600 × 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.2] (Other) ÷ [0.3] -÷ 0600 × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC NUMBER SIGN (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0A03 ÷ 0020 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0020 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0A03 ÷ 000D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] -÷ 0A03 × 0308 ÷ 000D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0A03 ÷ 000A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] -÷ 0A03 × 0308 ÷ 000A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0A03 ÷ 0001 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0001 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0A03 × 200C ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0A03 × 0308 × 200C ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0A03 ÷ 1F1E6 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0A03 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0A03 ÷ 0600 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0600 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0A03 × 0A03 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0A03 × 0308 × 0A03 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0A03 ÷ 1100 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0A03 × 0308 ÷ 1100 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0A03 ÷ 1160 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0A03 × 0308 ÷ 1160 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0A03 ÷ 11A8 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0A03 × 0308 ÷ 11A8 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0A03 ÷ AC00 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0A03 × 0308 ÷ AC00 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0A03 ÷ AC01 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0A03 × 0308 ÷ AC01 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0A03 × 0903 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 × 0308 × 0903 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 ÷ 0904 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0904 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 ÷ 0D4E ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0D4E ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0A03 ÷ 0915 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0915 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0A03 ÷ 231A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0A03 × 0308 ÷ 231A ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0A03 × 0300 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 0300 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0900 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 0900 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 094D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 094D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 200D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0A03 × 0308 × 200D ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0A03 ÷ 0378 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [999.0] (Other) ÷ [0.3] -÷ 0A03 × 0308 ÷ 0378 ÷ # ÷ [0.2] GURMUKHI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ AC01 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1F1E6 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0915 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1F1E6 ÷ 00A9 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 00A9 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1F1E6 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0020 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F1E6 × 0308 ÷ 0378 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD ÷ 000D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (CR) ÷ [0.3] +÷ 06DD × 0308 ÷ 000D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 06DD ÷ 000A ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (LF) ÷ [0.3] +÷ 06DD × 0308 ÷ 000A ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 06DD ÷ 0000 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) ÷ [5.0] (Control) ÷ [0.3] +÷ 06DD × 0308 ÷ 0000 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 06DD × 094D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 06DD × 0308 × 094D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 06DD × 0300 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 06DD × 0308 × 0300 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 06DD × 200C ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 06DD × 0308 × 200C ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 06DD × 200D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 06DD × 0308 × 200D ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 06DD × 1F1E6 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 06DD × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 06DD × 06DD ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 06DD × 0308 ÷ 06DD ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 06DD × 0903 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 06DD × 0308 × 0903 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 06DD × 1100 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 06DD × 0308 ÷ 1100 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 06DD × 1160 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 06DD × 0308 ÷ 1160 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 06DD × 11A8 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 06DD × 0308 ÷ 11A8 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 06DD × AC00 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 06DD × 0308 ÷ AC00 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 06DD × AC01 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 06DD × 0308 ÷ AC01 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 06DD × 0915 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 06DD × 0308 ÷ 0915 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 06DD × 00A9 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 00A9 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 06DD × 0020 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 0020 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0378 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.2] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 06DD × 0308 ÷ 0378 ÷ # ÷ [0.2] ARABIC END OF AYAH (Prepend) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (CR) ÷ [0.3] +÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0903 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (LF) ÷ [0.3] +÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0903 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [5.0] (Control) ÷ [0.3] +÷ 0903 × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0903 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0903 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0903 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0903 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0903 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0903 × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0903 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0903 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0903 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0903 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0903 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0903 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0903 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0903 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0903 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1100 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (CR) ÷ [0.3] -÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 1100 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 1100 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (LF) ÷ [0.3] -÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 1100 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] -÷ 1100 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 1100 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 1100 × 0308 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 1100 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1100 ÷ 0000 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [5.0] (Control) ÷ [0.3] +÷ 1100 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1100 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1100 × 0308 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1100 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1100 × 0308 × 200C ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 1100 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1100 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1100 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1100 × 0A03 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 1100 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1100 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1100 ÷ 06DD ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1100 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1100 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1100 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 1100 × 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1100 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 1100 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1100 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 1100 × AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1100 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 1100 × AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1100 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 ÷ 0904 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 ÷ 0D4E ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1100 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1100 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1100 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1100 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1100 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0900 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1100 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 094D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1100 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1100 × 0308 × 200D ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] (Other) ÷ [0.3] -÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 1100 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1100 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1100 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1100 ÷ 00A9 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1100 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1100 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1160 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (CR) ÷ [0.3] -÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 1160 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 1160 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (LF) ÷ [0.3] -÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 1160 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] -÷ 1160 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 1160 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 1160 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 1160 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 1160 ÷ 0000 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [5.0] (Control) ÷ [0.3] +÷ 1160 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 1160 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1160 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1160 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1160 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 1160 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 1160 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1160 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 1160 × 0A03 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 1160 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1160 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 1160 ÷ 06DD ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1160 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 1160 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 1160 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 1160 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 1160 × 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 1160 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 1160 × 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 1160 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 1160 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 1160 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 1160 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 1160 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 ÷ 0904 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 1160 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1160 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 1160 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1160 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 1160 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0900 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 1160 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 1160 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1160 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] (Other) ÷ [0.3] -÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 1160 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 1160 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1160 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1160 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 1160 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1160 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JUNGSEONG FILLER (V) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 11A8 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (CR) ÷ [0.3] -÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ 11A8 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ 11A8 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (LF) ÷ [0.3] -÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 11A8 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 11A8 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 11A8 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ 11A8 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 11A8 ÷ 0000 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [5.0] (Control) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 11A8 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 11A8 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 11A8 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 11A8 × 0308 × 200C ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ 11A8 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 11A8 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 11A8 × 0A03 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 11A8 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 11A8 ÷ 06DD ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 11A8 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 11A8 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ 11A8 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 11A8 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ 11A8 × 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 11A8 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ 11A8 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 11A8 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ 11A8 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 11A8 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 ÷ 0904 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 11A8 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 11A8 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 11A8 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 11A8 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0900 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 094D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 11A8 × 0308 × 200D ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] (Other) ÷ [0.3] -÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ 11A8 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 11A8 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 11A8 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 11A8 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 11A8 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL JONGSEONG KIYEOK (T) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ AC00 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (CR) ÷ [0.3] -÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ AC00 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ AC00 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (LF) ÷ [0.3] -÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ AC00 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] -÷ AC00 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ AC00 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ AC00 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ AC00 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ AC00 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [5.0] (Control) ÷ [0.3] +÷ AC00 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ AC00 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC00 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC00 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC00 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ AC00 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC00 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC00 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC00 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ AC00 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC00 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC00 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC00 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC00 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ AC00 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC00 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC00 × 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC00 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ AC00 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC00 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ AC00 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC00 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ AC00 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC00 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC00 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC00 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC00 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC00 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC00 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC00 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC00 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC00 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] (Other) ÷ [0.3] -÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ AC00 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC00 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC00 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC00 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC00 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC00 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ AC01 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (CR) ÷ [0.3] -÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] +÷ AC01 × 0308 ÷ 000D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] ÷ AC01 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (LF) ÷ [0.3] -÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ AC01 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] -÷ AC01 × 0308 ÷ 0001 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ AC01 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ AC01 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] +÷ AC01 × 0308 ÷ 000A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ AC01 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [5.0] (Control) ÷ [0.3] +÷ AC01 × 0308 ÷ 0000 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ AC01 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC01 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ AC01 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC01 × 0308 × 200C ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] ÷ AC01 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ AC01 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC01 × 0308 ÷ 0600 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ AC01 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ AC01 × 0308 × 0A03 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC01 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ AC01 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC01 × 0308 ÷ 06DD ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ AC01 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] ÷ AC01 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ AC01 × 0308 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC01 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ AC01 × 0308 ÷ 1160 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] ÷ AC01 × 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ AC01 × 0308 ÷ 11A8 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] ÷ AC01 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ AC01 × 0308 ÷ AC00 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] ÷ AC01 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ AC01 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 × 0308 × 0903 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 × 0308 ÷ 0904 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 × 0308 ÷ 0D4E ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ AC01 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC01 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ AC01 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC01 × 0308 ÷ 231A ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ AC01 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 0300 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 0900 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ AC01 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 094D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ AC01 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC01 × 0308 × 200D ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (Other) ÷ [0.3] -÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0903 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0903 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0903 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] -÷ 0903 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0903 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] -÷ 0903 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0903 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] -÷ 0903 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0903 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0903 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0903 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0903 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0903 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0903 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0903 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0903 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0903 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0903 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0903 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0903 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0903 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0903 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0903 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0903 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0903 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0903 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0903 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0903 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0903 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0903 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0903 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0903 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0903 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0903 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0903 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0903 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] -÷ 0903 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0904 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0904 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0904 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] -÷ 0904 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0904 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] -÷ 0904 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0904 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] -÷ 0904 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0904 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0904 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0904 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0904 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0904 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0904 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0904 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0904 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0904 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0904 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0904 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0904 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0904 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0904 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0904 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0904 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0904 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0904 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0904 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0904 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0904 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0904 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0904 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0904 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0904 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0904 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0904 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0904 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [999.0] (Other) ÷ [0.3] -÷ 0904 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0D4E × 0020 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] SPACE (Other) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0020 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0D4E ÷ 000D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (CR) ÷ [0.3] -÷ 0D4E × 0308 ÷ 000D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0D4E ÷ 000A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (LF) ÷ [0.3] -÷ 0D4E × 0308 ÷ 000A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0D4E ÷ 0001 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [5.0] (Control) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0001 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0D4E × 200C ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0D4E × 0308 × 200C ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0D4E × 1F1E6 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0D4E × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0D4E × 0600 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0600 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0D4E × 0A03 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0D4E × 0308 × 0A03 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0D4E × 1100 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0D4E × 0308 ÷ 1100 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0D4E × 1160 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0D4E × 0308 ÷ 1160 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0D4E × 11A8 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0D4E × 0308 ÷ 11A8 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0D4E × AC00 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0D4E × 0308 ÷ AC00 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0D4E × AC01 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0D4E × 0308 ÷ AC01 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0D4E × 0903 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0308 × 0903 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0904 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0904 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0D4E ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0D4E ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0D4E × 0915 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0915 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0D4E × 231A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] WATCH (ExtPict) ÷ [0.3] -÷ 0D4E × 0308 ÷ 231A ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0D4E × 0300 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 0300 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0900 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 0900 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 094D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 094D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 200D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0308 × 200D ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0D4E × 0378 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.2] (Other) ÷ [0.3] -÷ 0D4E × 0308 ÷ 0378 ÷ # ÷ [0.2] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0915 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0915 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0915 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (CR) ÷ [0.3] -÷ 0915 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0915 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (LF) ÷ [0.3] -÷ 0915 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0915 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3] -÷ 0915 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0915 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0915 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0915 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0915 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0915 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0915 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0915 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0915 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0915 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0915 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0915 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0915 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0915 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0915 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0915 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0915 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0915 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0915 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0915 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0915 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0915 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0915 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0915 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0915 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0915 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0915 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] (Other) ÷ [0.3] -÷ 0915 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 231A ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 231A × 0308 ÷ 0020 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 231A ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (CR) ÷ [0.3] -÷ 231A × 0308 ÷ 000D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 231A ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (LF) ÷ [0.3] -÷ 231A × 0308 ÷ 000A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 231A ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [5.0] (Control) ÷ [0.3] -÷ 231A × 0308 ÷ 0001 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 231A × 200C ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 231A × 0308 × 200C ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 231A ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 231A × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 231A ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 231A × 0308 ÷ 0600 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 231A × 0A03 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 231A × 0308 × 0A03 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 231A ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 231A × 0308 ÷ 1100 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 231A ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 231A × 0308 ÷ 1160 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 231A ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 231A × 0308 ÷ 11A8 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 231A ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 231A × 0308 ÷ AC00 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 231A ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 231A × 0308 ÷ AC01 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 231A × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A × 0308 × 0903 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A ÷ 0904 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 231A × 0308 ÷ 0904 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 231A ÷ 0D4E ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A × 0308 ÷ 0D4E ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 231A ÷ 0915 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 231A × 0308 ÷ 0915 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 231A ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 231A × 0308 ÷ 231A ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 231A × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 0300 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 231A × 0900 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 0900 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 231A × 094D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 094D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 231A × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 231A × 0308 × 200D ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 231A ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) ÷ [999.0] (Other) ÷ [0.3] -÷ 231A × 0308 ÷ 0378 ÷ # ÷ [0.2] WATCH (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0300 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0300 × 0308 ÷ 0020 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0300 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0300 × 0308 ÷ 000D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0300 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0300 × 0308 ÷ 000A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0300 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0300 × 0308 ÷ 0001 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0300 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0300 × 0308 × 200C ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0300 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0300 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0300 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0300 × 0308 ÷ 0600 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0300 × 0A03 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0300 × 0308 × 0A03 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0300 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0300 × 0308 ÷ 1100 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0300 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0300 × 0308 ÷ 1160 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0300 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0300 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0300 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0300 × 0308 ÷ AC00 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0300 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0300 × 0308 ÷ AC01 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0300 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 × 0308 × 0903 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 ÷ 0904 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 × 0308 ÷ 0904 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 ÷ 0D4E ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 × 0308 ÷ 0D4E ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0300 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0300 × 0308 ÷ 0915 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0300 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0300 × 0308 ÷ 231A ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0300 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 0300 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0900 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 0900 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0300 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 094D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0300 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0300 × 0308 × 200D ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0300 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0300 × 0308 ÷ 0378 ÷ # ÷ [0.2] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0900 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0900 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0900 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0900 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0900 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0900 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0900 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0900 × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0900 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0900 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0900 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0900 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0900 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0900 × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0900 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0900 × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0900 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0900 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0900 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0900 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0900 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0900 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0900 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0900 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0900 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0900 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0900 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0900 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0900 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0900 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0900 × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0900 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0900 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0900 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0900 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0900 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0900 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 094D ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 094D × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 094D ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 094D × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 094D ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 094D × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 094D ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 094D × 0308 ÷ 0001 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 094D × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 094D × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 094D ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 094D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 094D ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 094D × 0308 ÷ 0600 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 094D × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 094D × 0308 × 0A03 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 094D ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 094D × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 094D ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 094D × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 094D ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 094D × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 094D ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 094D × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 094D ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 094D × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 094D × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 094D × 0308 ÷ 0904 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 094D ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D × 0308 ÷ 0D4E ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 094D ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 094D × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 094D ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 094D × 0308 ÷ 231A ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 094D × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 094D × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 0900 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 094D × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 094D × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 094D × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 094D ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 094D × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 200D ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200D × 0308 ÷ 0020 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 200D ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 200D × 0308 ÷ 000D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 200D ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 200D × 0308 ÷ 000A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 200D ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 200D × 0308 ÷ 0001 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 200D × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200D × 0308 × 200C ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 200D ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200D × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 200D ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200D × 0308 ÷ 0600 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 200D × 0A03 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200D × 0308 × 0A03 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 200D ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200D × 0308 ÷ 1100 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 200D ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200D × 0308 ÷ 1160 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 200D ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200D × 0308 ÷ 11A8 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 200D ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200D × 0308 ÷ AC00 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 200D ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200D × 0308 ÷ AC01 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 200D × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D × 0308 × 0903 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200D × 0308 ÷ 0904 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 200D ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D × 0308 ÷ 0D4E ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 200D ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200D × 0308 ÷ 0915 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 200D ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200D × 0308 ÷ 231A ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 200D × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 0300 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 200D × 0900 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 0900 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 200D × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 094D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 200D × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200D × 0308 × 200D ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 200D ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 200D × 0308 ÷ 0378 ÷ # ÷ [0.2] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 0378 ÷ 0020 ÷ # ÷ [0.2] (Other) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] -÷ 0378 ÷ 000D ÷ # ÷ [0.2] (Other) ÷ [5.0] (CR) ÷ [0.3] -÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (CR) ÷ [0.3] -÷ 0378 ÷ 000A ÷ # ÷ [0.2] (Other) ÷ [5.0] (LF) ÷ [0.3] -÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (LF) ÷ [0.3] -÷ 0378 ÷ 0001 ÷ # ÷ [0.2] (Other) ÷ [5.0] (Control) ÷ [0.3] -÷ 0378 × 0308 ÷ 0001 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [5.0] (Control) ÷ [0.3] -÷ 0378 × 200C ÷ # ÷ [0.2] (Other) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0378 × 0308 × 200C ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH NON-JOINER (Extend) ÷ [0.3] -÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] -÷ 0378 ÷ 0600 ÷ # ÷ [0.2] (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0378 × 0308 ÷ 0600 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) ÷ [0.3] -÷ 0378 × 0A03 ÷ # ÷ [0.2] (Other) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0378 × 0308 × 0A03 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] GURMUKHI SIGN VISARGA (SpacingMark) ÷ [0.3] -÷ 0378 ÷ 1100 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 0378 ÷ 1160 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] -÷ 0378 ÷ 11A8 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] -÷ 0378 ÷ AC00 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] -÷ 0378 ÷ AC01 ÷ # ÷ [0.2] (Other) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] -÷ 0378 × 0903 ÷ # ÷ [0.2] (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 ÷ 0904 ÷ # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 × 0308 ÷ 0904 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER SHORT A (ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 ÷ 0D4E ÷ # ÷ [0.2] (Other) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 × 0308 ÷ 0D4E ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] MALAYALAM LETTER DOT REPH (Prepend_ConjunctLinkingScripts) ÷ [0.3] -÷ 0378 ÷ 0915 ÷ # ÷ [0.2] (Other) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0378 × 0308 ÷ 0915 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0378 ÷ 231A ÷ # ÷ [0.2] (Other) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0378 × 0308 ÷ 231A ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] WATCH (ExtPict) ÷ [0.3] -÷ 0378 × 0300 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] COMBINING GRAVE ACCENT (Extend_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0900 ÷ # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 0900 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN INVERTED CANDRABINDU (Extend_ConjunctLinkingScripts_ExtCccZwj) ÷ [0.3] -÷ 0378 × 094D ÷ # ÷ [0.2] (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 094D ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [0.3] -÷ 0378 × 200D ÷ # ÷ [0.2] (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0378 ÷ 0378 ÷ # ÷ [0.2] (Other) ÷ [999.0] (Other) ÷ [0.3] -÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] (Other) ÷ [0.3] -÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (Other) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] -÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [0.3] -÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] ARABIC LETTER NOON (Other) ÷ [0.3] -÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] SPACE (Other) ÷ [0.3] +÷ AC01 × 0308 ÷ AC01 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ AC01 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC01 × 0308 ÷ 0915 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ AC01 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 00A9 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ AC01 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 0020 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ AC01 × 0308 ÷ 0378 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (CR) ÷ [0.3] +÷ 0915 × 0308 ÷ 000D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0915 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (LF) ÷ [0.3] +÷ 0915 × 0308 ÷ 000A ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0915 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [5.0] (Control) ÷ [0.3] +÷ 0915 × 0308 ÷ 0000 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0915 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0915 × 0308 × 094D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0915 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0915 × 0308 × 0300 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0915 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0915 × 0308 × 200C ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0915 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0915 × 0308 × 200D ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0915 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0915 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0915 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0915 × 0308 ÷ 06DD ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0915 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0915 × 0308 × 0903 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0915 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0915 × 0308 ÷ 1100 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0915 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0915 × 0308 ÷ 1160 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0915 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0915 × 0308 ÷ 11A8 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0915 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0915 × 0308 ÷ AC00 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0915 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0915 × 0308 ÷ AC01 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0915 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 0308 ÷ 0915 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0915 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 00A9 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0915 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 0020 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 × 0308 ÷ 0378 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 ÷ 000D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 00A9 × 0308 ÷ 000D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 00A9 ÷ 000A ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 00A9 × 0308 ÷ 000A ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 00A9 ÷ 0000 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0000 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 00A9 × 094D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 00A9 × 0308 × 094D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 00A9 × 0300 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 00A9 × 0308 × 0300 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 00A9 × 200C ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 00A9 × 0308 × 200C ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 00A9 × 200D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 00A9 × 0308 × 200D ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 00A9 ÷ 1F1E6 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 00A9 ÷ 06DD ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 00A9 × 0308 ÷ 06DD ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 00A9 × 0903 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 00A9 × 0308 × 0903 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 00A9 ÷ 1100 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1100 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 00A9 ÷ 1160 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 00A9 × 0308 ÷ 1160 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 00A9 ÷ 11A8 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 00A9 × 0308 ÷ 11A8 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 00A9 ÷ AC00 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 00A9 × 0308 ÷ AC00 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 00A9 ÷ AC01 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 00A9 × 0308 ÷ AC01 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 00A9 ÷ 0915 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0915 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 00A9 ÷ 00A9 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 00A9 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 00A9 ÷ 0020 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0020 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 ÷ 0378 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 00A9 × 0308 ÷ 0378 ÷ # ÷ [0.2] COPYRIGHT SIGN (ExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 ÷ 000D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 0020 × 0308 ÷ 000D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0020 ÷ 000A ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 0020 × 0308 ÷ 000A ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0020 ÷ 0000 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 0020 × 0308 ÷ 0000 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0020 × 094D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0020 × 0308 × 094D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0020 × 0300 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 0308 × 0300 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 200C ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0020 × 0308 × 200C ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0020 × 200D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0020 × 0308 × 200D ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0020 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0020 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0020 ÷ 06DD ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0020 × 0308 ÷ 06DD ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0020 × 0903 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0020 × 0308 × 0903 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0020 ÷ 1100 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0020 × 0308 ÷ 1100 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0020 ÷ 1160 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0020 × 0308 ÷ 1160 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0020 ÷ 11A8 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0020 × 0308 ÷ 11A8 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0020 ÷ AC00 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0020 × 0308 ÷ AC00 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0020 ÷ AC01 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0020 × 0308 ÷ AC01 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0020 ÷ 0915 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0020 × 0308 ÷ 0915 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0020 ÷ 00A9 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 00A9 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0020 ÷ 0020 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 0020 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 ÷ 0378 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0020 × 0308 ÷ 0378 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 ÷ 000D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (CR) ÷ [0.3] +÷ 0378 × 0308 ÷ 000D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (CR) ÷ [0.3] +÷ 0378 ÷ 000A ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [0.3] +÷ 0378 × 0308 ÷ 000A ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (LF) ÷ [0.3] +÷ 0378 ÷ 0000 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [5.0] (Control) ÷ [0.3] +÷ 0378 × 0308 ÷ 0000 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [5.0] (Control) ÷ [0.3] +÷ 0378 × 094D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0378 × 0308 × 094D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [0.3] +÷ 0378 × 0300 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0378 × 0308 × 0300 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING GRAVE ACCENT (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0378 × 200C ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0378 × 0308 × 200C ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH NON-JOINER (ExtendmConjunctLinkermConjunctExtender) ÷ [0.3] +÷ 0378 × 200D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0378 × 0308 × 200D ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0378 ÷ 1F1E6 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0378 × 0308 ÷ 1F1E6 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) ÷ [0.3] +÷ 0378 ÷ 06DD ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0378 × 0308 ÷ 06DD ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] ARABIC END OF AYAH (Prepend) ÷ [0.3] +÷ 0378 × 0903 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0378 × 0308 × 0903 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [0.3] +÷ 0378 ÷ 1100 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0378 × 0308 ÷ 1100 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] +÷ 0378 ÷ 1160 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0378 × 0308 ÷ 1160 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JUNGSEONG FILLER (V) ÷ [0.3] +÷ 0378 ÷ 11A8 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0378 × 0308 ÷ 11A8 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL JONGSEONG KIYEOK (T) ÷ [0.3] +÷ 0378 ÷ AC00 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0378 × 0308 ÷ AC00 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GA (LV) ÷ [0.3] +÷ 0378 ÷ AC01 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0378 × 0308 ÷ AC01 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] HANGUL SYLLABLE GAG (LVT) ÷ [0.3] +÷ 0378 ÷ 0915 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0378 × 0308 ÷ 0915 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 0378 ÷ 00A9 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 00A9 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] COPYRIGHT SIGN (ExtPict) ÷ [0.3] +÷ 0378 ÷ 0020 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 0020 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 ÷ 0378 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0378 × 0308 ÷ 0378 ÷ # ÷ [0.2] (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 000D × 000A ÷ 0061 ÷ 000A ÷ 0308 ÷ # ÷ [0.2] (CR) × [3.0] (LF) ÷ [4.0] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [5.0] (LF) ÷ [4.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0061 × 0308 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 0020 × 200D ÷ 0646 ÷ # ÷ [0.2] SPACE (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] ARABIC LETTER NOON (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0646 × 200D ÷ 0020 ÷ # ÷ [0.2] ARABIC LETTER NOON (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] SPACE (XXmLinkingConsonantmExtPict) ÷ [0.3] ÷ 1100 × 1100 ÷ # ÷ [0.2] HANGUL CHOSEONG KIYEOK (L) × [6.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC00 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GA (LV) × [7.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] ÷ AC01 × 11A8 ÷ 1100 ÷ # ÷ [0.2] HANGUL SYLLABLE GAG (LVT) × [8.0] HANGUL JONGSEONG KIYEOK (T) ÷ [999.0] HANGUL CHOSEONG KIYEOK (L) ÷ [0.3] -÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 × 1F1E9 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [0.3] -÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 × 0903 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark_ConjunctLinkingScripts) ÷ [999.0] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 0061 ÷ 0600 × 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (Other) ÷ [0.3] -÷ 1F476 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3] -÷ 0061 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) ÷ [0.3] -÷ 0061 × 1F3FF ÷ 1F476 × 200D × 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] -÷ 1F476 × 1F3FF × 0308 × 200D × 1F476 × 1F3FF ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) × [9.0] COMBINING DIAERESIS (Extend_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ExtCccZwj) ÷ [0.3] -÷ 1F6D1 × 200D × 1F6D1 ÷ # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] -÷ 0061 × 200D ÷ 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] -÷ 2701 × 200D × 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [11.0] UPPER BLADE SCISSORS (Other) ÷ [0.3] -÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) ÷ [999.0] UPPER BLADE SCISSORS (Other) ÷ [0.3] -÷ 0915 ÷ 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 093C × 200D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 093C × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctLinkingScripts_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] ZERO WIDTH JOINER (ZWJ_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 0924 × 094D × 092F ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER YA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D ÷ 0061 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] LATIN SMALL LETTER A (Other) ÷ [0.3] -÷ 0061 × 094D ÷ 0924 ÷ # ÷ [0.2] LATIN SMALL LETTER A (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 003F × 094D ÷ 0924 ÷ # ÷ [0.2] QUESTION MARK (Other) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) ÷ [999.0] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] -÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (ConjunctLinkingScripts_LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinkingScripts_ConjunctLinker_ExtCccZwj) × [9.3] DEVANAGARI LETTER TA (ConjunctLinkingScripts_LinkingConsonant) ÷ [0.3] +÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [12.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 × 200D ÷ 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 200D ÷ 1F1E7 × 1F1E8 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 1F1E6 × 1F1E7 ÷ 1F1E8 × 1F1E9 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER A (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER B (RI) ÷ [999.0] REGIONAL INDICATOR SYMBOL LETTER C (RI) × [13.0] REGIONAL INDICATOR SYMBOL LETTER D (RI) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [0.3] +÷ 0061 × 0308 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 0903 ÷ 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.1] DEVANAGARI SIGN VISARGA (SpacingMark) ÷ [999.0] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 ÷ 0600 × 0062 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [999.0] ARABIC NUMBER SIGN (Prepend) × [9.2] LATIN SMALL LETTER B (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 1F476 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) ÷ [0.3] +÷ 0061 × 1F3FF ÷ 1F476 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) ÷ [0.3] +÷ 0061 × 1F3FF ÷ 1F476 × 200D × 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BABY (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 1F476 × 1F3FF × 0308 × 200D × 1F476 × 1F3FF ÷ # ÷ [0.2] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) × [9.0] COMBINING DIAERESIS (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] BABY (ExtPict) × [9.0] EMOJI MODIFIER FITZPATRICK TYPE-6 (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1F6D1 × 200D × 1F6D1 ÷ # ÷ [0.2] OCTAGONAL SIGN (ExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) × [11.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ 1F6D1 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] OCTAGONAL SIGN (ExtPict) ÷ [0.3] +÷ 2701 × 200D ÷ 2701 ÷ # ÷ [0.2] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 200D ÷ 2701 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] ZERO WIDTH JOINER (ZWJ) ÷ [999.0] UPPER BLADE SCISSORS (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0915 ÷ 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 093C × 200D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctExtendermConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 093C × 094D × 200D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN NUKTA (Extend_ConjunctExtendermConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] ZERO WIDTH JOINER (ZWJ) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 0924 × 094D × 092F ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER YA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D ÷ 0061 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) ÷ [0.3] +÷ 0061 × 094D ÷ 0924 ÷ # ÷ [0.2] LATIN SMALL LETTER A (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 003F × 094D ÷ 0924 ÷ # ÷ [0.2] QUESTION MARK (XXmLinkingConsonantmExtPict) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) ÷ [999.0] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0915 × 094D × 094D × 0924 ÷ # ÷ [0.2] DEVANAGARI LETTER KA (LinkingConsonant) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.0] DEVANAGARI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] DEVANAGARI LETTER TA (LinkingConsonant) ÷ [0.3] +÷ 0AB8 × 0AFB × 0ACD × 0AB8 × 0AFB ÷ # ÷ [0.2] GUJARATI LETTER SA (LinkingConsonant) × [9.0] GUJARATI SIGN SHADDA (Extend_ConjunctExtendermConjunctLinker) × [9.0] GUJARATI SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] GUJARATI LETTER SA (LinkingConsonant) × [9.0] GUJARATI SIGN SHADDA (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1019 × 1039 × 1018 ÷ 102C × 1037 ÷ # ÷ [0.2] MYANMAR LETTER MA (LinkingConsonant) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER BHA (LinkingConsonant) ÷ [999.0] MYANMAR VOWEL SIGN AA (XXmLinkingConsonantmExtPict) × [9.0] MYANMAR SIGN DOT BELOW (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1004 × 103A × 1039 × 1011 × 1039 × 1011 ÷ # ÷ [0.2] MYANMAR LETTER NGA (LinkingConsonant) × [9.0] MYANMAR SIGN ASAT (Extend_ConjunctExtendermConjunctLinker) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER THA (LinkingConsonant) × [9.0] MYANMAR SIGN VIRAMA (Extend_ConjunctLinker) × [9.3] MYANMAR LETTER THA (LinkingConsonant) ÷ [0.3] +÷ 1B12 × 1B01 ÷ 1B32 × 1B44 × 1B2F ÷ 1B32 × 1B44 × 1B22 × 1B44 × 1B2C ÷ 1B32 × 1B44 × 1B22 × 1B38 ÷ # ÷ [0.2] BALINESE LETTER OKARA TEDUNG (XXmLinkingConsonantmExtPict) × [9.0] BALINESE SIGN ULU CANDRA (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER WA (LinkingConsonant) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER TA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER YA (LinkingConsonant) ÷ [999.0] BALINESE LETTER SA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER TA (LinkingConsonant) × [9.0] BALINESE VOWEL SIGN SUKU (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 179F × 17D2 × 178F × 17D2 × 179A × 17B8 ÷ # ÷ [0.2] KHMER LETTER SA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER LETTER TA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER LETTER RO (LinkingConsonant) × [9.0] KHMER VOWEL SIGN II (Extend_ConjunctExtendermConjunctLinker) ÷ [0.3] +÷ 1B26 ÷ 1B17 × 1B44 × 1B13 ÷ # ÷ [0.2] BALINESE LETTER NA (LinkingConsonant) ÷ [999.0] BALINESE LETTER NGA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER KA (LinkingConsonant) ÷ [0.3] +÷ 1B27 ÷ 1B13 × 1B44 × 1B0B ÷ 1B0B × 1B04 ÷ # ÷ [0.2] BALINESE LETTER PA (LinkingConsonant) ÷ [999.0] BALINESE LETTER KA (LinkingConsonant) × [9.0] BALINESE ADEG ADEG (Extend_ConjunctLinker) × [9.3] BALINESE LETTER RA REPA (LinkingConsonant) ÷ [999.0] BALINESE LETTER RA REPA (LinkingConsonant) × [9.1] BALINESE SIGN BISAH (SpacingMark) ÷ [0.3] +÷ 1795 × 17D2 × 17AF ÷ 1798 ÷ # ÷ [0.2] KHMER LETTER PHA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER INDEPENDENT VOWEL QE (LinkingConsonant) ÷ [999.0] KHMER LETTER MO (LinkingConsonant) ÷ [0.3] +÷ 17A0 × 17D2 × 17AB ÷ 1791 × 17D0 ÷ 1799 ÷ # ÷ [0.2] KHMER LETTER HA (LinkingConsonant) × [9.0] KHMER SIGN COENG (Extend_ConjunctLinker) × [9.3] KHMER INDEPENDENT VOWEL RY (LinkingConsonant) ÷ [999.0] KHMER LETTER TO (LinkingConsonant) × [9.0] KHMER SIGN SAMYOK SANNYA (Extend_ConjunctExtendermConjunctLinker) ÷ [999.0] KHMER LETTER YO (LinkingConsonant) ÷ [0.3] # -# Lines: 1093 +# Lines: 766 # # EOF diff --git a/src/java.base/share/data/unicodedata/emoji/emoji-data.txt b/src/java.base/share/data/unicodedata/emoji/emoji-data.txt index 12f83273cf5..450252c4df3 100644 --- a/src/java.base/share/data/unicodedata/emoji/emoji-data.txt +++ b/src/java.base/share/data/unicodedata/emoji/emoji-data.txt @@ -1,11 +1,11 @@ # emoji-data.txt -# Date: 2024-05-01, 21:25:24 GMT -# Copyright (c) 2024 Unicode, Inc. +# Date: 2025-07-25, 17:54:31 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # Emoji Data for UTS #51 -# Used with Emoji Version 16.0 and subsequent minor revisions (if any) +# Version: 17.0 # # For documentation and usage, see https://www.unicode.org/reports/tr51 # @@ -340,6 +340,7 @@ 1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji # E17.0 [1] (🛘) landslide 1F6DC ; Emoji # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat @@ -408,6 +409,8 @@ 1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji # E16.0 [1] (🪉) harp +1FA8A ; Emoji # E17.0 [1] (🪊) trombone +1FA8E ; Emoji # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock @@ -421,6 +424,8 @@ 1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -430,10 +435,12 @@ 1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1431 +# Total elements: 1438 # ================================================ @@ -640,6 +647,7 @@ 1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji_Presentation # E17.0 [1] (🛘) landslide 1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival @@ -704,6 +712,8 @@ 1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji_Presentation # E16.0 [1] (🪉) harp +1FA8A ; Emoji_Presentation # E17.0 [1] (🪊) trombone +1FA8E ; Emoji_Presentation # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji_Presentation # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock @@ -717,6 +727,8 @@ 1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji_Presentation # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji_Presentation # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji_Presentation # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -726,10 +738,12 @@ 1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji_Presentation # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji_Presentation # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji_Presentation # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1212 +# Total elements: 1219 # ================================================ @@ -827,7 +841,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right 231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done 2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard -2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL 23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button 23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button 23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button @@ -844,106 +857,63 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud 2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman 2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet -2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR -2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION 260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone -260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX 2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check -2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X 2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage -2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE 2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock -2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX 261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up -261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX 2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones -2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN 2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard -2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH 2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross -2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM 262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent -262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE 262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol 262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang -2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH 2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face 263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face -263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY 2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign -2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH 2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign -2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO 2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces -2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT 265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn 2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit -2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT 2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit -2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT 2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit -2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT 2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs -2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS 267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol -267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL 267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity 267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol -2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 -2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG 2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick 2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor 2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords 2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol 2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic -2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER 2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear -269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES 269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis -269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT 26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage -26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN 26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol -26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN 26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle -26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL 26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn -26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE 26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball -26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING 26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud -26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN 26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain -26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR 26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus 26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick -26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING 26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet -26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES 26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains 26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry -26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD 26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine 26EA ; Extended_Pictographic# E0.6 [1] (⛪) church -26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE 26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground 26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole 26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry 26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat -26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS 26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball 26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent -26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL 26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump -26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS 2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors -2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS 2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button 2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand 270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand -270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL 270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil -2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB 2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib 2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark 2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply @@ -959,7 +929,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark 2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation 2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart -2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET 2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide 27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow 27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop @@ -973,19 +942,19 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark 3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button 3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button -1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND 1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon -1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F02C..1F02F ; Extended_Pictographic# E0.0 [4] (🀬..🀯) .. +1F094..1F09F ; Extended_Pictographic# E0.0 [12] (🂔..🂟) .. +1F0AF..1F0B0 ; Extended_Pictographic# E0.0 [2] (🂯..🂰) .. +1F0C0 ; Extended_Pictographic# E0.0 [1] (🃀) 1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker -1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (🃐..🃿) .. -1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH -1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL -1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F0D0 ; Extended_Pictographic# E0.0 [1] (🃐) +1F0F6..1F0FF ; Extended_Pictographic# E0.0 [10] (🃶..🃿) .. 1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) 1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button 1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type) 1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button -1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..🇥) MASK WORK SYMBOL.. +1F1AE..1F1E5 ; Extended_Pictographic# E0.0 [56] (🆮..🇥) .. 1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button 1F203..1F20F ; Extended_Pictographic# E0.0 [13] (🈃..🈏) .. 1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button @@ -994,7 +963,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (🈼..🈿) .. 1F249..1F24F ; Extended_Pictographic# E0.0 [7] (🉉..🉏) .. 1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button -1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (🉒..🋿) .. +1F252..1F25F ; Extended_Pictographic# E0.0 [14] (🉒..🉟) .. +1F266..1F2FF ; Extended_Pictographic# E0.0 [154] (🉦..🋿) .. 1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way 1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas 1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia @@ -1010,7 +980,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face 1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star 1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer -1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN 1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face 1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito 1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling @@ -1026,11 +995,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate 1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn 1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap -1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS 1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon -1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS 1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs -1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES 1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets 1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing 1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal @@ -1045,11 +1011,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office 1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office 1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle -1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT 1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag 1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag 1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette -1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE 1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label 1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora 1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit @@ -1086,7 +1050,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash 1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette 1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector -1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO 1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button 1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows 1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker @@ -1097,51 +1060,30 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol 1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope 1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button -1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS 1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove 1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah -1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA 1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock 1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty -1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK 1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock -1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY 1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick 1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing -1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE 1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips -1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL 1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon -1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN 1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed -1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND 1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute -1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX 1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart 1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer -1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS 1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer -1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE 1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball -1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE 1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture -1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER 1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet -1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES 1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar -1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL 1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper -1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART 1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger -1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS 1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head -1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT 1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble -1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE 1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble -1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD 1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot -1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK 1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map 1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai 1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face @@ -1210,26 +1152,22 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower 1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath 1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage -1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL 1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp 1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed 1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed 1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship 1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart -1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA 1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator -1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) .. +1F6D8 ; Extended_Pictographic# E17.0 [1] (🛘) landslide +1F6D9..1F6DB ; Extended_Pictographic# E0.0 [3] (🛙..🛛) .. 1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat -1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE 1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane -1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE 1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival 1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (🛭..🛯) .. 1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite -1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE 1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship 1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe 1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer @@ -1237,8 +1175,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw 1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate 1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) .. -1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS -1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE.. +1F7DA..1F7DF ; Extended_Pictographic# E0.0 [6] (🟚..🟟) .. 1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square 1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) .. 1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign @@ -1247,7 +1184,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F848..1F84F ; Extended_Pictographic# E0.0 [8] (🡈..🡏) .. 1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (🡚..🡟) .. 1F888..1F88F ; Extended_Pictographic# E0.0 [8] (🢈..🢏) .. -1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (🢮..🣿) .. +1F8AE..1F8AF ; Extended_Pictographic# E0.0 [2] (🢮..🢯) .. +1F8BC..1F8BF ; Extended_Pictographic# E0.0 [4] (🢼..🢿) .. +1F8C2..1F8CF ; Extended_Pictographic# E0.0 [14] (🣂..🣏) .. +1F8D9..1F8FF ; Extended_Pictographic# E0.0 [39] (🣙..🣿) .. 1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers 1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand 1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns @@ -1293,7 +1233,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person 1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks 1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet -1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING.. +1FA58..1FA5F ; Extended_Pictographic# E0.0 [8] (🩘..🩟) .. +1FA6E..1FA6F ; Extended_Pictographic# E0.0 [2] (🩮..🩯) .. 1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts 1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal 1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart @@ -1304,7 +1245,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Extended_Pictographic# E16.0 [1] (🪉) harp -1FA8A..1FA8E ; Extended_Pictographic# E0.0 [5] (🪊..🪎) .. +1FA8A ; Extended_Pictographic# E17.0 [1] (🪊) trombone +1FA8B..1FA8D ; Extended_Pictographic# E0.0 [3] (🪋..🪍) .. +1FA8E ; Extended_Pictographic# E17.0 [1] (🪎) treasure chest 1FA8F ; Extended_Pictographic# E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock @@ -1318,7 +1261,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Extended_Pictographic# E16.0 [1] (🫆) fingerprint -1FAC7..1FACD ; Extended_Pictographic# E0.0 [7] (🫇..🫍) .. +1FAC7 ; Extended_Pictographic# E0.0 [1] (🫇) +1FAC8 ; Extended_Pictographic# E17.0 [1] (🫈) hairy creature +1FAC9..1FACC ; Extended_Pictographic# E0.0 [4] (🫉..🫌) .. +1FACD ; Extended_Pictographic# E17.0 [1] (🫍) orca 1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -1329,12 +1275,14 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face 1FAE9 ; Extended_Pictographic# E16.0 [1] (🫩) face with bags under eyes -1FAEA..1FAEF ; Extended_Pictographic# E0.0 [6] (🫪..🫯) .. +1FAEA ; Extended_Pictographic# E17.0 [1] (🫪) distorted face +1FAEB..1FAEE ; Extended_Pictographic# E0.0 [4] (🫫..🫮) .. +1FAEF ; Extended_Pictographic# E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand 1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) .. 1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) .. -# Total elements: 3537 +# Total elements: 2848 #EOF diff --git a/src/java.base/share/legal/icu.md b/src/java.base/share/legal/icu.md index e27193e10be..634fea70d92 100644 --- a/src/java.base/share/legal/icu.md +++ b/src/java.base/share/legal/icu.md @@ -1,4 +1,4 @@ -## International Components for Unicode (ICU4J) v76.1 +## International Components for Unicode (ICU4J) v78.1 ### ICU4J License ``` @@ -6,7 +6,7 @@ UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE -Copyright © 2016-2024 Unicode, Inc. +Copyright © 2016-2025 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR diff --git a/src/java.base/share/legal/unicode.md b/src/java.base/share/legal/unicode.md index 8bd2ed8bd13..a1009c70c1c 100644 --- a/src/java.base/share/legal/unicode.md +++ b/src/java.base/share/legal/unicode.md @@ -1,4 +1,4 @@ -## The Unicode Standard, Unicode Character Database, Version 16.0.0 +## The Unicode Standard, Unicode Character Database, Version 17.0.0 ### Unicode Character Database ``` @@ -7,7 +7,7 @@ UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE -Copyright © 1991-2024 Unicode, Inc. +Copyright © 1991-2025 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR From 27a38d9093958ae4851bc61b8d3f0d71dc780823 Mon Sep 17 00:00:00 2001 From: Chad Rakoczy Date: Tue, 18 Nov 2025 20:28:33 +0000 Subject: [PATCH 010/114] 8371121: compiler/whitebox/DeoptimizeRelocatedNMethod.java fails with C1 Reviewed-by: thartmann, chagedorn --- .../whitebox/DeoptimizeRelocatedNMethod.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java b/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java index 42f29044e8c..c7cf355259b 100644 --- a/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java +++ b/test/hotspot/jtreg/compiler/whitebox/DeoptimizeRelocatedNMethod.java @@ -28,6 +28,8 @@ * @library /test/lib / * @modules java.base/jdk.internal.misc java.management * @requires vm.opt.DeoptimizeALot != true + * @requires vm.flavor == "server" & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) + * @requires !vm.emulatedClient * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbatch -XX:+SegmentedCodeCache @@ -55,8 +57,8 @@ public class DeoptimizeRelocatedNMethod { // Verify not initially compiled CompilerWhiteBoxTest.checkNotCompiled(method, false); - // Call function enough to compile - callFunction(); + // Enqueue method for compilation. This will block since background compilation is disabled + WHITE_BOX.enqueueMethodForCompilation(method, CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); // Verify now compiled CompilerWhiteBoxTest.checkCompiled(method, false); @@ -91,13 +93,6 @@ public class DeoptimizeRelocatedNMethod { function(); } - // Call function multiple times to trigger compilation - private static void callFunction() { - for (int i = 0; i < CompilerWhiteBoxTest.THRESHOLD; i++) { - function(); - } - } - public static void function() { FUNCTION_RESULT = Math.random(); } From 66fb015267058f9b5e6788eaeaa758be56ba553e Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Tue, 18 Nov 2025 21:51:28 +0000 Subject: [PATCH 011/114] 8357579: Compilation error: first argument in call to 'memset' is a pointer to non-trivially copyable type Co-authored-by: Ioi Lam Reviewed-by: iklam, asmehra --- src/hotspot/share/oops/resolvedFieldEntry.cpp | 13 +++- src/hotspot/share/oops/resolvedFieldEntry.hpp | 60 ++++--------------- .../share/oops/resolvedMethodEntry.cpp | 17 ++++-- .../share/oops/resolvedMethodEntry.hpp | 51 +++++++--------- 4 files changed, 52 insertions(+), 89 deletions(-) diff --git a/src/hotspot/share/oops/resolvedFieldEntry.cpp b/src/hotspot/share/oops/resolvedFieldEntry.cpp index dd0a81ce0f3..83f1a6919a6 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.cpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.cpp @@ -23,8 +23,17 @@ */ #include "cds/archiveBuilder.hpp" +#include "cppstdlib/type_traits.hpp" #include "oops/resolvedFieldEntry.hpp" +static_assert(std::is_trivially_copyable_v); + +// Detect inadvertently introduced trailing padding. +class ResolvedFieldEntryWithExtra : public ResolvedFieldEntry { + u1 _extra_field; +}; +static_assert(sizeof(ResolvedFieldEntryWithExtra) > sizeof(ResolvedFieldEntry)); + void ResolvedFieldEntry::print_on(outputStream* st) const { st->print_cr("Field Entry:"); @@ -45,9 +54,7 @@ void ResolvedFieldEntry::print_on(outputStream* st) const { #if INCLUDE_CDS void ResolvedFieldEntry::remove_unshareable_info() { - u2 saved_cpool_index = _cpool_index; - memset(this, 0, sizeof(*this)); - _cpool_index = saved_cpool_index; + *this = ResolvedFieldEntry(_cpool_index); } void ResolvedFieldEntry::mark_and_relocate() { diff --git a/src/hotspot/share/oops/resolvedFieldEntry.hpp b/src/hotspot/share/oops/resolvedFieldEntry.hpp index 1df4ae8d956..77ad4815730 100644 --- a/src/hotspot/share/oops/resolvedFieldEntry.hpp +++ b/src/hotspot/share/oops/resolvedFieldEntry.hpp @@ -43,6 +43,9 @@ // Field bytecodes start with a constant pool index as their operand, which is then rewritten to // a "field index", which is an index into the array of ResolvedFieldEntry. +// The explicit paddings are necessary for generating deterministic CDS archives. They prevent +// the C++ compiler from potentially inserting random values in unused gaps. + //class InstanceKlass; class ResolvedFieldEntry { friend class VMStructs; @@ -54,17 +57,9 @@ class ResolvedFieldEntry { u1 _tos_state; // TOS state u1 _flags; // Flags: [0000|00|is_final|is_volatile] u1 _get_code, _put_code; // Get and Put bytecodes of the field - - void copy_from(const ResolvedFieldEntry& other) { - _field_holder = other._field_holder; - _field_offset = other._field_offset; - _field_index = other._field_index; - _cpool_index = other._cpool_index; - _tos_state = other._tos_state; - _flags = other._flags; - _get_code = other._get_code; - _put_code = other._put_code; - } +#ifdef _LP64 + u4 _padding; +#endif public: ResolvedFieldEntry(u2 cpi) : @@ -75,48 +70,15 @@ public: _tos_state(0), _flags(0), _get_code(0), - _put_code(0) {} + _put_code(0) +#ifdef _LP64 + , _padding(0) +#endif + {} ResolvedFieldEntry() : ResolvedFieldEntry(0) {} - // Notes on copy constructor, copy assignment operator, and copy_from(). - // These are necessary for generating deterministic CDS archives. - // - // We have some unused padding on 64-bit platforms (4 bytes at the tail end). - // - // When ResolvedFieldEntries in a ConstantPoolCache are allocated from the metaspace, - // their entire content (including the padding) is filled with zeros. They are - // then initialized with initialize_resolved_entries_array() in cpCache.cpp from a - // GrowableArray. - // - // The GrowableArray is initialized in rewriter.cpp, using ResolvedFieldEntries that - // are originally allocated from the C++ stack. Functions like GrowableArray::expand_to() - // will also allocate ResolvedFieldEntries from the stack. These may have random bits - // in the padding as the C++ compiler is allowed to leave the padding in uninitialized - // states. - // - // If we use the default copy constructor and/or default copy assignment operator, - // the random padding will be copied into the GrowableArray, from there - // to the ConstantPoolCache, and eventually to the CDS archive. As a result, the - // CDS archive will contain random bits, causing failures in - // test/hotspot/jtreg/runtime/cds/DeterministicDump.java (usually on Windows). - // - // By using copy_from(), we can prevent the random padding from being copied, - // ensuring that the ResolvedFieldEntries in a ConstantPoolCache (and thus the - // CDS archive) will have all zeros in the padding. - - // Copy constructor - ResolvedFieldEntry(const ResolvedFieldEntry& other) { - copy_from(other); - } - - // Copy assignment operator - ResolvedFieldEntry& operator=(const ResolvedFieldEntry& other) { - copy_from(other); - return *this; - } - // Bit shift to get flags // Note: Only two flags exists at the moment but more could be added enum { diff --git a/src/hotspot/share/oops/resolvedMethodEntry.cpp b/src/hotspot/share/oops/resolvedMethodEntry.cpp index 2dc533dbee0..bb96ca86012 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.cpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.cpp @@ -23,9 +23,18 @@ */ #include "cds/archiveBuilder.hpp" +#include "cppstdlib/type_traits.hpp" #include "oops/method.hpp" #include "oops/resolvedMethodEntry.hpp" +static_assert(std::is_trivially_copyable_v); + +// Detect inadvertently introduced trailing padding. +class ResolvedMethodEntryWithExtra : public ResolvedMethodEntry { + u1 _extra_field; +}; +static_assert(sizeof(ResolvedMethodEntryWithExtra) > sizeof(ResolvedMethodEntry)); + bool ResolvedMethodEntry::check_no_old_or_obsolete_entry() { // return false if m refers to a non-deleted old or obsolete method if (_method != nullptr) { @@ -39,14 +48,10 @@ bool ResolvedMethodEntry::check_no_old_or_obsolete_entry() { void ResolvedMethodEntry::reset_entry() { if (has_resolved_references_index()) { u2 saved_resolved_references_index = _entry_specific._resolved_references_index; - u2 saved_cpool_index = _cpool_index; - memset(this, 0, sizeof(*this)); + *this = ResolvedMethodEntry(_cpool_index); set_resolved_references_index(saved_resolved_references_index); - _cpool_index = saved_cpool_index; } else { - u2 saved_cpool_index = _cpool_index; - memset(this, 0, sizeof(*this)); - _cpool_index = saved_cpool_index; + *this = ResolvedMethodEntry(_cpool_index); } } diff --git a/src/hotspot/share/oops/resolvedMethodEntry.hpp b/src/hotspot/share/oops/resolvedMethodEntry.hpp index c95efb751e9..802cf252a6d 100644 --- a/src/hotspot/share/oops/resolvedMethodEntry.hpp +++ b/src/hotspot/share/oops/resolvedMethodEntry.hpp @@ -61,6 +61,9 @@ // pool entry and thus the same resolved method entry. // The is_vfinal flag indicates method pointer for a final method or an index. +// The explicit paddings are necessary for generating deterministic CDS archives. They prevent +// the C++ compiler from potentially inserting random values in unused gaps. + class InstanceKlass; class ResolvedMethodEntry { friend class VMStructs; @@ -70,6 +73,7 @@ class ResolvedMethodEntry { InstanceKlass* _interface_klass; // for interface and static u2 _resolved_references_index; // Index of resolved references array that holds the appendix oop for invokehandle u2 _table_index; // vtable/itable index for virtual and interface calls + // The padding field is unused here, as the parent constructor zeroes the union. } _entry_specific; u2 _cpool_index; // Constant pool index @@ -80,51 +84,36 @@ class ResolvedMethodEntry { #ifdef ASSERT bool _has_interface_klass; bool _has_table_index; +# ifdef _LP64 + u2 _padding1; + u4 _padding2; +# else + u1 _padding1; + u1 _padding2; +# endif #endif - // See comments in resolvedFieldEntry.hpp about copy_from and padding. - // We have unused padding on debug builds. - void copy_from(const ResolvedMethodEntry& other) { - _method = other._method; - _entry_specific = other._entry_specific; - _cpool_index = other._cpool_index; - _number_of_parameters = other._number_of_parameters; - _tos_state = other._tos_state; - _flags = other._flags; - _bytecode1 = other._bytecode1; - _bytecode2 = other._bytecode2; -#ifdef ASSERT - _has_interface_klass = other._has_interface_klass; - _has_table_index = other._has_table_index; -#endif - } - // Constructors public: ResolvedMethodEntry(u2 cpi) : _method(nullptr), + _entry_specific{nullptr}, _cpool_index(cpi), _number_of_parameters(0), _tos_state(0), _flags(0), _bytecode1(0), - _bytecode2(0) { - _entry_specific._interface_klass = nullptr; - DEBUG_ONLY(_has_interface_klass = false;) - DEBUG_ONLY(_has_table_index = false;) - } + _bytecode2(0) +#ifdef ASSERT + , _has_interface_klass(false), + _has_table_index(false), + _padding1(0), + _padding2(0) +#endif + {} ResolvedMethodEntry() : ResolvedMethodEntry(0) {} - ResolvedMethodEntry(const ResolvedMethodEntry& other) { - copy_from(other); - } - - ResolvedMethodEntry& operator=(const ResolvedMethodEntry& other) { - copy_from(other); - return *this; - } - // Bit shift to get flags enum { From b086e34f7170631d7568dc50a7c075dc9c2f173b Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 18 Nov 2025 21:51:54 +0000 Subject: [PATCH 012/114] 8371771: CDS test SharedStringsStress.java failed with insufficient heap Reviewed-by: kvn --- .../runtime/cds/appcds/sharedStrings/SharedStringsStress.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java b/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java index c51a67c445b..4d176c949c6 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SharedStringsStress.java @@ -83,8 +83,10 @@ public class SharedStringsStress { dumpOutput.shouldContain("string table array (primary)"); dumpOutput.shouldContain("string table array (secondary)"); + // We could create up to 26MB of archived heap objects. Run with enough Xms to ensure + // SerialGC can accommodate the archived objects during VM start up. OutputAnalyzer execOutput = TestCommon.exec(appJar, - TestCommon.concat(vmOptionsPrefix, "-Xlog:aot,cds", "HelloString")); + TestCommon.concat(vmOptionsPrefix, "-Xlog:aot,cds", "-Xms128m", "HelloString")); TestCommon.checkExec(execOutput); } } From 256a9beffc106d6657a912a33f97e7f97acbb1e1 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Tue, 18 Nov 2025 22:29:37 +0000 Subject: [PATCH 013/114] 8280469: C2: CHA support for interface calls when inlining through method handle linker Reviewed-by: kvn, roland --- src/hotspot/share/ci/ciInstanceKlass.cpp | 3 +- src/hotspot/share/ci/ciInstanceKlass.hpp | 1 + src/hotspot/share/opto/doCall.cpp | 27 ++- .../cha/StrengthReduceInterfaceCall.java | 205 ++++++++++++++++-- 4 files changed, 204 insertions(+), 32 deletions(-) diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 9bbf005356c..64b9acf9146 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -605,7 +605,7 @@ bool ciInstanceKlass::is_leaf_type() { if (is_shared()) { return is_final(); // approximately correct } else { - return !has_subklass() && (nof_implementors() == 0); + return !has_subklass() && (!is_interface() || nof_implementors() == 0); } } @@ -619,6 +619,7 @@ bool ciInstanceKlass::is_leaf_type() { // This is OK, since any dependencies we decide to assert // will be checked later under the Compile_lock. ciInstanceKlass* ciInstanceKlass::implementor() { + assert(is_interface(), "required"); ciInstanceKlass* impl = _implementor; if (impl == nullptr) { if (is_shared()) { diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index ec8fc789c7d..1f887771f54 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -259,6 +259,7 @@ public: ciInstanceKlass* unique_implementor() { assert(is_loaded(), "must be loaded"); + assert(is_interface(), "must be"); ciInstanceKlass* impl = implementor(); return (impl != this ? impl : nullptr); } diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 754b0fa8d1c..91bb743618b 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -97,10 +97,9 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool Bytecodes::Code bytecode = caller->java_code_at_bci(bci); ciMethod* orig_callee = caller->get_method_at_bci(bci); - const bool is_virtual_or_interface = (bytecode == Bytecodes::_invokevirtual) || - (bytecode == Bytecodes::_invokeinterface) || - (orig_callee->intrinsic_id() == vmIntrinsics::_linkToVirtual) || - (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface); + const bool is_virtual = (bytecode == Bytecodes::_invokevirtual) || (orig_callee->intrinsic_id() == vmIntrinsics::_linkToVirtual); + const bool is_interface = (bytecode == Bytecodes::_invokeinterface) || (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface); + const bool is_virtual_or_interface = is_virtual || is_interface; const bool check_access = !orig_callee->is_method_handle_intrinsic(); // method handle intrinsics don't perform access checks @@ -339,17 +338,25 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool // number of implementors for decl_interface is 0 or 1. If // it's 0 then no class implements decl_interface and there's // no point in inlining. - if (call_does_dispatch && bytecode == Bytecodes::_invokeinterface) { - ciInstanceKlass* declared_interface = - caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + if (call_does_dispatch && is_interface) { + ciInstanceKlass* declared_interface = nullptr; + if (orig_callee->intrinsic_id() == vmIntrinsics::_linkToInterface) { + // MemberName doesn't keep information about resolved interface class (REFC) once + // resolution is over, but resolved method holder (DECC) can be used as a + // conservative approximation. + declared_interface = callee->holder(); + } else { + assert(!orig_callee->is_method_handle_intrinsic(), "not allowed"); + declared_interface = caller->get_declared_method_holder_at_bci(bci)->as_instance_klass(); + } + assert(declared_interface->is_interface(), "required"); ciInstanceKlass* singleton = declared_interface->unique_implementor(); if (singleton != nullptr) { assert(singleton != declared_interface, "not a unique implementor"); - assert(check_access, "required"); ciMethod* cha_monomorphic_target = - callee->find_monomorphic_target(caller->holder(), declared_interface, singleton); + callee->find_monomorphic_target(caller->holder(), declared_interface, singleton, check_access); if (cha_monomorphic_target != nullptr && cha_monomorphic_target->holder() != env()->Object_klass()) { // subtype check against Object is useless @@ -372,7 +379,7 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool } } } - } // call_does_dispatch && bytecode == Bytecodes::_invokeinterface + } // call_does_dispatch && is_interface // Nothing claimed the intrinsic, we go with straight-forward inlining // for already discovered intrinsic. diff --git a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java index aa014cfa63d..5e2dc2e3d56 100644 --- a/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java +++ b/test/hotspot/jtreg/compiler/cha/StrengthReduceInterfaceCall.java @@ -24,6 +24,7 @@ /* * @test * @requires !vm.graal.enabled + * @requires vm.opt.StressMethodHandleLinkerInlining == null | !vm.opt.StressMethodHandleLinkerInlining * @requires vm.opt.StressUnstableIfTraps == null | !vm.opt.StressUnstableIfTraps * @modules java.base/jdk.internal.misc * java.base/jdk.internal.vm.annotation @@ -55,6 +56,9 @@ package compiler.cha; import jdk.internal.vm.annotation.DontInline; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + import static compiler.cha.Utils.*; public class StrengthReduceInterfaceCall { @@ -66,6 +70,18 @@ public class StrengthReduceInterfaceCall { run(ThreeLevelHierarchyAbstractVsDefault.class); run(ThreeLevelDefaultHierarchy.class); run(ThreeLevelDefaultHierarchy1.class); + + // Implementation limitation: CHA is not performed by C1 during inlining through MH linkers. + if (!jdk.test.whitebox.code.Compiler.isC1Enabled()) { + run(ObjectToString.TestMH.class, ObjectToString.class); + run(ObjectHashCode.TestMH.class, ObjectHashCode.class); + run(TwoLevelHierarchyLinear.TestMH.class, TwoLevelHierarchyLinear.class); + run(ThreeLevelHierarchyLinear.TestMH.class, ThreeLevelHierarchyLinear.class); + run(ThreeLevelHierarchyAbstractVsDefault.TestMH.class, ThreeLevelHierarchyAbstractVsDefault.class); + run(ThreeLevelDefaultHierarchy.TestMH.class, ThreeLevelDefaultHierarchy.class); + run(ThreeLevelDefaultHierarchy1.TestMH.class, ThreeLevelDefaultHierarchy1.class); + } + System.out.println("TEST PASSED"); } @@ -87,7 +103,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public String toString() { return "DJ2"; }} @Override - public Object test(I i) { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ } + public Object test(I i) throws Throwable { return ObjectToStringHelper.testHelper(i); /* invokeinterface I.toString() */ } @TestCase public void testMono() { @@ -155,6 +171,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ObjectToString { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "toString", String.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return (String)TEST_MH.invokeExact(obj); // invokeinterface I.toString() + } + } } public static class ObjectHashCode extends ATest { @@ -175,7 +205,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public int hashCode() { return super.hashCode(); }} @Override - public Object test(I i) { + public Object test(I i) throws Throwable { return ObjectHashCodeHelper.testHelper(i); /* invokeinterface I.hashCode() */ } @@ -242,6 +272,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ObjectHashCode { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "hashCode", int.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return (int)TEST_MH.invokeExact(obj); // invokeinterface I.hashCode() + } + } } public static class TwoLevelHierarchyLinear extends ATest { @@ -263,7 +307,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -366,6 +410,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends TwoLevelHierarchyLinear { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelHierarchyLinear extends ATest { @@ -385,7 +443,7 @@ public class StrengthReduceInterfaceCall { static class DJ implements J { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); // I <: J.m ABSTRACT } @@ -404,10 +462,16 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // No deopt on not-yet-seen receiver // 2. No dependency invalidation: different context - initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT - K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT - K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT - assertCompiled(); + if (contextClass() == I.class) { + initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT + K2.class); // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT + assertCompiled(); + } else if (contextClass() == J.class) { + // no classes to initialize w/o breaking a dependency + } else { + throw new InternalError("unsupported context: " + contextClass()); + } // 3. Dependency invalidation: DI.m <: I initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT @@ -491,6 +555,30 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelHierarchyLinear { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } + } public static class ThreeLevelHierarchyAbstractVsDefault extends ATest { @@ -503,7 +591,7 @@ public class StrengthReduceInterfaceCall { static class C implements I { public Object m() { return CORRECT; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); // intf I.m OVERPASS } @@ -598,6 +686,20 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + public static class TestMH extends ThreeLevelHierarchyAbstractVsDefault { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelDefaultHierarchy extends ATest { @@ -617,7 +719,7 @@ public class StrengthReduceInterfaceCall { static class DK3 implements K3 {} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -636,11 +738,17 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // 2. No dependency invalidation - initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT - K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT - K2.class, // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT - DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT - assertCompiled(); + if (contextClass() == I.class) { + initialize(DJ.class, // DJ.m <: intf J.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J.m ABSTRACT + DK3.class); // DK3.m <: intf K3.m DEFAULT <: intf J.m ABSTRACT + assertCompiled(); + } else if (contextClass() == J.class) { + // no classes to initialize w/o breaking a dependency + } else { + throw new InternalError("unsupported context: " + contextClass()); + } // 3. Dependency invalidation initialize(DI.class); // DI.m <: intf I <: intf J.m ABSTRACT @@ -666,6 +774,29 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelDefaultHierarchy { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } public static class ThreeLevelDefaultHierarchy1 extends ATest { @@ -686,7 +817,7 @@ public class StrengthReduceInterfaceCall { static class DJ2 implements J2 { public Object m() { return WRONG; }} @DontInline - public Object test(I i) { + public Object test(I i) throws Throwable { return i.m(); } @@ -705,11 +836,20 @@ public class StrengthReduceInterfaceCall { assertCompiled(); // 2. No dependency invalidation - initialize(DJ1.class, - DJ2.class, - K1.class, - K2.class, - K3.class); + if (contextClass() == I.class) { + initialize(DJ1.class, // DJ1.m <: intf J1 + DJ2.class, // DJ2.m <: intf J2.m ABSTRACT + K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT + K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT + } else if (contextClass() == J2.class) { + initialize(DJ1.class, // DJ1.m <: intf J1 + K1.class, // intf K1 <: intf I <: intf J1, intf J2.m ABSTRACT + K2.class, // intf K2.m ABSTRACT <: intf I <: intf J1, intf J2.m ABSTRACT + K3.class); // intf K3.m DEFAULT <: intf I <: intf J1, intf J2.m ABSTRACT + } else { + throw new InternalError("unsupported context: " + contextClass()); + } assertCompiled(); // 3. Dependency invalidation @@ -742,5 +882,28 @@ public class StrengthReduceInterfaceCall { }); assertCompiled(); } + + Class contextClass() { + return I.class; + } + + public static class TestMH extends ThreeLevelDefaultHierarchy1 { + static final MethodHandle TEST_MH = findVirtualHelper(I.class, "m", Object.class, MethodHandles.lookup()); + + @Override + public void checkInvalidReceiver() { + // receiver type check failures trigger nmethod invalidation + } + + @Override + Class contextClass() { + return J2.class; + } + + @Override + public Object test(I obj) throws Throwable { + return TEST_MH.invokeExact(obj); // invokeinterface I.m() + } + } } } From aeea8497562aabda12f292ad93c9f0f6935cc842 Mon Sep 17 00:00:00 2001 From: John Engebretson Date: Tue, 18 Nov 2025 23:37:06 +0000 Subject: [PATCH 014/114] 8371164: ArrayList.addAll() optimizations Reviewed-by: smarks, ogillespie --- .../share/classes/java/util/ArrayList.java | 14 +- .../share/classes/java/util/Collections.java | 14 ++ test/jdk/java/util/Collection/MOAT.java | 26 +++- .../java/util/ArrayListBulkOpsBenchmark.java | 128 ++++++++++++++++++ 4 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java diff --git a/src/java.base/share/classes/java/util/ArrayList.java b/src/java.base/share/classes/java/util/ArrayList.java index c00b130a553..53e818b99c5 100644 --- a/src/java.base/share/classes/java/util/ArrayList.java +++ b/src/java.base/share/classes/java/util/ArrayList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -750,9 +750,17 @@ public class ArrayList extends AbstractList * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection c) { - Object[] a = c.toArray(); + Object[] a; + int numNew; + if (c.getClass() == ArrayList.class) { + ArrayList src = (ArrayList) c; + a = src.elementData; + numNew = src.size; + } else { + a = c.toArray(); + numNew = a.length; + } modCount++; - int numNew = a.length; if (numNew == 0) return false; Object[] elementData; diff --git a/src/java.base/share/classes/java/util/Collections.java b/src/java.base/share/classes/java/util/Collections.java index c48dbd8cf6c..316458d6f90 100644 --- a/src/java.base/share/classes/java/util/Collections.java +++ b/src/java.base/share/classes/java/util/Collections.java @@ -5253,6 +5253,20 @@ public final class Collections { public int hashCode() { return Objects.hashCode(element); } + @Override + public Object[] toArray() { + return new Object[] {element}; + } + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + if (a.length < 1) + a = (T[])Array.newInstance(a.getClass().getComponentType(), 1); + a[0] = (T)element; + if (a.length > 1) + a[1] = null; + return a; + } } /** diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index d0d27c8f91e..687ac9fbd5f 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -26,7 +26,7 @@ * @bug 6207984 6272521 6192552 6269713 6197726 6260652 5073546 4137464 * 4155650 4216399 4294891 6282555 6318622 6355327 6383475 6420753 * 6431845 4802633 6570566 6570575 6570631 6570924 6691185 6691215 - * 4802647 7123424 8024709 8193128 8327858 8368178 + * 4802647 7123424 8024709 8193128 8327858 8368178 8371164 * @summary Run many tests on many Collection and Map implementations * @author Martin Buchholz * @modules java.base/java.util:open @@ -887,6 +887,28 @@ public class MOAT { catch (Throwable t) { unexpected(t); } } + private static void testAddAll(Collection c) { + clear(c); + + // Test ArrayList source + ArrayList arrayListSource = new ArrayList<>(); + arrayListSource.add(42); + arrayListSource.add(99); + check(c.addAll(arrayListSource)); + equal(c.size(), arrayListSource.size()); + check(c.containsAll(arrayListSource)); + + clear(c); + + // Test non-ArrayList source + LinkedList linkedListSource = new LinkedList<>(); + linkedListSource.add(77); + linkedListSource.add(88); + check(c.addAll(linkedListSource)); + equal(c.size(), linkedListSource.size()); + check(c.containsAll(linkedListSource)); + } + private static void testConcurrentCollection(Collection c) { try { c.add(1); @@ -1294,6 +1316,8 @@ public class MOAT { clear(c); testStringElement(c); oneElement(c); testStringElement(c); + testAddAll(c); + if (c.getClass().getName().matches(".*concurrent.*")) testConcurrentCollection(c); diff --git a/test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java b/test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java new file mode 100644 index 00000000000..78b902f0c3a --- /dev/null +++ b/test/micro/org/openjdk/bench/java/util/ArrayListBulkOpsBenchmark.java @@ -0,0 +1,128 @@ +/* + * Copyright Amazon.com Inc. 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.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.WeakHashMap; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + + +/** + * Benchmark measuring ArrayList addAll() performance. + * + * Tests the performance of ArrayList.addAll() when copying from another ArrayList. + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +@Fork(value = 1, jvmArgs = { "-XX:+UseParallelGC", "-Xmx3g" }) +public class ArrayListBulkOpsBenchmark { + @Param({"0", "1", "5", "75"}) + int size; + + @Param({"ArrayList", "LinkedList"}) + String type; + + List source; + + @Setup(Level.Trial) + public void setup() { + switch (type) { + case "ArrayList" -> source = new ArrayList<>(size); + case "LinkedList" -> source = new LinkedList<>(); + } + for (int i = 0; i < size; i++) source.add("key" + i); + } + + @Benchmark + public ArrayList addAll() { + ArrayList result = new ArrayList<>(size); + result.addAll(source); + return result; + } + + static void poisonCallSites() { + HashMap hashMapSource = new HashMap<>(); + TreeSet treeSetSource = new TreeSet<>(); + WeakHashMap weakHashMapSource = new WeakHashMap<>(); + for (int i = 0; i < 75; i++) { + hashMapSource.put("key" + i, "value" + i); + treeSetSource.add("key" + i); + weakHashMapSource.put("key" + i, "value" + i); + } + // Poison ArrayList.addAll() with different Collection types + for (int i = 0; i < 40_000; i++) { + ArrayList temp = new ArrayList<>(); + temp.addAll(hashMapSource.entrySet()); + temp.addAll(treeSetSource); + temp.addAll(weakHashMapSource.keySet()); + } + } + + @BenchmarkMode(Mode.AverageTime) + @OutputTimeUnit(TimeUnit.NANOSECONDS) + @State(Scope.Benchmark) + @Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) + @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) + @Fork(value = 1, jvmArgs = { "-XX:+UseParallelGC", "-Xmx3g" }) + public static class SingletonSet { + Set singletonSetSource = Collections.singleton("key"); + + @Param({ "false", "true" }) + private boolean poison; + + @Setup(Level.Trial) + public void setup() { + if (poison) poisonCallSites(); + } + + @Benchmark + public ArrayList addAllSingletonSet() { + ArrayList result = new ArrayList<>(1); + result.addAll(singletonSetSource); + return result; + } + } +} From 152cd4d8bab7d3428d0330c56a3cb9ed7feef313 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Tue, 18 Nov 2025 23:43:22 +0000 Subject: [PATCH 015/114] 8371956: Convert OopStorage to use Atomic Reviewed-by: stefank, tschatzl --- src/hotspot/share/gc/shared/oopStorage.cpp | 157 +++++++++--------- src/hotspot/share/gc/shared/oopStorage.hpp | 11 +- .../share/gc/shared/oopStorage.inline.hpp | 19 ++- .../share/gc/shared/oopStorageParState.hpp | 5 +- .../gtest/gc/shared/test_oopStorage.cpp | 4 +- 5 files changed, 101 insertions(+), 95 deletions(-) diff --git a/src/hotspot/share/gc/shared/oopStorage.cpp b/src/hotspot/share/gc/shared/oopStorage.cpp index d52efc13dac..a1cc3ffa553 100644 --- a/src/hotspot/share/gc/shared/oopStorage.cpp +++ b/src/hotspot/share/gc/shared/oopStorage.cpp @@ -28,7 +28,7 @@ #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" #include "nmt/memTracker.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" @@ -122,7 +122,7 @@ OopStorage::ActiveArray::ActiveArray(size_t size) : {} OopStorage::ActiveArray::~ActiveArray() { - assert(_refcount == 0, "precondition"); + assert(_refcount.load_relaxed() == 0, "precondition"); } OopStorage::ActiveArray* OopStorage::ActiveArray::create(size_t size, @@ -144,32 +144,32 @@ size_t OopStorage::ActiveArray::size() const { } size_t OopStorage::ActiveArray::block_count() const { - return _block_count; + return _block_count.load_relaxed(); } size_t OopStorage::ActiveArray::block_count_acquire() const { - return AtomicAccess::load_acquire(&_block_count); + return _block_count.load_acquire(); } void OopStorage::ActiveArray::increment_refcount() const { - int new_value = AtomicAccess::add(&_refcount, 1); - assert(new_value >= 1, "negative refcount %d", new_value - 1); + int old_value = _refcount.fetch_then_add(1); + assert(old_value >= 0, "negative refcount %d", old_value); } bool OopStorage::ActiveArray::decrement_refcount() const { - int new_value = AtomicAccess::sub(&_refcount, 1); + int new_value = _refcount.sub_then_fetch(1); assert(new_value >= 0, "negative refcount %d", new_value); return new_value == 0; } bool OopStorage::ActiveArray::push(Block* block) { - size_t index = _block_count; + size_t index = _block_count.load_relaxed(); if (index < _size) { block->set_active_index(index); *block_ptr(index) = block; // Use a release_store to ensure all the setup is complete before // making the block visible. - AtomicAccess::release_store(&_block_count, index + 1); + _block_count.release_store(index + 1); return true; } else { return false; @@ -177,19 +177,19 @@ bool OopStorage::ActiveArray::push(Block* block) { } void OopStorage::ActiveArray::remove(Block* block) { - assert(_block_count > 0, "array is empty"); + assert(_block_count.load_relaxed() > 0, "array is empty"); size_t index = block->active_index(); assert(*block_ptr(index) == block, "block not present"); - size_t last_index = _block_count - 1; + size_t last_index = _block_count.load_relaxed() - 1; Block* last_block = *block_ptr(last_index); last_block->set_active_index(index); *block_ptr(index) = last_block; - _block_count = last_index; + _block_count.store_relaxed(last_index); } void OopStorage::ActiveArray::copy_from(const ActiveArray* from) { - assert(_block_count == 0, "array must be empty"); - size_t count = from->_block_count; + assert(_block_count.load_relaxed() == 0, "array must be empty"); + size_t count = from->_block_count.load_relaxed(); assert(count <= _size, "precondition"); Block* const* from_ptr = from->block_ptr(0); Block** to_ptr = block_ptr(0); @@ -198,7 +198,7 @@ void OopStorage::ActiveArray::copy_from(const ActiveArray* from) { assert(block->active_index() == i, "invariant"); *to_ptr++ = block; } - _block_count = count; + _block_count.store_relaxed(count); } // Blocks start with an array of BitsPerWord oop entries. That array @@ -230,14 +230,17 @@ OopStorage::Block::Block(const OopStorage* owner, void* memory) : assert(is_aligned(this, block_alignment), "misaligned block"); } +#ifdef ASSERT OopStorage::Block::~Block() { - assert(_release_refcount == 0, "deleting block while releasing"); - assert(_deferred_updates_next == nullptr, "deleting block with deferred update"); + assert(_release_refcount.load_relaxed() == 0, "deleting block while releasing"); + assert(_deferred_updates_next.load_relaxed() == nullptr, "deleting block with deferred update"); // Clear fields used by block_for_ptr and entry validation, which - // might help catch bugs. Volatile to prevent dead-store elimination. - const_cast(_allocated_bitmask) = 0; + // might help catch bugs. + _allocated_bitmask.store_relaxed(0); + // Volatile to prevent dead-store elimination. const_cast(_owner_address) = 0; } +#endif // ASSERT size_t OopStorage::Block::allocation_size() { // _data must be first member, so aligning Block aligns _data. @@ -272,16 +275,16 @@ uintx OopStorage::Block::bitmask_for_entry(const oop* ptr) const { bool OopStorage::Block::is_safe_to_delete() const { assert(is_empty(), "precondition"); OrderAccess::loadload(); - return (AtomicAccess::load_acquire(&_release_refcount) == 0) && - (AtomicAccess::load_acquire(&_deferred_updates_next) == nullptr); + return ((_release_refcount.load_acquire() == 0) && + (_deferred_updates_next.load_acquire() == nullptr)); } OopStorage::Block* OopStorage::Block::deferred_updates_next() const { - return _deferred_updates_next; + return _deferred_updates_next.load_relaxed(); } void OopStorage::Block::set_deferred_updates_next(Block* block) { - _deferred_updates_next = block; + _deferred_updates_next.store_relaxed(block); } bool OopStorage::Block::contains(const oop* ptr) const { @@ -321,9 +324,8 @@ void OopStorage::Block::atomic_add_allocated(uintx add) { // we can use an atomic add to implement the operation. The assert post // facto verifies the precondition held; if there were any set bits in // common, then after the add at least one of them will be zero. - uintx sum = AtomicAccess::add(&_allocated_bitmask, add); - assert((sum & add) == add, "some already present: %zu:%zu", - sum, add); + uintx sum = _allocated_bitmask.add_then_fetch(add); + assert((sum & add) == add, "some already present: %zu:%zu", sum, add); } oop* OopStorage::Block::allocate() { @@ -452,7 +454,7 @@ oop* OopStorage::allocate() { oop* result = block->allocate(); assert(result != nullptr, "allocation failed"); assert(!block->is_empty(), "postcondition"); - AtomicAccess::inc(&_allocation_count); // release updates outside lock. + _allocation_count.add_then_fetch(1u); // release updates outside lock. if (block->is_full()) { // Transitioning from not full to full. // Remove full blocks from consideration by future allocates. @@ -490,7 +492,7 @@ size_t OopStorage::allocate(oop** ptrs, size_t size) { assert(!is_empty_bitmask(taken), "invariant"); } // Drop lock, now that we've taken all available entries from block. size_t num_taken = population_count(taken); - AtomicAccess::add(&_allocation_count, num_taken); + _allocation_count.add_then_fetch(num_taken); // Fill ptrs from those taken entries. size_t limit = MIN2(num_taken, size); for (size_t i = 0; i < limit; ++i) { @@ -506,7 +508,7 @@ size_t OopStorage::allocate(oop** ptrs, size_t size) { assert(size == limit, "invariant"); assert(num_taken == (limit + population_count(taken)), "invariant"); block->release_entries(taken, this); - AtomicAccess::sub(&_allocation_count, num_taken - limit); + _allocation_count.sub_then_fetch(num_taken - limit); } log_trace(oopstorage, ref)("%s: bulk allocate %zu, returned %zu", name(), limit, num_taken - limit); @@ -527,9 +529,9 @@ bool OopStorage::try_add_block() { if (block == nullptr) return false; // Add new block to the _active_array, growing if needed. - if (!_active_array->push(block)) { + if (!_active_array.load_relaxed()->push(block)) { if (expand_active_array()) { - guarantee(_active_array->push(block), "push failed after expansion"); + guarantee(_active_array.load_relaxed()->push(block), "push failed after expansion"); } else { log_debug(oopstorage, blocks)("%s: failed active array expand", name()); Block::delete_block(*block); @@ -576,7 +578,7 @@ OopStorage::Block* OopStorage::block_for_allocation() { // indicate allocation failure. bool OopStorage::expand_active_array() { assert_lock_strong(_allocation_mutex); - ActiveArray* old_array = _active_array; + ActiveArray* old_array = _active_array.load_relaxed(); size_t new_size = 2 * old_array->size(); log_debug(oopstorage, blocks)("%s: expand active array %zu", name(), new_size); @@ -599,7 +601,7 @@ void OopStorage::replace_active_array(ActiveArray* new_array) { // Update new_array refcount to account for the new reference. new_array->increment_refcount(); // Install new_array, ensuring its initialization is complete first. - AtomicAccess::release_store(&_active_array, new_array); + _active_array.release_store(new_array); // Wait for any readers that could read the old array from _active_array. // Can't use GlobalCounter here, because this is called from allocate(), // which may be called in the scope of a GlobalCounter critical section @@ -617,7 +619,7 @@ void OopStorage::replace_active_array(ActiveArray* new_array) { // using it. OopStorage::ActiveArray* OopStorage::obtain_active_array() const { SingleWriterSynchronizer::CriticalSection cs(&_protect_active); - ActiveArray* result = AtomicAccess::load_acquire(&_active_array); + ActiveArray* result = _active_array.load_acquire(); result->increment_refcount(); return result; } @@ -625,7 +627,7 @@ OopStorage::ActiveArray* OopStorage::obtain_active_array() const { // Decrement refcount of array and destroy if refcount is zero. void OopStorage::relinquish_block_array(ActiveArray* array) const { if (array->decrement_refcount()) { - assert(array != _active_array, "invariant"); + assert(array != _active_array.load_relaxed(), "invariant"); ActiveArray::destroy(array); } } @@ -672,14 +674,14 @@ static void log_release_transitions(uintx releasing, void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) { assert(releasing != 0, "preconditon"); // Prevent empty block deletion when transitioning to empty. - AtomicAccess::inc(&_release_refcount); + _release_refcount.add_then_fetch(1u); // Atomically update allocated bitmask. - uintx old_allocated = _allocated_bitmask; + uintx old_allocated = _allocated_bitmask.load_relaxed(); while (true) { assert((releasing & ~old_allocated) == 0, "releasing unallocated entries"); uintx new_value = old_allocated ^ releasing; - uintx fetched = AtomicAccess::cmpxchg(&_allocated_bitmask, old_allocated, new_value); + uintx fetched = _allocated_bitmask.compare_exchange(old_allocated, new_value); if (fetched == old_allocated) break; // Successful update. old_allocated = fetched; // Retry with updated bitmask. } @@ -698,12 +700,12 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) { // then someone else has made such a claim and the deferred update has not // yet been processed and will include our change, so we don't need to do // anything further. - if (AtomicAccess::replace_if_null(&_deferred_updates_next, this)) { + if (_deferred_updates_next.compare_exchange(nullptr, this) == nullptr) { // Successfully claimed. Push, with self-loop for end-of-list. - Block* head = owner->_deferred_updates; + Block* head = owner->_deferred_updates.load_relaxed(); while (true) { - _deferred_updates_next = (head == nullptr) ? this : head; - Block* fetched = AtomicAccess::cmpxchg(&owner->_deferred_updates, head, this); + _deferred_updates_next.store_relaxed((head == nullptr) ? this : head); + Block* fetched = owner->_deferred_updates.compare_exchange(head, this); if (fetched == head) break; // Successful update. head = fetched; // Retry with updated head. } @@ -720,7 +722,7 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) { } } // Release hold on empty block deletion. - AtomicAccess::dec(&_release_refcount); + _release_refcount.sub_then_fetch(1u); } // Process one available deferred update. Returns true if one was processed. @@ -729,13 +731,13 @@ bool OopStorage::reduce_deferred_updates() { // Atomically pop a block off the list, if any available. // No ABA issue because this is only called by one thread at a time. // The atomicity is wrto pushes by release(). - Block* block = AtomicAccess::load_acquire(&_deferred_updates); + Block* block = _deferred_updates.load_acquire(); while (true) { if (block == nullptr) return false; // Try atomic pop of block from list. Block* tail = block->deferred_updates_next(); if (block == tail) tail = nullptr; // Handle self-loop end marker. - Block* fetched = AtomicAccess::cmpxchg(&_deferred_updates, block, tail); + Block* fetched = _deferred_updates.compare_exchange(block, tail); if (fetched == block) break; // Update successful. block = fetched; // Retry with updated block. } @@ -780,7 +782,7 @@ void OopStorage::release(const oop* ptr) { assert(block != nullptr, "%s: invalid release " PTR_FORMAT, name(), p2i(ptr)); log_trace(oopstorage, ref)("%s: releasing " PTR_FORMAT, name(), p2i(ptr)); block->release_entries(block->bitmask_for_entry(ptr), this); - AtomicAccess::dec(&_allocation_count); + _allocation_count.sub_then_fetch(1u); } void OopStorage::release(const oop* const* ptrs, size_t size) { @@ -806,7 +808,7 @@ void OopStorage::release(const oop* const* ptrs, size_t size) { } // Release the contiguous entries that are in block. block->release_entries(releasing, this); - AtomicAccess::sub(&_allocation_count, count); + _allocation_count.sub_then_fetch(count); } } @@ -837,7 +839,7 @@ OopStorage::OopStorage(const char* name, MemTag mem_tag) : _mem_tag(mem_tag), _needs_cleanup(false) { - _active_array->increment_refcount(); + _active_array.load_relaxed()->increment_refcount(); assert(_active_mutex->rank() < _allocation_mutex->rank(), "%s: active_mutex must have lower rank than allocation_mutex", _name); assert(Service_lock->rank() < _active_mutex->rank(), @@ -852,20 +854,21 @@ void OopStorage::delete_empty_block(const Block& block) { OopStorage::~OopStorage() { Block* block; - while ((block = _deferred_updates) != nullptr) { - _deferred_updates = block->deferred_updates_next(); + while ((block = _deferred_updates.load_relaxed()) != nullptr) { + _deferred_updates.store_relaxed(block->deferred_updates_next()); block->set_deferred_updates_next(nullptr); } while ((block = _allocation_list.head()) != nullptr) { _allocation_list.unlink(*block); } - bool unreferenced = _active_array->decrement_refcount(); + ActiveArray* array = _active_array.load_relaxed(); + bool unreferenced = array->decrement_refcount(); assert(unreferenced, "deleting storage while _active_array is referenced"); - for (size_t i = _active_array->block_count(); 0 < i; ) { - block = _active_array->at(--i); + for (size_t i = array->block_count(); 0 < i; ) { + block = array->at(--i); Block::delete_block(*block); } - ActiveArray::destroy(_active_array); + ActiveArray::destroy(array); os::free(const_cast(_name)); } @@ -894,7 +897,7 @@ bool OopStorage::should_report_num_dead() const { // face of frequent explicit ServiceThread wakeups, hence the defer period. // Global cleanup request state. -static volatile bool needs_cleanup_requested = false; +static Atomic needs_cleanup_requested{false}; // Time after which a cleanup is permitted. static jlong cleanup_permit_time = 0; @@ -906,12 +909,11 @@ const jlong cleanup_defer_period = 500 * NANOSECS_PER_MILLISEC; bool OopStorage::has_cleanup_work_and_reset() { assert_lock_strong(Service_lock); - if (AtomicAccess::load_acquire(&needs_cleanup_requested) && - os::javaTimeNanos() > cleanup_permit_time) { - cleanup_permit_time = - os::javaTimeNanos() + cleanup_defer_period; + if (needs_cleanup_requested.load_acquire() && + (os::javaTimeNanos() > cleanup_permit_time)) { + cleanup_permit_time = os::javaTimeNanos() + cleanup_defer_period; // Set the request flag false and return its old value. - AtomicAccess::release_store(&needs_cleanup_requested, false); + needs_cleanup_requested.release_store(false); return true; } else { return false; @@ -923,22 +925,22 @@ bool OopStorage::has_cleanup_work_and_reset() { void OopStorage::record_needs_cleanup() { // Set local flag first, else ServiceThread could wake up and miss // the request. - AtomicAccess::release_store(&_needs_cleanup, true); - AtomicAccess::release_store_fence(&needs_cleanup_requested, true); + _needs_cleanup.release_store(true); + needs_cleanup_requested.release_store_fence(true); } bool OopStorage::delete_empty_blocks() { // ServiceThread might have oopstorage work, but not for this object. // But check for deferred updates, which might provide cleanup work. - if (!AtomicAccess::load_acquire(&_needs_cleanup) && - (AtomicAccess::load_acquire(&_deferred_updates) == nullptr)) { + if (!_needs_cleanup.load_acquire() && + (_deferred_updates.load_acquire() == nullptr)) { return false; } MutexLocker ml(_allocation_mutex, Mutex::_no_safepoint_check_flag); // Clear the request before processing. - AtomicAccess::release_store_fence(&_needs_cleanup, false); + _needs_cleanup.release_store_fence(false); // Other threads could be adding to the empty block count or the // deferred update list while we're working. Set an upper bound on @@ -977,7 +979,7 @@ bool OopStorage::delete_empty_blocks() { // but don't re-notify, to avoid useless spinning of the // ServiceThread. Instead, iteration completion notifies. if (_concurrent_iteration_count > 0) return true; - _active_array->remove(block); + _active_array.load_relaxed()->remove(block); } // Remove block from _allocation_list and delete it. _allocation_list.unlink(*block); @@ -1001,8 +1003,9 @@ OopStorage::EntryStatus OopStorage::allocation_status(const oop* ptr) const { MutexLocker ml(_allocation_mutex, Mutex::_no_safepoint_check_flag); // Block could be a false positive, so get index carefully. size_t index = Block::active_index_safe(block); - if ((index < _active_array->block_count()) && - (block == _active_array->at(index)) && + ActiveArray* array = _active_array.load_relaxed(); + if ((index < array->block_count()) && + (block == array->at(index)) && block->contains(ptr)) { if ((block->allocated_bitmask() & block->bitmask_for_entry(ptr)) != 0) { return ALLOCATED_ENTRY; @@ -1015,7 +1018,7 @@ OopStorage::EntryStatus OopStorage::allocation_status(const oop* ptr) const { } size_t OopStorage::allocation_count() const { - return _allocation_count; + return _allocation_count.load_relaxed(); } size_t OopStorage::block_count() const { @@ -1084,7 +1087,7 @@ void OopStorage::BasicParState::update_concurrent_iteration_count(int value) { bool OopStorage::BasicParState::claim_next_segment(IterationData* data) { data->_processed += data->_segment_end - data->_segment_start; - size_t start = AtomicAccess::load_acquire(&_next_block); + size_t start = _next_block.load_acquire(); if (start >= _block_count) { return finish_iteration(data); // No more blocks available. } @@ -1097,11 +1100,11 @@ bool OopStorage::BasicParState::claim_next_segment(IterationData* data) { size_t max_step = 10; size_t remaining = _block_count - start; size_t step = MIN2(max_step, 1 + (remaining / _estimated_thread_count)); - // AtomicAccess::add with possible overshoot. This can perform better + // Atomic add with possible overshoot. This can perform better // than a CAS loop on some platforms when there is contention. // We can cope with the uncertainty by recomputing start/end from // the result of the add, and dealing with potential overshoot. - size_t end = AtomicAccess::add(&_next_block, step); + size_t end = _next_block.add_then_fetch(step); // _next_block may have changed, so recompute start from result of add. start = end - step; // _next_block may have changed so much that end has overshot. @@ -1128,15 +1131,15 @@ bool OopStorage::BasicParState::finish_iteration(const IterationData* data) cons } size_t OopStorage::BasicParState::num_dead() const { - return AtomicAccess::load(&_num_dead); + return _num_dead.load_relaxed(); } void OopStorage::BasicParState::increment_num_dead(size_t num_dead) { - AtomicAccess::add(&_num_dead, num_dead); + _num_dead.add_then_fetch(num_dead); } void OopStorage::BasicParState::report_num_dead() const { - _storage->report_num_dead(AtomicAccess::load(&_num_dead)); + _storage->report_num_dead(_num_dead.load_relaxed()); } const char* OopStorage::name() const { return _name; } @@ -1164,8 +1167,8 @@ bool OopStorage::Block::print_containing(const oop* addr, outputStream* st) { #ifndef PRODUCT void OopStorage::print_on(outputStream* st) const { - size_t allocations = _allocation_count; - size_t blocks = _active_array->block_count(); + size_t allocations = _allocation_count.load_relaxed(); + size_t blocks = _active_array.load_relaxed()->block_count(); double data_size = section_size * section_count; double alloc_percentage = percent_of((double)allocations, blocks * data_size); diff --git a/src/hotspot/share/gc/shared/oopStorage.hpp b/src/hotspot/share/gc/shared/oopStorage.hpp index 34c980a0586..6097eeaa4f4 100644 --- a/src/hotspot/share/gc/shared/oopStorage.hpp +++ b/src/hotspot/share/gc/shared/oopStorage.hpp @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "oops/oop.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/singleWriterSynchronizer.hpp" @@ -258,15 +259,15 @@ private: private: const char* _name; - ActiveArray* _active_array; + Atomic _active_array; AllocationList _allocation_list; - Block* volatile _deferred_updates; + Atomic _deferred_updates; Mutex* _allocation_mutex; Mutex* _active_mutex; NumDeadCallback _num_dead_callback; - // Volatile for racy unlocked accesses. - volatile size_t _allocation_count; + // Atomic for racy unlocked accesses. + Atomic _allocation_count; // Protection for _active_array. mutable SingleWriterSynchronizer _protect_active; @@ -278,7 +279,7 @@ private: MemTag _mem_tag; // Flag indicating this storage object is a candidate for empty block deletion. - volatile bool _needs_cleanup; + Atomic _needs_cleanup; // Clients construct via "create" factory function. OopStorage(const char* name, MemTag mem_tag); diff --git a/src/hotspot/share/gc/shared/oopStorage.inline.hpp b/src/hotspot/share/gc/shared/oopStorage.inline.hpp index 4fb1d8fcaf1..c2747781a6b 100644 --- a/src/hotspot/share/gc/shared/oopStorage.inline.hpp +++ b/src/hotspot/share/gc/shared/oopStorage.inline.hpp @@ -30,6 +30,7 @@ #include "cppstdlib/type_traits.hpp" #include "memory/allocation.hpp" #include "oops/oop.hpp" +#include "runtime/atomic.hpp" #include "runtime/safepoint.hpp" #include "utilities/align.hpp" #include "utilities/count_trailing_zeros.hpp" @@ -42,8 +43,8 @@ class OopStorage::ActiveArray { friend class OopStorage::TestAccess; size_t _size; - volatile size_t _block_count; - mutable volatile int _refcount; + Atomic _block_count; + mutable Atomic _refcount; // Block* _blocks[1]; // Pseudo flexible array member. ActiveArray(size_t size); @@ -104,7 +105,7 @@ inline OopStorage::Block** OopStorage::ActiveArray::block_ptr(size_t index) { } inline OopStorage::Block* OopStorage::ActiveArray::at(size_t index) const { - assert(index < _block_count, "precondition"); + assert(index < _block_count.load_relaxed(), "precondition"); return *block_ptr(index); } @@ -135,16 +136,16 @@ class OopStorage::Block /* No base class, to avoid messing up alignment. */ { oop _data[BitsPerWord]; static const unsigned _data_pos = 0; // Position of _data. - volatile uintx _allocated_bitmask; // One bit per _data element. + Atomic _allocated_bitmask; // One bit per _data element. intptr_t _owner_address; void* _memory; // Unaligned storage containing block. size_t _active_index; AllocationListEntry _allocation_list_entry; - Block* volatile _deferred_updates_next; - volatile uintx _release_refcount; + Atomic _deferred_updates_next; + Atomic _release_refcount; Block(const OopStorage* owner, void* memory); - ~Block(); + ~Block() NOT_DEBUG(= default); void check_index(unsigned index) const; unsigned get_index(const oop* ptr) const; @@ -322,7 +323,7 @@ inline const oop* OopStorage::Block::get_pointer(unsigned index) const { } inline uintx OopStorage::Block::allocated_bitmask() const { - return _allocated_bitmask; + return _allocated_bitmask.load_relaxed(); } inline uintx OopStorage::Block::bitmask_for_index(unsigned index) const { @@ -366,7 +367,7 @@ inline bool OopStorage::iterate_impl(F f, Storage* storage) { // Propagate const/non-const iteration to the block layer, by using // const or non-const blocks as corresponding to Storage. using BlockPtr = std::conditional_t::value, const Block*, Block*>; - ActiveArray* blocks = storage->_active_array; + ActiveArray* blocks = storage->_active_array.load_relaxed(); size_t limit = blocks->block_count(); for (size_t i = 0; i < limit; ++i) { BlockPtr block = blocks->at(i); diff --git a/src/hotspot/share/gc/shared/oopStorageParState.hpp b/src/hotspot/share/gc/shared/oopStorageParState.hpp index 046bf9de8c2..cad1a1f0cf6 100644 --- a/src/hotspot/share/gc/shared/oopStorageParState.hpp +++ b/src/hotspot/share/gc/shared/oopStorageParState.hpp @@ -27,6 +27,7 @@ #include "cppstdlib/type_traits.hpp" #include "gc/shared/oopStorage.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" ////////////////////////////////////////////////////////////////////////////// @@ -131,10 +132,10 @@ class OopStorage::BasicParState { const OopStorage* _storage; ActiveArray* _active_array; size_t _block_count; - volatile size_t _next_block; + Atomic _next_block; uint _estimated_thread_count; bool _concurrent; - volatile size_t _num_dead; + Atomic _num_dead; NONCOPYABLE(BasicParState); diff --git a/test/hotspot/gtest/gc/shared/test_oopStorage.cpp b/test/hotspot/gtest/gc/shared/test_oopStorage.cpp index 285cc2630a3..b343e7fc47f 100644 --- a/test/hotspot/gtest/gc/shared/test_oopStorage.cpp +++ b/test/hotspot/gtest/gc/shared/test_oopStorage.cpp @@ -47,7 +47,7 @@ public: typedef OopStorage::ActiveArray ActiveArray; static ActiveArray& active_array(const OopStorage& storage) { - return *storage._active_array; + return *storage._active_array.load_relaxed(); } static AllocationList& allocation_list(OopStorage& storage) { @@ -90,7 +90,7 @@ public: } static void block_array_set_block_count(ActiveArray* blocks, size_t count) { - blocks->_block_count = count; + blocks->_block_count.store_relaxed(count); } static const oop* get_block_pointer(const Block& block, unsigned index) { From 902aa4dcd297fef34cb302e468b030c48665ec84 Mon Sep 17 00:00:00 2001 From: Alexander Zuev Date: Tue, 18 Nov 2025 23:51:32 +0000 Subject: [PATCH 016/114] 8372120: Add missing sound keyword to MIDI tests Reviewed-by: kcr, dholmes --- .../javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java | 1 + test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java | 1 + .../sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java | 1 + test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java | 1 + .../javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java | 1 + 5 files changed, 5 insertions(+) diff --git a/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java b/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java index 27290738a0c..afac39b83fb 100644 --- a/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java +++ b/test/jdk/javax/sound/midi/MidiDeviceConnectors/TestAllDevices.java @@ -24,6 +24,7 @@ /** * @test * @bug 4933700 + * @key sound * @summary Tests that default devices return MidiDeviceTransmitter/Receiver and returned objects return correct MidiDevice * @compile TestAllDevices.java * @run main TestAllDevices diff --git a/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java b/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java index 00c57f46c98..7b8c4b0a3f2 100644 --- a/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java +++ b/test/jdk/javax/sound/midi/SysexMessage/SendRawSysexMessage.java @@ -35,6 +35,7 @@ import static javax.sound.midi.SysexMessage.SYSTEM_EXCLUSIVE; /** * @test * @bug 8074211 8237495 8301310 + * @key sound * @summary fail with memory errors when asked to send a sysex message starting * with 0xF7 */ diff --git a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java index efb57eeeae2..05ba16cded5 100644 --- a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java +++ b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/ExpectedNPEOnNull.java @@ -32,6 +32,7 @@ import static java.util.ServiceLoader.load; /** * @test * @bug 8143909 + * @key sound * @author Sergey Bylokhov */ public final class ExpectedNPEOnNull { diff --git a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java index 8eabb992bca..71d27c4943c 100644 --- a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java +++ b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/FakeInfo.java @@ -35,6 +35,7 @@ import static java.util.ServiceLoader.load; /** * @test * @bug 8059743 + * @key sound * @summary MidiDeviceProvider shouldn't returns incorrect results in case of * some unknown MidiDevice.Info * @author Sergey Bylokhov diff --git a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java index 685a5e8af62..5d37759fbad 100644 --- a/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java +++ b/test/jdk/javax/sound/midi/spi/MidiDeviceProvider/UnsupportedInfo.java @@ -30,6 +30,7 @@ import static java.util.ServiceLoader.load; /** * @test * @bug 8058115 + * @key sound * @summary MidiDeviceProvider shouldn't returns incorrect results in case of * unsupported MidiDevice.Info * @author Sergey Bylokhov From 02ff38f2d7f6abc0e4661e8226bc6780b7a11c3a Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 19 Nov 2025 05:04:34 +0000 Subject: [PATCH 017/114] 8363986: Heap region in CDS archive is not at deterministic address Reviewed-by: kvn, asmehra --- src/hotspot/share/cds/aotArtifactFinder.hpp | 2 +- src/hotspot/share/cds/aotMapLogger.cpp | 2 +- src/hotspot/share/cds/aotMappedHeapWriter.cpp | 105 ++++++++++++++++-- src/hotspot/share/cds/aotMappedHeapWriter.hpp | 44 ++++---- src/hotspot/share/cds/filemap.cpp | 10 +- src/hotspot/share/cds/heapShared.hpp | 2 +- test/hotspot/jtreg/ProblemList.txt | 1 - 7 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/hotspot/share/cds/aotArtifactFinder.hpp b/src/hotspot/share/cds/aotArtifactFinder.hpp index 405222a8753..05bcde6b0ac 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.hpp +++ b/src/hotspot/share/cds/aotArtifactFinder.hpp @@ -39,7 +39,7 @@ class TypeArrayKlass; // It also decides what Klasses must be cached in aot-initialized state. // // ArchiveBuilder uses [1] as roots to scan for all MetaspaceObjs that need to be cached. -// ArchiveHeapWriter uses [2] to create an image of the archived heap. +// HeapShared uses [2] to create an image of the archived heap. // // [1] is stored in _all_cached_classes in aotArtifactFinder.cpp. // [2] is stored in HeapShared::archived_object_cache(). diff --git a/src/hotspot/share/cds/aotMapLogger.cpp b/src/hotspot/share/cds/aotMapLogger.cpp index d0a63c56093..a252eae4b84 100644 --- a/src/hotspot/share/cds/aotMapLogger.cpp +++ b/src/hotspot/share/cds/aotMapLogger.cpp @@ -796,7 +796,7 @@ void AOTMapLogger::dumptime_log_mapped_heap_region(ArchiveMappedHeapInfo* heap_i address buffer_start = address(r.start()); // start of the current oop inside the buffer address buffer_end = address(r.end()); - address requested_base = UseCompressedOops ? (address)CompressedOops::base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; + address requested_base = UseCompressedOops ? AOTMappedHeapWriter::narrow_oop_base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; address requested_start = UseCompressedOops ? AOTMappedHeapWriter::buffered_addr_to_requested_addr(buffer_start) : requested_base; log_region_range("heap", buffer_start, buffer_end, requested_start); diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index ff9319d266b..98f400c989c 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -55,7 +55,7 @@ GrowableArrayCHeap* AOTMappedHeapWriter::_buffer = nullptr; -// The following are offsets from buffer_bottom() +bool AOTMappedHeapWriter::_is_writing_deterministic_heap = false; size_t AOTMappedHeapWriter::_buffer_used; // Heap root segments @@ -74,7 +74,7 @@ AOTMappedHeapWriter::_buffer_offset_to_source_obj_table = nullptr; DumpedInternedStrings *AOTMappedHeapWriter::_dumped_interned_strings = nullptr; typedef HashTable< - size_t, // offset of a filler from ArchiveHeapWriter::buffer_bottom() + size_t, // offset of a filler from AOTMappedHeapWriter::buffer_bottom() size_t, // size of this filler (in bytes) 127, // prime number AnyObj::C_HEAP, @@ -96,6 +96,45 @@ void AOTMappedHeapWriter::init() { _source_objs = new GrowableArrayCHeap(10000); guarantee(MIN_GC_REGION_ALIGNMENT <= G1HeapRegion::min_region_size_in_words() * HeapWordSize, "must be"); + + if (CDSConfig::old_cds_flags_used()) { + // With the old CDS workflow, we can guatantee determninistic output: given + // the same classlist file, we can generate the same static CDS archive. + // To ensure determinism, we always use the same compressed oop encoding + // (zero-based, no shift). See set_requested_address_range(). + _is_writing_deterministic_heap = true; + } else { + // Determninistic output is not supported by the new AOT workflow, so + // we don't force the (zero-based, no shift) encoding. This way, it is more + // likely that we can avoid oop relocation in the production run. + _is_writing_deterministic_heap = false; + } + } +} + +// For AOTMappedHeapWriter::narrow_oop_{mode, base, shift}(), see comments +// in AOTMappedHeapWriter::set_requested_address_range(), +CompressedOops::Mode AOTMappedHeapWriter::narrow_oop_mode() { + if (is_writing_deterministic_heap()) { + return CompressedOops::UnscaledNarrowOop; + } else { + return CompressedOops::mode(); + } +} + +address AOTMappedHeapWriter::narrow_oop_base() { + if (is_writing_deterministic_heap()) { + return (address)0; + } else { + return CompressedOops::base(); + } +} + +int AOTMappedHeapWriter::narrow_oop_shift() { + if (is_writing_deterministic_heap()) { + return 0; + } else { + return CompressedOops::shift(); } } @@ -116,7 +155,7 @@ void AOTMappedHeapWriter::write(GrowableArrayCHeap* roots, assert(CDSConfig::is_dumping_heap(), "sanity"); allocate_buffer(); copy_source_objs_to_buffer(roots); - set_requested_address(heap_info); + set_requested_address_range(heap_info); relocate_embedded_oops(roots, heap_info); } @@ -536,14 +575,55 @@ size_t AOTMappedHeapWriter::copy_one_source_obj_to_buffer(oop src_obj) { return buffered_obj_offset; } -void AOTMappedHeapWriter::set_requested_address(ArchiveMappedHeapInfo* info) { +// Set the range [_requested_bottom, _requested_top), the requested address range of all +// the archived heap objects in the production run. +// +// (1) UseCompressedOops == true && !is_writing_deterministic_heap() +// +// The archived objects are stored using the COOPS encoding of the assembly phase. +// We pick a range within the heap used by the assembly phase. +// +// In the production run, if different COOPS encodings are used: +// - The heap contents needs to be relocated. +// +// (2) UseCompressedOops == true && is_writing_deterministic_heap() +// +// We always use zero-based, zero-shift encoding. _requested_top is aligned to 0x10000000. +// +// (3) UseCompressedOops == false: +// +// In the production run, the heap range is usually picked (randomly) by the OS, so we +// will almost always need to perform relocation, regardless of how we pick the requested +// address range. +// +// So we just hard code it to NOCOOPS_REQUESTED_BASE. +// +void AOTMappedHeapWriter::set_requested_address_range(ArchiveMappedHeapInfo* info) { assert(!info->is_used(), "only set once"); size_t heap_region_byte_size = _buffer_used; assert(heap_region_byte_size > 0, "must archived at least one object!"); if (UseCompressedOops) { - if (UseG1GC) { + if (is_writing_deterministic_heap()) { + // Pick a heap range so that requested addresses can be encoded with zero-base/no shift. + // We align the requested bottom to at least 1 MB: if the production run uses G1 with a small + // heap (e.g., -Xmx256m), it's likely that we can map the archived objects at the + // requested location to avoid relocation. + // + // For other collectors or larger heaps, relocation is unavoidable, but is usually + // quite cheap. If you really want to avoid relocation, use the AOT workflow instead. + address heap_end = (address)0x100000000; + size_t alignment = MAX2(MIN_GC_REGION_ALIGNMENT, 1024 * 1024); + if (align_up(heap_region_byte_size, alignment) >= (size_t)heap_end) { + log_error(aot, heap)("cached heap space is too large: %zu bytes", heap_region_byte_size); + AOTMetaspace::unrecoverable_writing_error(); + } + _requested_bottom = align_down(heap_end - heap_region_byte_size, alignment); + } else if (UseG1GC) { + // For G1, pick the range at the top of the current heap. If the exact same heap sizes + // are used in the production run, it's likely that we can map the archived objects + // at the requested location to avoid relocation. address heap_end = (address)G1CollectedHeap::heap()->reserved().end(); log_info(aot, heap)("Heap end = %p", heap_end); _requested_bottom = align_down(heap_end - heap_region_byte_size, G1HeapRegion::GrainBytes); @@ -612,7 +692,14 @@ oop AOTMappedHeapWriter::load_oop_from_buffer(narrowOop* buffered_addr) { template void AOTMappedHeapWriter::relocate_field_in_buffer(T* field_addr_in_buffer, oop source_referent, CHeapBitMap* oopmap) { oop request_referent = source_obj_to_requested_obj(source_referent); - store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); + if (UseCompressedOops && is_writing_deterministic_heap()) { + // We use zero-based, 0-shift encoding, so the narrowOop is just the lower + // 32 bits of request_referent + intptr_t addr = cast_from_oop(request_referent); + *((narrowOop*)field_addr_in_buffer) = checked_cast(addr); + } else { + store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); + } if (request_referent != nullptr) { mark_oop_pointer(field_addr_in_buffer, oopmap); } @@ -918,9 +1005,9 @@ AOTMapLogger::OopDataIterator* AOTMappedHeapWriter::oop_iterator(ArchiveMappedHe address buffer_start = address(r.start()); address buffer_end = address(r.end()); - address requested_base = UseCompressedOops ? (address)CompressedOops::base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; - address requested_start = UseCompressedOops ? buffered_addr_to_requested_addr(buffer_start) : requested_base; - int requested_shift = CompressedOops::shift(); + address requested_base = UseCompressedOops ? AOTMappedHeapWriter::narrow_oop_base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; + address requested_start = UseCompressedOops ? AOTMappedHeapWriter::buffered_addr_to_requested_addr(buffer_start) : requested_base; + int requested_shift = AOTMappedHeapWriter::narrow_oop_shift(); intptr_t buffer_to_requested_delta = requested_start - buffer_start; uint64_t buffer_start_narrow_oop = 0xdeadbeed; if (UseCompressedOops) { diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.hpp b/src/hotspot/share/cds/aotMappedHeapWriter.hpp index 9a85b83d3d1..eafd38ac8bb 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.hpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.hpp @@ -29,6 +29,7 @@ #include "cds/heapShared.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" +#include "oops/compressedOops.hpp" #include "oops/oopHandle.hpp" #include "utilities/bitMap.hpp" #include "utilities/exceptions.hpp" @@ -71,7 +72,7 @@ class AOTMappedHeapWriter : AllStatic { // These are entered into HeapShared::archived_object_cache(). // // - "buffered objects" are copies of the "source objects", and are stored in into - // ArchiveHeapWriter::_buffer, which is a GrowableArray that sits outside of + // AOTMappedHeapWriter::_buffer, which is a GrowableArray that sits outside of // the valid heap range. Therefore we avoid using the addresses of these copies // as oops. They are usually called "buffered_addr" in the code (of the type "address"). // @@ -81,26 +82,11 @@ class AOTMappedHeapWriter : AllStatic { // - Each archived object has a "requested address" -- at run time, if the object // can be mapped at this address, we can avoid relocation. // - // The requested address is implemented differently depending on UseCompressedOops: + // The requested address of an archived object is essentially its buffered_addr + delta, + // where delta is (_requested_bottom - buffer_bottom()); // - // UseCompressedOops == true: - // The archived objects are stored assuming that the runtime COOPS compression - // scheme is exactly the same as in dump time (or else a more expensive runtime relocation - // would be needed.) - // - // At dump time, we assume that the runtime heap range is exactly the same as - // in dump time. The requested addresses of the archived objects are chosen such that - // they would occupy the top end of a G1 heap (TBD when dumping is supported by other - // collectors. See JDK-8298614). - // - // UseCompressedOops == false: - // At runtime, the heap range is usually picked (randomly) by the OS, so we will almost always - // need to perform relocation. Hence, the goal of the "requested address" is to ensure that - // the contents of the archived objects are deterministic. I.e., the oop fields of archived - // objects will always point to deterministic addresses. - // - // For G1, the archived heap is written such that the lowest archived object is placed - // at NOCOOPS_REQUESTED_BASE. (TBD after JDK-8298614). + // The requested addresses of all archived objects are within [_requested_bottom, _requested_top). + // See AOTMappedHeapWriter::set_requested_address_range() for more info. // ---------------------------------------------------------------------- public: @@ -111,6 +97,15 @@ public: // Shenandoah heap region size can never be smaller than 256K. static constexpr int MIN_GC_REGION_ALIGNMENT = 256 * K; + // The heap contents are required to be deterministic when dumping "old" CDS archives, in order + // to support reproducible lib/server/classes*.jsa when building the JDK. + static bool is_writing_deterministic_heap() { return _is_writing_deterministic_heap; } + + // The oop encoding used by the archived heap objects. + static CompressedOops::Mode narrow_oop_mode(); + static address narrow_oop_base(); + static int narrow_oop_shift(); + static const int INITIAL_TABLE_SIZE = 15889; // prime number static const int MAX_TABLE_SIZE = 1000000; @@ -121,6 +116,7 @@ private: int _field_offset; }; + static bool _is_writing_deterministic_heap; static GrowableArrayCHeap* _buffer; // The number of bytes that have written into _buffer (may be smaller than _buffer->length()). @@ -130,15 +126,15 @@ private: static HeapRootSegments _heap_root_segments; // The address range of the requested location of the archived heap objects. - static address _requested_bottom; - static address _requested_top; + static address _requested_bottom; // The requested address of the lowest archived heap object + static address _requested_top; // The exclusive end of the highest archived heap object static GrowableArrayCHeap* _native_pointers; static GrowableArrayCHeap* _source_objs; static DumpedInternedStrings *_dumped_interned_strings; // We sort _source_objs_order to minimize the number of bits in ptrmap and oopmap. - // See comments near the body of ArchiveHeapWriter::compare_objs_by_oop_fields(). + // See comments near the body of AOTMappedHeapWriter::compare_objs_by_oop_fields(). // The objects will be written in the order of: //_source_objs->at(_source_objs_order->at(0)._index) // source_objs->at(_source_objs_order->at(1)._index) @@ -200,7 +196,7 @@ private: static int filler_array_length(size_t fill_bytes); static HeapWord* init_filler_array_at_buffer_top(int array_length, size_t fill_bytes); - static void set_requested_address(ArchiveMappedHeapInfo* info); + static void set_requested_address_range(ArchiveMappedHeapInfo* info); static void mark_native_pointers(oop orig_obj); static void relocate_embedded_oops(GrowableArrayCHeap* roots, ArchiveMappedHeapInfo* info); static void compute_ptrmap(ArchiveMappedHeapInfo *info); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index ae92ce31058..61df0a86b41 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -216,12 +216,14 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _obj_alignment = ObjectAlignmentInBytes; _compact_strings = CompactStrings; _compact_headers = UseCompactObjectHeaders; +#if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { _object_streaming_mode = HeapShared::is_writing_streaming_mode(); - _narrow_oop_mode = CompressedOops::mode(); - _narrow_oop_base = CompressedOops::base(); - _narrow_oop_shift = CompressedOops::shift(); + _narrow_oop_mode = AOTMappedHeapWriter::narrow_oop_mode(); + _narrow_oop_base = AOTMappedHeapWriter::narrow_oop_base(); + _narrow_oop_shift = AOTMappedHeapWriter::narrow_oop_shift(); } +#endif _compressed_oops = UseCompressedOops; _compressed_class_ptrs = UseCompressedClassPointers; if (UseCompressedClassPointers) { @@ -911,7 +913,7 @@ void FileMapInfo::write_region(int region, char* base, size_t size, if (HeapShared::is_writing_mapping_mode()) { requested_base = (char*)AOTMappedHeapWriter::requested_address(); if (UseCompressedOops) { - mapping_offset = (size_t)((address)requested_base - CompressedOops::base()); + mapping_offset = (size_t)((address)requested_base - AOTMappedHeapWriter::narrow_oop_base()); assert((mapping_offset >> CompressedOops::shift()) << CompressedOops::shift() == mapping_offset, "must be"); } } else { diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 2c782f7231b..118c60faa60 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -332,7 +332,7 @@ public: // Used by CDSHeapVerifier. OopHandle _orig_referrer; - // The location of this object inside ArchiveHeapWriter::_buffer + // The location of this object inside {AOTMappedHeapWriter, AOTStreamedHeapWriter}::_buffer size_t _buffer_offset; // One or more fields in this object are pointing to non-null oops. diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index a45fdc09323..934ef03a987 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -100,7 +100,6 @@ gc/shenandoah/TestSieveObjects.java#no-tlab-genshen 8361099 generic-all # :hotspot_runtime -runtime/cds/DeterministicDump.java 8363986 macosx-x64,macosx-aarch64 runtime/jni/terminatedThread/TestTerminatedThread.java 8317789 aix-ppc64 runtime/Monitor/SyncOnValueBasedClassTest.java 8340995 linux-s390x runtime/os/TestTracePageSizes.java#no-options 8267460 linux-aarch64 From 99135d2e05bb501fe9f9f0d36abd25894d0f93de Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Wed, 19 Nov 2025 08:47:57 +0000 Subject: [PATCH 018/114] 8359145: Implement JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview) Reviewed-by: jlahoda --- .../java/lang/runtime/SwitchBootstraps.java | 2 +- .../com/sun/tools/javac/code/TypeTag.java | 12 +- .../com/sun/tools/javac/code/Types.java | 135 +++++++-- .../com/sun/tools/javac/comp/Attr.java | 2 +- .../com/sun/tools/javac/comp/Check.java | 29 +- .../javac/comp/ExhaustivenessComputer.java | 4 +- .../com/sun/tools/javac/comp/Lower.java | 5 +- .../sun/tools/javac/comp/TransPatterns.java | 2 +- .../tools/javac/patterns/Domination.java | 12 +- .../tools/javac/patterns/DominationWithPP.out | 14 + .../PrimitivePatternsSwitchConstants.java | 71 +++++ .../PrimitivePatternsSwitchConstants.out | 11 + .../PrimitivePatternsSwitchErrors.java | 67 ++++- .../PrimitivePatternsSwitchErrors.out | 11 +- ...veUnconditionallyExactInAssignability.java | 138 +++++++++ ...onditionallyExactInExhaustiveSwitches.java | 280 ++++++++++++++++++ .../tools/javac/patterns/T8332463a.java | 8 +- .../tools/javac/patterns/T8332463a.out | 6 + .../tools/javac/patterns/T8332463b.java | 5 +- .../tools/javac/patterns/T8332463b.out | 2 + .../tools/javac/types/UnknownTypeTest.java | 4 +- .../tools/lib/types/TypeHarness.java | 15 + 22 files changed, 776 insertions(+), 59 deletions(-) create mode 100644 test/langtools/tools/javac/patterns/DominationWithPP.out create mode 100644 test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java create mode 100644 test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out create mode 100644 test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java create mode 100644 test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java create mode 100644 test/langtools/tools/javac/patterns/T8332463a.out create mode 100644 test/langtools/tools/javac/patterns/T8332463b.out diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java index 99716baf439..30b6df0073e 100644 --- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java @@ -777,7 +777,7 @@ public final class SwitchBootstraps { return name + "$$TypeSwitch"; } - // this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives + // this method should be in sync with com.sun.tools.javac.code.Types.isUnconditionallyExactTypeBased private static boolean unconditionalExactnessCheck(Class selectorType, Class targetType) { Wrapper selectorWrapper = Wrapper.forBasicType(selectorType); Wrapper targetWrapper = Wrapper.forBasicType(targetType); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java index 0b97b119119..b2771556eb2 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java @@ -26,7 +26,7 @@ package com.sun.tools.javac.code; import com.sun.source.tree.Tree.Kind; - +import java.lang.runtime.ExactConversionsSupport; import javax.lang.model.type.TypeKind; import static com.sun.tools.javac.code.TypeTag.NumericClasses.*; @@ -186,6 +186,10 @@ public enum TypeTag { return (this.numericClass & tag.superClasses) != 0; } + public boolean isNumeric() { + return this.numericClass != 0; + } + /** Returns the number of type tags. */ public static int getTypeTagCount() { @@ -247,11 +251,11 @@ public enum TypeTag { case BOOLEAN: return 0 <= value && value <= 1; case BYTE: - return Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE; + return ExactConversionsSupport.isIntToByteExact(value); case CHAR: - return Character.MIN_VALUE <= value && value <= Character.MAX_VALUE; + return ExactConversionsSupport.isIntToCharExact(value); case SHORT: - return Short.MIN_VALUE <= value && value <= Short.MAX_VALUE; + return ExactConversionsSupport.isIntToShortExact(value); case INT: return true; default: diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index d59505555f2..3f3eb1f9623 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -26,6 +26,7 @@ package com.sun.tools.javac.code; import java.lang.ref.SoftReference; +import java.lang.runtime.ExactConversionsSupport; import java.util.HashSet; import java.util.HashMap; import java.util.Locale; @@ -5086,46 +5087,128 @@ public class Types { } // - // - /** Check unconditionality between any combination of reference or primitive types. + // + /** Check type-based unconditional exactness between any combination of + * reference or primitive types according to JLS 5.7.2. * - * Rules: - * an identity conversion - * a widening reference conversion - * a widening primitive conversion (delegates to `checkUnconditionallyExactPrimitives`) - * a boxing conversion - * a boxing conversion followed by a widening reference conversion + * The following are unconditionally exact regardless of the input + * expression: + * + * - an identity conversion + * - a widening reference conversion + * - an exact widening primitive conversion + * - a boxing conversion + * - a boxing conversion followed by a widening reference conversion * * @param source Source primitive or reference type * @param target Target primitive or reference type */ - public boolean isUnconditionallyExact(Type source, Type target) { + public boolean isUnconditionallyExactTypeBased(Type source, Type target) { if (isSameType(source, target)) { return true; } - return target.isPrimitive() - ? isUnconditionallyExactPrimitives(source, target) - : isSubtype(boxedTypeOrType(erasure(source)), target); + if (target.isPrimitive()) { + if (source.isPrimitive() && + ((source.getTag().isStrictSubRangeOf(target.getTag())) && + !((source.hasTag(BYTE) && target.hasTag(CHAR)) || + (source.hasTag(INT) && target.hasTag(FLOAT)) || + (source.hasTag(LONG) && (target.hasTag(DOUBLE) || target.hasTag(FLOAT)))))) return true; + else { + return false; + } + } else { + return isSubtype(boxedTypeOrType(erasure(source)), target); + } } - /** Check unconditionality between primitive types. + /** Check value-based unconditional exactness between any combination of + * reference or primitive types for the value of a constant expression + * according to JLS 5.7.2. * - * - widening from one integral type to another, - * - widening from one floating point type to another, - * - widening from byte, short, or char to a floating point type, - * - widening from int to double. + * The following can be unconditionally exact if the source primitive is a + * constant expression and the conversions is exact for that constant + * expression: * - * @param selectorType Type of selector - * @param targetType Target type + * - a narrowing primitive conversion + * - a widening and narrowing primitive conversion + * - a widening primitive conversion that is not exact + * + * @param source Source primitive or reference type, should be a numeric value + * @param target Target primitive or reference type */ - public boolean isUnconditionallyExactPrimitives(Type selectorType, Type targetType) { - return isSameType(selectorType, targetType) || - (selectorType.isPrimitive() && targetType.isPrimitive()) && - ((selectorType.getTag().isStrictSubRangeOf(targetType.getTag())) && - !((selectorType.hasTag(BYTE) && targetType.hasTag(CHAR)) || - (selectorType.hasTag(INT) && targetType.hasTag(FLOAT)) || - (selectorType.hasTag(LONG) && (targetType.hasTag(DOUBLE) || targetType.hasTag(FLOAT))))); + public boolean isUnconditionallyExactValueBased(Type source, Type target) { + if (!(source.constValue() instanceof Number value) || !target.getTag().isNumeric()) return false; + + switch (source.getTag()) { + case BYTE: + switch (target.getTag()) { + case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue()); + } + break; + case CHAR: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue()); + case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue()); + } + break; + case SHORT: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue()); + case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue()); + } + break; + case INT: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue()); + case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue()); + case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue()); + case FLOAT: return ExactConversionsSupport.isIntToFloatExact(value.intValue()); + } + break; + case FLOAT: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isFloatToByteExact(value.floatValue()); + case CHAR: return ExactConversionsSupport.isFloatToCharExact(value.floatValue()); + case SHORT: return ExactConversionsSupport.isFloatToShortExact(value.floatValue()); + case INT: return ExactConversionsSupport.isFloatToIntExact(value.floatValue()); + case LONG: return ExactConversionsSupport.isFloatToLongExact(value.floatValue()); + } + break; + case LONG: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isLongToByteExact(value.longValue()); + case CHAR: return ExactConversionsSupport.isLongToCharExact(value.longValue()); + case SHORT: return ExactConversionsSupport.isLongToShortExact(value.longValue()); + case INT: return ExactConversionsSupport.isLongToIntExact(value.longValue()); + case FLOAT: return ExactConversionsSupport.isLongToFloatExact(value.longValue()); + case DOUBLE: return ExactConversionsSupport.isLongToDoubleExact(value.longValue()); + } + break; + case DOUBLE: + switch (target.getTag()) { + case BYTE: return ExactConversionsSupport.isDoubleToByteExact(value.doubleValue()); + case CHAR: return ExactConversionsSupport.isDoubleToCharExact(value.doubleValue()); + case SHORT: return ExactConversionsSupport.isDoubleToShortExact(value.doubleValue()); + case INT: return ExactConversionsSupport.isDoubleToIntExact(value.doubleValue()); + case FLOAT: return ExactConversionsSupport.isDoubleToFloatExact(value.doubleValue()); + case LONG: return ExactConversionsSupport.isDoubleToLongExact(value.doubleValue()); + } + break; + } + return true; + } + + /** Check both type or value-based unconditional exactness between any + * combination of reference or primitive types for the value of a constant + * expression according to JLS 5.7.2. + * + * @param source Source primitive or reference type, should be a numeric value + * @param target Target primitive or reference type + */ + public boolean isUnconditionallyExactCombined(Type currentType, Type testType) { + return isUnconditionallyExactTypeBased(currentType, testType) || + (currentType.constValue() instanceof Number && isUnconditionallyExactValueBased(currentType, testType)); } // diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index a45f7466f9e..ad41adcc135 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -1861,7 +1861,7 @@ public class Attr extends JCTree.Visitor { boolean unconditional = unguarded && !patternType.isErroneous() && - types.isUnconditionallyExact(seltype, patternType); + types.isUnconditionallyExactTypeBased(seltype, patternType); if (unconditional) { if (hasUnconditionalPattern) { log.error(pat.pos(), Errors.DuplicateUnconditionalPattern); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 9098568f42a..94b14f3122f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -28,7 +28,6 @@ package com.sun.tools.javac.comp; import java.util.*; import java.util.function.BiConsumer; import java.util.function.BiPredicate; -import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.ToIntBiFunction; @@ -167,6 +166,7 @@ public class Check { allowModules = Feature.MODULES.allowedInSource(source); allowRecords = Feature.RECORDS.allowedInSource(source); allowSealed = Feature.SEALED_CLASSES.allowedInSource(source); + allowPrimitivePatterns = preview.isEnabled() && Feature.PRIMITIVE_PATTERNS.allowedInSource(source); } /** Character for synthetic names @@ -190,6 +190,10 @@ public class Check { */ private final boolean allowSealed; + /** Are primitive patterns allowed + */ + private final boolean allowPrimitivePatterns; + /** Whether to force suppression of deprecation and preview warnings. * This happens when attributing import statements for JDK 9+. * @see Feature#DEPRECATION_ON_IMPORT @@ -4764,21 +4768,26 @@ public class Check { JCCase testCase = caseAndLabel.fst; JCCaseLabel testCaseLabel = caseAndLabel.snd; Type testType = labelType(testCaseLabel); + + // an unconditional pattern cannot be followed by any other label + if (allowPrimitivePatterns && unconditionalCaseLabel == testCaseLabel && unconditionalCaseLabel != label) { + log.error(label.pos(), Errors.PatternDominated); + continue; + } + boolean dominated = false; - if (types.isUnconditionallyExact(currentType, testType) && - !currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) { - //the current label is potentially dominated by the existing (test) label, check: - if (label instanceof JCConstantCaseLabel) { - dominated |= !(testCaseLabel instanceof JCConstantCaseLabel) && + if (!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) { + // the current label is potentially dominated by the existing (test) label, check: + if (types.isUnconditionallyExactCombined(currentType, testType) && + label instanceof JCConstantCaseLabel) { + dominated = !(testCaseLabel instanceof JCConstantCaseLabel) && TreeInfo.unguardedCase(testCase); } else if (label instanceof JCPatternCaseLabel patternCL && testCaseLabel instanceof JCPatternCaseLabel testPatternCaseLabel && (testCase.equals(c) || TreeInfo.unguardedCase(testCase))) { - dominated = patternDominated(testPatternCaseLabel.pat, - patternCL.pat); + dominated = patternDominated(testPatternCaseLabel.pat, patternCL.pat); } } - if (dominated) { log.error(label.pos(), Errors.PatternDominated); } @@ -4798,7 +4807,7 @@ public class Check { private boolean patternDominated(JCPattern existingPattern, JCPattern currentPattern) { Type existingPatternType = types.erasure(existingPattern.type); Type currentPatternType = types.erasure(currentPattern.type); - if (!types.isUnconditionallyExact(currentPatternType, existingPatternType)) { + if (!types.isUnconditionallyExactTypeBased(currentPatternType, existingPatternType)) { return false; } if (currentPattern instanceof JCBindingPattern || diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java index 036e86dff5e..bbc330832f3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java @@ -568,8 +568,8 @@ public class ExhaustivenessComputer { Type pattype = types.erasure(bp.type); return seltype.isPrimitive() ? - types.isUnconditionallyExact(seltype, pattype) : - (bp.type.isPrimitive() && types.isUnconditionallyExact(types.unboxedType(seltype), bp.type)) || types.isSubtype(seltype, pattype); + types.isUnconditionallyExactTypeBased(seltype, pattype) : + (bp.type.isPrimitive() && types.isUnconditionallyExactTypeBased(types.unboxedType(seltype), bp.type)) || types.isSubtype(seltype, pattype); } return false; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index aee7f0afe39..2db3435a382 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -36,7 +36,6 @@ import com.sun.tools.javac.jvm.*; import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant; import com.sun.tools.javac.main.Option.PkgInfo; import com.sun.tools.javac.resources.CompilerProperties.Fragments; -import com.sun.tools.javac.resources.CompilerProperties.Notes; import com.sun.tools.javac.tree.*; import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; @@ -2835,7 +2834,7 @@ public class Lower extends TreeTranslator { JCExpression exactnessCheck; JCExpression instanceOfExpr = translate(tree.expr); - if (types.isUnconditionallyExact(tree.expr.type, tree.pattern.type)) { + if (types.isUnconditionallyExactTypeBased(tree.expr.type, tree.pattern.type)) { // instanceOfExpr; true prefixStatement = make.Exec(instanceOfExpr); exactnessCheck = make.Literal(BOOLEAN, 1).setType(syms.booleanType.constType(1)); @@ -2844,7 +2843,7 @@ public class Lower extends TreeTranslator { prefixStatement = null; exactnessCheck = getExactnessCheck(tree, instanceOfExpr); } else if (tree.expr.type.isReference()) { - if (types.isUnconditionallyExact(types.unboxedType(tree.expr.type), tree.pattern.type)) { + if (types.isUnconditionallyExactTypeBased(types.unboxedType(tree.expr.type), tree.pattern.type)) { // instanceOfExpr != null prefixStatement = null; exactnessCheck = makeBinary(NE, instanceOfExpr, makeNull()); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java index 58c36a5cf7c..11774c313e3 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java @@ -921,7 +921,7 @@ public class TransPatterns extends TreeTranslator { JCBindingPattern binding = (JCBindingPattern) instanceofCheck.pattern; hasUnconditional = (!types.erasure(binding.type).isPrimitive() ? instanceofCheck.allowNull : - types.isUnconditionallyExact(commonNestedExpression.type, types.erasure(binding.type))) && + types.isUnconditionallyExactTypeBased(commonNestedExpression.type, types.erasure(binding.type))) && accList.tail.isEmpty(); List newLabel; diff --git a/test/langtools/tools/javac/patterns/Domination.java b/test/langtools/tools/javac/patterns/Domination.java index a5cdb99b9c8..dd9d1153696 100644 --- a/test/langtools/tools/javac/patterns/Domination.java +++ b/test/langtools/tools/javac/patterns/Domination.java @@ -26,8 +26,8 @@ * @bug 8262891 8290709 * @summary Check the pattern domination error are reported correctly. * @compile/fail/ref=Domination.out -XDrawDiagnostics Domination.java + * @compile/fail/ref=DominationWithPP.out --enable-preview --source ${jdk.version} -XDrawDiagnostics Domination.java */ - public class Domination { int testDominatesError1(Object o) { switch (o) { @@ -218,4 +218,14 @@ public class Domination { case null : return -1; } } + + int testCasePatternDominatedbyPreceedingUnconditionalCasePattern () { + interface A {} + interface B {} + A aa = new A() {}; + switch (aa) { + case A a : return 1; + case B b : return -1; + } + } } diff --git a/test/langtools/tools/javac/patterns/DominationWithPP.out b/test/langtools/tools/javac/patterns/DominationWithPP.out new file mode 100644 index 00000000000..119cc003d07 --- /dev/null +++ b/test/langtools/tools/javac/patterns/DominationWithPP.out @@ -0,0 +1,14 @@ +Domination.java:35:18: compiler.err.pattern.dominated +Domination.java:43:18: compiler.err.pattern.dominated +Domination.java:51:18: compiler.err.pattern.dominated +Domination.java:67:18: compiler.err.pattern.dominated +Domination.java:88:18: compiler.err.pattern.dominated +Domination.java:113:18: compiler.err.pattern.dominated +Domination.java:144:18: compiler.err.pattern.dominated +Domination.java:153:18: compiler.err.pattern.dominated +Domination.java:184:18: compiler.err.pattern.dominated +Domination.java:193:18: compiler.err.pattern.dominated +Domination.java:202:18: compiler.err.pattern.dominated +Domination.java:211:18: compiler.err.pattern.dominated +Domination.java:228:18: compiler.err.pattern.dominated +13 errors diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java new file mode 100644 index 00000000000..2890b315e62 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.java @@ -0,0 +1,71 @@ +/* + * @test /nodynamiccopyright/ + * @summary Retain exhaustiveness properties of switches with a constant selector + * @enablePreview + * @compile/fail/ref=PrimitivePatternsSwitchConstants.out -XDrawDiagnostics -XDshould-stop.at=FLOW PrimitivePatternsSwitchConstants.java + */ +public class PrimitivePatternsSwitchConstants { + void testConstExpressions() { + switch (42) { // error: not exhaustive + case byte _ : + } + + switch (42l) { // error: not exhaustive + case byte _ : + } + + switch (123456) { // error: not exhaustive + case byte _ : + } + + switch (16_777_216) { // error: not exhaustive + case float _ : + } + + switch (16_777_217) { // error: not exhaustive + case float _ : + } + + switch (42d) { // error: not exhaustive + case float _ : + } + + switch (1) { // OK + case long _ : + } + + final int i = 42; + switch (i) { // OK + case long _ : + } + + switch (1) { // error: non-exhaustive + case Long _ : // error: widening primitive conversion and boxing is not supported + } + + switch (42) { + case byte bb -> {} + case int ii -> {} // OK + }; + + switch (42) { + case 42 -> {} + case int ii -> {} // OK + }; + + switch (42) { + case (byte) 42 -> {} + case int ii -> {} // OK + }; + + switch (42) { + case 42 -> {} + default -> {} // OK + }; + + switch (42) { + default -> {} // OK + case 42 -> {} + }; + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out new file mode 100644 index 00000000000..9a983a86437 --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchConstants.out @@ -0,0 +1,11 @@ +PrimitivePatternsSwitchConstants.java:43:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Long) +PrimitivePatternsSwitchConstants.java:9:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:13:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:17:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:21:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:25:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:29:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchConstants.java:42:9: compiler.err.not.exhaustive.statement +- compiler.note.preview.filename: PrimitivePatternsSwitchConstants.java, DEFAULT +- compiler.note.preview.recompile +8 errors diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java index 3d47c4ac9bc..dacd48f441c 100644 --- a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java @@ -59,7 +59,7 @@ public class PrimitivePatternsSwitchErrors { int i = 42; return switch (i) { case Integer ib -> ib; - case byte ip -> ip; // OK - not dominated! + case byte ip -> ip; // Error - dominated! }; } @@ -265,4 +265,69 @@ public class PrimitivePatternsSwitchErrors { public static boolean wideningReferenceConversionUnboxingAndNarrowingPrimitive(T i) { return i instanceof byte b; // not allowed as a conversion } + + public static void dominanceIntFloat() { + int ii = 42; + switch (ii) { + case int i -> {} + case float f -> {} // Error - dominated! + } + } + + public static void noDominanceIntFloat() { + int ii = 42; + switch (ii) { + case float f -> {} + case int i -> {} // ok + } + } + + public static void strengtheningDominance() { + byte x = 42; + switch (x) { + case short s -> {} + case 42 -> {} // error: dominated + } + + long l = 42l; + switch (l) { + case short s -> {} + case 42l -> {} // error: dominated + case long _ -> {} + } + + char c = 'a'; + switch (c) { + case short s -> {} + case 42 -> {} // error: dominated + case char _ -> {} + } + + int x2 = 42; + switch(x2) { + case float f -> {} + case 16_777_216 -> {} // error: dominated + default -> {} + } + + switch(x2) { + case float f -> {} + case 16_777_217 -> {} // OK + default -> {} + } + + switch(x2) { + case int ii -> {} + case float f -> {} // error: dominated + } + } + + public static void unconditionalFollowedByDefault() { + int ii = 42; + switch (ii) { + case int i -> {} + case float f -> {} // Error - dominated! + default -> {} // Error - unconditional and default + } + } } diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out index 75fd62016a0..5a0822a9aef 100644 --- a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out @@ -1,6 +1,7 @@ PrimitivePatternsSwitchErrors.java:15:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:24:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:31:24: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Long) +PrimitivePatternsSwitchErrors.java:62:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:70:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:78:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:84:18: compiler.err.prob.found.req: (compiler.misc.possible.loss.of.precision: long, byte) @@ -29,6 +30,14 @@ PrimitivePatternsSwitchErrors.java:248:18: compiler.err.prob.found.req: (compile PrimitivePatternsSwitchErrors.java:255:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Long, int) PrimitivePatternsSwitchErrors.java:261:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: java.lang.Short, char) PrimitivePatternsSwitchErrors.java:266:16: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: T, byte) +PrimitivePatternsSwitchErrors.java:273:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:289:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:295:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:302:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:309:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:321:18: compiler.err.pattern.dominated +PrimitivePatternsSwitchErrors.java:330:13: compiler.err.unconditional.pattern.and.default +PrimitivePatternsSwitchErrors.java:329:18: compiler.err.pattern.dominated PrimitivePatternsSwitchErrors.java:30:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:37:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:44:16: compiler.err.not.exhaustive @@ -43,4 +52,4 @@ PrimitivePatternsSwitchErrors.java:254:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:260:16: compiler.err.not.exhaustive - compiler.note.preview.filename: PrimitivePatternsSwitchErrors.java, DEFAULT - compiler.note.preview.recompile -43 errors \ No newline at end of file +52 errors diff --git a/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java new file mode 100644 index 00000000000..c75dff3f8ad --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInAssignability.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2025, 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 Check assignability of narrowing p.c. with constant expressions vs exact conversion methods + * @library /tools/lib/types + * @modules jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.comp + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.tree + * @build TypeHarness + * @compile PrimitiveUnconditionallyExactInAssignability.java + * @run main PrimitiveUnconditionallyExactInAssignability + */ + +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.TypeTag; + +import java.lang.runtime.ExactConversionsSupport; + +import static com.sun.tools.javac.code.TypeTag.ERROR; +import static com.sun.tools.javac.code.TypeTag.INT; + +public class PrimitiveUnconditionallyExactInAssignability extends TypeHarness { + PrimitiveUnconditionallyExactInAssignability() { + } + + void assertOriginalAssignmentNarrowingAndUnconditionality() { + // byte b = vs ExactConversionsSupport::isIntToByteExact + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MIN_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Short.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Byte.MIN_VALUE - 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((short) (Byte.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Byte.MAX_VALUE + 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((short) (Byte.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MAX_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Short.MAX_VALUE)); + + // byte b = vs ExactConversionsSupport::isIntToByteExact + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MIN_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Character.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((char) (Byte.MAX_VALUE + 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((char) (Byte.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MAX_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Character.MAX_VALUE)); + + // byte b = vs ExactConversionsSupport::isIntToByteExact + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MIN_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Integer.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Byte.MIN_VALUE - 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((int) (Byte.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Byte.MAX_VALUE + 1)), predef.byteType, ExactConversionsSupport.isIntToByteExact((int) (Byte.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MAX_VALUE), predef.byteType, ExactConversionsSupport.isIntToByteExact(Integer.MAX_VALUE)); + + // char c = vs ExactConversionsSupport::isIntToCharExact + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MIN_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Short.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Character.MIN_VALUE - 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((short) (Character.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((short) (Character.MAX_VALUE + 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((short) (Character.MIN_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Short.MAX_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Short.MAX_VALUE)); + + // char c = vs ExactConversionsSupport::isIntToCharExact + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MIN_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Integer.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Character.MIN_VALUE - 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((int) (Character.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Character.MAX_VALUE + 1)), predef.charType, ExactConversionsSupport.isIntToCharExact((int) (Character.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MAX_VALUE), predef.charType, ExactConversionsSupport.isIntToCharExact(Integer.MAX_VALUE)); + + // short b = vs ExactConversionsSupport::isIntToShortExact + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MIN_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Character.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((char) (Character.MAX_VALUE + 1)), predef.shortType, ExactConversionsSupport.isIntToShortExact((char) (Character.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Character.MAX_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Character.MAX_VALUE)); + + // short b = vs ExactConversionsSupport::isIntToShortExact + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MIN_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Integer.MIN_VALUE)); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Short.MIN_VALUE - 1)), predef.shortType, ExactConversionsSupport.isIntToShortExact((int) (Short.MIN_VALUE - 1))); + assertOriginaAndUpdatedAssignable(fac.Constant((int) (Short.MAX_VALUE + 1)), predef.shortType, ExactConversionsSupport.isIntToShortExact((int) (Short.MAX_VALUE + 1))); + assertOriginaAndUpdatedAssignable(fac.Constant(Integer.MAX_VALUE), predef.shortType, ExactConversionsSupport.isIntToShortExact(Integer.MAX_VALUE)); + } + // where + public void assertOriginaAndUpdatedAssignable(Type s, Type t, boolean expected) { + assertAssignable(s, t, originalIsAssignable(s, t)); + } + public boolean originalIsAssignable(Type t, Type s) { + if (t.hasTag(ERROR)) + return true; + if (t.getTag().isSubRangeOf(INT) && t.constValue() != null) { + int value = ((Number)t.constValue()).intValue(); + switch (s.getTag()) { + case BYTE: + case CHAR: + case SHORT: + case INT: + if (originalCheckRange(s.getTag(), value)) + return true; + break; + } + } + return types.isConvertible(t, s); + } + public boolean originalCheckRange(TypeTag that, int value) { + switch (that) { + case BOOLEAN: + return 0 <= value && value <= 1; + case BYTE: + return Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE; + case CHAR: + return Character.MIN_VALUE <= value && value <= Character.MAX_VALUE; + case SHORT: + return Short.MIN_VALUE <= value && value <= Short.MAX_VALUE; + case INT: + return true; + default: + throw new AssertionError(); + } + } + + private void error(String msg) { + throw new AssertionError("Unexpected result in original isAssignable: " + msg); + } + + public static void main(String[] args) { + PrimitiveUnconditionallyExactInAssignability harness = new PrimitiveUnconditionallyExactInAssignability(); + harness.assertOriginalAssignmentNarrowingAndUnconditionality(); + } +} diff --git a/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java new file mode 100644 index 00000000000..e6a204968dd --- /dev/null +++ b/test/langtools/tools/javac/patterns/PrimitiveUnconditionallyExactInExhaustiveSwitches.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2025, 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 Check the unconditionally exact for constant primitives used in the exhaustiveness check + * @library /tools/lib/types + * @modules jdk.compiler/com.sun.tools.javac.code + * jdk.compiler/com.sun.tools.javac.comp + * jdk.compiler/com.sun.tools.javac.file + * jdk.compiler/com.sun.tools.javac.util + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.tree + * @build TypeHarness + * @compile PrimitiveUnconditionallyExactInExhaustiveSwitches.java + * @run main PrimitiveUnconditionallyExactInExhaustiveSwitches + */ + +public class PrimitiveUnconditionallyExactInExhaustiveSwitches extends TypeHarness { + + PrimitiveUnconditionallyExactInExhaustiveSwitches() { + } + public void testByte() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))), predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.byteType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.byteType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.byteType, true); + } + public void testShort() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.shortType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.shortType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.shortType, true); + } + public void testChar() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.charType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.charType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.charType, true); + } + public void testInt() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.intType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.intType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.intType, true); + } + public void testLong() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.longType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.longType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.longType, true); + } + public void testFloat() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((byte) (0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.floatType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.floatType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.floatType, true); + } + public void testDouble() { + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MAX_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (0))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((byte) (Byte.MIN_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MAX_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (0))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((short) (Short.MIN_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MAX_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((char) (Character.MIN_VALUE))),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MAX_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Integer.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MAX_VALUE)),predef.doubleType, false); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((0L)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Long.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MAX_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((float) 0)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NaN)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.POSITIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Float.NEGATIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0f)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0f)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MAX_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant(((double) 0)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.MIN_VALUE)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NaN)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.POSITIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((Double.NEGATIVE_INFINITY)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((-0.0d)),predef.doubleType, true); + assertIsUnconditionallyExactConstantPrimitives(fac.Constant((+0.0d)),predef.doubleType, true); + } + + + public static void main(String[] args) { + PrimitiveUnconditionallyExactInExhaustiveSwitches harness = new PrimitiveUnconditionallyExactInExhaustiveSwitches(); + harness.testByte(); + harness.testShort(); + harness.testChar(); + harness.testInt(); + harness.testDouble(); + harness.testLong(); + harness.testFloat(); + } +} diff --git a/test/langtools/tools/javac/patterns/T8332463a.java b/test/langtools/tools/javac/patterns/T8332463a.java index 96aaad86a85..2501a330af2 100644 --- a/test/langtools/tools/javac/patterns/T8332463a.java +++ b/test/langtools/tools/javac/patterns/T8332463a.java @@ -25,14 +25,14 @@ * @test * @bug 8332463 * @summary Byte conditional pattern case element dominates short constant case element - * @compile --enable-preview --source ${jdk.version} T8332463a.java + * @compile/fail/ref=T8332463a.out -XDrawDiagnostics --enable-preview --source ${jdk.version} T8332463a.java */ public class T8332463a { public int test2() { Byte i = (byte) 42; return switch (i) { case Byte ib -> 1; - case short s -> 2; + case short s -> 2; // dominated }; } @@ -40,7 +40,7 @@ public class T8332463a { int i = 42; return switch (i) { case Integer ib -> 1; - case byte ip -> 2; + case byte ip -> 2; // dominated }; } @@ -48,7 +48,7 @@ public class T8332463a { int i = 42; return switch (i) { case Integer ib -> 1; - case (byte) 0 -> 2; + case (byte) 0 -> 2; // dominated }; } } diff --git a/test/langtools/tools/javac/patterns/T8332463a.out b/test/langtools/tools/javac/patterns/T8332463a.out new file mode 100644 index 00000000000..5a4eec46d82 --- /dev/null +++ b/test/langtools/tools/javac/patterns/T8332463a.out @@ -0,0 +1,6 @@ +T8332463a.java:35:18: compiler.err.pattern.dominated +T8332463a.java:43:18: compiler.err.pattern.dominated +T8332463a.java:51:18: compiler.err.pattern.dominated +- compiler.note.preview.filename: T8332463a.java, DEFAULT +- compiler.note.preview.recompile +3 errors diff --git a/test/langtools/tools/javac/patterns/T8332463b.java b/test/langtools/tools/javac/patterns/T8332463b.java index 7956fd31f9f..73338b2bd8a 100644 --- a/test/langtools/tools/javac/patterns/T8332463b.java +++ b/test/langtools/tools/javac/patterns/T8332463b.java @@ -26,15 +26,14 @@ * @bug 8332463 * @summary Byte conditional pattern case element dominates short constant case element * @enablePreview - * @compile T8332463b.java - * @compile --enable-preview --source ${jdk.version} T8332463b.java + * @compile/fail/ref=T8332463b.out -XDrawDiagnostics --enable-preview --source ${jdk.version} T8332463b.java */ public class T8332463b { public int test1() { Byte i = (byte) 42; return switch (i) { case Byte ib -> 1; - case (short) 0 -> 2; + case (short) 0 -> 2; // dominated }; } } diff --git a/test/langtools/tools/javac/patterns/T8332463b.out b/test/langtools/tools/javac/patterns/T8332463b.out new file mode 100644 index 00000000000..f912242a6c6 --- /dev/null +++ b/test/langtools/tools/javac/patterns/T8332463b.out @@ -0,0 +1,2 @@ +T8332463b.java:36:18: compiler.err.pattern.dominated +1 error diff --git a/test/langtools/tools/javac/types/UnknownTypeTest.java b/test/langtools/tools/javac/types/UnknownTypeTest.java index edb4ab3a9be..e79c583f142 100644 --- a/test/langtools/tools/javac/types/UnknownTypeTest.java +++ b/test/langtools/tools/javac/types/UnknownTypeTest.java @@ -73,7 +73,9 @@ public class UnknownTypeTest extends TypeHarness { types::isSameType, types::isSubtype, types::isSuperType, - types::isUnconditionallyExact, + types::isUnconditionallyExactValueBased, + types::isUnconditionallyExactTypeBased, + types::isUnconditionallyExactCombined, (t1, _) -> types.isArray(t1), (t1, _) -> types.isDerivedRaw(t1), (t1, _) -> types.isReifiable(t1), diff --git a/test/langtools/tools/lib/types/TypeHarness.java b/test/langtools/tools/lib/types/TypeHarness.java index 43bf2f961e9..296ea8d3f9b 100644 --- a/test/langtools/tools/lib/types/TypeHarness.java +++ b/test/langtools/tools/lib/types/TypeHarness.java @@ -188,6 +188,21 @@ public class TypeHarness { } } + /** assert that 's' is unconditionally exact to 't' */ + public void assertIsUnconditionallyExactConstantPrimitives(Type s, Type t) { + assertIsUnconditionallyExactConstantPrimitives(s, t, true); + } + + /** assert that 's' is/is not unconditionally exact to 't' */ + public void assertIsUnconditionallyExactConstantPrimitives(Type s, Type t, boolean expected) { + if (types.isUnconditionallyExactValueBased(s, t) != expected) { + String msg = expected ? + " is not unconditionally exact to " : + " is unconditionally exact to "; + error(s + msg + t); + } + } + /** assert that generic type 't' is well-formed */ public void assertValidGenericType(Type t) { assertValidGenericType(t, true); From 54893dc5c2a4702896029b1844bc9496325c8f26 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 19 Nov 2025 11:46:43 +0000 Subject: [PATCH 019/114] 8371985: Parallel: Move should_attempt_scavenge to ParallelScavengeHeap Reviewed-by: fandreuzzi, iwalulya --- .../gc/parallel/parallelScavengeHeap.cpp | 60 ++++++++++++++++-- .../gc/parallel/parallelScavengeHeap.hpp | 3 + src/hotspot/share/gc/parallel/psScavenge.cpp | 62 +------------------ src/hotspot/share/gc/parallel/psScavenge.hpp | 2 - 4 files changed, 59 insertions(+), 68 deletions(-) diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index ebaea3ecba4..747e2f3228c 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -370,6 +370,55 @@ void ParallelScavengeHeap::do_full_collection(bool clear_all_soft_refs) { PSParallelCompact::invoke(clear_all_soft_refs, should_do_max_compaction); } +bool ParallelScavengeHeap::should_attempt_young_gc() const { + const bool ShouldRunYoungGC = true; + const bool ShouldRunFullGC = false; + + if (!_young_gen->to_space()->is_empty()) { + log_debug(gc, ergo)("To-space is not empty; run full-gc instead."); + return ShouldRunFullGC; + } + + // Check if the predicted promoted bytes will overflow free space in old-gen. + PSAdaptiveSizePolicy* policy = _size_policy; + + size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes(); + size_t promotion_estimate = MIN2(avg_promoted, _young_gen->used_in_bytes()); + // Total free size after possible old gen expansion + size_t free_in_old_gen_with_expansion = _old_gen->max_gen_size() - _old_gen->used_in_bytes(); + + log_trace(gc, ergo)("average_promoted %zu; padded_average_promoted %zu", + (size_t) policy->average_promoted_in_bytes(), + (size_t) policy->padded_average_promoted_in_bytes()); + + if (promotion_estimate >= free_in_old_gen_with_expansion) { + log_debug(gc, ergo)("Run full-gc; predicted promotion size >= max free space in old-gen: %zu >= %zu", + promotion_estimate, free_in_old_gen_with_expansion); + return ShouldRunFullGC; + } + + if (UseAdaptiveSizePolicy) { + // Also checking OS has enough free memory to commit and expand old-gen. + // Otherwise, the recorded gc-pause-time might be inflated to include time + // of OS preparing free memory, resulting in inaccurate young-gen resizing. + assert(_old_gen->committed().byte_size() >= _old_gen->used_in_bytes(), "inv"); + // Use uint64_t instead of size_t for 32bit compatibility. + uint64_t free_mem_in_os; + if (os::free_memory(free_mem_in_os)) { + size_t actual_free = (size_t)MIN2(_old_gen->committed().byte_size() - _old_gen->used_in_bytes() + free_mem_in_os, + (uint64_t)SIZE_MAX); + if (promotion_estimate > actual_free) { + log_debug(gc, ergo)("Run full-gc; predicted promotion size > free space in old-gen and OS: %zu > %zu", + promotion_estimate, actual_free); + return ShouldRunFullGC; + } + } + } + + // No particular reasons to run full-gc, so young-gc. + return ShouldRunYoungGC; +} + static bool check_gc_heap_free_limit(size_t free_bytes, size_t capacity_bytes) { return (free_bytes * 100 / capacity_bytes) < GCHeapFreeLimit; } @@ -516,17 +565,18 @@ void ParallelScavengeHeap::collect(GCCause::Cause cause) { VMThread::execute(&op); } -void ParallelScavengeHeap::collect_at_safepoint(bool full) { +void ParallelScavengeHeap::collect_at_safepoint(bool is_full) { assert(!GCLocker::is_active(), "precondition"); bool clear_soft_refs = GCCause::should_clear_all_soft_refs(_gc_cause); - if (!full) { - bool success = PSScavenge::invoke(clear_soft_refs); - if (success) { + if (!is_full && should_attempt_young_gc()) { + bool young_gc_success = PSScavenge::invoke(clear_soft_refs); + if (young_gc_success) { return; } - // Upgrade to Full-GC if young-gc fails + log_debug(gc, heap)("Upgrade to Full-GC since Young-gc failed."); } + const bool should_do_max_compaction = false; PSParallelCompact::invoke(clear_soft_refs, should_do_max_compaction); } diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index f9161afc28f..0221fd2a90e 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -119,6 +119,9 @@ class ParallelScavengeHeap : public CollectedHeap { void print_tracing_info() const override; void stop() override {}; + // Returns true if a young GC should be attempted, false if a full GC is preferred. + bool should_attempt_young_gc() const; + public: ParallelScavengeHeap() : CollectedHeap(), diff --git a/src/hotspot/share/gc/parallel/psScavenge.cpp b/src/hotspot/share/gc/parallel/psScavenge.cpp index e738a13d464..d1d595df529 100644 --- a/src/hotspot/share/gc/parallel/psScavenge.cpp +++ b/src/hotspot/share/gc/parallel/psScavenge.cpp @@ -313,12 +313,6 @@ bool PSScavenge::invoke(bool clear_soft_refs) { assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint"); assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread"); - // Check for potential problems. - if (!should_attempt_scavenge()) { - log_info(gc, ergo)("Young-gc might fail so skipping"); - return false; - } - IsSTWGCActiveMark mark; _gc_timer.register_gc_start(); @@ -336,8 +330,7 @@ bool PSScavenge::invoke(bool clear_soft_refs) { PSOldGen* old_gen = heap->old_gen(); PSAdaptiveSizePolicy* size_policy = heap->size_policy(); - assert(young_gen->to_space()->is_empty(), - "Attempt to scavenge with live objects in to_space"); + assert(young_gen->to_space()->is_empty(), "precondition"); heap->increment_total_collections(); @@ -520,59 +513,6 @@ void PSScavenge::clean_up_failed_promotion() { NOT_PRODUCT(ParallelScavengeHeap::heap()->reset_promotion_should_fail();) } -bool PSScavenge::should_attempt_scavenge() { - const bool ShouldRunYoungGC = true; - const bool ShouldRunFullGC = false; - - ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); - PSYoungGen* young_gen = heap->young_gen(); - PSOldGen* old_gen = heap->old_gen(); - - if (!young_gen->to_space()->is_empty()) { - log_debug(gc, ergo)("To-space is not empty; run full-gc instead."); - return ShouldRunFullGC; - } - - // Check if the predicted promoted bytes will overflow free space in old-gen. - PSAdaptiveSizePolicy* policy = heap->size_policy(); - - size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes(); - size_t promotion_estimate = MIN2(avg_promoted, young_gen->used_in_bytes()); - // Total free size after possible old gen expansion - size_t free_in_old_gen_with_expansion = old_gen->max_gen_size() - old_gen->used_in_bytes(); - - log_trace(gc, ergo)("average_promoted %zu; padded_average_promoted %zu", - (size_t) policy->average_promoted_in_bytes(), - (size_t) policy->padded_average_promoted_in_bytes()); - - if (promotion_estimate >= free_in_old_gen_with_expansion) { - log_debug(gc, ergo)("Run full-gc; predicted promotion size >= max free space in old-gen: %zu >= %zu", - promotion_estimate, free_in_old_gen_with_expansion); - return ShouldRunFullGC; - } - - if (UseAdaptiveSizePolicy) { - // Also checking OS has enough free memory to commit and expand old-gen. - // Otherwise, the recorded gc-pause-time might be inflated to include time - // of OS preparing free memory, resulting in inaccurate young-gen resizing. - assert(old_gen->committed().byte_size() >= old_gen->used_in_bytes(), "inv"); - // Use uint64_t instead of size_t for 32bit compatibility. - uint64_t free_mem_in_os; - if (os::free_memory(free_mem_in_os)) { - size_t actual_free = (size_t)MIN2(old_gen->committed().byte_size() - old_gen->used_in_bytes() + free_mem_in_os, - (uint64_t)SIZE_MAX); - if (promotion_estimate > actual_free) { - log_debug(gc, ergo)("Run full-gc; predicted promotion size > free space in old-gen and OS: %zu > %zu", - promotion_estimate, actual_free); - return ShouldRunFullGC; - } - } - } - - // No particular reasons to run full-gc, so young-gc. - return ShouldRunYoungGC; -} - // Adaptive size policy support. void PSScavenge::set_young_generation_boundary(HeapWord* v) { _young_generation_boundary = v; diff --git a/src/hotspot/share/gc/parallel/psScavenge.hpp b/src/hotspot/share/gc/parallel/psScavenge.hpp index c297a46a46e..af9b91f74bc 100644 --- a/src/hotspot/share/gc/parallel/psScavenge.hpp +++ b/src/hotspot/share/gc/parallel/psScavenge.hpp @@ -64,8 +64,6 @@ class PSScavenge: AllStatic { static void clean_up_failed_promotion(); - static bool should_attempt_scavenge(); - // Private accessors static PSCardTable* card_table() { assert(_card_table != nullptr, "Sanity"); return _card_table; } static const ParallelScavengeTracer* gc_tracer() { return &_gc_tracer; } From d2926dfd9a242928877d0b1e40eac498073975bd Mon Sep 17 00:00:00 2001 From: Evgeny Astigeevich Date: Wed, 19 Nov 2025 12:11:23 +0000 Subject: [PATCH 020/114] 8371649: ZGC: AArch64: redundant OrderAccess::fence in ZBarrierSetAssembler::patch_barrier_relocation Reviewed-by: aph --- src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp index 5d4f0801ec6..07a2d6fbfa0 100644 --- a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp @@ -879,7 +879,6 @@ void ZBarrierSetAssembler::patch_barrier_relocation(address addr, int format) { ShouldNotReachHere(); } - OrderAccess::fence(); ICache::invalidate_word((address)patch_addr); } From 0b3df489e9d3b6d876a67793e082b930c17ade3e Mon Sep 17 00:00:00 2001 From: Renjith Kannath Pariyangad Date: Wed, 19 Nov 2025 12:13:37 +0000 Subject: [PATCH 021/114] 8372048: Performance improvement on Linux remote desktop Reviewed-by: azvegint, serb --- .../sun/awt/screencast/ScreencastHelper.java | 8 +++--- .../sun/awt/screencast/TokenStorage.java | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java index 33af39810d5..a8f7cd41a0e 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java @@ -63,9 +63,11 @@ public final class ScreencastHelper { private static final int DELAY_BEFORE_SESSION_CLOSE = 2000; private static volatile TimerTask timerTask = null; - private static final Timer timerCloseSession - = new Timer("auto-close screencast session", true); + private static class TimerHolder { + private static final Timer timerCloseSession = + new Timer("auto-close screencast session", true); + } private ScreencastHelper() {} @@ -143,7 +145,7 @@ public final class ScreencastHelper { } }; - timerCloseSession.schedule(timerTask, DELAY_BEFORE_SESSION_CLOSE); + TimerHolder.timerCloseSession.schedule(timerTask, DELAY_BEFORE_SESSION_CLOSE); } public static synchronized void getRGBPixels( diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java b/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java index 9db64725048..09dc84e74d0 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/TokenStorage.java @@ -238,6 +238,7 @@ final class TokenStorage { } private static WatchService watchService; + private static volatile boolean isWatcherThreadStarted = false; private static void setupWatch() { try { @@ -257,10 +258,6 @@ final class TokenStorage { "file watch %s\n", e); } } - - if (watchService != null) { - new WatcherThread(watchService).start(); - } } // called from native @@ -337,7 +334,27 @@ final class TokenStorage { return true; } + private static void startWatcherThreadIfNeeded() { + if (!isWatcherThreadStarted) { + // not sure if the double-checked locking is actually needed here + // the getTokens is only called from ScreencastHelper#getRGBPixels + // and ScreencastHelper#remoteDesktop* methods (which are synchronized), + // but it may change later. + synchronized (TokenStorage.class) { + if (!isWatcherThreadStarted) { + readTokens(PROPS_PATH); + if (watchService != null) { + new WatcherThread(watchService).start(); + } + isWatcherThreadStarted = true; + } + } + } + } + static Set getTokens(List affectedScreenBounds) { + startWatcherThreadIfNeeded(); + // We need an ordered set to store tokens // with exact matches at the beginning. LinkedHashSet result = new LinkedHashSet<>(); From ae4d9c2e6af0b899481c98742f4976c7769f39e5 Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Wed, 19 Nov 2025 12:14:07 +0000 Subject: [PATCH 022/114] 8371918: aarch64: Incorrect pointer dereference in TemplateInterpreterGenerator::generate_native_entry Reviewed-by: aph, shade --- src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp index c1eabed8ade..dd70c98797f 100644 --- a/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateInterpreterGenerator_aarch64.cpp @@ -1375,7 +1375,6 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { __ ldr(r10, Address(rmethod, Method::native_function_offset())); ExternalAddress unsatisfied(SharedRuntime::native_method_throw_unsatisfied_link_error_entry()); __ lea(rscratch2, unsatisfied); - __ ldr(rscratch2, rscratch2); __ cmp(r10, rscratch2); __ br(Assembler::NE, L); __ call_VM(noreg, From 0bff5f3dbe69ab2a59db771af1020b04c0132954 Mon Sep 17 00:00:00 2001 From: Anton Seoane Ampudia Date: Wed, 19 Nov 2025 13:02:07 +0000 Subject: [PATCH 023/114] 8213762: Deprecate Xmaxjitcodesize Reviewed-by: kvn, epeter --- src/hotspot/share/runtime/arguments.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 1ef2ee9de0d..55ee7641a5f 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -2483,6 +2483,9 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin } } else if (match_option(option, "-Xmaxjitcodesize", &tail) || match_option(option, "-XX:ReservedCodeCacheSize=", &tail)) { + if (match_option(option, "-Xmaxjitcodesize", &tail)) { + warning("Option -Xmaxjitcodesize was deprecated in JDK 26 and will likely be removed in a future release."); + } julong long_ReservedCodeCacheSize = 0; ArgsRange errcode = parse_memory_size(tail, &long_ReservedCodeCacheSize, 1); From f0afd89f66c0b42ff06fbb76378a5b2028b76a10 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 19 Nov 2025 15:19:04 +0000 Subject: [PATCH 024/114] 8357728: Avoid caching synthesized names in synthesized parameters Reviewed-by: jvernee --- .../classes/java/lang/reflect/Executable.java | 2 +- .../classes/java/lang/reflect/Parameter.java | 4 +- .../Parameter/SyntheticNameRetention.java | 81 +++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java diff --git a/src/java.base/share/classes/java/lang/reflect/Executable.java b/src/java.base/share/classes/java/lang/reflect/Executable.java index a22d0fa8076..4f32d33048d 100644 --- a/src/java.base/share/classes/java/lang/reflect/Executable.java +++ b/src/java.base/share/classes/java/lang/reflect/Executable.java @@ -431,7 +431,7 @@ public abstract sealed class Executable extends AccessibleObject // modifiers? Probably not in the general case, since // we'd have no way of knowing about them, but there // may be specific cases. - out[i] = new Parameter("arg" + i, 0, this, i); + out[i] = new Parameter(null, 0, this, i); return out; } diff --git a/src/java.base/share/classes/java/lang/reflect/Parameter.java b/src/java.base/share/classes/java/lang/reflect/Parameter.java index b8a57a9790b..d4a53e193a9 100644 --- a/src/java.base/share/classes/java/lang/reflect/Parameter.java +++ b/src/java.base/share/classes/java/lang/reflect/Parameter.java @@ -55,7 +55,7 @@ public final class Parameter implements AnnotatedElement { * absent, however, then {@code Executable} uses this constructor * to synthesize them. * - * @param name The name of the parameter. + * @param name The name of the parameter, or {@code null} if absent * @param modifiers The modifier flags for the parameter. * @param executable The executable which defines this parameter. * @param index The index of the parameter. @@ -104,7 +104,7 @@ public final class Parameter implements AnnotatedElement { * to the class file. */ public boolean isNamePresent() { - return executable.hasRealParameterData() && name != null; + return name != null; } /** diff --git a/test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java b/test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java new file mode 100644 index 00000000000..b77a231be88 --- /dev/null +++ b/test/jdk/java/lang/reflect/Parameter/SyntheticNameRetention.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, 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.MethodHandleProxies; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Parameter; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8357728 + * @summary Synthetic parameter names should not be retained. + * @modules java.base/java.lang.reflect:+open + * @run junit SyntheticNameRetention + */ +public class SyntheticNameRetention { + + class Inner { + Inner() {} + } + + public interface NameGetter { + String getRawName(Parameter parameter); + } + static final NameGetter GETTER; + + static { + try { + var lookup = MethodHandles.privateLookupIn(Parameter.class, MethodHandles.lookup()); + GETTER = MethodHandleProxies.asInterfaceInstance(NameGetter.class, lookup.findGetter(Parameter.class, "name", String.class)); + } catch (ReflectiveOperationException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + static Stream methods() throws Throwable { + return Stream.of(Inner.class.getDeclaredConstructor(SyntheticNameRetention.class), // Has MethodParameters with flags + SyntheticNameRetention.class.getDeclaredMethod("test", Executable.class)); // No MethodParameters + } + + @ParameterizedTest + @MethodSource("methods") + public void test(Executable exec) { + var params = exec.getParameters(); + for (int i = 0; i < params.length; i++) { + var param = params[i]; + assertEquals("arg" + i, param.getName(), "name " + i); + assertFalse(param.isNamePresent(), "name present " + i); + assertNull(GETTER.getRawName(param), "raw name " + i); + boolean mandated = exec instanceof Constructor && i == 0; + assertEquals(mandated, param.isImplicit(), "mandated " + i); + } + } +} From 3949b0f23cd9c936c12ac0306534bc38b5b8d298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Wed, 19 Nov 2025 15:40:57 +0000 Subject: [PATCH 025/114] 8371674: C2 fails with Missed optimization opportunity in PhaseIterGVN for MoveL2D Reviewed-by: epeter, chagedorn --- src/hotspot/share/opto/node.cpp | 7 ++- .../c2/TestMissingOptMoveX2YLoadX.java | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 93ded36363e..2452677caf3 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -1209,9 +1209,12 @@ bool Node::has_special_unique_user() const { if (this->is_Store()) { // Condition for back-to-back stores folding. return n->Opcode() == op && n->in(MemNode::Memory) == this; - } else if (this->is_Load() || this->is_DecodeN() || this->is_Phi()) { + } else if ((this->is_Load() || this->is_DecodeN() || this->is_Phi()) && n->Opcode() == Op_MemBarAcquire) { // Condition for removing an unused LoadNode or DecodeNNode from the MemBarAcquire precedence input - return n->Opcode() == Op_MemBarAcquire; + return true; + } else if (this->is_Load() && n->is_Move()) { + // Condition for MoveX2Y (LoadX mem) => LoadY mem + return true; } else if (op == Op_AddL) { // Condition for convL2I(addL(x,y)) ==> addI(convL2I(x),convL2I(y)) return n->Opcode() == Op_ConvL2I && n->in(1) == this; diff --git a/test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java b/test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java new file mode 100644 index 00000000000..1e6e01a8e33 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestMissingOptMoveX2YLoadX.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, 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 8371674 + * @summary An expression of the form "MoveX2Y (LoadX mem)" should be + * transformed into "LoadY mem". This test ensures that changes + * to the number of users of the Load node propagate as expected + * and that the optimization is not missed. + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-TieredCompilation -Xcomp + * -XX:CompileCommand=compileonly,compiler.c2.TestMissingOptMoveX2YLoadX::test* + * -XX:VerifyIterativeGVN=1110 compiler.c2.TestMissingOptMoveX2YLoadX + * @run main compiler.c2.TestMissingOptMoveX2YLoadX + * + */ + +package compiler.c2; + +public class TestMissingOptMoveX2YLoadX { + static final int N = 400; + static volatile long b; + + public static void main(String[] strArr) { + // could theoretically happen with other variants of MoveNode + // but there is no known reproducer for the other cases + Double.longBitsToDouble(0l); + testMoveL2D(); + } + + static void testMoveL2D() { + int e = 8, f, g = 9, h = 2, i[] = new int[N]; + long j[] = new long[N]; + while (++e < 37) { + for (f = 1; f < 7; f++) { + h >>>= (int)(--g - Double.longBitsToDouble(j[e])); + b -= b; + } + } + } +} \ No newline at end of file From 9ea8201b7494fe9107d4abd78c02ac765a5751d4 Mon Sep 17 00:00:00 2001 From: Alexander Matveev Date: Wed, 19 Nov 2025 16:07:20 +0000 Subject: [PATCH 026/114] 8363980: [macos] Add JDK specific keys/values to Info.plist of embedded runtime Reviewed-by: asemenyuk --- .../internal/MacPackagingPipeline.java | 21 +++++++++++-- .../internal/JLinkRuntimeBuilder.java | 5 ++++ .../internal/model/RuntimeBuilder.java | 10 +++++++ .../jdk/jpackage/test/JPackageCommand.java | 30 ++++++++++++------- .../jpackage/macosx/CustomInfoPListTest.java | 5 ++++ .../jpackage/share/AppImagePackageTest.java | 3 +- .../jpackage/share/CookedRuntimeTest.java | 17 +++++++++-- .../jpackage/share/PostImageScriptTest.java | 10 +++---- 8 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java index 6e63b73674e..b82b20c0c36 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -364,12 +364,21 @@ final class MacPackagingPipeline { final var app = env.app(); + // If the embedded runtime contains executable(s) in the "bin" + // subdirectory, we should use the standalone runtime info plist + // template. Otherwise, the user may be unable to run the "java" + // or other executables in the "bin" subdirectory of the embedded + // runtime. + final var useRuntimeInfoPlist = app.isRuntime() || + app.runtimeBuilder().orElseThrow().withNativeCommands() || + Files.isDirectory(env.resolvedLayout().runtimeDirectory().resolve("bin")); + Map data = new HashMap<>(); data.put("CF_BUNDLE_IDENTIFIER", app.bundleIdentifier()); data.put("CF_BUNDLE_NAME", app.bundleName()); data.put("CF_BUNDLE_VERSION", app.version()); data.put("CF_BUNDLE_SHORT_VERSION_STRING", app.shortVersion().toString()); - if (app.isRuntime()) { + if (useRuntimeInfoPlist) { data.put("CF_BUNDLE_VENDOR", app.vendor()); } @@ -377,12 +386,18 @@ final class MacPackagingPipeline { final String publicName; final String category; - if (app.isRuntime()) { + if (useRuntimeInfoPlist) { template = "Runtime-Info.plist.template"; + } else { + template = "ApplicationRuntime-Info.plist.template"; + } + + // Public name and category should be based on standalone runtime vs + // embedded runtime. + if (app.isRuntime()) { publicName = "Info.plist"; category = "resource.runtime-info-plist"; } else { - template = "ApplicationRuntime-Info.plist.template"; publicName = "Runtime-Info.plist"; category = "resource.app-runtime-info-plist"; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java index 2273d385936..6ac9758e179 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java @@ -83,6 +83,11 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { } } + @Override + public boolean withNativeCommands() { + return !jlinkCmdLine.contains("--strip-native-commands"); + } + static ModuleFinder createModuleFinder(Collection modulePath) { return ModuleFinder.compose( ModulePath.of(JarFile.runtimeVersion(), true, diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java index a0f5f077c70..89d370b58e0 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/RuntimeBuilder.java @@ -46,6 +46,16 @@ public interface RuntimeBuilder { */ void create(AppImageLayout appImageLayout) throws PackagerException; + /** + * Returns {@code true} if "--strip-native-commands" was not used with jlink. + * Default implementation returns {@code false}. + * + * @return {@code true} if "--strip-native-commands" was not used with jlink + */ + default boolean withNativeCommands() { + return false; + } + /** * Gets the default set of paths where jlink should look up for system Java * modules. diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 57915b91d8b..98d991e38f3 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -306,24 +306,16 @@ public class JPackageCommand extends CommandArguments { TKit.trace(String.format("Init fake runtime in [%s] directory", fakeRuntimeDir)); - Files.createDirectories(fakeRuntimeDir); - - if (TKit.isLinux()) { - // Need to make the code in rpm spec happy as it assumes there is - // always something in application image. - fakeRuntimeDir.resolve("bin").toFile().mkdir(); - } - if (TKit.isOSX()) { // Make MacAppImageBuilder happy createBulkFile.accept(fakeRuntimeDir.resolve(Path.of( "lib/jli/libjli.dylib"))); } - // Mak sure fake runtime takes some disk space. + // Make sure fake runtime takes some disk space. // Package bundles with 0KB size are unexpected and considered // an error by PackageTest. - createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("bin", "bulk"))); + createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("lib", "bulk"))); cmd.setArgumentValue("--runtime-image", fakeRuntimeDir); }); @@ -1227,6 +1219,24 @@ public class JPackageCommand extends CommandArguments { MacHelper.verifyUnsignedBundleSignature(cmd); } }), + MAC_RUNTIME_PLIST_JDK_KEY(cmd -> { + if (TKit.isOSX()) { + var appLayout = cmd.appLayout(); + var plistPath = appLayout.runtimeDirectory().resolve("Contents/Info.plist"); + var keyName = "JavaVM"; + var keyValue = MacHelper.readPList(plistPath).findDictValue(keyName); + if (cmd.isRuntime() || Files.isDirectory(appLayout.runtimeHomeDirectory().resolve("bin"))) { + // There are native launchers in the runtime + TKit.assertTrue(keyValue.isPresent(), String.format( + "Check the runtime plist file [%s] contains '%s' key", + plistPath, keyName)); + } else { + TKit.assertTrue(keyValue.isEmpty(), String.format( + "Check the runtime plist file [%s] contains NO '%s' key", + plistPath, keyName)); + } + } + }), PREDEFINED_APP_IMAGE_COPY(cmd -> { Optional.ofNullable(cmd.getArgumentValue("--app-image")).filter(_ -> { return !TKit.isOSX() || !MacHelper.signPredefinedAppImage(cmd); diff --git a/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java b/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java index b690b69269c..dd94330d039 100644 --- a/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java +++ b/test/jdk/tools/jpackage/macosx/CustomInfoPListTest.java @@ -25,6 +25,7 @@ import static java.util.Collections.unmodifiableSortedSet; import static java.util.Map.entry; import jdk.jpackage.internal.util.Slot; import static jdk.jpackage.internal.util.PListWriter.writeDict; +import static jdk.jpackage.internal.util.PListWriter.writeKey; import static jdk.jpackage.internal.util.PListWriter.writePList; import static jdk.jpackage.internal.util.PListWriter.writeString; import static jdk.jpackage.internal.util.XmlUtils.createXml; @@ -431,6 +432,10 @@ public class CustomInfoPListTest { writeString(xml, "CFBundleShortVersionString", value("CF_BUNDLE_SHORT_VERSION_STRING", cmd.version())); writeString(xml, "CFBundleVersion", value("CF_BUNDLE_VERSION", cmd.version())); writeString(xml, "CustomInfoPListFA", "DEPLOY_FILE_ASSOCIATIONS"); + writeKey(xml, "JavaVM"); + writeDict(xml, toXmlConsumer(() -> { + writeString(xml, "JVMVersion", value("CF_BUNDLE_VERSION", cmd.version())); + })); })); })); } diff --git a/test/jdk/tools/jpackage/share/AppImagePackageTest.java b/test/jdk/tools/jpackage/share/AppImagePackageTest.java index 94eb086e4c6..c3faa1ffb65 100644 --- a/test/jdk/tools/jpackage/share/AppImagePackageTest.java +++ b/test/jdk/tools/jpackage/share/AppImagePackageTest.java @@ -120,7 +120,8 @@ public class AppImagePackageTest { StandardAssert.MAIN_JAR_FILE, StandardAssert.MAIN_LAUNCHER_FILES, StandardAssert.MAC_BUNDLE_STRUCTURE, - StandardAssert.RUNTIME_DIRECTORY); + StandardAssert.RUNTIME_DIRECTORY, + StandardAssert.MAC_RUNTIME_PLIST_JDK_KEY); }) .run(Action.CREATE_AND_UNPACK); } diff --git a/test/jdk/tools/jpackage/share/CookedRuntimeTest.java b/test/jdk/tools/jpackage/share/CookedRuntimeTest.java index 28fba111c8f..e3304e01f84 100644 --- a/test/jdk/tools/jpackage/share/CookedRuntimeTest.java +++ b/test/jdk/tools/jpackage/share/CookedRuntimeTest.java @@ -51,10 +51,11 @@ import jdk.jpackage.test.TKit; public final class CookedRuntimeTest { public CookedRuntimeTest(String javaAppDesc, String jlinkOutputSubdir, - String runtimeSubdir) { + String runtimeSubdir, boolean withNativeCommands) { this.javaAppDesc = javaAppDesc; this.jlinkOutputSubdir = Path.of(jlinkOutputSubdir); this.runtimeSubdir = Path.of(runtimeSubdir); + this.withNativeCommands = withNativeCommands; } @Test @@ -90,6 +91,10 @@ public final class CookedRuntimeTest { "--no-header-files", "--no-man-pages"); + if (!withNativeCommands) { + jlink.addArgument("--strip-native-commands"); + } + if (moduleName != null) { jlink.addArguments("--add-modules", moduleName, "--module-path", Path.of(cmd.getArgumentValue("--module-path")).resolve( @@ -127,7 +132,14 @@ public final class CookedRuntimeTest { List data = new ArrayList<>(); for (var javaAppDesc : javaAppDescs) { for (var pathCfg : paths) { - data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1] }); + if (TKit.isOSX()) { + // On OSX platform we need to test both runtime root and runtime home + // directories with and without "bin" folder. + data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1], true }); + data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1], false }); + } else { + data.add(new Object[] { javaAppDesc, pathCfg[0], pathCfg[1], true }); + } } } @@ -137,4 +149,5 @@ public final class CookedRuntimeTest { private final String javaAppDesc; private final Path jlinkOutputSubdir; private final Path runtimeSubdir; + private final boolean withNativeCommands; } diff --git a/test/jdk/tools/jpackage/share/PostImageScriptTest.java b/test/jdk/tools/jpackage/share/PostImageScriptTest.java index 5e9127d8fa8..655658f036d 100644 --- a/test/jdk/tools/jpackage/share/PostImageScriptTest.java +++ b/test/jdk/tools/jpackage/share/PostImageScriptTest.java @@ -140,7 +140,7 @@ public class PostImageScriptTest { runtimeDir = Path.of(""); } - final Path runtimeBinDir = runtimeDir.resolve("bin"); + final Path runtimeLibDir = runtimeDir.resolve("lib"); if (TKit.isWindows()) { final List script = new ArrayList<>(); @@ -148,16 +148,16 @@ public class PostImageScriptTest { script.addAll(WinGlobals.JS_FS.expr()); script.addAll(List.of( "WScript.Echo('PWD: ' + fs.GetFolder(shell.CurrentDirectory).Path)", - String.format("WScript.Echo('Probe directory: %s')", runtimeBinDir), - String.format("fs.GetFolder('%s')", runtimeBinDir.toString().replace('\\', '/')) + String.format("WScript.Echo('Probe directory: %s')", runtimeLibDir), + String.format("fs.GetFolder('%s')", runtimeLibDir.toString().replace('\\', '/')) )); JPackageUserScript.POST_IMAGE.create(cmd, script); } else { JPackageUserScript.POST_IMAGE.create(cmd, List.of( "set -e", "printf 'PWD: %s\\n' \"$PWD\"", - String.format("printf 'Probe directory: %%s\\n' '%s'", runtimeBinDir), - String.format("[ -d '%s' ]", runtimeBinDir) + String.format("printf 'Probe directory: %%s\\n' '%s'", runtimeLibDir), + String.format("[ -d '%s' ]", runtimeLibDir) )); } }); From 223cc6451860f10fe8095705da07aaf7e882188f Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Wed, 19 Nov 2025 19:14:33 +0000 Subject: [PATCH 027/114] 8343316: Review and update tests using explicit provider names Reviewed-by: rhalade --- .../KeyAgreement/DHGenSharedSecret.java | 11 ++++++---- .../KeyAgreement/DHKeyAgreement2.java | 21 ++++++++++--------- .../KeyAgreement/DHKeyAgreement3.java | 17 ++++++++------- .../Provider/ProviderVersionCheck.java | 7 ++++++- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java index 9fe96d967dc..a89efa7bddd 100644 --- a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java +++ b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHGenSharedSecret.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -39,6 +39,9 @@ import jdk.test.lib.security.SecurityUtils; public class DHGenSharedSecret { + private static final String PROVIDER_NAME = + System.getProperty("test.provider.name", "SunJCE"); + public static void main(String[] args) throws Exception { DHGenSharedSecret test = new DHGenSharedSecret(); test.run(); @@ -57,7 +60,7 @@ public class DHGenSharedSecret { // generate keyPairs using parameters KeyPairGenerator keyGen = - KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator.getInstance("DH", PROVIDER_NAME); keyGen.initialize(spec); // Alice generates her key pairs @@ -77,11 +80,11 @@ public class DHGenSharedSecret { // bob uses it to generate Secret X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(alicePubKeyEnc); - KeyFactory bobKeyFac = KeyFactory.getInstance("DH", "SunJCE"); + KeyFactory bobKeyFac = KeyFactory.getInstance("DH", PROVIDER_NAME); PublicKey alicePubKey = bobKeyFac.generatePublic(x509Spec); - KeyAgreement bobAlice = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement bobAlice = KeyAgreement.getInstance("DH", PROVIDER_NAME); start = System.currentTimeMillis(); bobAlice.init(keyB.getPrivate()); bobAlice.doPhase(alicePubKey, true); diff --git a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java index da583c9dc29..c972c8a696b 100644 --- a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java +++ b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -53,7 +53,8 @@ import jdk.test.lib.security.SecurityUtils; public class DHKeyAgreement2 { - private static final String SUNJCE = "SunJCE"; + private static final String PROVIDER_NAME = System.getProperty( + "test.provider.name", "SunJCE"); // Hex formatter to upper case with ":" delimiter private static final HexFormat HEX_FORMATTER = HexFormat.ofDelimiter(":").withUpperCase(); @@ -90,7 +91,7 @@ public class DHKeyAgreement2 { // Some central authority creates new DH parameters System.err.println("Creating Diffie-Hellman parameters ..."); AlgorithmParameterGenerator paramGen - = AlgorithmParameterGenerator.getInstance("DH", SUNJCE); + = AlgorithmParameterGenerator.getInstance("DH", PROVIDER_NAME); paramGen.init(primeSize); AlgorithmParameters params = paramGen.generateParameters(); dhParameterSpec = (DHParameterSpec)params.getParameterSpec @@ -108,7 +109,7 @@ public class DHKeyAgreement2 { * above */ System.err.println("ALICE: Generate DH keypair ..."); - KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", SUNJCE); + KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); aliceKpairGen.initialize(dhParameterSpec); KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); System.out.println("Alice DH public key:\n" + @@ -117,14 +118,14 @@ public class DHKeyAgreement2 { aliceKpair.getPrivate().toString()); DHParameterSpec dhParamSpec = ((DHPublicKey)aliceKpair.getPublic()).getParams(); - AlgorithmParameters algParams = AlgorithmParameters.getInstance("DH", SUNJCE); + AlgorithmParameters algParams = AlgorithmParameters.getInstance("DH", PROVIDER_NAME); algParams.init(dhParamSpec); System.out.println("Alice DH parameters:\n" + algParams.toString()); // Alice executes Phase1 of her version of the DH protocol System.err.println("ALICE: Execute PHASE1 ..."); - KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", SUNJCE); + KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); aliceKeyAgree.init(aliceKpair.getPrivate()); // Alice encodes her public key, and sends it over to Bob. @@ -135,7 +136,7 @@ public class DHKeyAgreement2 { * in encoded format. * He instantiates a DH public key from the encoded key material. */ - KeyFactory bobKeyFac = KeyFactory.getInstance("DH", SUNJCE); + KeyFactory bobKeyFac = KeyFactory.getInstance("DH", PROVIDER_NAME); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec (alicePubKeyEnc); PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec); @@ -149,7 +150,7 @@ public class DHKeyAgreement2 { // Bob creates his own DH key pair System.err.println("BOB: Generate DH keypair ..."); - KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", SUNJCE); + KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); bobKpairGen.initialize(dhParamSpec); KeyPair bobKpair = bobKpairGen.generateKeyPair(); System.out.println("Bob DH public key:\n" + @@ -159,7 +160,7 @@ public class DHKeyAgreement2 { // Bob executes Phase1 of his version of the DH protocol System.err.println("BOB: Execute PHASE1 ..."); - KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", SUNJCE); + KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); bobKeyAgree.init(bobKpair.getPrivate()); // Bob encodes his public key, and sends it over to Alice. @@ -171,7 +172,7 @@ public class DHKeyAgreement2 { * Before she can do so, she has to instanticate a DH public key * from Bob's encoded key material. */ - KeyFactory aliceKeyFac = KeyFactory.getInstance("DH", SUNJCE); + KeyFactory aliceKeyFac = KeyFactory.getInstance("DH", PROVIDER_NAME); x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc); PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec); System.err.println("ALICE: Execute PHASE2 ..."); diff --git a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java index d4f70ea2563..5315d62aee8 100644 --- a/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java +++ b/test/jdk/com/sun/crypto/provider/KeyAgreement/DHKeyAgreement3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, 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 @@ -50,6 +50,9 @@ import jdk.test.lib.security.SecurityUtils; public class DHKeyAgreement3 { + private static final String PROVIDER_NAME = + System.getProperty("test.provider.name", "SunJCE"); + // Hex formatter to upper case with ":" delimiter private static final HexFormat HEX_FORMATTER = HexFormat.ofDelimiter(":").withUpperCase(); @@ -70,36 +73,36 @@ public class DHKeyAgreement3 { // Alice creates her own DH key pair System.err.println("ALICE: Generate DH keypair ..."); - KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); aliceKpairGen.initialize(dhParamSpec); KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); // Bob creates his own DH key pair System.err.println("BOB: Generate DH keypair ..."); - KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); bobKpairGen.initialize(dhParamSpec); KeyPair bobKpair = bobKpairGen.generateKeyPair(); // Carol creates her own DH key pair System.err.println("CAROL: Generate DH keypair ..."); - KeyPairGenerator carolKpairGen = KeyPairGenerator.getInstance("DH", "SunJCE"); + KeyPairGenerator carolKpairGen = KeyPairGenerator.getInstance("DH", PROVIDER_NAME); carolKpairGen.initialize(dhParamSpec); KeyPair carolKpair = carolKpairGen.generateKeyPair(); // Alice initialize System.err.println("ALICE: Initialize ..."); - KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); aliceKeyAgree.init(aliceKpair.getPrivate()); // Bob initialize System.err.println("BOB: Initialize ..."); - KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); bobKeyAgree.init(bobKpair.getPrivate()); // Carol initialize System.err.println("CAROL: Initialize ..."); - KeyAgreement carolKeyAgree = KeyAgreement.getInstance("DH", "SunJCE"); + KeyAgreement carolKeyAgree = KeyAgreement.getInstance("DH", PROVIDER_NAME); carolKeyAgree.init(carolKpair.getPrivate()); diff --git a/test/jdk/java/security/Provider/ProviderVersionCheck.java b/test/jdk/java/security/Provider/ProviderVersionCheck.java index 43d1c502b3f..2af2be0a7ad 100644 --- a/test/jdk/java/security/Provider/ProviderVersionCheck.java +++ b/test/jdk/java/security/Provider/ProviderVersionCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2025, 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 @@ -42,6 +42,11 @@ public class ProviderVersionCheck { for (Provider p: Security.getProviders()) { System.out.print(p.getName() + " "); + if (p.getName().equals(System.getProperty("test.provider.name"))) { + // Version numbers of non JDK-providers do not match JDK version number. + continue; + } + String specVersion = System.getProperty("java.specification.version"); if (p.getVersion() != Double.parseDouble(specVersion)) { System.out.println("failed. " + "Version received was " + From 6f1c5733ed4a1d1a1e099681f1f292acf827d9dc Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Wed, 19 Nov 2025 20:05:09 +0000 Subject: [PATCH 028/114] 8371923: Update LockFreeStack for Atomic Reviewed-by: iwalulya, dholmes --- src/hotspot/share/utilities/lockFreeStack.hpp | 51 +++++++++++++------ .../gtest/utilities/test_lockFreeStack.cpp | 34 ++++++------- 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/hotspot/share/utilities/lockFreeStack.hpp b/src/hotspot/share/utilities/lockFreeStack.hpp index 43bc58fbc44..3f63482a268 100644 --- a/src/hotspot/share/utilities/lockFreeStack.hpp +++ b/src/hotspot/share/utilities/lockFreeStack.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_UTILITIES_LOCKFREESTACK_HPP #define SHARE_UTILITIES_LOCKFREESTACK_HPP +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -34,11 +35,14 @@ // a result, there is no allocation involved in adding objects to the stack // or removing them from the stack. // -// To be used in a LockFreeStack of objects of type T, an object of -// type T must have a list entry member of type T* volatile, with an -// non-member accessor function returning a pointer to that member. A -// LockFreeStack is associated with the class of its elements and an -// entry member from that class. +// To be used in a LockFreeStack of objects of type T, an object of type T +// must have a list entry member. A list entry member is a data member whose +// type is either (1) Atomic, or (2) T* volatile. There must be a +// non-member or static member function returning a pointer to that member, +// which is used to provide access to it by a LockFreeStack. A LockFreeStack +// is associated with the class of its elements and an entry member from that +// class by being specialized on the element class and a pointer to the +// function for accessing that entry member. // // An object can be in multiple stacks at the same time, so long as // each stack uses a different entry member. That is, the class of the @@ -52,12 +56,12 @@ // // \tparam T is the class of the elements in the stack. // -// \tparam next_ptr is a function pointer. Applying this function to +// \tparam next_accessor is a function pointer. Applying this function to // an object of type T must return a pointer to the list entry member // of the object associated with the LockFreeStack type. -template +template class LockFreeStack { - T* volatile _top; + Atomic _top; void prepend_impl(T* first, T* last) { T* cur = top(); @@ -65,12 +69,21 @@ class LockFreeStack { do { old = cur; set_next(*last, cur); - cur = AtomicAccess::cmpxchg(&_top, cur, first); + cur = _top.compare_exchange(cur, first); } while (old != cur); } NONCOPYABLE(LockFreeStack); + template + static constexpr void use_atomic_access_impl(NextAccessor) { + static_assert(DependentAlwaysFalse, "Invalid next accessor"); + } + static constexpr bool use_atomic_access_impl(T* volatile* (*)(T&)) { return true; } + static constexpr bool use_atomic_access_impl(Atomic* (*)(T&)) { return false; } + + static constexpr bool use_atomic_access = use_atomic_access_impl(next_accessor); + public: LockFreeStack() : _top(nullptr) {} ~LockFreeStack() { assert(empty(), "stack not empty"); } @@ -89,7 +102,7 @@ public: new_top = next(*result); } // CAS even on empty pop, for consistent membar behavior. - result = AtomicAccess::cmpxchg(&_top, result, new_top); + result = _top.compare_exchange(result, new_top); } while (result != old); if (result != nullptr) { set_next(*result, nullptr); @@ -101,7 +114,7 @@ public: // list of elements. Acts as a full memory barrier. // postcondition: empty() T* pop_all() { - return AtomicAccess::xchg(&_top, (T*)nullptr); + return _top.exchange(nullptr); } // Atomically adds value to the top of this stack. Acts as a full @@ -143,9 +156,9 @@ public: // Return true if the stack is empty. bool empty() const { return top() == nullptr; } - // Return the most recently pushed element, or nullptr if the stack is empty. + // Return the most recently pushed element, or null if the stack is empty. // The returned element is not removed from the stack. - T* top() const { return AtomicAccess::load(&_top); } + T* top() const { return _top.load_relaxed(); } // Return the number of objects in the stack. There must be no concurrent // pops while the length is being determined. @@ -160,7 +173,11 @@ public: // Return the entry following value in the list used by the // specialized LockFreeStack class. static T* next(const T& value) { - return AtomicAccess::load(next_ptr(const_cast(value))); + if constexpr (use_atomic_access) { + return AtomicAccess::load(next_accessor(const_cast(value))); + } else { + return next_accessor(const_cast(value))->load_relaxed(); + } } // Set the entry following value to new_next in the list used by the @@ -168,7 +185,11 @@ public: // if value is in an instance of this specialization of LockFreeStack, // there must be no concurrent push or pop operations on that stack. static void set_next(T& value, T* new_next) { - AtomicAccess::store(next_ptr(value), new_next); + if constexpr (use_atomic_access) { + AtomicAccess::store(next_accessor(value), new_next); + } else { + next_accessor(value)->store_relaxed(new_next); + } } }; diff --git a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp index 3a9d24ad61e..fac17e87016 100644 --- a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp +++ b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp @@ -22,7 +22,7 @@ */ #include "memory/allocation.inline.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/lockFreeStack.hpp" #include "threadHelper.inline.hpp" @@ -33,12 +33,12 @@ class LockFreeStackTestElement { typedef LockFreeStackTestElement Element; - Element* volatile _entry; - Element* volatile _entry1; + Atomic _entry; + Atomic _entry1; size_t _id; - static Element* volatile* entry_ptr(Element& e) { return &e._entry; } - static Element* volatile* entry1_ptr(Element& e) { return &e._entry1; } + static Atomic* entry_ptr(Element& e) { return &e._entry; } + static Atomic* entry1_ptr(Element& e) { return &e._entry1; } public: LockFreeStackTestElement(size_t id = 0) : _entry(), _entry1(), _id(id) {} @@ -202,17 +202,17 @@ class LockFreeStackTestThread : public JavaTestThread { uint _id; TestStack* _from; TestStack* _to; - volatile size_t* _processed; + Atomic* _processed; size_t _process_limit; size_t _local_processed; - volatile bool _ready; + Atomic _ready; public: LockFreeStackTestThread(Semaphore* post, uint id, TestStack* from, TestStack* to, - volatile size_t* processed, + Atomic* processed, size_t process_limit) : JavaTestThread(post), _id(id), @@ -225,21 +225,21 @@ public: {} virtual void main_run() { - AtomicAccess::release_store_fence(&_ready, true); + _ready.release_store_fence(true); while (true) { Element* e = _from->pop(); if (e != nullptr) { _to->push(*e); - AtomicAccess::inc(_processed); + _processed->fetch_then_add(1u); ++_local_processed; - } else if (AtomicAccess::load_acquire(_processed) == _process_limit) { + } else if (_processed->load_acquire() == _process_limit) { tty->print_cr("thread %u processed %zu", _id, _local_processed); return; } } } - bool ready() const { return AtomicAccess::load_acquire(&_ready); } + bool ready() const { return _ready.load_acquire(); } }; TEST_VM(LockFreeStackTest, stress) { @@ -248,8 +248,8 @@ TEST_VM(LockFreeStackTest, stress) { TestStack start_stack; TestStack middle_stack; TestStack final_stack; - volatile size_t stage1_processed = 0; - volatile size_t stage2_processed = 0; + Atomic stage1_processed{0}; + Atomic stage2_processed{0}; const size_t nelements = 10000; Element* elements = NEW_C_HEAP_ARRAY(Element, nelements, mtOther); @@ -272,7 +272,7 @@ TEST_VM(LockFreeStackTest, stress) { for (uint i = 0; i < ARRAY_SIZE(threads); ++i) { TestStack* from = &start_stack; TestStack* to = &middle_stack; - volatile size_t* processed = &stage1_processed; + Atomic* processed = &stage1_processed; if (i >= stage1_threads) { from = &middle_stack; to = &final_stack; @@ -293,8 +293,8 @@ TEST_VM(LockFreeStackTest, stress) { } // Verify expected state. - ASSERT_EQ(nelements, stage1_processed); - ASSERT_EQ(nelements, stage2_processed); + ASSERT_EQ(nelements, stage1_processed.load_relaxed()); + ASSERT_EQ(nelements, stage2_processed.load_relaxed()); ASSERT_EQ(0u, initial_stack.length()); ASSERT_EQ(0u, start_stack.length()); ASSERT_EQ(0u, middle_stack.length()); From f5bc6ee90d73da00cab5cad283b9517c692bc895 Mon Sep 17 00:00:00 2001 From: Kim Barrett Date: Wed, 19 Nov 2025 20:56:21 +0000 Subject: [PATCH 029/114] 8369187: Add wrapper for that forbids use of global allocation and deallocation functions Reviewed-by: stefank, erikj, jrose --- make/hotspot/lib/CompileGtest.gmk | 1 + src/hotspot/share/code/relocInfo.cpp | 3 +- src/hotspot/share/code/relocInfo.hpp | 3 +- src/hotspot/share/cppstdlib/new.hpp | 154 ++++++++++++++++++ src/hotspot/share/gc/shared/bufferNode.cpp | 3 +- .../share/gc/shared/partialArrayState.cpp | 3 +- .../gc/z/zDeferredConstructed.inline.hpp | 3 +- src/hotspot/share/memory/allocation.hpp | 3 +- src/hotspot/share/memory/arena.cpp | 1 + src/hotspot/share/memory/arena.hpp | 2 - src/hotspot/share/utilities/debug.cpp | 2 +- .../share/utilities/deferredStatic.hpp | 3 +- src/hotspot/share/utilities/elfFile.cpp | 2 +- .../share/utilities/globalDefinitions.hpp | 21 +++ .../gtest/utilities/test_lockFreeStack.cpp | 3 +- 15 files changed, 187 insertions(+), 20 deletions(-) create mode 100644 src/hotspot/share/cppstdlib/new.hpp diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk index d615e254f5a..60912992134 100644 --- a/make/hotspot/lib/CompileGtest.gmk +++ b/make/hotspot/lib/CompileGtest.gmk @@ -95,6 +95,7 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBJVM, \ EXTRA_OBJECT_FILES := $(BUILD_LIBJVM_ALL_OBJS), \ DEFAULT_CFLAGS := false, \ CFLAGS := $(JVM_CFLAGS) \ + -DHOTSPOT_GTEST \ -I$(GTEST_FRAMEWORK_SRC)/googletest/include \ -I$(GTEST_FRAMEWORK_SRC)/googlemock/include \ $(addprefix -I, $(GTEST_TEST_SRC)), \ diff --git a/src/hotspot/share/code/relocInfo.cpp b/src/hotspot/share/code/relocInfo.cpp index 286d407c94b..2a6335e2118 100644 --- a/src/hotspot/share/code/relocInfo.cpp +++ b/src/hotspot/share/code/relocInfo.cpp @@ -26,6 +26,7 @@ #include "code/compiledIC.hpp" #include "code/nmethod.hpp" #include "code/relocInfo.hpp" +#include "cppstdlib/new.hpp" #include "cppstdlib/type_traits.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" @@ -37,8 +38,6 @@ #include "utilities/checkedCast.hpp" #include "utilities/copy.hpp" -#include - const RelocationHolder RelocationHolder::none; // its type is relocInfo::none diff --git a/src/hotspot/share/code/relocInfo.hpp b/src/hotspot/share/code/relocInfo.hpp index a6a08815d10..6f1778ef479 100644 --- a/src/hotspot/share/code/relocInfo.hpp +++ b/src/hotspot/share/code/relocInfo.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_CODE_RELOCINFO_HPP #define SHARE_CODE_RELOCINFO_HPP +#include "cppstdlib/new.hpp" #include "memory/allocation.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/osInfo.hpp" @@ -32,8 +33,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -#include - class CodeBlob; class Metadata; class NativeMovConstReg; diff --git a/src/hotspot/share/cppstdlib/new.hpp b/src/hotspot/share/cppstdlib/new.hpp new file mode 100644 index 00000000000..3536ac13288 --- /dev/null +++ b/src/hotspot/share/cppstdlib/new.hpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2022, 2025, 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. + * + */ + +#ifndef SHARE_CPPSTDLIB_NEW_HPP +#define SHARE_CPPSTDLIB_NEW_HPP + +#include "utilities/compilerWarnings.hpp" + +// HotSpot usage: +// Only the following may be used: +// * std::nothrow_t, std::nothrow +// * std::align_val_t +// * The non-allocating forms of `operator new` and `operator new[]` are +// implicitly used by the corresponding `new` and `new[]` expressions. +// - operator new(size_t, void*) noexcept +// - operator new[](size_t, void*) noexcept +// Note that the non-allocating forms of `operator delete` and `operator +// delete[]` are not used, since they are only invoked by a placement new +// expression that fails by throwing an exception. But they might still +// end up being referenced in such a situation. + +BEGIN_ALLOW_FORBIDDEN_FUNCTIONS +#include "utilities/vmassert_uninstall.hpp" + +#include + +#include "utilities/vmassert_reinstall.hpp" // don't reorder +END_ALLOW_FORBIDDEN_FUNCTIONS + +// Deprecation declarations to forbid use of the default global allocator. +// See C++17 21.6.1 Header synopsis. + +namespace std { + +#if 0 +// We could deprecate exception types, for completeness, but don't bother. We +// already have exceptions disabled, and run into compiler bugs when we try. +// +// gcc -Wattributes => type attributes ignored after type is already defined +// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122167 +// +// clang -Wignored-attributes => attribute declaration must precede definition +// The clang warning is https://github.com/llvm/llvm-project/issues/135481, +// which should be fixed in clang 21. +class [[deprecated]] bad_alloc; +class [[deprecated]] bad_array_new_length; +#endif // #if 0 + +// Forbid new_handler manipulation by HotSpot code, leaving it untouched for +// use by application code. +[[deprecated]] new_handler get_new_handler() noexcept; +[[deprecated]] new_handler set_new_handler(new_handler) noexcept; + +// Prefer HotSpot mechanisms for padding. +// +// The syntax for redeclaring these for deprecation is tricky, and not +// supported by some versions of some compilers. Dispatch on compiler and +// version to decide whether to redeclare deprecated. + +#if defined(__clang__) +#if __clang_major__ >= 19 +// clang18 and earlier may accept the declaration but go wrong with uses. +// Different warnings and link-time failures are both possible. +#define CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES 1 +#endif // restrict clang version + +#elif defined(__GNUC__) +#if (__GNUC__ > 13) || (__GNUC__ == 13 && __GNUC_MINOR__ >= 2) +// g++11.5 accepts the declaration and reports deprecation for uses, but also +// has link-time failure for uses. Haven't tested intermediate versions. +#define CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES 1 +#endif // restrict gcc version + +#elif defined(_MSVC) +// VS2022-17.13.2 => error C2370: '...': redefinition; different storage class + +#endif // Compiler dispatch + +// Redeclare deprecated if such is supported. +#ifdef CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES +[[deprecated]] extern const size_t hardware_destructive_interference_size; +[[deprecated]] extern const size_t hardware_constructive_interference_size; +#undef CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES +#endif // CAN_DEPRECATE_HARDWARE_INTERFERENCE_SIZES + +} // namespace std + +// Forbid using the global allocator by HotSpot code. +// This doesn't provide complete coverage. Some global allocation and +// deallocation functions are implicitly declared in all translation units, +// without needing to include ; see C++17 6.7.4. So this doesn't remove +// the need for the link-time verification that these functions aren't used. +// +// But don't poison them when compiling gtests. The gtest framework, the +// HotSpot wrapper around it (gtestMain.cpp), and even some tests, all have +// new/new[] and delete/delete[] expressions that use the default global +// allocator. We also don't apply the link-time check for gtests, for the +// same reason. +#ifndef HOTSPOT_GTEST + +[[deprecated]] void* operator new(std::size_t); +[[deprecated]] void* operator new(std::size_t, std::align_val_t); +[[deprecated]] void* operator new(std::size_t, const std::nothrow_t&) noexcept; +[[deprecated]] void* operator new(std::size_t, std::align_val_t, + const std::nothrow_t&) noexcept; + +[[deprecated]] void operator delete(void*) noexcept; +[[deprecated]] void operator delete(void*, std::size_t) noexcept; +[[deprecated]] void operator delete(void*, std::align_val_t) noexcept; +[[deprecated]] void operator delete(void*, std::size_t, std::align_val_t) noexcept; +[[deprecated]] void operator delete(void*, const std::nothrow_t&) noexcept; +[[deprecated]] void operator delete(void*, std::align_val_t, + const std::nothrow_t&) noexcept; + +[[deprecated]] void* operator new[](std::size_t); +[[deprecated]] void* operator new[](std::size_t, std::align_val_t); +[[deprecated]] void* operator new[](std::size_t, const std::nothrow_t&) noexcept; +[[deprecated]] void* operator new[](std::size_t, std::align_val_t, + const std::nothrow_t&) noexcept; + +[[deprecated]] void operator delete[](void*) noexcept; +[[deprecated]] void operator delete[](void*, std::size_t) noexcept; +[[deprecated]] void operator delete[](void*, std::align_val_t) noexcept; +[[deprecated]] void operator delete[](void*, std::size_t, std::align_val_t) noexcept; +[[deprecated]] void operator delete[](void*, const std::nothrow_t&) noexcept; +[[deprecated]] void operator delete[](void*, std::align_val_t, + const std::nothrow_t&) noexcept; + +#endif // HOTSPOT_GTEST + +// Allow (don't poison) the non-allocating forms from [new.delete.placement]. + +#endif // SHARE_CPPSTDLIB_NEW_HPP diff --git a/src/hotspot/share/gc/shared/bufferNode.cpp b/src/hotspot/share/gc/shared/bufferNode.cpp index b064f9c7efe..90e50f52e84 100644 --- a/src/hotspot/share/gc/shared/bufferNode.cpp +++ b/src/hotspot/share/gc/shared/bufferNode.cpp @@ -22,12 +22,11 @@ * */ +#include "cppstdlib/new.hpp" #include "gc/shared/bufferNode.hpp" #include "memory/allocation.inline.hpp" #include "utilities/debug.hpp" -#include - BufferNode::AllocatorConfig::AllocatorConfig(size_t size) : _buffer_capacity(size) { diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index f913f3db4ba..39c1fe4fc78 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -22,6 +22,7 @@ * */ +#include "cppstdlib/new.hpp" #include "gc/shared/partialArrayState.hpp" #include "memory/allocation.inline.hpp" #include "memory/arena.hpp" @@ -33,8 +34,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -#include - PartialArrayState::PartialArrayState(oop src, oop dst, size_t index, size_t length, size_t initial_refcount) diff --git a/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp b/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp index d6d35ecddcd..f686bc78d15 100644 --- a/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp +++ b/src/hotspot/share/gc/z/zDeferredConstructed.inline.hpp @@ -27,10 +27,9 @@ #include "gc/z/zDeferredConstructed.hpp" +#include "cppstdlib/new.hpp" #include "cppstdlib/type_traits.hpp" -#include - template inline ZDeferredConstructed::ZDeferredConstructed() DEBUG_ONLY(: _initialized(false)) { diff --git a/src/hotspot/share/memory/allocation.hpp b/src/hotspot/share/memory/allocation.hpp index 35180fdba5e..963ca04aadf 100644 --- a/src/hotspot/share/memory/allocation.hpp +++ b/src/hotspot/share/memory/allocation.hpp @@ -25,14 +25,13 @@ #ifndef SHARE_MEMORY_ALLOCATION_HPP #define SHARE_MEMORY_ALLOCATION_HPP +#include "cppstdlib/new.hpp" #include "memory/allStatic.hpp" #include "nmt/memTag.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -#include - class outputStream; class Thread; class JavaThread; diff --git a/src/hotspot/share/memory/arena.cpp b/src/hotspot/share/memory/arena.cpp index b9968083e0e..2de3f837c00 100644 --- a/src/hotspot/share/memory/arena.cpp +++ b/src/hotspot/share/memory/arena.cpp @@ -24,6 +24,7 @@ */ #include "compiler/compilationMemoryStatistic.hpp" +#include "cppstdlib/new.hpp" #include "memory/allocation.inline.hpp" #include "memory/arena.hpp" #include "memory/resourceArea.hpp" diff --git a/src/hotspot/share/memory/arena.hpp b/src/hotspot/share/memory/arena.hpp index b4a0546babf..a8450b5543a 100644 --- a/src/hotspot/share/memory/arena.hpp +++ b/src/hotspot/share/memory/arena.hpp @@ -31,8 +31,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" -#include - // The byte alignment to be used by Arena::Amalloc. #define ARENA_AMALLOC_ALIGNMENT BytesPerLong #define ARENA_ALIGN(x) (align_up((x), ARENA_AMALLOC_ALIGNMENT)) diff --git a/src/hotspot/share/utilities/debug.cpp b/src/hotspot/share/utilities/debug.cpp index 89c0a1ebc08..de39fe32dc1 100644 --- a/src/hotspot/share/utilities/debug.cpp +++ b/src/hotspot/share/utilities/debug.cpp @@ -29,6 +29,7 @@ #include "code/vtableStubs.hpp" #include "compiler/compileBroker.hpp" #include "compiler/disassembler.hpp" +#include "cppstdlib/new.hpp" #include "gc/shared/collectedHeap.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" @@ -63,7 +64,6 @@ #include "utilities/unsigned5.hpp" #include "utilities/vmError.hpp" -#include #include #include diff --git a/src/hotspot/share/utilities/deferredStatic.hpp b/src/hotspot/share/utilities/deferredStatic.hpp index 56bdb9b8e6b..3a32f920fe8 100644 --- a/src/hotspot/share/utilities/deferredStatic.hpp +++ b/src/hotspot/share/utilities/deferredStatic.hpp @@ -25,11 +25,10 @@ #ifndef SHARE_UTILITIES_DEFERREDSTATIC_HPP #define SHARE_UTILITIES_DEFERREDSTATIC_HPP +#include "cppstdlib/new.hpp" #include "cppstdlib/type_traits.hpp" #include "utilities/globalDefinitions.hpp" -#include - // The purpose of this class is to provide control over the initialization // time for an object of type T with static storage duration. An instance of // this class provides storage for an object, sized and aligned for T. The diff --git a/src/hotspot/share/utilities/elfFile.cpp b/src/hotspot/share/utilities/elfFile.cpp index 9ea19b38276..0b7713e9ca9 100644 --- a/src/hotspot/share/utilities/elfFile.cpp +++ b/src/hotspot/share/utilities/elfFile.cpp @@ -25,6 +25,7 @@ #if !defined(_WINDOWS) && !defined(__APPLE__) +#include "cppstdlib/new.hpp" #include "jvm_io.h" #include "logging/log.hpp" #include "memory/allocation.inline.hpp" @@ -37,7 +38,6 @@ #include "utilities/ostream.hpp" #include -#include #include #include diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 1910759b434..3284fd3bd15 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1386,4 +1386,25 @@ template inline constexpr bool DependentAlwaysFalse = false; // handled. bool IEEE_subnormal_handling_OK(); +//---------------------------------------------------------------------------------------------------- +// Forbid using the global allocator by HotSpot code. +// +// This is a subset of allocator and deallocator functions. These are +// implicitly declared in all translation units, without needing to include +// ; see C++17 6.7.4. This isn't even the full set of those; implicit +// declarations involving std::align_val_t are not covered here, since that +// type is defined in . A translation unit that doesn't include is +// still likely to include this file. See cppstdlib/new.hpp for more details. +#ifndef HOTSPOT_GTEST + +[[deprecated]] void* operator new(std::size_t); +[[deprecated]] void operator delete(void*) noexcept; +[[deprecated]] void operator delete(void*, std::size_t) noexcept; + +[[deprecated]] void* operator new[](std::size_t); +[[deprecated]] void operator delete[](void*) noexcept; +[[deprecated]] void operator delete[](void*, std::size_t) noexcept; + +#endif // HOTSPOT_GTEST + #endif // SHARE_UTILITIES_GLOBALDEFINITIONS_HPP diff --git a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp index fac17e87016..bdba49b48c0 100644 --- a/test/hotspot/gtest/utilities/test_lockFreeStack.cpp +++ b/test/hotspot/gtest/utilities/test_lockFreeStack.cpp @@ -21,6 +21,7 @@ * questions. */ +#include "cppstdlib/new.hpp" #include "memory/allocation.inline.hpp" #include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" @@ -28,8 +29,6 @@ #include "threadHelper.inline.hpp" #include "unittest.hpp" -#include - class LockFreeStackTestElement { typedef LockFreeStackTestElement Element; From 1535d08f0ee5da42d9db9e196d6a620aabe9feea Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 19 Nov 2025 20:58:23 +0000 Subject: [PATCH 030/114] 8371944: AOT configuration is corrupted when app closes System.out Reviewed-by: kvn, iveresov --- src/hotspot/share/cds/aotMetaspace.cpp | 46 ++++++++--- src/hotspot/share/cds/aotMetaspace.hpp | 2 + src/hotspot/share/cds/dynamicArchive.cpp | 1 + src/hotspot/share/cds/filemap.cpp | 2 + src/hotspot/share/cds/filemap.hpp | 1 + .../cds/appcds/aotCache/CloseSystemOut.java | 82 +++++++++++++++++++ 6 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 8642b1a6de8..42d41e6ae89 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -114,6 +114,7 @@ intx AOTMetaspace::_relocation_delta; char* AOTMetaspace::_requested_base_address; Array* AOTMetaspace::_archived_method_handle_intrinsics = nullptr; bool AOTMetaspace::_use_optimized_module_handling = true; +FileMapInfo* AOTMetaspace::_output_mapinfo = nullptr; // The CDS archive is divided into the following regions: // rw - read-write metadata @@ -322,6 +323,24 @@ void AOTMetaspace::initialize_for_static_dump() { AOTMetaspace::unrecoverable_writing_error(); } _symbol_region.init(&_symbol_rs, &_symbol_vs); + if (CDSConfig::is_dumping_preimage_static_archive()) { + // We are in the AOT training run. User code is executed. + // + // On Windows, if the user code closes System.out and we open the AOT config file for output + // only at VM exit, we might get back the same file HANDLE as stdout, and the AOT config + // file may get corrupted by UL logs. By opening early, we ensure that the output + // HANDLE is different than stdout so we can avoid such corruption. + open_output_mapinfo(); + } else { + // No need for the above as we won't execute any user code. + } +} + +void AOTMetaspace::open_output_mapinfo() { + const char* static_archive = CDSConfig::output_archive_path(); + assert(static_archive != nullptr, "sanity"); + _output_mapinfo = new FileMapInfo(static_archive, true); + _output_mapinfo->open_as_output(); } // Called by universe_post_init() @@ -655,15 +674,14 @@ private: public: - VM_PopulateDumpSharedSpace(StaticArchiveBuilder& b) : - VM_Operation(), _mapped_heap_info(), _streamed_heap_info(), _map_info(nullptr), _builder(b) {} + VM_PopulateDumpSharedSpace(StaticArchiveBuilder& b, FileMapInfo* map_info) : + VM_Operation(), _mapped_heap_info(), _streamed_heap_info(), _map_info(map_info), _builder(b) {} bool skip_operation() const { return false; } VMOp_Type type() const { return VMOp_PopulateDumpSharedSpace; } ArchiveMappedHeapInfo* mapped_heap_info() { return &_mapped_heap_info; } ArchiveStreamedHeapInfo* streamed_heap_info() { return &_streamed_heap_info; } - FileMapInfo* map_info() const { return _map_info; } void doit(); // outline because gdb sucks bool allow_nested_vm_operations() const { return true; } }; // class VM_PopulateDumpSharedSpace @@ -795,12 +813,6 @@ void VM_PopulateDumpSharedSpace::doit() { CppVtables::zero_archived_vtables(); // Write the archive file - if (CDSConfig::is_dumping_final_static_archive()) { - FileMapInfo::free_current_info(); // FIXME: should not free current info - } - const char* static_archive = CDSConfig::output_archive_path(); - assert(static_archive != nullptr, "sanity"); - _map_info = new FileMapInfo(static_archive, true); _map_info->populate_header(AOTMetaspace::core_region_alignment()); _map_info->set_early_serialized_data(early_serialized_data); _map_info->set_serialized_data(serialized_data); @@ -1138,7 +1150,14 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS } #endif - VM_PopulateDumpSharedSpace op(builder); + if (!CDSConfig::is_dumping_preimage_static_archive()) { + if (CDSConfig::is_dumping_final_static_archive()) { + FileMapInfo::free_current_info(); // FIXME: should not free current info + } + open_output_mapinfo(); + } + + VM_PopulateDumpSharedSpace op(builder, _output_mapinfo); VMThread::execute(&op); if (AOTCodeCache::is_on_for_dump() && CDSConfig::is_dumping_final_static_archive()) { @@ -1152,7 +1171,9 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS CDSConfig::disable_dumping_aot_code(); } - bool status = write_static_archive(&builder, op.map_info(), op.mapped_heap_info(), op.streamed_heap_info()); + bool status = write_static_archive(&builder, _output_mapinfo, op.mapped_heap_info(), op.streamed_heap_info()); + assert(!_output_mapinfo->is_open(), "Must be closed already"); + _output_mapinfo = nullptr; if (status && CDSConfig::is_dumping_preimage_static_archive()) { tty->print_cr("%s AOTConfiguration recorded: %s", CDSConfig::has_temp_aot_config_file() ? "Temporary" : "", AOTConfiguration); @@ -1173,11 +1194,10 @@ bool AOTMetaspace::write_static_archive(ArchiveBuilder* builder, // relocate the data so that it can be mapped to AOTMetaspace::requested_base_address() // without runtime relocation. builder->relocate_to_requested(); - - map_info->open_as_output(); if (!map_info->is_open()) { return false; } + map_info->prepare_for_writing(); builder->write_archive(map_info, mapped_heap_info, streamed_heap_info); return true; } diff --git a/src/hotspot/share/cds/aotMetaspace.hpp b/src/hotspot/share/cds/aotMetaspace.hpp index bfd9f4bcc75..1712a7865ad 100644 --- a/src/hotspot/share/cds/aotMetaspace.hpp +++ b/src/hotspot/share/cds/aotMetaspace.hpp @@ -60,6 +60,7 @@ class AOTMetaspace : AllStatic { static char* _requested_base_address; static bool _use_optimized_module_handling; static Array* _archived_method_handle_intrinsics; + static FileMapInfo* _output_mapinfo; public: enum { @@ -185,6 +186,7 @@ public: private: static void read_extra_data(JavaThread* current, const char* filename) NOT_CDS_RETURN; static void fork_and_dump_final_static_archive(TRAPS); + static void open_output_mapinfo(); static bool write_static_archive(ArchiveBuilder* builder, FileMapInfo* map_info, ArchiveMappedHeapInfo* mapped_heap_info, diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 85e59e23f8c..8fae8dabf8c 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -353,6 +353,7 @@ void DynamicArchiveBuilder::write_archive(char* serialized_data, AOTClassLocatio assert(dynamic_info != nullptr, "Sanity"); dynamic_info->open_as_output(); + dynamic_info->prepare_for_writing(); ArchiveBuilder::write_archive(dynamic_info, nullptr, nullptr); address base = _requested_dynamic_archive_bottom; diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 61df0a86b41..0eeb96bb269 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -779,7 +779,9 @@ void FileMapInfo::open_as_output() { } _fd = fd; _file_open = true; +} +void FileMapInfo::prepare_for_writing() { // Seek past the header. We will write the header after all regions are written // and their CRCs computed. size_t header_bytes = header()->header_size(); diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 2a761843e47..fbd3c8e1681 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -365,6 +365,7 @@ public: // File manipulation. bool open_as_input() NOT_CDS_RETURN_(false); void open_as_output(); + void prepare_for_writing(); void write_header(); void write_region(int region, char* base, size_t size, bool read_only, bool allow_exec); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java new file mode 100644 index 00000000000..1f4111ecfb1 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/CloseSystemOut.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025, 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 AOT configuration should not be corrupted even if the app closes System.out in the training run + * @bug 8371944 + * @library /test/jdk/lib/testlibrary /test/lib + * @build CloseSystemOut + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar CloseSystemOutApp + * @run driver CloseSystemOut + */ + +import java.io.PrintWriter; +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; + +public class CloseSystemOut { + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + static final String mainClass = "CloseSystemOutApp"; + + public static void main(String[] args) throws Exception { + Tester tester = new Tester(); + tester.run(new String[] {"AOT", "--two-step-training"} ); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] {mainClass}; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) { + if (runMode != RunMode.ASSEMBLY) { + out.shouldContain("Hello Confused World"); + } + } + } +} + +class CloseSystemOutApp { + public static void main(String args[]) { + // Naive code that ends up closing System.out/err when we + // leave the "try" block + try (var err = new PrintWriter(System.err); + var out = new PrintWriter(System.out)) { + out.println("Hello Confused World"); + } + } +} From c8e64e7c33cabcc5c94616808b9c59ab5b7cd14e Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Wed, 19 Nov 2025 23:22:40 +0000 Subject: [PATCH 031/114] 8372118: Test tools/jpackage/macosx/DmgContentTest.java failed Reviewed-by: almatvee --- .../macosx/classes/jdk/jpackage/internal/MacDmgPackager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java index 6e13a2ff0c1..4ccc459109f 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgPackager.java @@ -30,6 +30,7 @@ import static jdk.jpackage.internal.util.PathUtils.normalizedAbsolutePathString; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.text.MessageFormat; import java.util.Base64; @@ -288,7 +289,7 @@ record MacDmgPackager(BuildEnv env, MacDmgPackage pkg, Path outputDir, // Copy app image, since we did not create DMG with it, but instead we created // empty one. if (copyAppImage) { - FileUtils.copyRecursive(srcFolder, mountedVolume); + FileUtils.copyRecursive(srcFolder, mountedVolume, LinkOption.NOFOLLOW_LINKS); } try { From 2acd8776f26686a93708eb9fc408ff4e2bbe287c Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 20 Nov 2025 01:29:49 +0000 Subject: [PATCH 032/114] 8371440: jpackage should exit with an error if it finds multiple matching signing certificates Reviewed-by: almatvee --- .../internal/SigningIdentityBuilder.java | 7 ++-- .../resources/MacResources.properties | 2 +- .../tools/jpackage/macosx/MacSignTest.java | 40 +++++++++++-------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java index bf8f1519fe1..f90e76bb23d 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/SigningIdentityBuilder.java @@ -156,9 +156,10 @@ final class SigningIdentityBuilder { return certs.getFirst(); } default -> { - Log.error(I18N.format("error.multiple.certs.found", certificateSelector.signingIdentities().getFirst(), - keychain.map(Keychain::name).orElse(""))); - return certs.getFirst(); + throw I18N.buildConfigException("error.multiple.certs.found", + certificateSelector.signingIdentities().getFirst(), + keychain.map(Keychain::name).orElse("") + ).create(); } } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index 58c3bbbc025..afa71d84d5c 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -36,7 +36,7 @@ error.must-sign-app-store=Mac App Store apps must be signed, and signing has bee error.must-sign-app-store.advice=Use --mac-sign option with appropriate user-name and keychain error.certificate.expired=Error: Certificate expired {0} error.cert.not.found=No certificate found matching [{0}] using keychain [{1}] -error.multiple.certs.found=WARNING: Multiple certificates found matching [{0}] using keychain [{1}], using first one +error.multiple.certs.found=Multiple certificates matching name [{0}] found in keychain [{1}] error.app-image.mac-sign.required=Error: --mac-sign option is required with predefined application image and with type [app-image] error.tool.failed.with.output=Error: "{0}" failed with following output: error.invalid-runtime-image-missing-file=Runtime image "{0}" is missing "{1}" file diff --git a/test/jdk/tools/jpackage/macosx/MacSignTest.java b/test/jdk/tools/jpackage/macosx/MacSignTest.java index 014fbc84548..af7cf448bdc 100644 --- a/test/jdk/tools/jpackage/macosx/MacSignTest.java +++ b/test/jdk/tools/jpackage/macosx/MacSignTest.java @@ -39,6 +39,7 @@ import jdk.jpackage.test.JPackageStringBundle; import jdk.jpackage.test.MacHelper; import jdk.jpackage.test.MacSign; import jdk.jpackage.test.MacSign.CertificateRequest; +import jdk.jpackage.test.MacSign.CertificateType; import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageType; import jdk.jpackage.test.TKit; @@ -155,20 +156,13 @@ public class MacSignTest { } @Test - // Case "--mac-signing-key-user-name": jpackage selects first certificate - // found with warning message. Certificate hash is pass to "codesign" in this - // case. - @Parameter({"IMAGE", "0", "GOOD_SIGNING_KEY_USER_NAME"}) - @Parameter({"MAC_DMG", "0", "GOOD_SIGNING_KEY_USER_NAME"}) - @Parameter({"MAC_PKG", "0", "GOOD_SIGNING_KEY_USER_NAME_PKG", "GOOD_SIGNING_KEY_USER_NAME"}) - - // Case "--mac-app-image-sign-identity": sign identity will be pass to - // "codesign" and "codesign" should fail due to multiple certificates with - // same common name found. - @Parameter({"IMAGE", "1", "GOOD_CODESIGN_SIGN_IDENTITY"}) - @Parameter({"MAC_PKG", "1", "GOOD_CODESIGN_SIGN_IDENTITY", "GOOD_PKG_SIGN_IDENTITY"}) - @Parameter({"MAC_PKG", "1", "GOOD_PKG_SIGN_IDENTITY"}) - public static void testMultipleCertificates(PackageType type, int jpackageExitCode, SignOption... options) { + @Parameter({"IMAGE", "GOOD_SIGNING_KEY_USER_NAME"}) + @Parameter({"MAC_DMG", "GOOD_SIGNING_KEY_USER_NAME"}) + @Parameter({"MAC_PKG", "GOOD_SIGNING_KEY_USER_NAME_PKG", "GOOD_SIGNING_KEY_USER_NAME"}) + @Parameter({"IMAGE", "GOOD_CODESIGN_SIGN_IDENTITY"}) + @Parameter({"MAC_PKG", "GOOD_CODESIGN_SIGN_IDENTITY", "GOOD_PKG_SIGN_IDENTITY"}) + @Parameter({"MAC_PKG", "GOOD_PKG_SIGN_IDENTITY"}) + public static void testMultipleCertificates(PackageType type, SignOption... options) { MacSign.withKeychain(keychain -> { final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain) @@ -176,9 +170,19 @@ public class MacSignTest { .addArguments(Stream.of(options).map(SignOption::args).flatMap(List::stream).toList()) .setPackageType(type); - SignOption.configureOutputValidation(cmd, List.of(options), opt -> { + Predicate filter = opt -> { + if (type == PackageType.MAC_PKG && options.length > 1) { + // Only the first error will be reported and it should always be + // for the app image signing, not for the PKG signing. + return opt.identityType() == CertificateType.CODE_SIGN; + } else { + return true; + } + }; + + SignOption.configureOutputValidation(cmd, Stream.of(options).filter(filter).toList(), opt -> { return JPackageStringBundle.MAIN.cannedFormattedString("error.multiple.certs.found", opt.identityName(), keychain.name()); - }).execute(jpackageExitCode); + }).execute(1); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.DUPLICATE.keychain()); } @@ -244,6 +248,10 @@ public class MacSignTest { return cert.name(); } + CertificateType identityType() { + return cert.type(); + } + List args() { return List.of(option, shortName ? cert.shortName() : cert.name()); } From a3b1affbfb23eeef32749164aae316e5d55fffaa Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Thu, 20 Nov 2025 02:18:44 +0000 Subject: [PATCH 033/114] 8372046: compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java fails IR verification Reviewed-by: mhaessig, epeter --- .../floatingpoint/TestSubNodeFloatDoubleNegation.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java b/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java index 4c7092ec654..d96e64baa36 100644 --- a/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java +++ b/test/hotspot/jtreg/compiler/floatingpoint/TestSubNodeFloatDoubleNegation.java @@ -38,7 +38,13 @@ import jdk.test.lib.Asserts; public class TestSubNodeFloatDoubleNegation { public static void main(String[] args) { - TestFramework.runWithFlags("--add-modules=jdk.incubator.vector", "-XX:CompileCommand=inline,jdk.incubator.vector.Float16::*"); + // Disable inlining for java.lang.Float::float16ToFloat and java.lang.Float::floatToFloat16. + // Otherwise, they could be inlined into testHalfFloat on platforms where there is no support + // for fp16, which causes unexpected IR graph. + TestFramework.runWithFlags("--add-modules=jdk.incubator.vector", + "-XX:CompileCommand=inline,jdk.incubator.vector.Float16::*", + "-XX:CompileCommand=dontinline,java.lang.Float::float16ToFloat", + "-XX:CompileCommand=dontinline,java.lang.Float::floatToFloat16"); } @Run(test = { "testHalfFloat", "testFloat", "testDouble" }) From 473471c1f1d3cd42a057dfd602d452196c53aa00 Mon Sep 17 00:00:00 2001 From: Henry Jen Date: Thu, 20 Nov 2025 05:30:40 +0000 Subject: [PATCH 034/114] 8369838: Likely invalid assert or function call in jimage.cpp Reviewed-by: dholmes --- src/hotspot/share/classfile/classLoader.cpp | 37 +++++++++---------- .../share/native/libjimage/jimage.cpp | 2 +- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 082c745f4c3..12fbda899b9 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -412,31 +412,30 @@ ClassFileStream* ClassPathImageEntry::open_stream(JavaThread* current, const cha // ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current, const char* name, ClassLoaderData* loader_data) { jlong size; - JImageLocationRef location = (*JImageFindResource)(jimage_non_null(), "", get_jimage_version_string(), name, &size); + JImageLocationRef location = 0; - if (location == 0) { - TempNewSymbol class_name = SymbolTable::new_symbol(name); - TempNewSymbol pkg_name = ClassLoader::package_from_class_name(class_name); + TempNewSymbol class_name = SymbolTable::new_symbol(name); + TempNewSymbol pkg_name = ClassLoader::package_from_class_name(class_name); - if (pkg_name != nullptr) { - if (!Universe::is_module_initialized()) { - location = (*JImageFindResource)(jimage_non_null(), JAVA_BASE_NAME, get_jimage_version_string(), name, &size); - } else { - PackageEntry* package_entry = ClassLoader::get_package_entry(pkg_name, loader_data); - if (package_entry != nullptr) { - ResourceMark rm(current); - // Get the module name - ModuleEntry* module = package_entry->module(); - assert(module != nullptr, "Boot classLoader package missing module"); - assert(module->is_named(), "Boot classLoader package is in unnamed module"); - const char* module_name = module->name()->as_C_string(); - if (module_name != nullptr) { - location = (*JImageFindResource)(jimage_non_null(), module_name, get_jimage_version_string(), name, &size); - } + if (pkg_name != nullptr) { + if (!Universe::is_module_initialized()) { + location = (*JImageFindResource)(jimage_non_null(), JAVA_BASE_NAME, get_jimage_version_string(), name, &size); + } else { + PackageEntry* package_entry = ClassLoader::get_package_entry(pkg_name, loader_data); + if (package_entry != nullptr) { + ResourceMark rm(current); + // Get the module name + ModuleEntry* module = package_entry->module(); + assert(module != nullptr, "Boot classLoader package missing module"); + assert(module->is_named(), "Boot classLoader package is in unnamed module"); + const char* module_name = module->name()->as_C_string(); + if (module_name != nullptr) { + location = (*JImageFindResource)(jimage_non_null(), module_name, get_jimage_version_string(), name, &size); } } } } + if (location != 0) { if (UsePerfData) { ClassLoader::perf_sys_classfile_bytes_read()->inc(size); diff --git a/src/java.base/share/native/libjimage/jimage.cpp b/src/java.base/share/native/libjimage/jimage.cpp index 10e85eb2520..91a86f992e6 100644 --- a/src/java.base/share/native/libjimage/jimage.cpp +++ b/src/java.base/share/native/libjimage/jimage.cpp @@ -110,7 +110,7 @@ JIMAGE_FindResource(JImageFile* image, size_t nameLen = strlen(name); size_t index; - // TBD: assert(moduleNameLen > 0 && "module name must be non-empty"); + assert(moduleNameLen > 0 && "module name must be non-empty"); assert(nameLen > 0 && "name must non-empty"); // If the concatenated string is too long for the buffer, return not found From 5d3e73b9e512b55cdf554158b19a4ec642dc1f1a Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Thu, 20 Nov 2025 06:14:40 +0000 Subject: [PATCH 035/114] 8371248: Crash in -Xdoclint with invalid @link Reviewed-by: hannesw, vromero --- .../com/sun/tools/javac/api/JavacTrees.java | 4 +++ .../com/sun/tools/javac/comp/Attr.java | 2 +- .../tools/javac/doctree/ReferenceTest.java | 30 +++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java index 2eca26de838..f933ef36565 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java @@ -473,6 +473,10 @@ public class JavacTrees extends DocTrees { if (memberName == null) return tsym; + if (tsym.type.isPrimitive()) { + return null; + } + final List paramTypes; if (ref.paramTypes == null) paramTypes = null; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index ad41adcc135..c723caf1843 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -377,7 +377,7 @@ public class Attr extends JCTree.Visitor { @Override @DefinedBy(Api.COMPILER_TREE) public Symbol visitMemberSelect(MemberSelectTree node, Env env) { Symbol site = visit(node.getExpression(), env); - if (site.kind == ERR || site.kind == ABSENT_TYP || site.kind == HIDDEN) + if (site == null || site.kind == ERR || site.kind == ABSENT_TYP || site.kind == HIDDEN) return site; Name name = (Name)node.getIdentifier(); if (site.kind == PCK) { diff --git a/test/langtools/tools/javac/doctree/ReferenceTest.java b/test/langtools/tools/javac/doctree/ReferenceTest.java index 46c3d40e73a..540cb9a6621 100644 --- a/test/langtools/tools/javac/doctree/ReferenceTest.java +++ b/test/langtools/tools/javac/doctree/ReferenceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 7021614 8278373 8164094 + * @bug 7021614 8278373 8164094 8371248 * @summary extend com.sun.source API to support parsing javadoc comments * @summary check references in at-see and {at-link} tags * @modules jdk.compiler @@ -43,6 +43,7 @@ import com.sun.source.util.DocTrees; import com.sun.source.util.TreePath; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; @@ -83,6 +84,31 @@ import javax.tools.Diagnostic.Kind; * {@link #trees Field} * {@link #getSupportedSourceVersion Method} * {@link #init(ProcessingEnvironment Method} + * {@link double Class} + * {@link double.NAN Bad} + * {@link double#NAN Bad} + * {@link double#double Bad} + * {@link java.base/double Bad} + * + * {@link List Interface} + * {@link List.add Bad} + * {@link List#add Method} + * {@link List#add(Object) Method} + * {@link Map.Entry Interface} + * {@link Map.Entry Interface} + * {@link Map.Entry.getKey Bad} + * {@link Map.Entry#getKey Method} + * {@link Map.Entry#setValue(Object) Method} + * + * {@link java.base/java.util.List Bad} + * {@link java.base/java.util.List.add Bad} + * {@link java.base/java.util.List#add Bad} + * {@link java.base/java.util.List#add(Object) Bad} + * {@link java.base/java.util.Map.Entry Bad} + * {@link java.base/java.util.Map.Entry Bad} + * {@link java.base/java.util.Map.Entry.getKey Bad} + * {@link java.base/java.util.Map.Entry#getKey Bad} + * {@link java.base/java.util.Map.Entry#setValue(Object) Bad} * * @see java.lang Package * @see java.lang.ERROR Bad From 72c45a4d923a294108995e24951bec24dfc70410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6len?= Date: Thu, 20 Nov 2025 07:08:46 +0000 Subject: [PATCH 036/114] 8355225: Test gtest/AsyncLogGtest.java failed at droppingMessage_vm: apparent log corruption Reviewed-by: dholmes, syan --- test/hotspot/gtest/logging/test_asynclog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/gtest/logging/test_asynclog.cpp b/test/hotspot/gtest/logging/test_asynclog.cpp index fdc3795e9db..2634b8dac77 100644 --- a/test/hotspot/gtest/logging/test_asynclog.cpp +++ b/test/hotspot/gtest/logging/test_asynclog.cpp @@ -253,6 +253,8 @@ TEST_VM_F(AsyncLogTest, droppingMessage) { set_log_config(TestLogFileName, "logging=debug"); test_asynclog_drop_messages(); + AsyncLogWriter::flush(); + fflush(nullptr); bool messages_dropped = file_contains_substring(TestLogFileName, "messages dropped due to async logging"); if (!messages_dropped) { stringStream content; From 852141b9d42ada168a008aea63045deddca29190 Mon Sep 17 00:00:00 2001 From: Sean Coffey Date: Thu, 20 Nov 2025 07:32:06 +0000 Subject: [PATCH 037/114] 8372004: Have SSLLogger implement System.Logger Reviewed-by: dfuchs, weijun --- .../share/classes/sun/security/ssl/Alert.java | 2 +- .../sun/security/ssl/AlpnExtension.java | 18 +- .../security/ssl/CertSignAlgsExtension.java | 8 +- .../sun/security/ssl/CertStatusExtension.java | 32 +- .../ssl/CertificateAuthoritiesExtension.java | 16 +- .../sun/security/ssl/CertificateMessage.java | 32 +- .../sun/security/ssl/CertificateRequest.java | 22 +- .../sun/security/ssl/CertificateStatus.java | 6 +- .../sun/security/ssl/CertificateVerify.java | 26 +- .../sun/security/ssl/ChangeCipherSpec.java | 6 +- .../classes/sun/security/ssl/ClientHello.java | 42 +- .../sun/security/ssl/CookieExtension.java | 10 +- .../sun/security/ssl/DHClientKeyExchange.java | 4 +- .../sun/security/ssl/DHServerKeyExchange.java | 4 +- .../sun/security/ssl/DTLSInputRecord.java | 76 ++-- .../sun/security/ssl/DTLSOutputRecord.java | 22 +- .../security/ssl/ECDHClientKeyExchange.java | 8 +- .../security/ssl/ECDHServerKeyExchange.java | 4 +- .../security/ssl/ECPointFormatsExtension.java | 6 +- .../sun/security/ssl/EncryptedExtensions.java | 4 +- .../ssl/ExtendedMasterSecretExtension.java | 10 +- .../classes/sun/security/ssl/Finished.java | 16 +- .../sun/security/ssl/HandshakeContext.java | 12 +- .../sun/security/ssl/HandshakeOutStream.java | 2 +- .../sun/security/ssl/HelloRequest.java | 10 +- .../sun/security/ssl/HelloVerifyRequest.java | 4 +- .../sun/security/ssl/KeyShareExtension.java | 32 +- .../classes/sun/security/ssl/KeyUpdate.java | 8 +- .../sun/security/ssl/MaxFragExtension.java | 18 +- .../classes/sun/security/ssl/NamedGroup.java | 8 +- .../sun/security/ssl/NewSessionTicket.java | 38 +- .../sun/security/ssl/OutputRecord.java | 4 +- .../security/ssl/PreSharedKeyExtension.java | 34 +- .../ssl/PredefinedDHParameterSpecs.java | 6 +- .../ssl/PskKeyExchangeModesExtension.java | 8 +- .../security/ssl/QuicEngineOutputRecord.java | 8 +- .../sun/security/ssl/QuicKeyManager.java | 26 +- .../sun/security/ssl/QuicTLSEngineImpl.java | 6 +- .../security/ssl/RSAClientKeyExchange.java | 4 +- .../sun/security/ssl/RSAKeyExchange.java | 11 +- .../security/ssl/RSAServerKeyExchange.java | 4 +- .../sun/security/ssl/RenegoInfoExtension.java | 24 +- .../security/ssl/SSLAlgorithmConstraints.java | 2 +- .../classes/sun/security/ssl/SSLCipher.java | 44 +- .../sun/security/ssl/SSLConfiguration.java | 8 +- .../sun/security/ssl/SSLContextImpl.java | 32 +- .../sun/security/ssl/SSLEngineImpl.java | 14 +- .../security/ssl/SSLEngineInputRecord.java | 10 +- .../security/ssl/SSLEngineOutputRecord.java | 26 +- .../sun/security/ssl/SSLExtension.java | 2 +- .../sun/security/ssl/SSLExtensions.java | 32 +- .../classes/sun/security/ssl/SSLLogger.java | 415 +++++++++--------- .../security/ssl/SSLMasterKeyDerivation.java | 3 +- .../security/ssl/SSLSessionContextImpl.java | 8 +- .../sun/security/ssl/SSLSessionImpl.java | 16 +- .../sun/security/ssl/SSLSocketImpl.java | 46 +- .../security/ssl/SSLSocketInputRecord.java | 14 +- .../security/ssl/SSLSocketOutputRecord.java | 32 +- .../sun/security/ssl/SSLTransport.java | 8 +- .../classes/sun/security/ssl/ServerHello.java | 26 +- .../sun/security/ssl/ServerHelloDone.java | 4 +- .../sun/security/ssl/ServerNameExtension.java | 20 +- .../security/ssl/SessionTicketExtension.java | 24 +- .../ssl/SignatureAlgorithmsExtension.java | 4 +- .../sun/security/ssl/SignatureScheme.java | 20 +- .../security/ssl/StatusResponseManager.java | 62 +-- .../security/ssl/SunX509KeyManagerImpl.java | 4 +- .../ssl/SupportedGroupsExtension.java | 20 +- .../ssl/SupportedVersionsExtension.java | 18 +- .../sun/security/ssl/TransportContext.java | 20 +- .../security/ssl/TrustManagerFactoryImpl.java | 10 +- .../sun/security/ssl/TrustStoreManager.java | 16 +- .../classes/sun/security/ssl/Utilities.java | 6 +- .../sun/security/ssl/X509Authentication.java | 26 +- .../ssl/X509KeyManagerCertChecking.java | 14 +- .../sun/security/ssl/X509KeyManagerImpl.java | 14 +- .../security/ssl/X509TrustManagerImpl.java | 12 +- .../classes/sun/security/util/DomainName.java | 9 +- .../sun/security/util/HostnameChecker.java | 10 +- .../SSLLogger/DebugPropertyValuesTest.java | 3 +- 80 files changed, 848 insertions(+), 847 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/Alert.java b/src/java.base/share/classes/sun/security/ssl/Alert.java index ed5e079bf44..d172206326f 100644 --- a/src/java.base/share/classes/sun/security/ssl/Alert.java +++ b/src/java.base/share/classes/sun/security/ssl/Alert.java @@ -238,7 +238,7 @@ public enum Alert { TransportContext tc = (TransportContext)context; AlertMessage am = new AlertMessage(tc, m); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Received alert message", am); } diff --git a/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java b/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java index aa5933ddab0..f03a65c8410 100644 --- a/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java @@ -157,7 +157,7 @@ final class AlpnExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Ignore client unavailable extension: " + SSLExtension.CH_ALPN.name); @@ -170,7 +170,7 @@ final class AlpnExtension { String[] laps = chc.sslConfig.applicationProtocols; if ((laps == null) || (laps.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "No available application protocols"); } @@ -183,7 +183,7 @@ final class AlpnExtension { int length = ap.getBytes(alpnCharset).length; if (length == 0) { // log the configuration problem - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.severe( "Application protocol name cannot be empty"); } @@ -197,7 +197,7 @@ final class AlpnExtension { listLength += (length + 1); } else { // log the configuration problem - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.severe( "Application protocol name (" + ap + ") exceeds the size limit (" + @@ -212,7 +212,7 @@ final class AlpnExtension { if (listLength > MAX_AP_LIST_LENGTH) { // log the configuration problem - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.severe( "The configured application protocols (" + Arrays.toString(laps) + @@ -266,7 +266,7 @@ final class AlpnExtension { if (!shc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { shc.applicationProtocol = ""; shc.conContext.applicationProtocol = ""; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Ignore server unavailable extension: " + SSLExtension.CH_ALPN.name); @@ -288,7 +288,7 @@ final class AlpnExtension { if (noAPSelector && noAlpnProtocols) { shc.applicationProtocol = ""; shc.conContext.applicationProtocol = ""; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore server unenabled extension: " + SSLExtension.CH_ALPN.name); @@ -378,7 +378,7 @@ final class AlpnExtension { (AlpnSpec)shc.handshakeExtensions.get(SSLExtension.CH_ALPN); if (requestedAlps == null) { // Ignore, this extension was not requested and accepted. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SSLExtension.SH_ALPN.name); @@ -423,7 +423,7 @@ final class AlpnExtension { // Ignore, no negotiated application layer protocol. shc.applicationProtocol = ""; shc.conContext.applicationProtocol = ""; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no negotiated application layer protocol"); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java b/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java index 2125a148162..2d03d5fef98 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java @@ -94,7 +94,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); @@ -144,7 +144,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); @@ -235,7 +235,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); @@ -283,7 +283,7 @@ final class CertSignAlgsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "signature_algorithms_cert extension"); diff --git a/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java b/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java index 49713b6db11..d6c1cec5735 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java @@ -144,7 +144,7 @@ final class CertStatusExtension { if (statusType == CertStatusRequestType.OCSP.id) { this.statusRequest = new OCSPStatusRequest(statusType, encoded); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Unknown certificate status request " + "(status type: " + statusType + ")"); @@ -196,7 +196,7 @@ final class CertStatusExtension { if (type == CertStatusRequestType.OCSP.id) { this.statusResponse = new OCSPStatusResponse(type, respData); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Unknown certificate status response " + "(status type: " + type + ")"); @@ -557,7 +557,7 @@ final class CertStatusExtension { } if (!chc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SSLExtension.CH_STATUS_REQUEST.name); @@ -598,7 +598,7 @@ final class CertStatusExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (!shc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + SSLExtension.CH_STATUS_REQUEST.name); } @@ -656,7 +656,7 @@ final class CertStatusExtension { shc.handshakeExtensions.get(SSLExtension.CH_STATUS_REQUEST); if (spec == null) { // Ignore, no status_request extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Ignore unavailable extension: " + SSLExtension.CH_STATUS_REQUEST.name); } @@ -666,7 +666,7 @@ final class CertStatusExtension { // Is it a session resuming? if (shc.isResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No status_request response for session resuming"); } @@ -839,7 +839,7 @@ final class CertStatusExtension { statusRequests.add( new OCSPStatusRequest(statusType, encoded)); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Unknown certificate status request " + "(status type: " + statusType + ")"); @@ -915,7 +915,7 @@ final class CertStatusExtension { } if (!chc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST_V2)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable status_request_v2 extension"); } @@ -957,7 +957,7 @@ final class CertStatusExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (!shc.sslConfig.isAvailable(SSLExtension.CH_STATUS_REQUEST_V2)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable status_request_v2 extension"); } @@ -1017,7 +1017,7 @@ final class CertStatusExtension { shc.handshakeExtensions.get(SSLExtension.CH_STATUS_REQUEST_V2); if (spec == null) { // Ignore, no status_request_v2 extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable status_request_v2 extension"); } @@ -1027,7 +1027,7 @@ final class CertStatusExtension { // Is it a session resuming? if (shc.isResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No status_request_v2 response for session resumption"); } @@ -1112,7 +1112,7 @@ final class CertStatusExtension { // Stapling needs to be active and have valid data to proceed if (shc.stapleParams == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Stapling is disabled for this connection"); } @@ -1121,7 +1121,7 @@ final class CertStatusExtension { // There needs to be a non-null CertificateEntry to proceed if (shc.currentCertEntry == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Found null CertificateEntry in context"); } return null; @@ -1139,7 +1139,7 @@ final class CertStatusExtension { byte[] respBytes = shc.stapleParams.responseMap.get(x509Cert); if (respBytes == null) { // We're done with this entry. Clear it from the context - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("No status response found for " + x509Cert.getSubjectX500Principal()); @@ -1149,7 +1149,7 @@ final class CertStatusExtension { } // Build a proper response buffer from the stapling information - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Found status response for " + x509Cert.getSubjectX500Principal() + ", response length: " + respBytes.length); @@ -1208,7 +1208,7 @@ final class CertStatusExtension { respList.add(spec.statusResponse.encodedResponse); chc.handshakeSession.setStatusResponses(respList); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignoring stapled data on resumed session"); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java b/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java index 43bac16f0ea..cc513eb30ba 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java @@ -192,7 +192,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); @@ -205,7 +205,7 @@ final class CertificateAuthoritiesExtension { X509Certificate[] caCerts = chc.sslContext.getX509TrustManager().getAcceptedIssuers(); if (caCerts.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No available certificate authorities"); } @@ -216,7 +216,7 @@ final class CertificateAuthoritiesExtension { List encodedCAs = CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts); if (encodedCAs.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "The number of CAs exceeds the maximum size " + "of the certificate_authorities extension"); @@ -270,7 +270,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); @@ -319,7 +319,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CR_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); @@ -332,7 +332,7 @@ final class CertificateAuthoritiesExtension { X509Certificate[] caCerts = shc.sslContext.getX509TrustManager().getAcceptedIssuers(); if (caCerts.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No available certificate authorities"); } @@ -343,7 +343,7 @@ final class CertificateAuthoritiesExtension { List encodedCAs = CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts); if (encodedCAs.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Too many certificate authorities to use " + "the certificate_authorities extension"); @@ -397,7 +397,7 @@ final class CertificateAuthoritiesExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CR_CERTIFICATE_AUTHORITIES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable " + "certificate_authorities extension"); diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java index d4587d35ae9..2a2db34cab9 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java @@ -265,7 +265,7 @@ final class CertificateMessage { shc.handshakeSession.setLocalCertificates(x509Possession.popCerts); T12CertificateMessage cm = new T12CertificateMessage(shc, x509Possession.popCerts); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server Certificate handshake message", cm); } @@ -293,7 +293,7 @@ final class CertificateMessage { // an empty cert chain instead. if (x509Possession == null) { if (chc.negotiatedProtocol.useTLS10PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication, " + "use empty Certificate message instead"); @@ -302,7 +302,7 @@ final class CertificateMessage { x509Possession = new X509Possession(null, new X509Certificate[0]); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication, " + "send a no_certificate alert"); @@ -324,7 +324,7 @@ final class CertificateMessage { } T12CertificateMessage cm = new T12CertificateMessage(chc, x509Possession.popCerts); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client Certificate handshake message", cm); } @@ -360,13 +360,13 @@ final class CertificateMessage { T12CertificateMessage cm = new T12CertificateMessage(hc, message); if (hc.sslConfig.isClientMode) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Certificate handshake message", cm); } onCertificate((ClientHandshakeContext)context, cm); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Certificate handshake message", cm); } @@ -501,7 +501,7 @@ final class CertificateMessage { try { thisSubjectAltNames = thisCert.getSubjectAlternativeNames(); } catch (CertificateParsingException cpe) { - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine( "Attempt to obtain subjectAltNames extension failed!"); } @@ -511,7 +511,7 @@ final class CertificateMessage { try { prevSubjectAltNames = prevCert.getSubjectAlternativeNames(); } catch (CertificateParsingException cpe) { - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine( "Attempt to obtain subjectAltNames extension failed!"); } @@ -980,7 +980,7 @@ final class CertificateMessage { certEnt.extensions.produce(shc, enabledCTExts); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced server Certificate message", cm); } @@ -997,7 +997,7 @@ final class CertificateMessage { ClientHelloMessage clientHello) { if (hc.peerRequestedCertSignSchemes == null || hc.peerRequestedCertSignSchemes.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No signature_algorithms(_cert) in ClientHello"); } @@ -1021,7 +1021,7 @@ final class CertificateMessage { SSLPossession pos = X509Authentication .createPossession(hc, supportedKeyTypes); if (pos == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available authentication scheme"); } } @@ -1034,14 +1034,14 @@ final class CertificateMessage { SSLPossession pos = choosePossession(chc, clientHello); X509Certificate[] localCerts; if (pos == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No available client authentication scheme"); } localCerts = new X509Certificate[0]; } else { chc.handshakePossessions.add(pos); if (!(pos instanceof X509Possession x509Possession)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication"); } @@ -1067,7 +1067,7 @@ final class CertificateMessage { throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Failed to produce client Certificate message", ce); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced client Certificate message", cm); } @@ -1108,13 +1108,13 @@ final class CertificateMessage { T13CertificateMessage cm = new T13CertificateMessage(hc, message); if (hc.sslConfig.isClientMode) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Certificate handshake message", cm); } onConsumeCertificate((ClientHandshakeContext)context, cm); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Certificate handshake message", cm); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java index 66b8c048703..a297d9d21b2 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java @@ -297,7 +297,7 @@ final class CertificateRequest { shc.sslContext.getX509TrustManager().getAcceptedIssuers(); T10CertificateRequestMessage crm = new T10CertificateRequestMessage( shc, caCerts, shc.negotiatedCipherSuite.keyExchange); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateRequest handshake message", crm); } @@ -360,7 +360,7 @@ final class CertificateRequest { T10CertificateRequestMessage crm = new T10CertificateRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateRequest handshake message", crm); } @@ -400,7 +400,7 @@ final class CertificateRequest { } if (clientAlias == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available client authentication"); } return; @@ -408,7 +408,7 @@ final class CertificateRequest { PrivateKey clientPrivateKey = km.getPrivateKey(clientAlias); if (clientPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available client private key"); } return; @@ -416,7 +416,7 @@ final class CertificateRequest { X509Certificate[] clientCerts = km.getCertificateChain(clientAlias); if ((clientCerts == null) || (clientCerts.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available client certificate"); } return; @@ -655,7 +655,7 @@ final class CertificateRequest { T12CertificateRequestMessage crm = new T12CertificateRequestMessage( shc, caCerts, shc.negotiatedCipherSuite.keyExchange, certReqSignAlgs); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateRequest handshake message", crm); } @@ -717,7 +717,7 @@ final class CertificateRequest { T12CertificateRequestMessage crm = new T12CertificateRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateRequest handshake message", crm); } @@ -784,7 +784,7 @@ final class CertificateRequest { T12CertificateRequestMessage crm) { if (hc.peerRequestedCertSignSchemes == null || hc.peerRequestedCertSignSchemes.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No signature and hash algorithms " + "in CertificateRequest"); } @@ -823,7 +823,7 @@ final class CertificateRequest { SSLPossession pos = X509Authentication .createPossession(hc, supportedKeyTypes); if (pos == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available authentication scheme"); } } @@ -933,7 +933,7 @@ final class CertificateRequest { SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions( SSLHandshake.CERTIFICATE_REQUEST, shc.negotiatedProtocol); crm.extensions.produce(shc, extTypes); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced CertificateRequest message", crm); } @@ -985,7 +985,7 @@ final class CertificateRequest { T13CertificateRequestMessage crm = new T13CertificateRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateRequest handshake message", crm); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java b/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java index 11b2c5e587d..a1048e423d1 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java @@ -281,7 +281,7 @@ final class CertificateStatus { new CertificateStatusMessage(chc, message); // Log the message - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server CertificateStatus handshake message", cst); @@ -325,7 +325,7 @@ final class CertificateStatus { // Create the CertificateStatus message from info in the CertificateStatusMessage csm = new CertificateStatusMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server CertificateStatus handshake message", csm); } @@ -358,7 +358,7 @@ final class CertificateStatus { // status_request[_v2] extension. 2) The CertificateStatus // message was not sent. This means that cert path checking // was deferred, but must happen immediately. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Server did not send CertificateStatus, " + "checking cert chain without status info."); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java b/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java index 09d07d8e62d..18ea2b9c3de 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java @@ -248,7 +248,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -258,7 +258,7 @@ final class CertificateVerify { S30CertificateVerifyMessage cvm = new S30CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateVerify handshake message", cvm); } @@ -300,7 +300,7 @@ final class CertificateVerify { S30CertificateVerifyMessage cvm = new S30CertificateVerifyMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } @@ -503,7 +503,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -513,7 +513,7 @@ final class CertificateVerify { T10CertificateVerifyMessage cvm = new T10CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateVerify handshake message", cvm); } @@ -555,7 +555,7 @@ final class CertificateVerify { T10CertificateVerifyMessage cvm = new T10CertificateVerifyMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } @@ -754,7 +754,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -764,7 +764,7 @@ final class CertificateVerify { T12CertificateVerifyMessage cvm = new T12CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced CertificateVerify handshake message", cvm); } @@ -806,7 +806,7 @@ final class CertificateVerify { T12CertificateVerifyMessage cvm = new T12CertificateVerifyMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } @@ -1092,7 +1092,7 @@ final class CertificateVerify { if (x509Possession == null || x509Possession.popPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 credentials negotiated for CertificateVerify"); } @@ -1113,7 +1113,7 @@ final class CertificateVerify { X509Possession x509Possession) throws IOException { T13CertificateVerifyMessage cvm = new T13CertificateVerifyMessage(shc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server CertificateVerify handshake message", cvm); } @@ -1130,7 +1130,7 @@ final class CertificateVerify { X509Possession x509Possession) throws IOException { T13CertificateVerifyMessage cvm = new T13CertificateVerifyMessage(chc, x509Possession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client CertificateVerify handshake message", cvm); } @@ -1173,7 +1173,7 @@ final class CertificateVerify { T13CertificateVerifyMessage cvm = new T13CertificateVerifyMessage(hc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming CertificateVerify handshake message", cvm); } diff --git a/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java b/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java index 4ea61161c1d..d3eac8f13af 100644 --- a/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java +++ b/src/java.base/share/classes/sun/security/ssl/ChangeCipherSpec.java @@ -108,7 +108,7 @@ final class ChangeCipherSpec { ") and protocol version (" + hc.negotiatedProtocol + ")"); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ChangeCipherSpec message"); } @@ -142,7 +142,7 @@ final class ChangeCipherSpec { throw tc.fatal(Alert.UNEXPECTED_MESSAGE, "Malformed or unexpected ChangeCipherSpec message"); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ChangeCipherSpec message"); } @@ -237,7 +237,7 @@ final class ChangeCipherSpec { throw tc.fatal(Alert.UNEXPECTED_MESSAGE, "Malformed or unexpected ChangeCipherSpec message"); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ChangeCipherSpec message"); } diff --git a/src/java.base/share/classes/sun/security/ssl/ClientHello.java b/src/java.base/share/classes/sun/security/ssl/ClientHello.java index c9432ea3979..421673d625d 100644 --- a/src/java.base/share/classes/sun/security/ssl/ClientHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java @@ -430,7 +430,7 @@ final class ClientHello { if (!session.isRejoinable()) { session = null; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, the session is not rejoinable"); @@ -443,7 +443,7 @@ final class ClientHello { sessionSuite = session.getSuite(); if (!chc.isNegotiable(sessionSuite)) { session = null; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, unavailable session cipher suite"); @@ -456,7 +456,7 @@ final class ClientHello { sessionVersion = session.getProtocolVersion(); if (!chc.isNegotiable(sessionVersion)) { session = null; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, unavailable protocol version"); @@ -513,7 +513,7 @@ final class ClientHello { String sessionIdentityAlg = session.getIdentificationProtocol(); if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, endpoint id" + " algorithm does not match, requested: " + @@ -524,7 +524,7 @@ final class ClientHello { } if (session != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Try resuming session", session); } @@ -547,7 +547,7 @@ final class ClientHello { cipherSuites = List.of(sessionSuite); } - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "No new session is allowed, so try to resume " + @@ -634,7 +634,7 @@ final class ClientHello { SSLHandshake.CLIENT_HELLO, chc.activeProtocols); chm.extensions.produce(chc, extTypes); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ClientHello handshake message", chm); } @@ -700,7 +700,7 @@ final class ClientHello { // // The HelloVerifyRequest consumer should have updated the // ClientHello handshake message with cookie. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ClientHello(cookie) handshake message", chc.initialClientHelloMsg); @@ -734,7 +734,7 @@ final class ClientHello { // TLS 1.3 // The HelloRetryRequest consumer should have updated the // ClientHello handshake message with cookie. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ClientHello(HRR) handshake message", chc.initialClientHelloMsg); @@ -790,7 +790,7 @@ final class ClientHello { ClientHelloMessage chm = new ClientHelloMessage(shc, message, enabledExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ClientHello handshake message", chm); } @@ -820,7 +820,7 @@ final class ClientHello { negotiateProtocol(context, clientHello.clientVersion); } context.negotiatedProtocol = negotiatedProtocol; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Negotiated protocol version: " + negotiatedProtocol.name); } @@ -980,7 +980,7 @@ final class ClientHello { boolean resumingSession = (previous != null) && previous.isRejoinable(); if (!resumingSession) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -993,7 +993,7 @@ final class ClientHello { previous.getProtocolVersion(); if (sessionProtocol != shc.negotiatedProtocol) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, not the same protocol version"); @@ -1008,7 +1008,7 @@ final class ClientHello { previous.getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1023,7 +1023,7 @@ final class ClientHello { if ((!shc.isNegotiable(suite)) || (!clientHello.cipherSuites.contains(suite))) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1039,7 +1039,7 @@ final class ClientHello { String sessionIdentityAlg = previous.getIdentificationProtocol(); if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, endpoint id" + " algorithm does not match, requested: " + @@ -1054,7 +1054,7 @@ final class ClientHello { shc.isResumption = resumingSession; shc.resumingSession = resumingSession ? previous : null; - if (!resumingSession && SSLLogger.isOn && + if (!resumingSession && SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Session not resumed."); } @@ -1321,7 +1321,7 @@ final class ClientHello { boolean resumingSession = (previous != null) && previous.isRejoinable(); if (!resumingSession) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1334,7 +1334,7 @@ final class ClientHello { previous.getProtocolVersion(); if (sessionProtocol != shc.negotiatedProtocol) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, not the same protocol version"); @@ -1350,7 +1350,7 @@ final class ClientHello { previous.getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -1365,7 +1365,7 @@ final class ClientHello { if ((!shc.isNegotiable(suite)) || (!clientHello.cipherSuites.contains(suite))) { resumingSession = false; - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + diff --git a/src/java.base/share/classes/sun/security/ssl/CookieExtension.java b/src/java.base/share/classes/sun/security/ssl/CookieExtension.java index d54a1a3e63d..2c22dd121ba 100644 --- a/src/java.base/share/classes/sun/security/ssl/CookieExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/CookieExtension.java @@ -117,7 +117,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.CH_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -154,7 +154,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.CH_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -218,7 +218,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.HRR_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -253,7 +253,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.HRR_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } @@ -280,7 +280,7 @@ public class CookieExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.HRR_COOKIE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable cookie extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java index fb5d6feef55..53f9896a3e4 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java @@ -187,7 +187,7 @@ final class DHClientKeyExchange { chc.handshakePossessions.add(dhePossession); DHClientKeyExchangeMessage ckem = new DHClientKeyExchangeMessage(chc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced DH ClientKeyExchange handshake message", ckem); } @@ -268,7 +268,7 @@ final class DHClientKeyExchange { DHClientKeyExchangeMessage ckem = new DHClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming DH ClientKeyExchange handshake message", ckem); } diff --git a/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java index 2df62d50fb8..744ff59f402 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/DHServerKeyExchange.java @@ -481,7 +481,7 @@ final class DHServerKeyExchange { ServerHandshakeContext shc = (ServerHandshakeContext)context; DHServerKeyExchangeMessage skem = new DHServerKeyExchangeMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced DH ServerKeyExchange handshake message", skem); } @@ -512,7 +512,7 @@ final class DHServerKeyExchange { DHServerKeyExchangeMessage skem = new DHServerKeyExchangeMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming DH ServerKeyExchange handshake message", skem); } diff --git a/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java b/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java index 4e82fd25a7b..e880f36e846 100644 --- a/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java @@ -125,7 +125,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { return null; } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw read", packet); } @@ -150,7 +150,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { int contentLen = ((packet.get() & 0xFF) << 8) | (packet.get() & 0xFF); // pos: 11, 12 - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine("READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + " " + ContentType.nameOf(contentType) + ", length = " + @@ -162,7 +162,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (this.readEpoch > recordEpoch) { // Reset the position of the packet buffer. packet.position(recLim); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine("READ: discard this old record", recordEnS); } return null; @@ -181,7 +181,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { packet.position(recLim); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Premature record (epoch), discard it."); } @@ -223,7 +223,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { plaintextFragment = plaintext.fragment; contentType = plaintext.contentType; } catch (GeneralSecurityException gse) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + gse); } @@ -241,7 +241,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Cleanup the handshake reassembler if necessary. if ((reassembler != null) && (reassembler.handshakeEpoch < recordEpoch)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Cleanup the handshake reassembler"); } @@ -273,7 +273,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (hsFrag == null) { // invalid, discard this record - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Invalid handshake message, discard it."); } @@ -296,7 +296,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { return pt == null ? null : new Plaintext[] { pt }; } - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("The reassembler is not initialized yet."); } @@ -356,7 +356,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { int remaining = plaintextFragment.remaining(); if (remaining < handshakeHeaderSize) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "too small record to hold a handshake fragment"); } @@ -368,7 +368,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Fail fast for unknown handshake message. byte handshakeType = plaintextFragment.get(); // pos: 0 if (!SSLHandshake.isKnown(handshakeType)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "unknown handshake type size, Handshake.msg_type = " + (handshakeType & 0xFF)); @@ -404,7 +404,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { ((plaintextFragment.get() & 0xFF) << 8) | (plaintextFragment.get() & 0xFF); // pos: 9-11 if ((remaining - handshakeHeaderSize) < fragmentLength) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "not a complete handshake fragment in the record"); } @@ -748,7 +748,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // It's OK to discard retransmission as the handshake hash // is computed as if each handshake message had been sent // as a single fragment. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Have got the full message, discard it."); } @@ -769,7 +769,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // The ranges SHOULD NOT overlap. if (hole.offset > hsf.fragmentOffset || hole.limit < fragmentLimit) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Discard invalid record: " + "handshake fragment ranges are overlapping"); } @@ -837,7 +837,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { } // Read the random (32 bytes) if (fragmentData.remaining() < 32) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Rejected client hello fragment (bad random len) " + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); } @@ -861,7 +861,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Cookie byte[] cookie = Record.getBytes8(fragmentData); if (firstHello && cookie.length != 0) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Rejected initial client hello fragment (bad cookie len) " + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); } @@ -897,7 +897,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { } } } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Rejected client hello fragment " + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); } @@ -1029,7 +1029,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { int previousEpoch = nextRecordEpoch - 1; if (rf.recordEpoch < previousEpoch) { // Too old to use, discard this record. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Too old epoch to use this record, discard it."); } @@ -1075,7 +1075,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (!isDesired) { // Too old to use, discard this retransmitted record - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Too old retransmission to use, discard it."); } @@ -1088,7 +1088,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Previously disordered record for the current epoch. // // Should have been retransmitted. Discard this record. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Lagging behind record (sequence), discard it."); } @@ -1126,7 +1126,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { Plaintext acquirePlaintext() throws SSLProtocolException { if (bufferedFragments.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("No received handshake messages"); } return null; @@ -1147,7 +1147,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Reset the next handshake flight. resetHandshakeFlight(precedingFlight); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Received a retransmission flight."); } @@ -1159,7 +1159,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { } if (!flightIsReady) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "The handshake flight is not ready to use: " + handshakeFlight.handshakeType); @@ -1244,7 +1244,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (readEpoch != rFrag.recordEpoch) { if (readEpoch > rFrag.recordEpoch) { // discard old records - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Discard old buffered ciphertext fragments."); } @@ -1256,7 +1256,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { flightIsReady = false; } - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet ready to decrypt the cached fragments."); } @@ -1273,7 +1273,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { plaintextFragment = plaintext.fragment; rFrag.contentType = plaintext.contentType; } catch (GeneralSecurityException gse) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Discard invalid record: ", gse); } @@ -1295,7 +1295,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (hsFrag == null) { // invalid, discard this record - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Invalid handshake fragment, discard it", plaintextFragment); @@ -1446,7 +1446,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (expectCCSFlight) { // Have the ChangeCipherSpec/Finished flight been received? boolean isReady = hasFinishedMessage(); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Has the final flight been received? " + isReady); } @@ -1454,7 +1454,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { return isReady; } - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("No flight is received yet."); } @@ -1467,7 +1467,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // single handshake message flight boolean isReady = hasCompleted(flightType); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Is the handshake message completed? " + isReady); } @@ -1481,7 +1481,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (flightType == SSLHandshake.SERVER_HELLO.id) { // Firstly, check the first flight handshake message. if (!hasCompleted(flightType)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "The ServerHello message is not completed yet."); } @@ -1493,7 +1493,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // an abbreviated handshake // if (hasFinishedMessage()) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("It's an abbreviated handshake."); } @@ -1507,7 +1507,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { SSLHandshake.SERVER_HELLO_DONE.id); if ((holes == null) || !holes.isEmpty()) { // Not yet got the final message of the flight. - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet got the ServerHelloDone message"); } @@ -1519,7 +1519,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { boolean isReady = hasCompleted(bufferedFragments, handshakeFlight.minMessageSeq, handshakeFlight.maxMessageSeq); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Is the ServerHello flight (message " + handshakeFlight.minMessageSeq + "-" + @@ -1542,7 +1542,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // Firstly, check the first flight handshake message. if (!hasCompleted(flightType)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "The ClientKeyExchange or client Certificate " + "message is not completed yet."); @@ -1556,7 +1556,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (needClientVerify(bufferedFragments) && !hasCompleted(SSLHandshake.CERTIFICATE_VERIFY.id)) { - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet have the CertificateVerify message"); } @@ -1567,7 +1567,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { if (!hasFinishedMessage()) { // not yet have the ChangeCipherSpec/Finished messages - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Not yet have the ChangeCipherSpec and " + "Finished messages"); @@ -1580,7 +1580,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { boolean isReady = hasCompleted(bufferedFragments, handshakeFlight.minMessageSeq, handshakeFlight.maxMessageSeq); - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Is the ClientKeyExchange flight (message " + handshakeFlight.minMessageSeq + "-" + @@ -1594,7 +1594,7 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { // // Otherwise, need to receive more handshake messages. // - if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Need to receive more handshake messages"); } diff --git a/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java index 691ac32c26b..162dbb58eec 100644 --- a/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/DTLSOutputRecord.java @@ -92,7 +92,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { void changeWriteCiphers(SSLWriteCipher writeCipher, boolean useChangeCipherSpec) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -120,7 +120,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { @Override void encodeAlert(byte level, byte description) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } @@ -137,7 +137,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { @Override void encodeChangeCipherSpec() { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -154,7 +154,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { void encodeHandshake(byte[] source, int offset, int length) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); @@ -179,14 +179,14 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { if (isClosed) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data or cached messages"); } return null; } else if (isCloseWaiting) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data"); } @@ -201,7 +201,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { ByteBuffer destination) throws IOException { if (writeCipher.authenticator.seqNumOverflow()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); @@ -269,7 +269,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { destination.limit(destination.position()); destination.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.APPLICATION_DATA.name + @@ -282,7 +282,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { dstPos, dstLim, headerSize, protocolVersion); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = destination.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); @@ -497,7 +497,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { dstBuf.limit(dstBuf.position()); dstBuf.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.nameOf(memo.contentType) + @@ -511,7 +511,7 @@ final class DTLSOutputRecord extends OutputRecord implements DTLSRecord { ProtocolVersion.valueOf(memo.majorVersion, memo.minorVersion)); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = dstBuf.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); diff --git a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java index e1c1b1377ad..a626f6f34d0 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java @@ -199,7 +199,7 @@ final class ECDHClientKeyExchange { ECDHClientKeyExchangeMessage cke = new ECDHClientKeyExchangeMessage( chc, sslPossession.encode()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ECDH ClientKeyExchange handshake message", cke); } @@ -308,7 +308,7 @@ final class ECDHClientKeyExchange { // parse either handshake message containing either EC/XEC. ECDHClientKeyExchangeMessage cke = new ECDHClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ECDH ClientKeyExchange handshake message", cke); } @@ -397,7 +397,7 @@ final class ECDHClientKeyExchange { new ECDHClientKeyExchangeMessage( chc, sslPossession.encode()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ECDHE ClientKeyExchange handshake message", cke); } @@ -490,7 +490,7 @@ final class ECDHClientKeyExchange { // parse the EC/XEC handshake message ECDHClientKeyExchangeMessage cke = new ECDHClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ECDHE ClientKeyExchange handshake message", cke); } diff --git a/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java index b31c0ba9cb9..a02a8438163 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/ECDHServerKeyExchange.java @@ -489,7 +489,7 @@ final class ECDHServerKeyExchange { ServerHandshakeContext shc = (ServerHandshakeContext)context; ECDHServerKeyExchangeMessage skem = new ECDHServerKeyExchangeMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ECDH ServerKeyExchange handshake message", skem); } @@ -522,7 +522,7 @@ final class ECDHServerKeyExchange { // AlgorithmConstraints are checked during decoding ECDHServerKeyExchangeMessage skem = new ECDHServerKeyExchangeMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ECDH ServerKeyExchange handshake message", skem); } diff --git a/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java b/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java index 580e1d416de..72b7c950374 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/ECPointFormatsExtension.java @@ -171,7 +171,7 @@ final class ECPointFormatsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_EC_POINT_FORMATS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable ec_point_formats extension"); } @@ -193,7 +193,7 @@ final class ECPointFormatsExtension { return extData; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Need no ec_point_formats extension"); } @@ -221,7 +221,7 @@ final class ECPointFormatsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_EC_POINT_FORMATS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable ec_point_formats extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java b/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java index d1975b5caa4..b5be927f0aa 100644 --- a/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java +++ b/src/java.base/share/classes/sun/security/ssl/EncryptedExtensions.java @@ -134,7 +134,7 @@ final class EncryptedExtensions { SSLHandshake.ENCRYPTED_EXTENSIONS, shc.negotiatedProtocol); eem.extensions.produce(shc, extTypes); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced EncryptedExtensions message", eem); } @@ -168,7 +168,7 @@ final class EncryptedExtensions { EncryptedExtensionsMessage eem = new EncryptedExtensionsMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming EncryptedExtensions handshake message", eem); } diff --git a/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java b/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java index ff4694c8c7c..6bacbfbd1d8 100644 --- a/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/ExtendedMasterSecretExtension.java @@ -119,7 +119,7 @@ final class ExtendedMasterSecretExtension { if (!chc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) || !SSLConfiguration.useExtendedMasterSecret || !chc.conContext.protocolVersion.useTLS10PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extended_master_secret extension"); } @@ -162,7 +162,7 @@ final class ExtendedMasterSecretExtension { if (!shc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) || !SSLConfiguration.useExtendedMasterSecret || !shc.negotiatedProtocol.useTLS10PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + CH_EXTENDED_MASTER_SECRET.name); } @@ -182,7 +182,7 @@ final class ExtendedMasterSecretExtension { // with a full handshake. shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption which did not use " + "Extended Master Secret extension"); @@ -213,7 +213,7 @@ final class ExtendedMasterSecretExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) || !SSLConfiguration.useExtendedMasterSecret) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + CH_EXTENDED_MASTER_SECRET.name); } @@ -252,7 +252,7 @@ final class ExtendedMasterSecretExtension { } else { // Otherwise, continue with a full handshake. shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "missing Extended Master Secret extension"); diff --git a/src/java.base/share/classes/sun/security/ssl/Finished.java b/src/java.base/share/classes/sun/security/ssl/Finished.java index 04fe61760d0..4238ced8f01 100644 --- a/src/java.base/share/classes/sun/security/ssl/Finished.java +++ b/src/java.base/share/classes/sun/security/ssl/Finished.java @@ -390,7 +390,7 @@ final class Finished { // Change write cipher and delivery ChangeCipherSpec message. ChangeCipherSpec.t10Producer.produce(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client Finished handshake message", fm); } @@ -453,7 +453,7 @@ final class Finished { // Change write cipher and delivery ChangeCipherSpec message. ChangeCipherSpec.t10Producer.produce(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server Finished handshake message", fm); } @@ -542,7 +542,7 @@ final class Finished { private void onConsumeFinished(ClientHandshakeContext chc, ByteBuffer message) throws IOException { FinishedMessage fm = new FinishedMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Finished handshake message", fm); } @@ -602,7 +602,7 @@ final class Finished { } FinishedMessage fm = new FinishedMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Finished handshake message", fm); } @@ -681,7 +681,7 @@ final class Finished { chc.handshakeHash.update(); FinishedMessage fm = new FinishedMessage(chc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client Finished handshake message", fm); } @@ -778,7 +778,7 @@ final class Finished { shc.handshakeHash.update(); FinishedMessage fm = new FinishedMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server Finished handshake message", fm); } @@ -930,7 +930,7 @@ final class Finished { } FinishedMessage fm = new FinishedMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Finished handshake message", fm); } @@ -1073,7 +1073,7 @@ final class Finished { } FinishedMessage fm = new FinishedMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Finished handshake message", fm); } diff --git a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java index 8455ddfc65d..a5f340d5203 100644 --- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java +++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java @@ -284,14 +284,14 @@ abstract class HandshakeContext implements ConnectionContext { found = true; break; } - } else if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "Ignore unsupported cipher suite: " + suite + " for " + protocol.name); } } - if (!found && (SSLLogger.isOn) && SSLLogger.isOn("handshake")) { + if (!found && (SSLLogger.isOn()) && SSLLogger.isOn("handshake")) { SSLLogger.fine( "No available cipher suite for " + protocol.name); } @@ -335,7 +335,7 @@ abstract class HandshakeContext implements ConnectionContext { } if (!isSupported && - SSLLogger.isOn && SSLLogger.isOn("verbose")) { + SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.finest( "Ignore unsupported cipher suite: " + suite); } @@ -556,7 +556,7 @@ abstract class HandshakeContext implements ConnectionContext { cachedStatus.put(groupType, groupAvailable); if (!groupAvailable && - SSLLogger.isOn && SSLLogger.isOn("verbose")) { + SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine( "No activated named group in " + groupType); } @@ -570,13 +570,13 @@ abstract class HandshakeContext implements ConnectionContext { } } - if (!retval && SSLLogger.isOn && SSLLogger.isOn("verbose")) { + if (!retval && SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("No active named group(s), ignore " + suite); } return retval; - } else if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("verbose")) { SSLLogger.fine("Ignore disabled cipher suite: " + suite); } diff --git a/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java b/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java index 61936442502..2a05881180d 100644 --- a/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java +++ b/src/java.base/share/classes/sun/security/ssl/HandshakeOutStream.java @@ -61,7 +61,7 @@ public class HandshakeOutStream extends ByteArrayOutputStream { if (!outputRecord.isClosed()) { outputRecord.encodeHandshake(buf, 0, count); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake messages", ByteBuffer.wrap(buf, 0, count)); } diff --git a/src/java.base/share/classes/sun/security/ssl/HelloRequest.java b/src/java.base/share/classes/sun/security/ssl/HelloRequest.java index f4da66b5dd3..39464992db5 100644 --- a/src/java.base/share/classes/sun/security/ssl/HelloRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/HelloRequest.java @@ -101,7 +101,7 @@ final class HelloRequest { ServerHandshakeContext shc = (ServerHandshakeContext)context; HelloRequestMessage hrm = new HelloRequestMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced HelloRequest handshake message", hrm); } @@ -137,7 +137,7 @@ final class HelloRequest { ServerHandshakeContext shc = (ServerHandshakeContext)context; HelloRequestMessage hrm = new HelloRequestMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced HelloRequest handshake message", hrm); } @@ -177,7 +177,7 @@ final class HelloRequest { // be sent by the server at any time. Please don't clean up this // handshake consumer. HelloRequestMessage hrm = new HelloRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming HelloRequest handshake message", hrm); } @@ -190,7 +190,7 @@ final class HelloRequest { } if (!chc.conContext.secureRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Continue with insecure renegotiation"); } @@ -206,7 +206,7 @@ final class HelloRequest { // SSLHandshake.CLIENT_HELLO.produce(context, hrm); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore HelloRequest, handshaking is in progress"); } diff --git a/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java b/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java index f28ae16de88..8d3f1048c91 100644 --- a/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/HelloVerifyRequest.java @@ -140,7 +140,7 @@ final class HelloVerifyRequest { HelloVerifyRequestMessage hvrm = new HelloVerifyRequestMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced HelloVerifyRequest handshake message", hvrm); } @@ -197,7 +197,7 @@ final class HelloVerifyRequest { HelloVerifyRequestMessage hvrm = new HelloVerifyRequestMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming HelloVerifyRequest handshake message", hvrm); } diff --git a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java index 98e4693e917..8d785f7515a 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java @@ -90,7 +90,7 @@ final class KeyShareExtension { Record.putInt16(m, namedGroupId); Record.putBytes16(m, keyExchange); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unlikely IOException", ioe); } @@ -222,7 +222,7 @@ final class KeyShareExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SSLExtension.CH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable key_share extension"); } @@ -237,7 +237,7 @@ final class KeyShareExtension { namedGroups = chc.clientRequestedNamedGroups; if (namedGroups == null || namedGroups.isEmpty()) { // No supported groups. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore key_share extension, no supported groups"); } @@ -287,7 +287,7 @@ final class KeyShareExtension { NamedGroup ng) { SSLKeyExchange ke = SSLKeyExchange.valueOf(ng); if (ke == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No key exchange for named group " + ng.name); } @@ -323,7 +323,7 @@ final class KeyShareExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (shc.handshakeExtensions.containsKey(SSLExtension.CH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "The key_share extension has been loaded"); } @@ -332,7 +332,7 @@ final class KeyShareExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.CH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable key_share extension"); } @@ -346,7 +346,7 @@ final class KeyShareExtension { NamedGroup ng = NamedGroup.valueOf(entry.namedGroupId); if (ng == null || !NamedGroup.isActivatable(shc.sslConfig, shc.algorithmConstraints, ng)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unsupported named group: " + @@ -364,7 +364,7 @@ final class KeyShareExtension { if (!shc.algorithmConstraints.permits( EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), namedGroupCredentials.getPublicKey())) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "key share entry of " + ng + " does not " + @@ -379,7 +379,7 @@ final class KeyShareExtension { credentials.add(kaCred); } } catch (GeneralSecurityException ex) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Cannot decode named group: " + NamedGroup.nameOf(entry.namedGroupId)); @@ -522,7 +522,7 @@ final class KeyShareExtension { SSLExtension.CH_KEY_SHARE); if (kss == null) { // Unlikely, no key_share extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no client key_share extension"); } @@ -531,7 +531,7 @@ final class KeyShareExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.SH_KEY_SHARE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no available server key_share extension"); } @@ -542,7 +542,7 @@ final class KeyShareExtension { if ((shc.handshakeCredentials == null) || shc.handshakeCredentials.isEmpty()) { // Unlikely, HelloRetryRequest should be used earlier. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No available client key share entries"); } @@ -562,7 +562,7 @@ final class KeyShareExtension { SSLKeyExchange ke = SSLKeyExchange.valueOf(ng); if (ke == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No key exchange for named group " + ng.name); } @@ -597,7 +597,7 @@ final class KeyShareExtension { if (keyShare == null) { // Unlikely, HelloRetryRequest should be used instead earlier. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No available server key_share extension"); } @@ -708,7 +708,7 @@ final class KeyShareExtension { ClientHandshakeContext chc = (ClientHandshakeContext)context; // Cannot use the previous requested key shares anymore. - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine( "No key_share extension in ServerHello, " + "cleanup the key shares if necessary"); @@ -801,7 +801,7 @@ final class KeyShareExtension { for (NamedGroup ng : shc.clientRequestedNamedGroups) { if (NamedGroup.isActivatable(shc.sslConfig, shc.algorithmConstraints, ng)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "HelloRetryRequest selected named group: " + ng.name); diff --git a/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java b/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java index 2b17c7406a3..4e5a9683079 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyUpdate.java @@ -191,7 +191,7 @@ final class KeyUpdate { // The consuming happens in client side only. PostHandshakeContext hc = (PostHandshakeContext)context; KeyUpdateMessage km = new KeyUpdateMessage(hc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming KeyUpdate post-handshake message", km); } @@ -235,7 +235,7 @@ final class KeyUpdate { rc.baseSecret = nplus1; hc.conContext.inputRecord.changeReadCiphers(rc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyUpdate: read key updated"); } } catch (GeneralSecurityException gse) { @@ -276,7 +276,7 @@ final class KeyUpdate { return null; } KeyUpdateMessage km = (KeyUpdateMessage)message; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced KeyUpdate post-handshake message", km); } @@ -328,7 +328,7 @@ final class KeyUpdate { // changeWriteCiphers() implementation. wc.baseSecret = nplus1; hc.conContext.outputRecord.changeWriteCiphers(wc, km.status.id); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyUpdate: write key updated"); } diff --git a/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java b/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java index a07e81be914..25500c7ac57 100644 --- a/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/MaxFragExtension.java @@ -176,7 +176,7 @@ final class MaxFragExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_MAX_FRAGMENT_LENGTH)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable max_fragment_length extension"); } @@ -213,7 +213,7 @@ final class MaxFragExtension { } else { // log and ignore, no MFL extension. chc.maxFragmentLength = -1; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No available max_fragment_length extension can " + "be used for fragment size of " + @@ -243,7 +243,7 @@ final class MaxFragExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (!shc.sslConfig.isAvailable(CH_MAX_FRAGMENT_LENGTH)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable max_fragment_length extension"); } @@ -288,7 +288,7 @@ final class MaxFragExtension { MaxFragLenSpec spec = (MaxFragLenSpec) shc.handshakeExtensions.get(CH_MAX_FRAGMENT_LENGTH); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable max_fragment_length extension"); } @@ -305,7 +305,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); @@ -413,7 +413,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); @@ -455,7 +455,7 @@ final class MaxFragExtension { MaxFragLenSpec spec = (MaxFragLenSpec) shc.handshakeExtensions.get(CH_MAX_FRAGMENT_LENGTH); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable max_fragment_length extension"); } @@ -472,7 +472,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); @@ -578,7 +578,7 @@ final class MaxFragExtension { // For better interoperability, abort the maximum // fragment length negotiation, rather than terminate // the connection with a fatal alert. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Abort the maximum fragment length negotiation, " + "may overflow the maximum packet size limit."); diff --git a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java index 46280a05355..0c708b194cb 100644 --- a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java +++ b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java @@ -273,7 +273,7 @@ enum NamedGroup { | NoSuchAlgorithmException exp) { if (namedGroupSpec != NamedGroupSpec.NAMED_GROUP_XDH) { mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No AlgorithmParameters for " + name, exp); } @@ -294,7 +294,7 @@ enum NamedGroup { // AlgorithmParameters.getInstance(name); } catch (NoSuchAlgorithmException nsae) { mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No AlgorithmParameters for " + name, nsae); } @@ -382,7 +382,7 @@ enum NamedGroup { for (String ss : namedGroups) { NamedGroup ng = NamedGroup.nameOf(ss); if (ng == null || !ng.isAvailable) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore the named group (" + ss @@ -811,7 +811,7 @@ enum NamedGroup { } if (groupList.isEmpty() && - SSLLogger.isOn && SSLLogger.isOn("ssl")) { + SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("No default named groups"); } } diff --git a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java index 4c879e0dc4d..89b0a72bb32 100644 --- a/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java +++ b/src/java.base/share/classes/sun/security/ssl/NewSessionTicket.java @@ -202,7 +202,7 @@ final class NewSessionTicket { this.ticket = Record.getBytes16(m); if (ticket.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No ticket in the NewSessionTicket handshake message"); } @@ -329,7 +329,7 @@ final class NewSessionTicket { if (hc instanceof ServerHandshakeContext) { // Is this session resumable? if (!hc.handshakeSession.isRejoinable()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "session is not resumable"); } @@ -347,7 +347,7 @@ final class NewSessionTicket { SSLExtension.PSK_KEY_EXCHANGE_MODES); if (pkemSpec == null || !pkemSpec.contains(PskKeyExchangeMode.PSK_DHE_KE)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "client does not support psk_dhe_ke"); } @@ -358,7 +358,7 @@ final class NewSessionTicket { // Check if we have sent a PSK already, then we know it is // using an allowable PSK exchange key mode. if (!hc.handshakeSession.isPSKable()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "No session ticket allowed in this session"); } @@ -372,7 +372,7 @@ final class NewSessionTicket { hc.sslContext.engineGetServerSessionContext(); int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session ticket produced: " + "session timeout is too long"); } @@ -459,7 +459,7 @@ final class NewSessionTicket { if (!nstm.isValid()) { hc.statelessResumption = false; } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced NewSessionTicket stateless " + "post-handshake message", nstm); } @@ -474,7 +474,7 @@ final class NewSessionTicket { sessionCache.getSessionTimeout(), hc.sslContext.getSecureRandom(), nonce, newId.getId()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced NewSessionTicket " + "post-handshake message", nstm); } @@ -488,7 +488,7 @@ final class NewSessionTicket { return nstm; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No NewSessionTicket created"); } @@ -526,7 +526,7 @@ final class NewSessionTicket { shc.sslContext.engineGetServerSessionContext(); int sessionTimeoutSeconds = sessionCache.getSessionTimeout(); if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session timeout is too long. No ticket sent."); } @@ -540,7 +540,7 @@ final class NewSessionTicket { NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(shc, sessionTimeoutSeconds, new SessionTicketSpec().encrypt(shc, sessionCopy)); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced NewSessionTicket stateless handshake message", nstm); @@ -579,7 +579,7 @@ final class NewSessionTicket { HandshakeContext hc = (HandshakeContext)context; NewSessionTicketMessage nstm = new T13NewSessionTicketMessage(hc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming NewSessionTicket message", nstm); } @@ -590,7 +590,7 @@ final class NewSessionTicket { // discard tickets with timeout 0 if (nstm.ticketLifetime <= 0 || nstm.ticketLifetime > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Discarding NewSessionTicket with lifetime " + nstm.ticketLifetime, nstm); @@ -599,7 +599,7 @@ final class NewSessionTicket { } if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session cache lifetime is too long. " + "Discarding ticket."); @@ -611,7 +611,7 @@ final class NewSessionTicket { SecretKey resumptionMasterSecret = sessionToSave.getResumptionMasterSecret(); if (resumptionMasterSecret == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session has no resumption master secret. " + "Ignoring ticket."); @@ -637,7 +637,7 @@ final class NewSessionTicket { sessionCopy.setPskIdentity(nstm.ticket); sessionCache.put(sessionCopy, sessionCopy.isPSK()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("MultiNST PSK (Server): " + Utilities.toHexString(Arrays.copyOf(nstm.ticket, 16))); } @@ -665,7 +665,7 @@ final class NewSessionTicket { NewSessionTicketMessage nstm = new T12NewSessionTicketMessage(hc, message); if (nstm.ticket.length == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("NewSessionTicket ticket was empty"); } return; @@ -674,7 +674,7 @@ final class NewSessionTicket { // discard tickets with timeout 0 if (nstm.ticketLifetime <= 0 || nstm.ticketLifetime > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Discarding NewSessionTicket with lifetime " + nstm.ticketLifetime, nstm); @@ -686,7 +686,7 @@ final class NewSessionTicket { hc.sslContext.engineGetClientSessionContext(); if (sessionCache.getSessionTimeout() > MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Session cache lifetime is too long. " + "Discarding ticket."); @@ -695,7 +695,7 @@ final class NewSessionTicket { } hc.handshakeSession.setPskIdentity(nstm.ticket); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming NewSessionTicket\n" + nstm); } } diff --git a/src/java.base/share/classes/sun/security/ssl/OutputRecord.java b/src/java.base/share/classes/sun/security/ssl/OutputRecord.java index f2c30b3ff72..416d5d1b5ef 100644 --- a/src/java.base/share/classes/sun/security/ssl/OutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/OutputRecord.java @@ -188,7 +188,7 @@ abstract class OutputRecord recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -222,7 +222,7 @@ abstract class OutputRecord recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "key_update handshake message"); } diff --git a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java index 819fdd589cb..b99c0175838 100644 --- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java @@ -341,7 +341,7 @@ final class PreSharedKeyExtension { ServerHandshakeContext shc = (ServerHandshakeContext)context; // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.CH_PRE_SHARED_KEY)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable pre_shared_key extension"); } @@ -393,7 +393,7 @@ final class PreSharedKeyExtension { } } if (b == null || s == null) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Stateless session ticket invalid"); @@ -402,7 +402,7 @@ final class PreSharedKeyExtension { } if (s != null && canRejoin(clientHello, shc, s)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Resuming session: ", s); } @@ -435,7 +435,7 @@ final class PreSharedKeyExtension { // Check protocol version if (result && s.getProtocolVersion() != shc.negotiatedProtocol) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, incorrect protocol version"); @@ -449,7 +449,7 @@ final class PreSharedKeyExtension { try { s.getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + @@ -466,7 +466,7 @@ final class PreSharedKeyExtension { if (result && !shc.localSupportedCertSignAlgs.containsAll(sessionSigAlgs)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Can't resume. Session uses different " + "signature algorithms"); } @@ -480,7 +480,7 @@ final class PreSharedKeyExtension { if (result && identityAlg != null) { String sessionIdentityAlg = s.getIdentificationProtocol(); if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, endpoint id" + @@ -494,7 +494,7 @@ final class PreSharedKeyExtension { // Ensure cipher suite can be negotiated if (result && (!shc.isNegotiable(s.getSuite()) || !clientHello.cipherSuites.contains(s.getSuite()))) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, unavailable session cipher suite"); @@ -653,7 +653,7 @@ final class PreSharedKeyExtension { // The producing happens in client side only. ClientHandshakeContext chc = (ClientHandshakeContext)context; if (!chc.isResumption || chc.resumingSession == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session to resume."); } return null; @@ -663,7 +663,7 @@ final class PreSharedKeyExtension { Collection sessionSigAlgs = chc.resumingSession.getLocalSupportedSignatureSchemes(); if (!chc.localSupportedCertSignAlgs.containsAll(sessionSigAlgs)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Existing session uses different " + "signature algorithms"); } @@ -673,7 +673,7 @@ final class PreSharedKeyExtension { // The session must have a pre-shared key SecretKey psk = chc.resumingSession.getPreSharedKey(); if (psk == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Existing session has no PSK."); } return null; @@ -687,7 +687,7 @@ final class PreSharedKeyExtension { } if (chc.pskIdentity == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "PSK has no identity, or identity was already used"); } @@ -699,7 +699,7 @@ final class PreSharedKeyExtension { chc.sslContext.engineGetClientSessionContext(); sessionCache.remove(chc.resumingSession.getSessionId(), true); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Found resumable session. Preparing PSK message."); SSLLogger.fine( @@ -836,7 +836,7 @@ final class PreSharedKeyExtension { public void absent(ConnectionContext context, HandshakeMessage message) throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Handling pre_shared_key absence."); } @@ -901,7 +901,7 @@ final class PreSharedKeyExtension { } SHPreSharedKeySpec shPsk = new SHPreSharedKeySpec(chc, buffer); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Received pre_shared_key extension: ", shPsk); } @@ -911,7 +911,7 @@ final class PreSharedKeyExtension { "Selected identity index is not in correct range."); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Resuming session: ", chc.resumingSession); } @@ -925,7 +925,7 @@ final class PreSharedKeyExtension { HandshakeMessage message) throws IOException { ClientHandshakeContext chc = (ClientHandshakeContext)context; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Handling pre_shared_key absence."); } diff --git a/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java b/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java index e510fe92b0e..c826d9c89e3 100644 --- a/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java +++ b/src/java.base/share/classes/sun/security/ssl/PredefinedDHParameterSpecs.java @@ -246,7 +246,7 @@ final class PredefinedDHParameterSpecs { Matcher spacesMatcher = spacesPattern.matcher(property); property = spacesMatcher.replaceAll(""); - if (SSLLogger.isOn && SSLLogger.isOn("sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("sslctx")) { SSLLogger.fine( "The Security Property " + PROPERTY_NAME + ": " + property); @@ -262,7 +262,7 @@ final class PredefinedDHParameterSpecs { String primeModulus = paramsFinder.group(1); BigInteger p = new BigInteger(primeModulus, 16); if (!p.isProbablePrime(PRIME_CERTAINTY)) { - if (SSLLogger.isOn && SSLLogger.isOn("sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("sslctx")) { SSLLogger.fine( "Prime modulus p in Security Property, " + PROPERTY_NAME + ", is not a prime: " + @@ -279,7 +279,7 @@ final class PredefinedDHParameterSpecs { DHParameterSpec spec = new DHParameterSpec(p, g); defaultParams.put(primeLen, spec); } - } else if (SSLLogger.isOn && SSLLogger.isOn("sslctx")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("sslctx")) { SSLLogger.fine("Invalid Security Property, " + PROPERTY_NAME + ", definition"); } diff --git a/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java b/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java index 07707c5163a..a4f343ccb06 100644 --- a/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/PskKeyExchangeModesExtension.java @@ -184,7 +184,7 @@ final class PskKeyExchangeModesExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.PSK_KEY_EXCHANGE_MODES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable psk_key_exchange_modes extension"); } @@ -216,7 +216,7 @@ final class PskKeyExchangeModesExtension { if (!spec.contains(PskKeyExchangeMode.PSK_DHE_KE)) { shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "no supported psk_dhe_ke PSK key exchange mode"); @@ -247,7 +247,7 @@ final class PskKeyExchangeModesExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.PSK_KEY_EXCHANGE_MODES)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unavailable psk_key_exchange_modes extension"); } @@ -287,7 +287,7 @@ final class PskKeyExchangeModesExtension { if (shc.isResumption) { // resumingSession may not be set shc.isResumption = false; shc.resumingSession = null; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "no supported psk_dhe_ke PSK key exchange mode"); diff --git a/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java index 893eb282116..7e307ba9d27 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicEngineOutputRecord.java @@ -75,14 +75,14 @@ final class QuicEngineOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } return; } if (level == Alert.Level.WARNING.level) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Suppressing warning-level " + "alert message: " + Alert.nameOf(description)); } @@ -90,7 +90,7 @@ final class QuicEngineOutputRecord extends OutputRecord implements SSLRecord { } if (alert != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Suppressing subsequent alert: " + description + ", original: " + alert.id); } @@ -109,7 +109,7 @@ final class QuicEngineOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); diff --git a/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java b/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java index fb9077af022..4613dcf96ff 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicKeyManager.java @@ -244,7 +244,7 @@ sealed abstract class QuicKeyManager if (toDiscard == null) { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding keys (keyphase=" + toDiscard.writeCipher.getKeyPhase() + ") of " + this.keySpace + " key space"); @@ -389,7 +389,7 @@ sealed abstract class QuicKeyManager if (toDiscard == null) { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding keys (keyphase=" + toDiscard.writeCipher.getKeyPhase() + ") of " + this.keySpace + " key space"); @@ -570,7 +570,7 @@ sealed abstract class QuicKeyManager if (series == null) { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding key (series) of " + this.keySpace + " key space"); } @@ -611,7 +611,7 @@ sealed abstract class QuicKeyManager if (series.canUseOldDecryptKey(packetNumber)) { final QuicReadCipher oldReadCipher = series.old; assert oldReadCipher != null : "old key is unexpectedly null"; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("using old read key to decrypt packet: " + packetNumber + ", with incoming key phase: " + keyPhase + ", current key phase: " + @@ -633,7 +633,7 @@ sealed abstract class QuicKeyManager // KEY_UPDATE_ERROR. This indicates that a peer has // received and acknowledged a packet that initiates a key // update, but has not updated keys in response. - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("peer used incorrect key, was" + " expected to use updated key of" + " key phase: " + currentKeyPhase + @@ -646,7 +646,7 @@ sealed abstract class QuicKeyManager } return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("detected ONE_RTT key update, current key " + "phase: " + currentKeyPhase + ", incoming key phase: " + keyPhase @@ -717,7 +717,7 @@ sealed abstract class QuicKeyManager } final long numEncrypted = cipher.getNumEncrypted(); if (numEncrypted >= 0.8 * confidentialityLimit) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("about to reach confidentiality limit, " + "attempting to initiate a 1-RTT key update," + " packet number: " + @@ -732,7 +732,7 @@ sealed abstract class QuicKeyManager : "key phase of updated key unexpectedly matches " + "the key phase " + cipher.getKeyPhase() + " of current keys"; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "1-RTT key update initiated, new key phase: " + newKeyPhase); @@ -755,7 +755,7 @@ sealed abstract class QuicKeyManager // current key phase. This ensures that keys are // available to both peers before // another key update can be initiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "skipping key update initiation because peer " + "hasn't yet sent us a packet encrypted with " + @@ -803,7 +803,7 @@ sealed abstract class QuicKeyManager // (we avoid timing attacks by not generating // keys during decryption, our key generation // only happens during encryption) - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("next keys unavailable," + " won't decrypt a packet which appears to be" + " a key update"); @@ -815,7 +815,7 @@ sealed abstract class QuicKeyManager // use the next keys to attempt decrypting currentKeySeries.next.readCipher.decryptPacket(packetNumber, packet, headerLength, output); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "decrypted using next keys for peer-initiated" + " key update; will now switch to new key phase: " + @@ -1025,14 +1025,14 @@ sealed abstract class QuicKeyManager // update the key series this.keySeries = newSeries; if (oldReadCipher != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "discarding old read key of key phase: " + oldReadCipher.getKeyPhase()); } oldReadCipher.discard(false); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("discarding write key of key phase: " + writeCipherToDiscard.getKeyPhase()); } diff --git a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java index 6765f554fcc..18790a58c11 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java @@ -560,7 +560,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { // incoming crypto buffer is null. Validate message type, // check if size is available byte messageType = payload.get(payload.position()); - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine("Received message of type 0x" + Integer.toHexString(messageType & 0xFF)); } @@ -835,7 +835,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { final boolean confirmed = HANDSHAKE_STATE_HANDLE.compareAndSet(this, NEED_SEND_HANDSHAKE_DONE, HANDSHAKE_CONFIRMED); if (confirmed) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine("QuicTLSEngine (server) marked handshake " + "state as HANDSHAKE_CONFIRMED"); } @@ -853,7 +853,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { final boolean confirmed = HANDSHAKE_STATE_HANDLE.compareAndSet(this, NEED_RECV_HANDSHAKE_DONE, HANDSHAKE_CONFIRMED); if (confirmed) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "QuicTLSEngine (client) received HANDSHAKE_DONE," + " marking state as HANDSHAKE_DONE"); diff --git a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java index 701ba35174e..ec91cb4509a 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java @@ -190,7 +190,7 @@ final class RSAClientKeyExchange { throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Cannot generate RSA premaster secret", gse); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced RSA ClientKeyExchange handshake message", ckem); } @@ -270,7 +270,7 @@ final class RSAClientKeyExchange { RSAClientKeyExchangeMessage ckem = new RSAClientKeyExchangeMessage(shc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming RSA ClientKeyExchange handshake message", ckem); } diff --git a/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java index 311ac97e744..d176d7311d0 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAKeyExchange.java @@ -35,7 +35,6 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.RSAPublicKey; -import java.security.spec.AlgorithmParameterSpec; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; @@ -150,7 +149,7 @@ final class RSAKeyExchange { needFailover = !KeyUtil.isOracleJCEProvider( cipher.getProvider().getName()); } catch (InvalidKeyException | UnsupportedOperationException iue) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("The Cipher provider " + safeProviderName(cipher) + " caused exception: " + iue.getMessage()); @@ -197,7 +196,7 @@ final class RSAKeyExchange { try { return cipher.getProvider().toString(); } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Retrieving The Cipher provider name" + " caused exception ", e); } @@ -205,7 +204,7 @@ final class RSAKeyExchange { try { return cipher.toString() + " (provider name not available)"; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Retrieving The Cipher name" + " caused exception ", e); } @@ -220,7 +219,7 @@ final class RSAKeyExchange { int clientVersion, int serverVersion, byte[] encodedSecret, SecureRandom generator) throws GeneralSecurityException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Generating a premaster secret"); } @@ -235,7 +234,7 @@ final class RSAKeyExchange { } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException iae) { // unlikely to happen, otherwise, must be a provider exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("RSA premaster secret generation error", iae); } diff --git a/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java index 8633b9458ce..43f2ef95ba8 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAServerKeyExchange.java @@ -264,7 +264,7 @@ final class RSAServerKeyExchange { RSAServerKeyExchangeMessage skem = new RSAServerKeyExchangeMessage( shc, x509Possession, rsaPossession); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced RSA ServerKeyExchange handshake message", skem); } @@ -296,7 +296,7 @@ final class RSAServerKeyExchange { RSAServerKeyExchangeMessage skem = new RSAServerKeyExchangeMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming RSA ServerKeyExchange handshake message", skem); } diff --git a/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java b/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java index e1348badd30..90b9e999925 100644 --- a/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/RenegoInfoExtension.java @@ -138,7 +138,7 @@ final class RenegoInfoExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_RENEGOTIATION_INFO)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable renegotiation_info extension"); } @@ -182,7 +182,7 @@ final class RenegoInfoExtension { return extData; } else { // not secure renegotiation if (HandshakeContext.allowUnsafeRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Using insecure renegotiation"); } @@ -216,7 +216,7 @@ final class RenegoInfoExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_RENEGOTIATION_INFO)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Ignore unavailable extension: " + CH_RENEGOTIATION_INFO.name); } @@ -280,7 +280,7 @@ final class RenegoInfoExtension { for (int id : clientHello.cipherSuiteIds) { if (id == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV.id) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Safe renegotiation, using the SCSV signaling"); } @@ -294,7 +294,7 @@ final class RenegoInfoExtension { "Failed to negotiate the use of secure renegotiation"); } // otherwise, allow legacy hello message - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Warning: No renegotiation " + "indication in ClientHello, allow legacy ClientHello"); } @@ -306,13 +306,13 @@ final class RenegoInfoExtension { "Inconsistent secure renegotiation indication"); } else { // renegotiation, not secure if (HandshakeContext.allowUnsafeRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Using insecure renegotiation"); } } else { // Unsafe renegotiation should have been aborted in // earlier processes. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Terminate insecure renegotiation"); } throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, @@ -345,7 +345,7 @@ final class RenegoInfoExtension { if (requestedSpec == null && !shc.conContext.secureRenegotiation) { // Ignore, no renegotiation_info extension or SCSV signaling // requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable renegotiation_info extension"); } @@ -354,7 +354,7 @@ final class RenegoInfoExtension { if (!shc.conContext.secureRenegotiation) { // Ignore, no secure renegotiation is negotiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No secure renegotiation has been negotiated"); } @@ -515,7 +515,7 @@ final class RenegoInfoExtension { "Failed to negotiate the use of secure renegotiation"); } // otherwise, allow legacy hello message - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Warning: No renegotiation " + "indication in ServerHello, allow legacy ServerHello"); } @@ -527,13 +527,13 @@ final class RenegoInfoExtension { "Inconsistent secure renegotiation indication"); } else { // renegotiation, not secure if (HandshakeContext.allowUnsafeRenegotiation) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Using insecure renegotiation"); } } else { // Unsafe renegotiation should have been aborted in // earlier processes. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Terminate insecure renegotiation"); } throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, diff --git a/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java b/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java index 1d5a4c4e73d..594766ea0fd 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmConstraints.java @@ -454,7 +454,7 @@ final class SSLAlgorithmConstraints implements AlgorithmConstraints { .equalsIgnoreCase(paramDigestAlg)); } catch (InvalidParameterSpecException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid AlgorithmParameters: " + parameters + "; Error: " + e.getMessage()); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java index 4a52a2ea583..5dfa5be3420 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java @@ -392,7 +392,7 @@ enum SSLCipher { if (values[1].contains(tag[0])) { index = 0; } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("jdk.tls.keyLimits: Unknown action: " + entry); } @@ -413,13 +413,13 @@ enum SSLCipher { "Length exceeded limits"); } } catch (NumberFormatException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("jdk.tls.keyLimits: " + e.getMessage() + ": " + entry); } continue; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("jdk.tls.keyLimits: entry = " + entry + ". " + values[0] + ":" + tag[index] + " = " + size); } @@ -468,7 +468,7 @@ enum SSLCipher { Cipher.getInstance(transformation); return true; } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Transformation " + transformation + " is" + " not available."); } @@ -860,7 +860,7 @@ enum SSLCipher { "JCE provider " + cipher.getProvider().getName(), sbe); } pt.position(pos); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -930,7 +930,7 @@ enum SSLCipher { authenticator.increaseSequenceNumber(); } - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.finest( "Padded plaintext before ENCRYPTION", bb.duplicate()); } @@ -1050,7 +1050,7 @@ enum SSLCipher { "JCE provider " + cipher.getProvider().getName(), sbe); } - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Padded plaintext after DECRYPTION", pt.duplicate().position(pos)); @@ -1182,7 +1182,7 @@ enum SSLCipher { int len = addPadding(bb, blockSize); bb.position(pos); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Padded plaintext before ENCRYPTION", bb.duplicate()); @@ -1326,7 +1326,7 @@ enum SSLCipher { "JCE provider " + cipher.getProvider().getName(), sbe); } - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine("Padded plaintext after DECRYPTION", pt.duplicate().position(pos)); } @@ -1478,7 +1478,7 @@ enum SSLCipher { int len = addPadding(bb, blockSize); bb.position(pos); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Padded plaintext before ENCRYPTION", bb.duplicate()); @@ -1650,7 +1650,7 @@ enum SSLCipher { pt.position(pos); pt.limit(pos + len); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -1737,7 +1737,7 @@ enum SSLCipher { // DON'T encrypt the nonce for AEAD mode. int len, pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); @@ -1823,7 +1823,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyLimit read side: algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -1932,7 +1932,7 @@ enum SSLCipher { contentType = pt.get(i); pt.limit(i); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -1984,7 +1984,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("KeyLimit write side: algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -2026,7 +2026,7 @@ enum SSLCipher { cipher.updateAAD(aad); int len, pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); @@ -2182,7 +2182,7 @@ enum SSLCipher { pt.position(pos); pt.limit(pos + len); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -2231,7 +2231,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -2273,7 +2273,7 @@ enum SSLCipher { // DON'T encrypt the nonce for AEAD mode. int pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); @@ -2450,7 +2450,7 @@ enum SSLCipher { contentType = pt.get(i); pt.limit(i); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext after DECRYPTION", pt.duplicate()); } @@ -2499,7 +2499,7 @@ enum SSLCipher { keyLimitCountdown = cipherLimits.getOrDefault( algorithm.toUpperCase(Locale.ENGLISH) + ":" + tag[0], 0L); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("algorithm = " + algorithm + ":" + tag[0] + "\ncountdown value = " + keyLimitCountdown); @@ -2541,7 +2541,7 @@ enum SSLCipher { cipher.updateAAD(aad); int pos = bb.position(); - if (SSLLogger.isOn && SSLLogger.isOn("plaintext")) { + if (SSLLogger.isOn() && SSLLogger.isOn("plaintext")) { SSLLogger.fine( "Plaintext before ENCRYPTION", bb.duplicate()); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java index 6d1834ad2b7..ace60e41af9 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java @@ -204,7 +204,7 @@ final class SSLConfiguration implements Cloneable { if (nstServerCount == null || nstServerCount < 0 || nstServerCount > 10) { serverNewSessionTicketCount = SERVER_NST_DEFAULT; - if (nstServerCount != null && SSLLogger.isOn && + if (nstServerCount != null && SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "jdk.tls.server.newSessionTicketCount defaults to " + @@ -213,7 +213,7 @@ final class SSLConfiguration implements Cloneable { } } else { serverNewSessionTicketCount = nstServerCount; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "jdk.tls.server.newSessionTicketCount set to " + serverNewSessionTicketCount); @@ -586,7 +586,7 @@ final class SSLConfiguration implements Cloneable { String property = System.getProperty(propertyName); // this method is called from class initializer; logging here // will occasionally pin threads and deadlock if called from a virtual thread - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx") + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx") && !Thread.currentThread().isVirtual()) { SSLLogger.fine( "System property " + propertyName + " is set to '" + @@ -615,7 +615,7 @@ final class SSLConfiguration implements Cloneable { if (scheme != null && scheme.isAvailable) { signatureSchemes.add(schemeName); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx") + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx") && !Thread.currentThread().isVirtual()) { SSLLogger.fine( "The current installed providers do not " + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java index d50a9a10b76..be324eb0949 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java @@ -104,11 +104,11 @@ public abstract class SSLContextImpl extends SSLContextSpi { * first connection to time out and fail. Make sure it is * primed and ready by getting some initial output from it. */ - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.finest("trigger seeding of SecureRandom"); } secureRandom.nextInt(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.finest("done seeding of SecureRandom"); } @@ -143,7 +143,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { return (X509ExtendedKeyManager)km; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.warning( "X509KeyManager passed to SSLContext.init(): need an " + "X509ExtendedKeyManager for SSLEngine use"); @@ -246,7 +246,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { contextLock.lock(); try { if (statusResponseManager == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.finest( "Initializing StatusResponseManager"); } @@ -383,7 +383,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { suite.name, null)) { suites.add(suite); isSupported = true; - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx,verbose")) { SSLLogger.fine( "Ignore disabled cipher suite: " + suite.name); @@ -392,7 +392,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { break; } - if (!isSupported && SSLLogger.isOn && + if (!isSupported && SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx,verbose")) { SSLLogger.finest( "Ignore unsupported cipher suite: " + suite); @@ -410,7 +410,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { String propertyName) { String property = System.getProperty(propertyName); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.fine( "System property " + propertyName + " is set to '" + property + "'"); @@ -437,7 +437,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { try { suite = CipherSuite.nameOf(cipherSuiteNames[i]); } catch (IllegalArgumentException iae) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.fine( "Unknown or unsupported cipher suite name: " + cipherSuiteNames[i]); @@ -449,7 +449,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { if (suite != null && suite.isAvailable()) { cipherSuites.add(suite); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx")) { SSLLogger.fine( "The current installed providers do not " + "support cipher suite: " + cipherSuiteNames[i]); @@ -907,7 +907,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { tmMediator = getTrustManagers(); } catch (Exception e) { reserved = e; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.warning( "Failed to load default trust managers", e); } @@ -919,7 +919,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { kmMediator = getKeyManagers(); } catch (Exception e) { reserved = e; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.warning( "Failed to load default key managers", e); } @@ -977,7 +977,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { String defaultKeyStore = props.get("keyStore"); String defaultKeyStoreType = props.get("keyStoreType"); String defaultKeyStoreProvider = props.get("keyStoreProvider"); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.fine("keyStore is : " + defaultKeyStore); SSLLogger.fine("keyStore type is : " + defaultKeyStoreType); @@ -1007,7 +1007,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { // Try to initialize key store. if ((defaultKeyStoreType.length()) != 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.finest("init keystore"); } if (defaultKeyStoreProvider.isEmpty()) { @@ -1030,7 +1030,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { /* * Try to initialize key manager. */ - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.fine("init keymanager of type " + KeyManagerFactory.getDefaultAlgorithm()); } @@ -1068,7 +1068,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { // exception object, which may be not garbage collection // friendly as 'reservedException' is a static filed. reserved = new KeyManagementException(e.getMessage()); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.warning( "Failed to load default SSLContext", e); } @@ -1097,7 +1097,7 @@ public abstract class SSLContextImpl extends SSLContextSpi { super.engineInit(DefaultManagersHolder.keyManagers, DefaultManagersHolder.trustManagers, null); } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,defaultctx")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,defaultctx")) { SSLLogger.fine("default context init failed: ", e); } throw e; diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java index 4b19f5a9d7b..5e23e6ee37b 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -330,7 +330,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { // application data may be discarded accordingly. As could // be an issue for some applications. This impact can be // mitigated by sending the last flight twice. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.finest("retransmit the last flight messages"); } @@ -397,7 +397,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { if ((conContext.handshakeContext == null) && !conContext.isOutboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger key update"); } beginHandshake(); @@ -419,7 +419,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { !conContext.isOutboundClosed() && !conContext.isInboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger NST"); } conContext.conSession.updateNST = false; @@ -612,7 +612,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { } catch (SSLException ssle) { // Need to discard invalid records for DTLS protocols. if (sslContext.isDTLS()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.finest("Discard invalid DTLS records", ssle); } @@ -780,7 +780,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing inbound of SSLEngine"); } @@ -819,7 +819,7 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport { return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing outbound of SSLEngine"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java index 1bfbd9f51bf..6e08fc71664 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineInputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -172,7 +172,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { return null; } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw read", packet); } @@ -209,7 +209,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { byte minorVersion = packet.get(); // pos: 2 int contentLen = Record.getInt16(packet); // pos: 3, 4 - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + @@ -388,7 +388,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { * error message, one that's treated as fatal by * clients (Otherwise we'll hang.) */ - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "Requested to negotiate unsupported SSLv2!"); } @@ -410,7 +410,7 @@ final class SSLEngineInputRecord extends InputRecord implements SSLRecord { ByteBuffer converted = convertToClientHello(packet); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine( "[Converted] ClientHello", converted); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java index 4a689a84d5f..1c8751e66fe 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineOutputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -73,7 +73,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { @Override void encodeAlert(byte level, byte description) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } @@ -91,7 +91,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { void encodeHandshake(byte[] source, int offset, int length) { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); @@ -138,7 +138,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { @Override void encodeChangeCipherSpec() { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -171,14 +171,14 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { ByteBuffer[] dsts, int dstsOffset, int dstsLength) throws IOException { if (isClosed) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data or cached messages"); } return null; } else if (isCloseWaiting) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "application data"); } @@ -193,7 +193,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { ByteBuffer destination) throws IOException { if (writeCipher.authenticator.seqNumOverflow()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); @@ -275,7 +275,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { destination.limit(destination.position()); destination.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.APPLICATION_DATA.name + @@ -288,7 +288,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { dstPos, dstLim, headerSize, protocolVersion); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = destination.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); @@ -317,7 +317,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { // // Please don't change the limit of the destination buffer. destination.put(SSLRecord.v2NoCipher); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", SSLRecord.v2NoCipher); } @@ -331,7 +331,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { // deliver the SSLv2 format ClientHello message // // Please don't change the limit of the destination buffer. - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { if (SSLLogger.isOn("record")) { SSLLogger.fine(Thread.currentThread().getName() + ", WRITE: SSLv2 ClientHello message" + @@ -525,7 +525,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { dstBuf.limit(dstBuf.position()); dstBuf.position(dstContent); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.nameOf(memo.contentType) + @@ -543,7 +543,7 @@ final class SSLEngineOutputRecord extends OutputRecord implements SSLRecord { memo.encodeCipher.dispose(); } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer temporary = dstBuf.duplicate(); temporary.limit(temporary.position()); temporary.position(dstPos); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java index fb0490d70f1..47a0d0b0e44 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java @@ -844,7 +844,7 @@ enum SSLExtension implements SSLStringizer { String property = System.getProperty(propertyName); // this method is called from class initializer; logging here // will occasionally pin threads and deadlock if called from a virtual thread - if (SSLLogger.isOn && SSLLogger.isOn("ssl,sslctx") + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,sslctx") && !Thread.currentThread().isVirtual()) { SSLLogger.fine( "System property " + propertyName + " is set to '" + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java index 5ad93cfc836..66f6293302e 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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 @@ -43,7 +43,7 @@ final class SSLExtensions { // Extension map for debug logging private final Map logMap = - SSLLogger.isOn ? new LinkedHashMap<>() : null; + SSLLogger.isOn() ? new LinkedHashMap<>() : null; SSLExtensions(HandshakeMessage handshakeMessage) { this.handshakeMessage = handshakeMessage; @@ -93,7 +93,7 @@ final class SSLExtensions { // However, the implementation of the limit is complicated // and inefficient, and may not worthy the maintenance. isSupported = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Received buggy supported_groups extension " + "in the ServerHello handshake message"); @@ -143,7 +143,7 @@ final class SSLExtensions { m.get(extData); logMap.put(extId, extData); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unknown or unsupported extension", toString(extId, extData)); @@ -171,7 +171,7 @@ final class SSLExtensions { for (SSLExtension extension : extensions) { if (context.negotiatedProtocol != null && !extension.isAvailable(context.negotiatedProtocol)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unsupported extension: " + extension.name); } @@ -181,7 +181,7 @@ final class SSLExtensions { if (!extMap.containsKey(extension)) { if (extension.onLoadAbsence != null) { extension.absentOnLoad(context, handshakeMessage); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + extension.name); } @@ -190,7 +190,7 @@ final class SSLExtensions { if (extension.onLoadConsumer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unsupported extension: " + extension.name); } @@ -200,7 +200,7 @@ final class SSLExtensions { ByteBuffer m = ByteBuffer.wrap(extMap.get(extension)); extension.consumeOnLoad(context, handshakeMessage, m); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consumed extension: " + extension.name); } } @@ -215,7 +215,7 @@ final class SSLExtensions { if (!extMap.containsKey(extension)) { if (extension.onTradeAbsence != null) { extension.absentOnTrade(context, handshakeMessage); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + extension.name); } @@ -223,7 +223,7 @@ final class SSLExtensions { } if (extension.onTradeConsumer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore impact of unsupported extension: " + extension.name); @@ -232,7 +232,7 @@ final class SSLExtensions { } extension.consumeOnTrade(context, handshakeMessage); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Populated with extension: " + extension.name); } } @@ -245,7 +245,7 @@ final class SSLExtensions { SSLExtension[] extensions) throws IOException { for (SSLExtension extension : extensions) { if (extMap.containsKey(extension)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore, duplicated extension: " + extension.name); @@ -254,7 +254,7 @@ final class SSLExtensions { } if (extension.networkProducer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no extension producer defined: " + extension.name); @@ -267,7 +267,7 @@ final class SSLExtensions { extMap.put(extension, encoded); encodedLength += encoded.length + 4; // extension_type (2) // extension_data length(2) - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { // The extension is not available in the context. SSLLogger.fine( "Ignore, context unavailable extension: " + @@ -284,7 +284,7 @@ final class SSLExtensions { SSLExtension[] extensions) throws IOException { for (SSLExtension extension : extensions) { if (extension.networkProducer == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore, no extension producer defined: " + extension.name); @@ -305,7 +305,7 @@ final class SSLExtensions { encodedLength += encoded.length + 4; // extension_type (2) // extension_data length(2) - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { // The extension is not available in the context. SSLLogger.fine( "Ignore, context unavailable extension: " + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLLogger.java b/src/java.base/share/classes/sun/security/ssl/SSLLogger.java index f55ab27d297..7fa6fbf91b5 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLLogger.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLLogger.java @@ -29,8 +29,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.lang.System.Logger; -import java.lang.System.Logger.Level; import java.nio.ByteBuffer; import java.security.cert.Certificate; import java.security.cert.Extension; @@ -41,6 +39,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; +import jdk.internal.vm.annotation.ForceInline; import sun.security.util.HexDumpEncoder; import sun.security.util.Debug; import sun.security.x509.*; @@ -58,10 +57,13 @@ import static sun.security.ssl.Utilities.LINE_SEP; * logging mechanisms. If the system property "javax.net.debug" is defined * and non-empty, a private debug logger implemented in this class is used. */ -public final class SSLLogger { +public final class SSLLogger implements System.Logger { private static final System.Logger logger; private static final String property; - public static final boolean isOn; + private static final boolean isOn; + + private final String loggerName; + private final boolean useCompactFormat; static { @@ -76,7 +78,7 @@ public final class SSLLogger { help(); } - logger = new SSLConsoleLogger("javax.net.ssl", p); + logger = new SSLLogger("javax.net.ssl", p); } isOn = true; } else { @@ -86,6 +88,105 @@ public final class SSLLogger { } } + private SSLLogger(String loggerName, String options) { + this.loggerName = loggerName; + options = options.toLowerCase(Locale.ENGLISH); + this.useCompactFormat = !options.contains("expand"); + } + + /** + * Return true if the "javax.net.debug" property contains the + * debug check points, or System.Logger is used. + */ + public static boolean isOn(String checkPoints) { + if (property == null) { // debugging is turned off + return false; + } else if (property.isEmpty()) { // use System.Logger + return true; + } // use provider logger + + String[] options = checkPoints.split(","); + for (String option : options) { + option = option.trim(); + if (!SSLLogger.hasOption(option)) { + return false; + } + } + + return true; + } + + @ForceInline + public static boolean isOn() { + return isOn; + } + + private static boolean hasOption(String option) { + option = option.toLowerCase(Locale.ENGLISH); + if (property.contains("all")) { + return true; + } else { + // remove first occurrence of "sslctx" since + // it interferes with search for "ssl" + String modified = property.replaceFirst("sslctx", ""); + if (modified.contains("ssl")) { + // don't enable data and plaintext options by default + if (!(option.equals("data") + || option.equals("packet") + || option.equals("plaintext"))) { + return true; + } + } + } + + return property.contains(option); + } + + public static void severe(String msg, Object... params) { + SSLLogger.log0(Level.ERROR, msg, params); + } + + public static void warning(String msg, Object... params) { + SSLLogger.log0(Level.WARNING, msg, params); + } + + public static void info(String msg, Object... params) { + SSLLogger.log0(Level.INFO, msg, params); + } + + public static void fine(String msg, Object... params) { + SSLLogger.log0(Level.DEBUG, msg, params); + } + + public static void finer(String msg, Object... params) { + SSLLogger.log0(Level.TRACE, msg, params); + } + + public static void finest(String msg, Object... params) { + SSLLogger.log0(Level.TRACE, msg, params); + } + + private static void log0(Level level, String msg, Object... params) { + if (logger != null && logger.isLoggable(level)) { + if (params == null || params.length == 0) { + logger.log(level, msg); + } else { + try { + String formatted = + SSLSimpleFormatter.formatParameters(params); + // use the customized log method for SSLLogger + if (logger instanceof SSLLogger) { + logger.log(level, msg, formatted); + } else { + logger.log(level, msg + ":" + LINE_SEP + formatted); + } + } catch (Exception exp) { + // ignore it, just for debugging. + } + } + } + } + private static void help() { System.err.println(); System.err.println("help print the help messages"); @@ -117,94 +218,6 @@ public final class SSLLogger { System.exit(0); } - /** - * Return true if the "javax.net.debug" property contains the - * debug check points, or System.Logger is used. - */ - public static boolean isOn(String checkPoints) { - if (property == null) { // debugging is turned off - return false; - } else if (property.isEmpty()) { // use System.Logger - return true; - } // use provider logger - - String[] options = checkPoints.split(","); - for (String option : options) { - option = option.trim(); - if (!SSLLogger.hasOption(option)) { - return false; - } - } - - return true; - } - - private static boolean hasOption(String option) { - option = option.toLowerCase(Locale.ENGLISH); - if (property.contains("all")) { - return true; - } else { - // remove first occurrence of "sslctx" since - // it interferes with search for "ssl" - String modified = property.replaceFirst("sslctx", ""); - if (modified.contains("ssl")) { - // don't enable data and plaintext options by default - if (!(option.equals("data") - || option.equals("packet") - || option.equals("plaintext"))) { - return true; - } - } - } - - return property.contains(option); - } - - public static void severe(String msg, Object... params) { - SSLLogger.log(Level.ERROR, msg, params); - } - - public static void warning(String msg, Object... params) { - SSLLogger.log(Level.WARNING, msg, params); - } - - public static void info(String msg, Object... params) { - SSLLogger.log(Level.INFO, msg, params); - } - - public static void fine(String msg, Object... params) { - SSLLogger.log(Level.DEBUG, msg, params); - } - - public static void finer(String msg, Object... params) { - SSLLogger.log(Level.TRACE, msg, params); - } - - public static void finest(String msg, Object... params) { - SSLLogger.log(Level.TRACE, msg, params); - } - - private static void log(Level level, String msg, Object... params) { - if (logger != null && logger.isLoggable(level)) { - if (params == null || params.length == 0) { - logger.log(level, msg); - } else { - try { - String formatted = - SSLSimpleFormatter.formatParameters(params); - // use the customized log method for SSLConsoleLogger - if (logger instanceof SSLConsoleLogger) { - logger.log(level, msg, formatted); - } else { - logger.log(level, msg + ":" + LINE_SEP + formatted); - } - } catch (Exception exp) { - // ignore it, just for debugging. - } - } - } - } - static String toString(Object... params) { try { return SSLSimpleFormatter.formatParameters(params); @@ -216,65 +229,55 @@ public final class SSLLogger { // Logs a warning message and always returns false. This method // can be used as an OR Predicate to add a log in a stream filter. public static boolean logWarning(String option, String s) { - if (SSLLogger.isOn && SSLLogger.isOn(option)) { + if (SSLLogger.isOn() && SSLLogger.isOn(option)) { SSLLogger.warning(s); } return false; } - private static class SSLConsoleLogger implements Logger { - private final String loggerName; - private final boolean useCompactFormat; + @Override + public String getName() { + return loggerName; + } - SSLConsoleLogger(String loggerName, String options) { - this.loggerName = loggerName; - options = options.toLowerCase(Locale.ENGLISH); - this.useCompactFormat = !options.contains("expand"); - } + @Override + public boolean isLoggable(Level level) { + return level != Level.OFF; + } - @Override - public String getName() { - return loggerName; - } - - @Override - public boolean isLoggable(Level level) { - return level != Level.OFF; - } - - @Override - public void log(Level level, - ResourceBundle rb, String message, Throwable thrwbl) { - if (isLoggable(level)) { - try { - String formatted = + @Override + public void log(Level level, + ResourceBundle rb, String message, Throwable thrwbl) { + if (isLoggable(level)) { + try { + String formatted = SSLSimpleFormatter.format(this, level, message, thrwbl); - System.err.write(formatted.getBytes(UTF_8)); - } catch (Exception exp) { - // ignore it, just for debugging. - } + System.err.write(formatted.getBytes(UTF_8)); + } catch (Exception exp) { + // ignore it, just for debugging. } } + } - @Override - public void log(Level level, - ResourceBundle rb, String message, Object... params) { - if (isLoggable(level)) { - try { - String formatted = + @Override + public void log(Level level, + ResourceBundle rb, String message, Object... params) { + if (isLoggable(level)) { + try { + String formatted = SSLSimpleFormatter.format(this, level, message, params); - System.err.write(formatted.getBytes(UTF_8)); - } catch (Exception exp) { - // ignore it, just for debugging. - } + System.err.write(formatted.getBytes(UTF_8)); + } catch (Exception exp) { + // ignore it, just for debugging. } } } private static class SSLSimpleFormatter { private static final String PATTERN = "yyyy-MM-dd kk:mm:ss.SSS z"; - private static final DateTimeFormatter dateTimeFormat = DateTimeFormatter.ofPattern(PATTERN, Locale.ENGLISH) - .withZone(ZoneId.systemDefault()); + private static final DateTimeFormatter dateTimeFormat = + DateTimeFormatter.ofPattern(PATTERN, Locale.ENGLISH) + .withZone(ZoneId.systemDefault()); private static final MessageFormat basicCertFormat = new MessageFormat( """ @@ -290,68 +293,68 @@ public final class SSLLogger { Locale.ENGLISH); private static final MessageFormat extendedCertFormat = - new MessageFormat( - """ - "version" : "v{0}", - "serial number" : "{1}", - "signature algorithm": "{2}", - "issuer" : "{3}", - "not before" : "{4}", - "not after" : "{5}", - "subject" : "{6}", - "subject public key" : "{7}", - "extensions" : [ - {8} - ] - """, - Locale.ENGLISH); + new MessageFormat( + """ + "version" : "v{0}", + "serial number" : "{1}", + "signature algorithm": "{2}", + "issuer" : "{3}", + "not before" : "{4}", + "not after" : "{5}", + "subject" : "{6}", + "subject public key" : "{7}", + "extensions" : [ + {8} + ] + """, + Locale.ENGLISH); private static final MessageFormat messageFormatNoParas = - new MessageFormat( - """ - '{' - "logger" : "{0}", - "level" : "{1}", - "thread id" : "{2}", - "thread name" : "{3}", - "time" : "{4}", - "caller" : "{5}", - "message" : "{6}" - '}' - """, - Locale.ENGLISH); + new MessageFormat( + """ + '{' + "logger" : "{0}", + "level" : "{1}", + "thread id" : "{2}", + "thread name" : "{3}", + "time" : "{4}", + "caller" : "{5}", + "message" : "{6}" + '}' + """, + Locale.ENGLISH); private static final MessageFormat messageCompactFormatNoParas = - new MessageFormat( - "{0}|{1}|{2}|{3}|{4}|{5}|{6}" + LINE_SEP, - Locale.ENGLISH); + new MessageFormat( + "{0}|{1}|{2}|{3}|{4}|{5}|{6}" + LINE_SEP, + Locale.ENGLISH); private static final MessageFormat messageFormatWithParas = - new MessageFormat( - """ - '{' - "logger" : "{0}", - "level" : "{1}", - "thread id" : "{2}", - "thread name" : "{3}", - "time" : "{4}", - "caller" : "{5}", - "message" : "{6}", - "specifics" : [ - {7} - ] - '}' - """, - Locale.ENGLISH); + new MessageFormat( + """ + '{' + "logger" : "{0}", + "level" : "{1}", + "thread id" : "{2}", + "thread name" : "{3}", + "time" : "{4}", + "caller" : "{5}", + "message" : "{6}", + "specifics" : [ + {7} + ] + '}' + """, + Locale.ENGLISH); private static final MessageFormat messageCompactFormatWithParas = - new MessageFormat( - """ - {0}|{1}|{2}|{3}|{4}|{5}|{6} ( - {7} - ) - """, - Locale.ENGLISH); + new MessageFormat( + """ + {0}|{1}|{2}|{3}|{4}|{5}|{6} ( + {7} + ) + """, + Locale.ENGLISH); private static final MessageFormat keyObjectFormat = new MessageFormat( """ @@ -364,8 +367,8 @@ public final class SSLLogger { // log message // log message // ... - private static String format(SSLConsoleLogger logger, Level level, - String message, Object ... parameters) { + private static String format(SSLLogger logger, Level level, + String message, Object... parameters) { if (parameters == null || parameters.length == 0) { Object[] messageFields = { @@ -394,9 +397,9 @@ public final class SSLLogger { formatCaller(), message, (logger.useCompactFormat ? - formatParameters(parameters) : - Utilities.indent(formatParameters(parameters))) - }; + formatParameters(parameters) : + Utilities.indent(formatParameters(parameters))) + }; if (logger.useCompactFormat) { return messageCompactFormatWithParas.format(messageFields); @@ -414,7 +417,7 @@ public final class SSLLogger { .findFirst().orElse("unknown caller")); } - private static String formatParameters(Object ... parameters) { + private static String formatParameters(Object... parameters) { StringBuilder builder = new StringBuilder(512); boolean isFirst = true; for (Object parameter : parameters) { @@ -425,21 +428,21 @@ public final class SSLLogger { } if (parameter instanceof Throwable) { - builder.append(formatThrowable((Throwable)parameter)); + builder.append(formatThrowable((Throwable) parameter)); } else if (parameter instanceof Certificate) { - builder.append(formatCertificate((Certificate)parameter)); + builder.append(formatCertificate((Certificate) parameter)); } else if (parameter instanceof ByteArrayInputStream) { builder.append(formatByteArrayInputStream( - (ByteArrayInputStream)parameter)); + (ByteArrayInputStream) parameter)); } else if (parameter instanceof ByteBuffer) { - builder.append(formatByteBuffer((ByteBuffer)parameter)); + builder.append(formatByteBuffer((ByteBuffer) parameter)); } else if (parameter instanceof byte[]) { builder.append(formatByteArrayInputStream( - new ByteArrayInputStream((byte[])parameter))); + new ByteArrayInputStream((byte[]) parameter))); } else if (parameter instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry mapParameter = - (Map.Entry)parameter; + (Map.Entry) parameter; builder.append(formatMapEntry(mapParameter)); } else { builder.append(formatObject(parameter)); @@ -462,7 +465,7 @@ public final class SSLLogger { Object[] fields = { "throwable", builder.toString() - }; + }; return keyObjectFormat.format(fields); } @@ -479,7 +482,7 @@ public final class SSLLogger { StringBuilder builder = new StringBuilder(512); try { X509CertImpl x509 = - X509CertImpl.toImpl((X509Certificate)certificate); + X509CertImpl.toImpl((X509Certificate) certificate); X509CertInfo certInfo = x509.getInfo(); CertificateExtensions certExts = certInfo.getExtensions(); if (certExts == null) { @@ -528,7 +531,7 @@ public final class SSLLogger { Object[] fields = { "certificate", builder.toString() - }; + }; return Utilities.indent(keyObjectFormat.format(fields)); } @@ -591,13 +594,13 @@ public final class SSLLogger { formatted = builder.toString(); } else if (value instanceof byte[]) { formatted = "\"" + key + "\": \"" + - Utilities.toHexString((byte[])value) + "\""; + Utilities.toHexString((byte[]) value) + "\""; } else if (value instanceof Byte) { formatted = "\"" + key + "\": \"" + - HexFormat.of().toHexDigits((byte)value) + "\""; + HexFormat.of().toHexDigits((byte) value) + "\""; } else { formatted = "\"" + key + "\": " + - "\"" + value.toString() + "\""; + "\"" + value.toString() + "\""; } return Utilities.indent(formatted); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java index db5887c5e8e..4de29b7570a 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.ProviderException; -import java.security.spec.AlgorithmParameterSpec; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import sun.security.internal.spec.TlsMasterSecretParameterSpec; @@ -152,7 +151,7 @@ enum SSLMasterKeyDerivation implements SSLKeyDerivationGenerator { // // For RSA premaster secrets, do not signal a protocol error // due to the Bleichenbacher attack. See comments further down. - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.fine("RSA master secret generation error.", iae); } throw new ProviderException(iae); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java index 4baa3304fee..f713f723ea0 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java @@ -339,7 +339,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { if (t < 0 || t > NewSessionTicket.MAX_TICKET_LIFETIME) { timeout = DEFAULT_SESSION_TIMEOUT; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout given " + "jdk.tls.server.sessionTicketTimeout: " + t + ". Set to default value " + timeout); @@ -349,7 +349,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { } } catch (NumberFormatException e) { setSessionTimeout(DEFAULT_SESSION_TIMEOUT); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout for " + "jdk.tls.server.sessionTicketTimeout: " + s + ". Set to default value " + timeout); @@ -363,7 +363,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { if (defaultCacheLimit >= 0) { return defaultCacheLimit; - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "invalid System Property javax.net.ssl.sessionCacheSize, " + "use the default session cache size (" + @@ -371,7 +371,7 @@ final class SSLSessionContextImpl implements SSLSessionContext { } } catch (Exception e) { // unlikely, log it for safe - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "the System Property javax.net.ssl.sessionCacheSize is " + "not available, use the default value (" + diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 1bf561c47e6..f3a4b964158 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -223,7 +223,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.identificationProtocol = hc.sslConfig.identificationProtocol; this.boundValues = new ConcurrentHashMap<>(); - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Session initialized: " + this); } } @@ -256,7 +256,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.maximumPacketSize = baseSession.maximumPacketSize; this.boundValues = baseSession.boundValues; - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Session initialized: " + this); } } @@ -455,7 +455,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { if (same) { this.localCerts = ((X509Possession) pos).popCerts; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,session")) { SSLLogger.fine("Restored " + len + " local certificates from session ticket" + " for algorithms " + Arrays.toString(certAlgs)); @@ -463,7 +463,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } else { this.localCerts = null; this.invalidated = true; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,session")) { SSLLogger.warning("Local certificates can not be restored " + "from session ticket " + "for algorithms " + Arrays.toString(certAlgs)); @@ -482,7 +482,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { // If there is no getMasterSecret with TLS1.2 or under, do not resume. if (!protocolVersion.useTLS13PlusSpec() && getMasterSecret().getEncoded() == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("No MasterSecret, cannot make stateless" + " ticket"); } @@ -490,7 +490,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } if (boundValues != null && boundValues.size() > 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("There are boundValues, cannot make" + " stateless ticket"); } @@ -862,7 +862,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { void setSuite(CipherSuite suite) { cipherSuite = suite; - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Negotiating session: " + this); } } @@ -1132,7 +1132,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { return; } invalidated = true; - if (SSLLogger.isOn && SSLLogger.isOn("session")) { + if (SSLLogger.isOn() && SSLLogger.isOn("session")) { SSLLogger.finest("Invalidated session: " + this); } for (SSLSessionImpl child : childSessions) { diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java index be95a09006f..ab01a9d85be 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -370,7 +370,7 @@ public final class SSLSocketImpl // start handshaking, if failed, the connection will be closed. ensureNegotiated(false); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("handshake")) { SSLLogger.severe("handshake failed", ioe); } @@ -573,7 +573,7 @@ public final class SSLSocketImpl return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("duplex close of SSLSocket"); } @@ -591,7 +591,7 @@ public final class SSLSocketImpl } } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("SSLSocket duplex close failed. Debug info only. Exception details:", ioe); } } finally { @@ -601,7 +601,7 @@ public final class SSLSocketImpl closeSocket(false); } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("SSLSocket close failed. Debug info only. Exception details:", ioe); } } finally { @@ -696,7 +696,7 @@ public final class SSLSocketImpl "close_notify message cannot be sent."); } else { super.shutdownOutput(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "SSLSocket output duplex close failed: " + "SO_LINGER timeout, " + @@ -717,7 +717,7 @@ public final class SSLSocketImpl // failed to send the close_notify message. // conContext.conSession.invalidate(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Invalidate the session: SO_LINGER timeout, " + "close_notify message cannot be sent."); @@ -832,7 +832,7 @@ public final class SSLSocketImpl return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close inbound of SSLSocket"); } @@ -868,7 +868,7 @@ public final class SSLSocketImpl return; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close outbound of SSLSocket"); } conContext.closeOutbound(); @@ -1027,7 +1027,7 @@ public final class SSLSocketImpl // filed is checked here, in case the closing process is // still in progress. if (hasDepleted) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("The input stream has been depleted"); } @@ -1048,7 +1048,7 @@ public final class SSLSocketImpl // Double check if the input stream has been depleted. if (hasDepleted) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("The input stream is closing"); } @@ -1134,7 +1134,7 @@ public final class SSLSocketImpl @Override public void close() throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing input stream"); } @@ -1142,7 +1142,7 @@ public final class SSLSocketImpl SSLSocketImpl.this.close(); } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("input stream close failed. Debug info only. Exception details:", ioe); } } @@ -1218,7 +1218,7 @@ public final class SSLSocketImpl socketInputRecord.deplete( conContext.isNegotiated && (getSoTimeout() > 0)); } catch (Exception ex) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "input stream close depletion failed", ex); } @@ -1327,7 +1327,7 @@ public final class SSLSocketImpl @Override public void close() throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("Closing output stream"); } @@ -1335,7 +1335,7 @@ public final class SSLSocketImpl SSLSocketImpl.this.close(); } catch (IOException ioe) { // ignore the exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("output stream close failed. Debug info only. Exception details:", ioe); } } @@ -1543,7 +1543,7 @@ public final class SSLSocketImpl if ((conContext.handshakeContext == null) && !conContext.isOutboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger key update"); } startHandshake(); @@ -1562,7 +1562,7 @@ public final class SSLSocketImpl !conContext.isOutboundClosed() && !conContext.isInboundClosed() && !conContext.isBroken) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("trigger new session ticket"); } conContext.conSession.updateNST = false; @@ -1670,7 +1670,7 @@ public final class SSLSocketImpl * This method never returns normally, it always throws an IOException. */ private void handleException(Exception cause) throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("handling exception", cause); } @@ -1747,7 +1747,7 @@ public final class SSLSocketImpl @Override public void shutdown() throws IOException { if (!isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close the underlying socket"); } @@ -1773,7 +1773,7 @@ public final class SSLSocketImpl } private void closeSocket(boolean selfInitiated) throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("close the SSL connection " + (selfInitiated ? "(initiative)" : "(passive)")); } @@ -1828,7 +1828,7 @@ public final class SSLSocketImpl * transport without waiting for the responding close_notify. */ private void waitForClose() throws IOException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("wait for close_notify or alert"); } @@ -1838,7 +1838,7 @@ public final class SSLSocketImpl try { Plaintext plainText = decode(null); // discard and continue - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( "discard plaintext while waiting for close", plainText); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java index 09223a4485d..ce7ab630730 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -210,7 +210,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { int contentLen = ((header[3] & 0xFF) << 8) + (header[4] & 0xFF); // pos: 3, 4 - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + @@ -243,7 +243,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { readFully(contentLen); recordBody.flip(); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "READ: " + ProtocolVersion.nameOf(majorVersion, minorVersion) + @@ -406,7 +406,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { */ os.write(SSLRecord.v2NoCipher); // SSLv2Hello - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { if (SSLLogger.isOn("record")) { SSLLogger.fine( "Requested to negotiate unsupported SSLv2!"); @@ -445,7 +445,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { ByteBuffer converted = convertToClientHello(recordBody); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine( "[Converted] ClientHello", converted); } @@ -488,13 +488,13 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { private static int read(InputStream is, byte[] buf, int off, int len) throws IOException { int readLen = is.read(buf, off, len); if (readLen < 0) { - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw read: EOF"); } throw new EOFException("SSL peer shut down incorrectly"); } - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { ByteBuffer bb = ByteBuffer.wrap(buf, off, readLen); SSLLogger.fine("Raw read", bb); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java index a7809754ed0..e83ad15db22 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketOutputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2025, 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 @@ -55,7 +55,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "alert message: " + Alert.nameOf(description)); } @@ -67,7 +67,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { write(level); write(description); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine("WRITE: " + protocolVersion.name + " " + ContentType.ALERT.name + "(" + Alert.nameOf(description) + ")" + @@ -81,7 +81,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -99,7 +99,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "handshake message", ByteBuffer.wrap(source, offset, length)); @@ -127,7 +127,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { int limit = v2ClientHello.limit(); handshakeHash.deliver(record, 2, (limit - 2)); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: SSLv2 ClientHello message" + ", length = " + limit); @@ -141,7 +141,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(record, 0, limit); deliverStream.flush(); - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(record, 0, limit))); } @@ -177,7 +177,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { return; } - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.HANDSHAKE.name + @@ -191,7 +191,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -212,7 +212,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { recordLock.lock(); try { if (isClosed()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound has closed, ignore outbound " + "change_cipher_spec message"); } @@ -231,7 +231,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException // deliverStream.flush(); // flush in Finished - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -257,7 +257,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { return; } - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.HANDSHAKE.name + @@ -271,7 +271,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } @@ -293,7 +293,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { } if (writeCipher.authenticator.seqNumOverflow()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "sequence number extremely close to overflow " + "(2^64-1 packets). Closing connection."); @@ -330,7 +330,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { count = position; write(source, offset, fragLen); - if (SSLLogger.isOn && SSLLogger.isOn("record")) { + if (SSLLogger.isOn() && SSLLogger.isOn("record")) { SSLLogger.fine( "WRITE: " + protocolVersion.name + " " + ContentType.APPLICATION_DATA.name + @@ -345,7 +345,7 @@ final class SSLSocketOutputRecord extends OutputRecord implements SSLRecord { deliverStream.write(buf, 0, count); // may throw IOException deliverStream.flush(); // may throw IOException - if (SSLLogger.isOn && SSLLogger.isOn("packet")) { + if (SSLLogger.isOn() && SSLLogger.isOn("packet")) { SSLLogger.fine("Raw write", (new ByteArrayInputStream(buf, 0, count))); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLTransport.java b/src/java.base/share/classes/sun/security/ssl/SSLTransport.java index c248b48fb0a..9298e016f63 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLTransport.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLTransport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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 @@ -113,7 +113,7 @@ interface SSLTransport { // Code to deliver SSLv2 error message for SSL/TLS connections. if (!context.sslContext.isDTLS()) { context.outputRecord.encodeV2NoCipher(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("may be talking to SSLv2"); } } @@ -161,7 +161,7 @@ interface SSLTransport { if (context.handshakeContext != null && context.handshakeContext.sslConfig.enableRetransmissions && context.sslContext.isDTLS()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.finest("retransmitted handshake flight"); } @@ -181,7 +181,7 @@ interface SSLTransport { // Note that JDK does not support 0-RTT yet. Otherwise, it is // needed to check early_data. if (!context.isNegotiated) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,verbose")) { SSLLogger.warning("unexpected application data " + "before handshake completion"); } diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHello.java b/src/java.base/share/classes/sun/security/ssl/ServerHello.java index 1d2faa5351f..76c266a628a 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -365,7 +365,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol); shm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ServerHello handshake message", shm); } @@ -440,7 +440,7 @@ final class ServerHello { } // The cipher suite has been negotiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("use cipher suite " + cs.name); } @@ -453,7 +453,7 @@ final class ServerHello { if (ke != null) { SSLPossession[] hcds = ke.createPossessions(shc); if ((hcds != null) && (hcds.length != 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "use legacy cipher suite " + cs.name); } @@ -570,7 +570,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol); shm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced ServerHello handshake message", shm); } @@ -723,14 +723,14 @@ final class ServerHello { } // The cipher suite has been negotiated. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("use cipher suite " + cs.name); } return cs; } if (legacySuite != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "use legacy cipher suite " + legacySuite.name); } @@ -783,7 +783,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.HELLO_RETRY_REQUEST, shc.negotiatedProtocol); hhrm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced HelloRetryRequest handshake message", hhrm); } @@ -845,7 +845,7 @@ final class ServerHello { shc.sslConfig.getEnabledExtensions( SSLHandshake.MESSAGE_HASH, shc.negotiatedProtocol); hhrm.extensions.produce(shc, serverHelloExtensions); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Reproduced HelloRetryRequest handshake message", hhrm); } @@ -886,7 +886,7 @@ final class ServerHello { } ServerHelloMessage shm = new ServerHelloMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Consuming ServerHello handshake message", shm); } @@ -931,7 +931,7 @@ final class ServerHello { } chc.negotiatedProtocol = serverVersion; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Negotiated protocol version: " + serverVersion.name); } @@ -986,7 +986,7 @@ final class ServerHello { chc.conContext.protocolVersion = chc.negotiatedProtocol; chc.conContext.outputRecord.setVersion(chc.negotiatedProtocol); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Negotiated protocol version: " + serverVersion.name); } @@ -1132,7 +1132,7 @@ final class ServerHello { chc.handshakeSession = new SSLSessionImpl(chc, chc.negotiatedCipherSuite, newId); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Locally assigned Session Id: " + newId.toString()); } @@ -1204,7 +1204,7 @@ final class ServerHello { private static void setUpPskKD(HandshakeContext hc, SecretKey psk) throws SSLHandshakeException { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Using PSK to derive early secret"); } diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java b/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java index 7136b36ffc2..a86257ab3d0 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHelloDone.java @@ -93,7 +93,7 @@ final class ServerHelloDone { ServerHandshakeContext shc = (ServerHandshakeContext)context; ServerHelloDoneMessage shdm = new ServerHelloDoneMessage(shc); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced ServerHelloDone handshake message", shdm); } @@ -147,7 +147,7 @@ final class ServerHelloDone { ServerHelloDoneMessage shdm = new ServerHelloDoneMessage(chc, message); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming ServerHelloDone handshake message", shdm); } diff --git a/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java b/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java index 96c3fe2fa6a..1b3c8ec3eba 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerNameExtension.java @@ -216,7 +216,7 @@ final class ServerNameExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_SERVER_NAME)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unavailable server_name extension"); } @@ -261,7 +261,7 @@ final class ServerNameExtension { return extData; } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("Unable to indicate server name"); } return null; @@ -287,7 +287,7 @@ final class ServerNameExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_SERVER_NAME)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + CH_SERVER_NAME.name); } @@ -305,7 +305,7 @@ final class ServerNameExtension { if (!shc.sslConfig.sniMatchers.isEmpty()) { sni = chooseSni(shc.sslConfig.sniMatchers, spec.serverNames); if (sni != null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "server name indication (" + sni + ") is accepted"); @@ -322,7 +322,7 @@ final class ServerNameExtension { // connection with a "missing_extension" alert. // // We do not reject client without SNI extension currently. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "no server name matchers, " + "ignore server name indication"); @@ -347,7 +347,7 @@ final class ServerNameExtension { // so don't include the pre-shared key in the // ServerHello handshake message shc.handshakeExtensions.remove(SH_PRE_SHARED_KEY); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "abort session resumption, " + "different server name indication used"); @@ -441,7 +441,7 @@ final class ServerNameExtension { CHServerNamesSpec spec = (CHServerNamesSpec) shc.handshakeExtensions.get(CH_SERVER_NAME); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable extension: " + SH_SERVER_NAME.name); } @@ -451,7 +451,7 @@ final class ServerNameExtension { // When resuming a session, the server MUST NOT include a // server_name extension in the server hello. if (shc.isResumption || shc.negotiatedServerName == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No expected server name indication response"); } @@ -528,7 +528,7 @@ final class ServerNameExtension { CHServerNamesSpec spec = (CHServerNamesSpec) shc.handshakeExtensions.get(CH_SERVER_NAME); if (spec == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "Ignore unavailable extension: " + EE_SERVER_NAME.name); } @@ -538,7 +538,7 @@ final class ServerNameExtension { // When resuming a session, the server MUST NOT include a // server_name extension in the server hello. if (shc.isResumption || shc.negotiatedServerName == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest( "No expected server name indication response"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java index 9a84bbad8fd..1a0283cf859 100644 --- a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java @@ -93,7 +93,7 @@ final class SessionTicketExtension { kt = Integer.parseInt(s) * 1000; // change to ms if (kt < 0 || kt > NewSessionTicket.MAX_TICKET_LIFETIME) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout for " + "jdk.tls.server.statelessKeyTimeout: " + kt + ". Set to default value " + @@ -103,7 +103,7 @@ final class SessionTicketExtension { } } catch (NumberFormatException e) { kt = TIMEOUT_DEFAULT; - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Invalid timeout for " + "jdk.tls.server.statelessKeyTimeout: " + s + ". Set to default value " + TIMEOUT_DEFAULT + @@ -252,7 +252,7 @@ final class SessionTicketExtension { Integer.BYTES + iv.length + 1, encrypted.length); return result; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Encryption failed." + e); } return new byte[0]; @@ -294,7 +294,7 @@ final class SessionTicketExtension { return out; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Decryption failed." + e); } } @@ -309,7 +309,7 @@ final class SessionTicketExtension { gos.write(input, 0, decompressedLen); gos.finish(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("decompressed bytes: " + decompressedLen + "; compressed bytes: " + baos.size()); } @@ -328,7 +328,7 @@ final class SessionTicketExtension { new ByteArrayInputStream(bytes))) { byte[] out = gis.readAllBytes(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("compressed bytes: " + compressedLen + "; decompressed bytes: " + out.length); } @@ -394,7 +394,7 @@ final class SessionTicketExtension { // If the context does not allow stateless tickets, exit if (!((SSLSessionContextImpl)chc.sslContext. engineGetClientSessionContext()).statelessEnabled()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Stateless resumption not supported"); } return null; @@ -406,7 +406,7 @@ final class SessionTicketExtension { if (!chc.isResumption || chc.resumingSession == null || chc.resumingSession.getPskIdentity() == null || chc.resumingSession.getProtocolVersion().useTLS13PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Stateless resumption supported"); } return new byte[0]; @@ -450,7 +450,7 @@ final class SessionTicketExtension { shc.statelessResumption = true; if (buffer.remaining() == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Client accepts session tickets."); } return; @@ -462,11 +462,11 @@ final class SessionTicketExtension { if (b != null) { shc.resumingSession = new SSLSessionImpl(shc, b); shc.isResumption = true; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Valid stateless session ticket found"); } } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Invalid stateless session ticket found"); } } @@ -546,7 +546,7 @@ final class SessionTicketExtension { // Disable stateless resumption if server doesn't send the extension. if (chc.statelessResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.info( "Server doesn't support stateless resumption"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java b/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java index b298da05e9a..dddeb523516 100644 --- a/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SignatureAlgorithmsExtension.java @@ -182,7 +182,7 @@ final class SignatureAlgorithmsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable signature_algorithms extension"); } @@ -218,7 +218,7 @@ final class SignatureAlgorithmsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable( SSLExtension.CH_SIGNATURE_ALGORITHMS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable signature_algorithms extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java b/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java index 5a8f103082b..b91fc17fd29 100644 --- a/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java +++ b/src/java.base/share/classes/sun/security/ssl/SignatureScheme.java @@ -204,7 +204,7 @@ enum SignatureScheme { NoSuchAlgorithmException | RuntimeException exp) { // Signature.getParameters() may throw RuntimeException. mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "RSASSA-PSS signature with " + hash + " is not supported by the underlying providers", exp); @@ -297,7 +297,7 @@ enum SignatureScheme { Signature.getInstance(algorithm); } catch (Exception e) { mediator = false; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Signature algorithm, " + algorithm + ", is not supported by the underlying providers"); @@ -432,7 +432,7 @@ enum SignatureScheme { for (SignatureScheme ss: schemesToCheck) { if (!ss.isAvailable) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore unsupported signature scheme: " + ss.name); @@ -451,12 +451,12 @@ enum SignatureScheme { if (isMatch) { if (ss.isPermitted(constraints, scopes)) { supported.add(ss); - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore disabled signature scheme: " + ss.name); } - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore inactive signature scheme: " + ss.name); @@ -476,7 +476,7 @@ enum SignatureScheme { for (int ssid : algorithmIds) { SignatureScheme ss = SignatureScheme.valueOf(ssid); if (ss == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unsupported signature scheme: " + SignatureScheme.nameOf(ssid)); @@ -486,7 +486,7 @@ enum SignatureScheme { && ss.isAllowed(constraints, protocolVersion, scopes)) { supported.add(ss); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unsupported signature scheme: " + ss.name); } @@ -545,7 +545,7 @@ enum SignatureScheme { } } - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore the signature algorithm (" + ss + @@ -574,7 +574,7 @@ enum SignatureScheme { } } - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore the legacy signature algorithm (" + ss + @@ -660,7 +660,7 @@ enum SignatureScheme { return signer; } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException nsae) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Ignore unsupported signature algorithm (" + diff --git a/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java b/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java index ec200c6e495..d8c4a8ccc3e 100644 --- a/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java +++ b/src/java.base/share/classes/sun/security/ssl/StatusResponseManager.java @@ -119,13 +119,13 @@ final class StatusResponseManager { if (cert.getExtensionValue( PKIXExtensions.OCSPNoCheck_Id.toString()) != null) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "OCSP NoCheck extension found. OCSP will be skipped"); } return null; } else if (defaultResponder != null && respOverride) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Responder override: URI is " + defaultResponder); } @@ -165,7 +165,7 @@ final class StatusResponseManager { Map responseMap = new HashMap<>(); List requestList = new ArrayList<>(); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Beginning check: Type = " + type + ", Chain length = " + chain.length); @@ -192,7 +192,7 @@ final class StatusResponseManager { requestList.add(new OCSPFetchCall(sInfo, ocspReq)); } } catch (IOException exc) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Exception during CertId creation: ", exc); } @@ -219,14 +219,14 @@ final class StatusResponseManager { requestList.add(new OCSPFetchCall(sInfo, ocspReq)); } } catch (IOException exc) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Exception during CertId creation: ", exc); } } } } else { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Unsupported status request type: " + type); } } @@ -257,7 +257,7 @@ final class StatusResponseManager { // that, otherwise just log the ExecutionException Throwable cause = Optional.ofNullable( exc.getCause()).orElse(exc); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Exception during OCSP fetch: " + cause); } @@ -266,13 +266,13 @@ final class StatusResponseManager { if (info != null && info.responseData != null) { responseMap.put(info.cert, info.responseData.ocspBytes); - } else if (SSLLogger.isOn && + } else if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Completed task had no response data"); } } else { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Found cancelled task"); } } @@ -280,7 +280,7 @@ final class StatusResponseManager { } catch (InterruptedException intex) { // Log and reset the interrupted state Thread.currentThread().interrupt(); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Interrupt occurred while fetching: " + intex); } @@ -308,7 +308,7 @@ final class StatusResponseManager { for (Extension ext : ocspRequest.extensions) { if (ext.getId().equals( PKIXExtensions.OCSPNonce_Id.toString())) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Nonce extension found, skipping cache check"); } @@ -323,14 +323,14 @@ final class StatusResponseManager { // and do not return it as a cache hit. if (respEntry != null && respEntry.nextUpdate != null && respEntry.nextUpdate.before(new Date())) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "nextUpdate threshold exceeded, purging from cache"); } respEntry = null; } - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Check cache for SN" + Debug.toString(cid.getSerialNumber()) + ": " + (respEntry != null ? "HIT" : "MISS")); @@ -493,7 +493,7 @@ final class StatusResponseManager { */ @Override public StatusInfo call() { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Starting fetch for SN " + Debug.toString(statInfo.cid.getSerialNumber())); @@ -505,13 +505,13 @@ final class StatusResponseManager { if (statInfo.responder == null) { // If we have no URI then there's nothing to do // but return. - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Null URI detected, OCSP fetch aborted"); } return statInfo; } else { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Attempting fetch from " + statInfo.responder); } @@ -541,7 +541,7 @@ final class StatusResponseManager { statInfo.cid); // Get the response status and act on it appropriately - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("OCSP Status: " + cacheEntry.status + " (" + respBytes.length + " bytes)"); } @@ -554,7 +554,7 @@ final class StatusResponseManager { addToCache(statInfo.cid, cacheEntry); } } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Caught exception: ", ioe); } } @@ -573,12 +573,12 @@ final class StatusResponseManager { // If no cache lifetime has been set on entries then // don't cache this response if there is no nextUpdate field if (entry.nextUpdate == null && cacheLifetime == 0) { - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine("Not caching this OCSP response"); } } else { responseCache.put(certId, entry); - if (SSLLogger.isOn && SSLLogger.isOn("respmgr")) { + if (SSLLogger.isOn() && SSLLogger.isOn("respmgr")) { SSLLogger.fine( "Added response for SN " + Debug.toString(certId.getSerialNumber()) + @@ -600,7 +600,7 @@ final class StatusResponseManager { // is necessary. Also, we will only staple if we're doing a full // handshake. if (!shc.sslContext.isStaplingEnabled(false) || shc.isResumption) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Staping disabled or is a resumed session"); } return null; @@ -623,7 +623,7 @@ final class StatusResponseManager { // selection yet, only accept a request if the ResponderId field // is empty. Finally, we'll only do this in (D)TLS 1.2 or earlier. if (statReqV2 != null && !shc.negotiatedProtocol.useTLS13PlusSpec()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.fine("SH Processing status_request_v2 extension"); } // RFC 6961 stapling @@ -660,7 +660,7 @@ final class StatusResponseManager { req = reqItems[ocspIdx]; type = CertStatusRequestType.valueOf(req.statusType); } else { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: No suitable request " + "found in the status_request_v2 extension."); @@ -678,7 +678,7 @@ final class StatusResponseManager { // we will try processing an asserted status_request. if ((statReq != null) && (ext == null || type == null || req == null)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.fine("SH Processing status_request extension"); } ext = SSLExtension.CH_STATUS_REQUEST; @@ -692,7 +692,7 @@ final class StatusResponseManager { if (ocspReq.responderIds.isEmpty()) { req = ocspReq; } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: No suitable request " + "found in the status_request extension."); } @@ -704,7 +704,7 @@ final class StatusResponseManager { // find a suitable StatusRequest, then stapling is disabled. // The ext, type and req variables must have been set to continue. if (type == null || req == null || ext == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No suitable status_request or " + "status_request_v2, stapling is disabled"); } @@ -721,7 +721,7 @@ final class StatusResponseManager { } if (x509Possession == null) { // unlikely - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: no X.509 certificates found. " + "Stapling is disabled."); } @@ -743,7 +743,7 @@ final class StatusResponseManager { responses = statRespMgr.get(fetchType, req, certs, shc.statusRespTimeout, TimeUnit.MILLISECONDS); if (!responses.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Response manager returned " + responses.size() + " entries."); } @@ -752,7 +752,7 @@ final class StatusResponseManager { if (type == CertStatusRequestType.OCSP) { byte[] respDER = responses.get(certs[0]); if (respDER == null || respDER.length == 0) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: Null or zero-length " + "response found for leaf certificate. " + @@ -763,7 +763,7 @@ final class StatusResponseManager { } params = new StaplingParameters(ext, type, req, responses); } else { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: no OCSP responses obtained. " + "Stapling is disabled."); } @@ -771,7 +771,7 @@ final class StatusResponseManager { } else { // This should not happen, but if lazy initialization of the // StatusResponseManager doesn't occur we should turn off stapling. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.finest("Warning: lazy initialization " + "of the StatusResponseManager failed. " + "Stapling is disabled."); diff --git a/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java index 6bf138f4e45..1fa2356d1de 100644 --- a/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java @@ -129,7 +129,7 @@ final class SunX509KeyManagerImpl extends X509KeyManagerCertChecking { X509Credentials cred = new X509Credentials((PrivateKey) key, (X509Certificate[]) certs); credentialsMap.put(alias, cred); - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("found key for : " + alias, (Object[]) certs); } } @@ -315,7 +315,7 @@ final class SunX509KeyManagerImpl extends X509KeyManagerCertChecking { } if (results == null) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("KeyMgr: no matching key found"); } return null; diff --git a/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java b/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java index 57e5f8c9093..67cb37988a1 100644 --- a/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SupportedGroupsExtension.java @@ -164,7 +164,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } @@ -177,7 +177,7 @@ final class SupportedGroupsExtension { for (String name : chc.sslConfig.namedGroups) { NamedGroup ng = NamedGroup.nameOf(name); if (ng == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unspecified named group: " + name); } @@ -193,14 +193,14 @@ final class SupportedGroupsExtension { ng.isSupported(chc.activeCipherSuites) && ng.isPermitted(chc.algorithmConstraints)) { namedGroups.add(ng); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore inactive or disabled named group: " + ng.name); } } if (namedGroups.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("no available named group"); } @@ -244,7 +244,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } @@ -319,7 +319,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } @@ -335,7 +335,7 @@ final class SupportedGroupsExtension { for (String name : shc.sslConfig.namedGroups) { NamedGroup ng = NamedGroup.nameOf(name); if (ng == null) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unspecified named group: " + name); @@ -352,14 +352,14 @@ final class SupportedGroupsExtension { ng.isSupported(shc.activeCipherSuites) && ng.isPermitted(shc.algorithmConstraints)) { namedGroups.add(ng); - } else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + } else if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore inactive or disabled named group: " + ng.name); } } if (namedGroups.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("no available named group"); } @@ -399,7 +399,7 @@ final class SupportedGroupsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(EE_SUPPORTED_GROUPS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable supported_groups extension"); } diff --git a/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java b/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java index 6efdcef0d29..58597c3008c 100644 --- a/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SupportedVersionsExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -168,7 +168,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(CH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + CH_SUPPORTED_VERSIONS.name); @@ -216,7 +216,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(CH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + CH_SUPPORTED_VERSIONS.name); @@ -308,7 +308,7 @@ final class SupportedVersionsExtension { shc.handshakeExtensions.get(CH_SUPPORTED_VERSIONS); if (svs == null) { // Unlikely, no key_share extension requested. - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Ignore unavailable supported_versions extension"); } @@ -317,7 +317,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SH_SUPPORTED_VERSIONS.name); @@ -356,7 +356,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(SH_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + SH_SUPPORTED_VERSIONS.name); @@ -399,7 +399,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(HRR_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + HRR_SUPPORTED_VERSIONS.name); @@ -441,7 +441,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!chc.sslConfig.isAvailable(HRR_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable extension: " + HRR_SUPPORTED_VERSIONS.name); @@ -483,7 +483,7 @@ final class SupportedVersionsExtension { // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(HRR_SUPPORTED_VERSIONS)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "[Reproduce] Ignore unavailable extension: " + HRR_SUPPORTED_VERSIONS.name); diff --git a/src/java.base/share/classes/sun/security/ssl/TransportContext.java b/src/java.base/share/classes/sun/security/ssl/TransportContext.java index 9595dc8b3fd..980d9c4a6ce 100644 --- a/src/java.base/share/classes/sun/security/ssl/TransportContext.java +++ b/src/java.base/share/classes/sun/security/ssl/TransportContext.java @@ -270,7 +270,7 @@ final class TransportContext implements ConnectionContext { try { outputRecord.encodeAlert(Alert.Level.WARNING.level, alert.id); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Warning: failed to send warning alert " + alert, ioe); } @@ -330,7 +330,7 @@ final class TransportContext implements ConnectionContext { // so we'll do it here. if (closeReason != null) { if (cause == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Closed transport, general or untracked problem"); } @@ -341,7 +341,7 @@ final class TransportContext implements ConnectionContext { if (cause instanceof SSLException) { throw (SSLException)cause; } else { // unlikely, but just in case. - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Closed transport, unexpected rethrowing", cause); } @@ -364,7 +364,7 @@ final class TransportContext implements ConnectionContext { } // shutdown the transport - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.severe("Fatal (" + alert + "): " + diagnostic, cause); } @@ -380,7 +380,7 @@ final class TransportContext implements ConnectionContext { try { inputRecord.close(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Fatal: input record closure failed", ioe); } @@ -411,7 +411,7 @@ final class TransportContext implements ConnectionContext { try { outputRecord.encodeAlert(Alert.Level.FATAL.level, alert.id); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Fatal: failed to send fatal alert " + alert, ioe); } @@ -424,7 +424,7 @@ final class TransportContext implements ConnectionContext { try { outputRecord.close(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Fatal: output record closure failed", ioe); } @@ -440,7 +440,7 @@ final class TransportContext implements ConnectionContext { try { transport.shutdown(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("Fatal: transport closure failed", ioe); } @@ -526,7 +526,7 @@ final class TransportContext implements ConnectionContext { passiveInboundClose(); } } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("inbound closure failed", ioe); } } @@ -583,7 +583,7 @@ final class TransportContext implements ConnectionContext { try { initiateOutboundClose(); } catch (IOException ioe) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning("outbound closure failed", ioe); } } diff --git a/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java b/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java index bac7735de07..8b89fecefa8 100644 --- a/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/TrustManagerFactoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, 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 @@ -48,24 +48,24 @@ abstract class TrustManagerFactoryImpl extends TrustManagerFactorySpi { trustManager = getInstance(TrustStoreManager.getTrustedCerts()); } catch (SecurityException se) { // eat security exceptions but report other throwables - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", se); } } catch (Error err) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", err); } throw err; } catch (RuntimeException re) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", re); } throw re; } catch (Exception e) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "SunX509: skip default keystore", e); } diff --git a/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java b/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java index e2c353847c7..a44f3a4e32d 100644 --- a/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java +++ b/src/java.base/share/classes/sun/security/ssl/TrustStoreManager.java @@ -108,7 +108,7 @@ final class TrustStoreManager { this.storeFile = storeFile; this.lastModified = lastModified; - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "trustStore is: " + storeName + "\n" + "trustStore type is: " + storeType + "\n" + @@ -151,7 +151,7 @@ final class TrustStoreManager { } // Not break, the file is inaccessible. - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "Inaccessible trust store: " + @@ -267,7 +267,7 @@ final class TrustStoreManager { } // Reload a new key store. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reload the trust store"); } @@ -321,7 +321,7 @@ final class TrustStoreManager { // Reload the trust store if needed. if (ks == null) { - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reload the trust store"); } ks = loadKeyStore(descriptor); @@ -329,12 +329,12 @@ final class TrustStoreManager { } // Reload trust certs from the key store. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reload trust certs"); } certs = loadTrustedCerts(ks); - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("Reloaded " + certs.size() + " trust certs"); } @@ -355,7 +355,7 @@ final class TrustStoreManager { descriptor.storeFile == null) { // No file available, no KeyStore available. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine("No available key store"); } @@ -382,7 +382,7 @@ final class TrustStoreManager { ks.load(bis, password); } catch (FileNotFoundException fnfe) { // No file available, no KeyStore available. - if (SSLLogger.isOn && SSLLogger.isOn("trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "Not available key store: " + descriptor.storeName); } diff --git a/src/java.base/share/classes/sun/security/ssl/Utilities.java b/src/java.base/share/classes/sun/security/ssl/Utilities.java index 50cd3bee751..458551b9d8a 100644 --- a/src/java.base/share/classes/sun/security/ssl/Utilities.java +++ b/src/java.base/share/classes/sun/security/ssl/Utilities.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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 @@ -70,7 +70,7 @@ final class Utilities { SNIServerName serverName = sniList.get(i); if (serverName.getType() == StandardConstants.SNI_HOST_NAME) { sniList.set(i, sniHostName); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "the previous server name in SNI (" + serverName + ") was replaced with (" + sniHostName + ")"); @@ -116,7 +116,7 @@ final class Utilities { return new SNIHostName(hostname); } catch (IllegalArgumentException iae) { // don't bother to handle illegal host_name - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine(hostname + "\" " + "is not a legal HostName for server name indication"); } diff --git a/src/java.base/share/classes/sun/security/ssl/X509Authentication.java b/src/java.base/share/classes/sun/security/ssl/X509Authentication.java index 5abc2cb1bf4..6aedff02c34 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509Authentication.java +++ b/src/java.base/share/classes/sun/security/ssl/X509Authentication.java @@ -201,7 +201,7 @@ enum X509Authentication implements SSLAuthentication { private static SSLPossession createClientPossession( ClientHandshakeContext chc, String[] keyTypes) { X509ExtendedKeyManager km = chc.sslContext.getX509KeyManager(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("X509KeyManager class: " + km.getClass().getName()); } @@ -243,7 +243,7 @@ enum X509Authentication implements SSLAuthentication { } if (clientAlias == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("No X.509 cert selected for " + Arrays.toString(keyTypes)); } @@ -252,7 +252,7 @@ enum X509Authentication implements SSLAuthentication { PrivateKey clientPrivateKey = km.getPrivateKey(clientAlias); if (clientPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( clientAlias + " is not a private key entry"); } @@ -261,7 +261,7 @@ enum X509Authentication implements SSLAuthentication { X509Certificate[] clientCerts = km.getCertificateChain(clientAlias); if ((clientCerts == null) || (clientCerts.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest(clientAlias + " is a private key entry with no cert chain stored"); } @@ -270,7 +270,7 @@ enum X509Authentication implements SSLAuthentication { String privateKeyAlgorithm = clientPrivateKey.getAlgorithm(); if (!Arrays.asList(keyTypes).contains(privateKeyAlgorithm)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( clientAlias + " private key algorithm " + privateKeyAlgorithm + " not in request list"); @@ -280,7 +280,7 @@ enum X509Authentication implements SSLAuthentication { String publicKeyAlgorithm = clientCerts[0].getPublicKey().getAlgorithm(); if (!privateKeyAlgorithm.equals(publicKeyAlgorithm)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( clientAlias + " private or public key is not of " + "same algorithm: " + @@ -296,7 +296,7 @@ enum X509Authentication implements SSLAuthentication { private static SSLPossession createServerPossession( ServerHandshakeContext shc, String[] keyTypes) { X509ExtendedKeyManager km = shc.sslContext.getX509KeyManager(); - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("X509KeyManager class: " + km.getClass().getName()); } @@ -337,7 +337,7 @@ enum X509Authentication implements SSLAuthentication { } if (serverAlias == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest("No X.509 cert selected for " + keyType); } continue; @@ -345,7 +345,7 @@ enum X509Authentication implements SSLAuthentication { PrivateKey serverPrivateKey = km.getPrivateKey(serverAlias); if (serverPrivateKey == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( serverAlias + " is not a private key entry"); } @@ -354,7 +354,7 @@ enum X509Authentication implements SSLAuthentication { X509Certificate[] serverCerts = km.getCertificateChain(serverAlias); if ((serverCerts == null) || (serverCerts.length == 0)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.finest( serverAlias + " is not a certificate entry"); } @@ -364,7 +364,7 @@ enum X509Authentication implements SSLAuthentication { PublicKey serverPublicKey = serverCerts[0].getPublicKey(); if ((!serverPrivateKey.getAlgorithm().equals(keyType)) || (!serverPublicKey.getAlgorithm().equals(keyType))) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( serverAlias + " private or public key is not of " + keyType + " algorithm"); @@ -379,7 +379,7 @@ enum X509Authentication implements SSLAuthentication { if (!shc.negotiatedProtocol.useTLS13PlusSpec() && keyType.equals("EC")) { if (!(serverPublicKey instanceof ECPublicKey)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning(serverAlias + " public key is not an instance of ECPublicKey"); } @@ -398,7 +398,7 @@ enum X509Authentication implements SSLAuthentication { ((shc.clientRequestedNamedGroups != null) && !shc.clientRequestedNamedGroups.contains(namedGroup))) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.warning( "Unsupported named group (" + namedGroup + ") used in the " + serverAlias + " certificate"); diff --git a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java index 9484ab4f830..2a1f01273bd 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java +++ b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java @@ -116,7 +116,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { } if (keyIndex == -1) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Ignore alias " + alias + ": key algorithm does not match"); } @@ -134,7 +134,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { } } if (!found) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine( "Ignore alias " + alias + ": issuers do not match"); @@ -150,7 +150,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { !conformsToAlgorithmConstraints(constraints, chain, checkType.getValidator())) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Ignore alias " + alias + ": certificate chain does not conform to " + "algorithm constraints"); @@ -219,7 +219,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { checker.init(false); } catch (CertPathValidatorException cpve) { // unlikely to happen - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine( "Cannot initialize algorithm constraints checker", cpve); @@ -235,7 +235,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { // We don't care about the unresolved critical extensions. checker.check(cert, Collections.emptySet()); } catch (CertPathValidatorException cpve) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Certificate does not conform to " + "algorithm constraints", cert, cpve); } @@ -392,7 +392,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { serverName.getEncoded()); } catch (IllegalArgumentException iae) { // unlikely to happen, just in case ... - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine("Illegal server name: " + serverName); @@ -408,7 +408,7 @@ abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { X509TrustManagerImpl.checkIdentity(hostname, cert, idAlgorithm); } catch (CertificateException e) { - if (SSLLogger.isOn && + if (SSLLogger.isOn() && SSLLogger.isOn("keymanager")) { SSLLogger.fine( "Certificate identity does not match " diff --git a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java index c607fe0f25d..72f079db175 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java @@ -228,7 +228,7 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { || (secondDot - firstDot < 2) || (alias.length() - secondDot < 2)) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.warning("Invalid alias format: " + alias); } return null; @@ -255,7 +255,7 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { NoSuchAlgorithmException | IndexOutOfBoundsException e) { // ignore and only log exception - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.warning("Exception thrown while getting an alias " + alias + ": " + e); } @@ -295,7 +295,7 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { if (results != null) { for (EntryStatus status : results) { if (status.checkResult == CheckResult.OK) { - if (SSLLogger.isOn + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("Choosing key: " + status); } @@ -312,13 +312,13 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { } } if (allResults == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("No matching key found"); } return null; } Collections.sort(allResults); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine( "No good matching key found, " + "returning best match out of", allResults); @@ -358,13 +358,13 @@ final class X509KeyManagerImpl extends X509KeyManagerCertChecking { } } if (allResults == null || allResults.isEmpty()) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("No matching alias found"); } return null; } Collections.sort(allResults); - if (SSLLogger.isOn && SSLLogger.isOn("ssl,keymanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,keymanager")) { SSLLogger.fine("Getting aliases", allResults); } return toAliases(allResults); diff --git a/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java index d82b94a1d7d..1bbe0bfb9c7 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/X509TrustManagerImpl.java @@ -81,7 +81,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager this.trustedCerts = trustedCerts; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("adding as trusted certificates", (Object[])trustedCerts.toArray(new X509Certificate[0])); } @@ -98,7 +98,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager trustedCerts = v.getTrustedCertificates(); serverValidator = v; - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("adding as trusted certificates", (Object[])trustedCerts.toArray(new X509Certificate[0])); } @@ -242,7 +242,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager null, checkClientTrusted ? null : authType); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Found trusted certificate", trustedChain[trustedChain.length - 1]); } @@ -288,7 +288,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager null, checkClientTrusted ? null : authType); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Found trusted certificate", trustedChain[trustedChain.length - 1]); } @@ -331,7 +331,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager null, checkClientTrusted ? null : authType); } - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Found trusted certificate", trustedChain[trustedChain.length - 1]); } @@ -365,7 +365,7 @@ final class X509TrustManagerImpl extends X509ExtendedTrustManager hostname = new SNIHostName(sniName.getEncoded()); } catch (IllegalArgumentException iae) { // unlikely to happen, just in case ... - if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl,trustmanager")) { SSLLogger.fine("Illegal server name: " + sniName); } } diff --git a/src/java.base/share/classes/sun/security/util/DomainName.java b/src/java.base/share/classes/sun/security/util/DomainName.java index 4f577f1114c..465c155ab87 100644 --- a/src/java.base/share/classes/sun/security/util/DomainName.java +++ b/src/java.base/share/classes/sun/security/util/DomainName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, 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 @@ -45,7 +45,6 @@ import java.util.zip.ZipInputStream; import static java.nio.charset.StandardCharsets.UTF_8; -import jdk.internal.util.StaticProperty; import sun.security.ssl.SSLLogger; /** @@ -193,7 +192,7 @@ class DomainName { } return getRules(tld, new ZipInputStream(pubSuffixStream)); } catch (IOException e) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine( "cannot parse public suffix data for " + tld + ": " + e.getMessage()); @@ -210,7 +209,7 @@ class DomainName { is = new FileInputStream(f); } catch (FileNotFoundException e) { } if (is == null) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl") && + if (SSLLogger.isOn() && SSLLogger.isOn("ssl") && SSLLogger.isOn("trustmanager")) { SSLLogger.fine( "lib/security/public_suffix_list.dat not found"); @@ -231,7 +230,7 @@ class DomainName { } } if (!found) { - if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + if (SSLLogger.isOn() && SSLLogger.isOn("ssl")) { SSLLogger.fine("Domain " + tld + " not found"); } return null; diff --git a/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/src/java.base/share/classes/sun/security/util/HostnameChecker.java index 1374bc6d535..65115c9aeaf 100644 --- a/src/java.base/share/classes/sun/security/util/HostnameChecker.java +++ b/src/java.base/share/classes/sun/security/util/HostnameChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2025, 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 @@ -271,7 +271,7 @@ public class HostnameChecker { name = IDN.toUnicode(IDN.toASCII(name)); template = IDN.toUnicode(IDN.toASCII(template)); } catch (RuntimeException re) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine("Failed to normalize to Unicode: " + re); } @@ -308,7 +308,7 @@ public class HostnameChecker { String template, boolean chainsToPublicCA) { // not ok if it is a single wildcard character or "*." if (template.equals("*") || template.equals("*.")) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "Certificate domain name has illegal single " + "wildcard character: " + template); @@ -328,7 +328,7 @@ public class HostnameChecker { // not ok if there is no dot after wildcard (ex: "*com") if (firstDotIndex == -1) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "Certificate domain name has illegal wildcard, " + "no dot after wildcard character: " + template); @@ -353,7 +353,7 @@ public class HostnameChecker { // Is it a top-level domain? if (wildcardedDomain.equalsIgnoreCase(templateDomainSuffix)) { - if (SSLLogger.isOn) { + if (SSLLogger.isOn()) { SSLLogger.fine( "Certificate domain name has illegal " + "wildcard for top-level public suffix: " + template); diff --git a/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java b/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java index fbd20e588d1..f32d0e381a6 100644 --- a/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java +++ b/test/jdk/sun/security/ssl/SSLLogger/DebugPropertyValuesTest.java @@ -23,7 +23,7 @@ /** * @test - * @bug 8350582 8340312 8369995 + * @bug 8350582 8340312 8369995 8372004 * @library /test/lib /javax/net/ssl/templates * @summary Correct the parsing of the ssl value in javax.net.debug * @run junit DebugPropertyValuesTest @@ -91,6 +91,7 @@ public class DebugPropertyValuesTest extends SSLSocketTemplate { List.of("FINE: adding as trusted certificates:" + System.lineSeparator() + " \"certificate\" : \\{", + "sun.security.ssl.SSLSocketImpl close", "FINE: Produced ClientHello handshake message:" + System.lineSeparator() + "\"ClientHello\": \\{", From 6fc8e4998019a2f3ef05ff3e73a4c855c0366d7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Thu, 20 Nov 2025 09:13:57 +0000 Subject: [PATCH 038/114] 8372097: C2: PhasePrintLevel requires setting PrintPhaseLevel explicitly to be active Reviewed-by: mhaessig, chagedorn --- src/hotspot/share/opto/c2_globals.hpp | 2 +- src/hotspot/share/opto/compile.cpp | 2 +- .../compiler/oracle/TestPhasePrintLevel.java | 110 ++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 0a4f231c49b..2b2b4db47b1 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -428,7 +428,7 @@ "0=print nothing except PhasePrintLevel directives, " \ "6=all details printed. " \ "Level of detail of printouts can be set on a per-method level " \ - "as well by using CompileCommand=PrintPhaseLevel.") \ + "as well by using CompileCommand=PhasePrintLevel.") \ range(-1, 6) \ \ develop(bool, PrintIdealGraph, false, \ diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 6babc13e1b3..89b5e36b120 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -5233,7 +5233,7 @@ void Compile::end_method() { #ifndef PRODUCT bool Compile::should_print_phase(const int level) const { - return PrintPhaseLevel > 0 && directive()->PhasePrintLevelOption >= level && + return PrintPhaseLevel >= 0 && directive()->PhasePrintLevelOption >= level && _method != nullptr; // Do not print phases for stubs. } diff --git a/test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java b/test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java new file mode 100644 index 00000000000..cf60c5c9be2 --- /dev/null +++ b/test/hotspot/jtreg/compiler/oracle/TestPhasePrintLevel.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025, 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 compiler.oracle; + +import java.util.ArrayList; +import java.util.List; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import compiler.lib.ir_framework.CompilePhase; + +/** + * @test + * @bug 8372097 + * @summary Checks that -XX:CompileCommand=PhasePrintLevel,... interacts with + * -XX:PrintPhaseLevel as expected. + * @library /test/lib / + * @requires vm.debug & vm.compiler2.enabled & vm.flagless + * @run driver compiler.oracle.TestPhasePrintLevel + */ + +public class TestPhasePrintLevel { + + static final String level1Phase = CompilePhase.FINAL_CODE.getName(); + static final String level2Phase = CompilePhase.GLOBAL_CODE_MOTION.getName(); + + public static void main(String[] args) throws Exception { + // Test flag level < 0: nothing should be printed regardless of the compile command level. + test(-1, -1, null, level1Phase); + test(-1, 0, null, level1Phase); + test(-1, 1, null, level1Phase); + + // Test flag level = 0: the compile command level should determine what is printed. + test(0, -1, null, level1Phase); + test(0, 0, null, level1Phase); + test(0, 1, level1Phase, null); + test(0, 2, level2Phase, null); + + // Test flag level > 0: the compile command level should take precedence. + test(1, -1, null, level1Phase); + test(1, 0, null, level1Phase); + test(1, 1, level1Phase, null); + test(2, 1, level1Phase, level2Phase); + test(1, 2, level2Phase, null); + } + + static void test(int flagLevel, int compileCommandLevel, String expectedPhase, String unexpectedPhase) throws Exception { + List options = new ArrayList(); + options.add("-Xbatch"); + options.add("-XX:CompileOnly=" + getTestName()); + options.add("-XX:PrintPhaseLevel=" + flagLevel); + options.add("-XX:CompileCommand=PhasePrintLevel," + getTestName() + "," + compileCommandLevel); + options.add(getTestClass()); + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0) + .shouldContain("CompileCommand: PhasePrintLevel compiler/oracle/TestPhasePrintLevel$TestMain.test intx PhasePrintLevel = " + compileCommandLevel) + .shouldNotContain("CompileCommand: An error occurred during parsing") + .shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + if (expectedPhase != null) { + oa.shouldContain(expectedPhase); + } + if (unexpectedPhase != null) { + oa.shouldNotContain(unexpectedPhase); + } + } + + static String getTestClass() { + return TestMain.class.getName(); + } + + static String getTestName() { + return getTestClass() + "::test"; + } + + static class TestMain { + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + test(i); + } + } + + static void test(int i) { + if ((i % 1000) == 0) { + System.out.println("Hello World!"); + } + } + } +} From b41146cd1e5d412f69b893bfb2fd65b6206bb0d2 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 20 Nov 2025 09:32:57 +0000 Subject: [PATCH 039/114] 8367531: Template Framework: use scopes and tokens instead of misbehaving immediate-return-queries Co-authored-by: Christian Hagedorn Reviewed-by: rcastanedalo, mhaessig, chagedorn --- .../arguments/TestMethodArguments.java | 4 +- .../jtreg/compiler/igvn/ExpressionFuzzer.java | 16 +- .../lib/template_framework/AddNameToken.java | 4 + .../lib/template_framework/CodeFrame.java | 123 +- .../lib/template_framework/DataName.java | 219 +- .../compiler/lib/template_framework/Hook.java | 92 +- .../template_framework/HookAnchorToken.java | 5 +- .../template_framework/HookInsertToken.java | 6 +- .../HookIsAnchoredToken.java | 37 + .../lib/template_framework/LetToken.java | 38 + .../template_framework/NameCountToken.java | 39 + .../template_framework/NameForEachToken.java | 41 + .../template_framework/NameHasAnyToken.java | 39 + .../template_framework/NameSampleToken.java | 43 + .../lib/template_framework/NameSet.java | 1 + .../template_framework/NamesToListToken.java | 41 + .../lib/template_framework/Renderer.java | 187 +- .../{TemplateBody.java => ScopeToken.java} | 10 +- .../template_framework/ScopeTokenImpl.java | 42 + ...othingToken.java => SetFuelCostToken.java} | 5 +- .../template_framework/StructuralName.java | 161 +- .../lib/template_framework/Template.java | 510 ++-- .../lib/template_framework/TemplateFrame.java | 111 +- .../lib/template_framework/TemplateToken.java | 10 +- .../lib/template_framework/Token.java | 29 +- .../lib/template_framework/TokenParser.java | 2 +- .../library/Expression.java | 4 +- .../library/PrimitiveType.java | 4 +- .../library/TestFrameworkClass.java | 10 +- .../superword/TestAliasingFuzzer.java | 66 +- .../examples/TestAdvanced.java | 6 +- .../examples/TestExpressions.java | 4 +- .../examples/TestPrimitiveTypes.java | 31 +- .../examples/TestSimple.java | 4 +- .../examples/TestTutorial.java | 836 +++++-- .../examples/TestWithTestFrameworkClass.java | 6 +- .../tests/TestExpression.java | 8 +- .../template_framework/tests/TestFormat.java | 6 +- .../tests/TestTemplate.java | 2207 ++++++++++++++--- 39 files changed, 3988 insertions(+), 1019 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java rename test/hotspot/jtreg/compiler/lib/template_framework/{TemplateBody.java => ScopeToken.java} (78%) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java rename test/hotspot/jtreg/compiler/lib/template_framework/{NothingToken.java => SetFuelCostToken.java} (89%) diff --git a/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java b/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java index 6ff830e85c6..306d0176aad 100644 --- a/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java +++ b/test/hotspot/jtreg/compiler/arguments/TestMethodArguments.java @@ -60,7 +60,7 @@ public class TestMethodArguments { : IntStream.range(0, numberOfArguments) .mapToObj(i -> "x" + i) .collect(Collectors.joining(" + ")); - return Template.make(() -> Template.body( + return Template.make(() -> Template.scope( Template.let("type", type.name()), Template.let("boxedType", type.boxedTypeName()), Template.let("arguments", arguments), @@ -115,7 +115,7 @@ public class TestMethodArguments { tests.add(generateTest(CodeGenerationDataNameType.longs(), i / 2).asToken()); tests.add(generateTest(CodeGenerationDataNameType.doubles(), i / 2).asToken()); } - return Template.make(() -> Template.body( + return Template.make(() -> Template.scope( Template.let("classpath", comp.getEscapedClassPathOfCompiledClasses()), """ import java.util.Arrays; diff --git a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java index 60b11e8ffbc..40bfb2e4319 100644 --- a/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java +++ b/test/hotspot/jtreg/compiler/igvn/ExpressionFuzzer.java @@ -45,7 +45,7 @@ import java.util.stream.IntStream; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; import compiler.lib.template_framework.library.CodeGenerationDataNameType; @@ -99,7 +99,7 @@ public class ExpressionFuzzer { // Create the body for the test. We use it twice: compiled and reference. // Execute the expression and catch expected Exceptions. - var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List arguments, String checksum) -> body( + var bodyTemplate = Template.make("expression", "arguments", "checksum", (Expression expression, List arguments, String checksum) -> scope( """ try { """, @@ -167,14 +167,14 @@ public class ExpressionFuzzer { default -> throw new RuntimeException("not handled: " + type.name()); }; StringPair cmp = cmps.get(RANDOM.nextInt(cmps.size())); - return body( + return scope( ", ", cmp.s0(), type.con(), cmp.s1() ); }); // Checksum method: returns not just the value, but also does some range / bit checks. // This gives us enhanced verification on the range / bits of the result type. - var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> body( + var checksumTemplate = Template.make("expression", "checksum", (Expression expression, String checksum) -> scope( let("returnType", expression.returnType), """ @ForceInline @@ -201,7 +201,7 @@ public class ExpressionFuzzer { // We need to prepare some random values to pass into the test method. We generate the values // once, and pass the same values into both the compiled and reference method. - var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body( + var valueTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope( "#type #name = ", (type instanceof PrimitiveType pt) ? pt.callLibraryRNG() : type.con(), ";\n" @@ -213,7 +213,7 @@ public class ExpressionFuzzer { // // To ensure that both the compiled and reference method use the same constraint, we put // the computation in a ForceInline method. - var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> body( + var constrainArgumentMethodTemplate = Template.make("name", "type", (String name, CodeGenerationDataNameType type) -> scope( """ @ForceInline public static #type constrain_#name(#type v) { @@ -247,7 +247,7 @@ public class ExpressionFuzzer { """ )); - var constrainArgumentTemplate = Template.make("name", (String name) -> body( + var constrainArgumentTemplate = Template.make("name", (String name) -> scope( """ #name = constrain_#name(#name); """ @@ -279,7 +279,7 @@ public class ExpressionFuzzer { } } } - return body( + return scope( let("methodArguments", methodArguments.stream().map(ma -> ma.name).collect(Collectors.joining(", "))), let("methodArgumentsWithTypes", diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java index 4f1f7e569bf..ceb1cc263fc 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/AddNameToken.java @@ -23,4 +23,8 @@ package compiler.lib.template_framework; +/** + * Represents the addition of the specified {@link Name} to the current scope, + * or an outer scope if the inner scope is transparent to {@link Name}s. + */ record AddNameToken(Name name) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java index 5c4ff55614f..765e9bc42ba 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/CodeFrame.java @@ -29,22 +29,96 @@ import java.util.ArrayList; import java.util.List; /** - * The {@link CodeFrame} represents a frame (i.e. scope) of code, appending {@link Code} to the {@code 'codeList'} + * The {@link CodeFrame} represents a frame (i.e. scope) of generated code by appending {@link Code} to the {@link #codeList} * as {@link Token}s are rendered, and adding names to the {@link NameSet}s with {@link Template#addStructuralName}/ - * {@link Template#addDataName}. {@link Hook}s can be added to a frame, which allows code to be inserted at that - * location later. When a {@link Hook} is {@link Hook#anchor}ed, it separates the Template into an outer and inner - * {@link CodeFrame}, ensuring that names that are added inside the inner frame are only available inside that frame. + * {@link Template#addDataName}. {@link Hook}s can be added to a code frame, which allows code to be inserted at that + * location later. * *

- * On the other hand, each {@link TemplateFrame} represents the frame (or scope) of exactly one use of a - * Template. + * The {@link CodeFrame} thus implements the {@link Name} non-transparency aspect of {@link ScopeToken}. * *

- * For simple Template nesting, the {@link CodeFrame}s and {@link TemplateFrame}s overlap exactly. - * However, when using {@link Hook#insert}, we simply nest {@link TemplateFrame}s, going further "in", - * but we jump to an outer {@link CodeFrame}, ensuring that we insert {@link Code} at the outer frame, - * and operating on the names of the outer frame. Once the {@link Hook#insert}ion is complete, we jump - * back to the caller {@link TemplateFrame} and {@link CodeFrame}. + * The {@link CodeFrame}s are nested relative to the order of the final rendered code. This can + * diverge from the nesting order of the {@link Template} when using {@link Hook#insert}, where + * the execution jumps from the current (caller) {@link CodeFrame} scope to the scope of the + * {@link Hook#anchor}. This ensures that the {@link Name}s of the anchor scope are accessed, + * and not the ones from the caller scope. Once the {@link Hook#insert}ion is complete, we + * jump back to the caller {@link CodeFrame}. + * + *

+ * Note, that {@link CodeFrame}s and {@link TemplateFrame}s often go together. But they do diverge when + * we call {@link Hook#insert}. On the {@link CodeFrame} side, the inserted scope is nested in the anchoring + * scope, so that the inserted scope has access to the Names of the anchoring scope, and not the caller + * scope. But the {@link TemplateFrame} of the inserted scope is nested in the caller scope, so + * that the inserted scope has access to hashtag replacements of the caller scope, and not the + * anchoring scope. + */ + +/* + * Below, we look at an example, and show the use of CodeFrames (c) and TemplateFrames (t). + * + * Explanations: + * - Generally, every scope has a CodeFrame and a TemplateFrame. There can be multiple + * scopes inside a Template, and so there can be multiple CodeFrames and TemplateFrames. + * In the drawing below, we draw the frames vertically, and give each a unique id. + * - When we nest scopes inside scopes, we create a new CodeFrame and a new TemplateFrame, + * and so they grow the same nested structure. Example: t3 is nested inside t2 and + * c3 is nested inside c2b. + * - The exception to this: + * - At a hook.anchor, there are two CodeFrames. The first one (e.g. c2a) we call the + * hook CodeFrame, it is kept empty until we insert code to the hook. The second + * (e.g. c2b) we call the inner CodeFrame of the anchoring, into which we keep + * generating the code that is inside the scope of the hook.anchor. + * - At a hook.insert, the TemplateFrame (e.g. t4) is nested into the caller (e.g. t3), + * while the CodeFrame (e.g. c4) is nested into the anchoring CodeFrame (e.g. c2a). + * + * Template( + * t1 c1 + * t1 c1 + * t1 c1 Anchoring Scope + * t1 c1 hook.anchor(scope( + * t1 c1 t2 c2a + * t1 c1 t2 c2a <------ CodeFrame nesting--------+ + * t1 c1 t2 c2a with generated code | + * t1 c1 t2 and Names | + * t1 c1 t2 ^ | + * t1 c1 t2 +- Two CodeFramees | + * t1 c1 t2 v | + * t1 c1 t2 | + * t1 c1 t2 c2b | + * t1 c1 t2 c2b | + * t1 c1 t2 c2b Caller Scope | + * t1 c1 t2 c2b ... scope( | + * t1 c1 t2 c2b ... t3 c3 | Insertion Scope + * t1 c1 t2 c2b ... t3 c3 | hook.insert(transparentScope( + * t1 c1 t2 c2b ... t3 c3 | t4 c4 + * t1 c1 t2 c2b ... t3 c3 +---- t4 ----c4 + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 <-- TemplateFrame nesting ---t4 c4 + * t1 c1 t2 c2b ... t3 c3 with hashtag t4 c4 // t: Concerns Template Frame + * t1 c1 t2 c2b ... t3 c3 and setFuelCost t4 c4 // c: Concerns Code Frame + * t1 c1 t2 c2b ... t3 c3 t4 c4 "use hashtag #x" -> t: hashtag queried in Insertion (t4) and Caller Scope (t3) + * t1 c1 t2 c2b ... t3 c3 t4 c4 c: code added to Anchoring Scope (c2a) + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 t4 c4 let("x", 42) -> t: hashtag definition escapes to Caller Scope (t3) because + * t1 c1 t2 c2b ... t3 c3 t4 c4 Insertion Scope is transparent + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 t4 c4 dataNames(...)...sample() -> c: sample from Insertion (c4) and Anchoring Scope (c2a) + * t1 c1 t2 c2b ... t3 c3 t4 c4 (CodeFrame nesting: c2a -> c4) + * t1 c1 t2 c2b ... t3 c3 t4 c4 addDataName(...) -> c: names escape to the Caller Scope (c3) because + * t1 c1 t2 c2b ... t3 c3 t4 c4 Insertion Scope is transparent + * t1 c1 t2 c2b ... t3 c3 t4 c4 + * t1 c1 t2 c2b ... t3 c3 )) + * t1 c1 t2 c2b ... t3 c3 + * t1 c1 t2 c2b ... t3 c3 + * t1 c1 t2 c2b ... ) + * t1 c1 t2 c2b + * t1 c1 t2 c2b + * t1 c1 )) + * t1 c1 + * t1 c1 + * ) + * */ class CodeFrame { public final CodeFrame parent; @@ -78,25 +152,16 @@ class CodeFrame { } /** - * Creates a normal frame, which has a {@link #parent} and which defines an inner - * {@link NameSet}, for the names that are generated inside this frame. Once this - * frame is exited, the name from inside this frame are not available anymore. + * Creates a normal frame, which has a {@link #parent}. It can either be + * transparent for names, meaning that names are added and accessed to and + * from an outer frame. Names that are added in a transparent frame are + * still available in the outer frames, as far out as the next non-transparent + * frame. If a frame is non-transparent, this frame defines an inner + * {@link NameSet}, for the names that are generated inside this frame. Once + * this frame is exited, the names from inside this frame are not available. */ - public static CodeFrame make(CodeFrame parent) { - return new CodeFrame(parent, false); - } - - /** - * Creates a special frame, which has a {@link #parent} but uses the {@link NameSet} - * from the parent frame, allowing {@link Template#addDataName}/ - * {@link Template#addStructuralName} to persist in the outer frame when the current frame - * is exited. This is necessary for {@link Hook#insert}, where we would possibly want to - * make field or variable definitions during the insertion that are not just local to the - * insertion but affect the {@link CodeFrame} that we {@link Hook#anchor} earlier and are - * now {@link Hook#insert}ing into. - */ - public static CodeFrame makeTransparentForNames(CodeFrame parent) { - return new CodeFrame(parent, true); + public static CodeFrame make(CodeFrame parent, boolean isTransparentForNames) { + return new CodeFrame(parent, isTransparentForNames); } void addString(String s) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java b/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java index f45a4db8a1e..4a82608567f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/DataName.java @@ -24,6 +24,7 @@ package compiler.lib.template_framework; import java.util.List; +import java.util.function.Function; /** * {@link DataName}s represent things like fields and local variables, and can be added to the local @@ -114,18 +115,36 @@ public record DataName(String name, DataName.Type type, boolean mutable, int wei this(mutability, null, null); } + // Wrap the FilteredSet as a Predicate. + private record DataNamePredicate(FilteredSet fs) implements NameSet.Predicate { + public boolean check(Name type) { + return fs.check(type); + } + public String toString() { + return fs.toString(); + } + } + NameSet.Predicate predicate() { if (subtype == null && supertype == null) { throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); } - return (Name name) -> { - if (!(name instanceof DataName dataName)) { return false; } - if (mutability == Mutability.MUTABLE && !dataName.mutable()) { return false; } - if (mutability == Mutability.IMMUTABLE && dataName.mutable()) { return false; } - if (subtype != null && !dataName.type().isSubtypeOf(subtype)) { return false; } - if (supertype != null && !supertype.isSubtypeOf(dataName.type())) { return false; } - return true; - }; + return new DataNamePredicate(this); + } + + boolean check(Name name) { + if (!(name instanceof DataName dataName)) { return false; } + if (mutability == Mutability.MUTABLE && !dataName.mutable()) { return false; } + if (mutability == Mutability.IMMUTABLE && dataName.mutable()) { return false; } + if (subtype != null && !dataName.type().isSubtypeOf(subtype)) { return false; } + if (supertype != null && !supertype.isSubtypeOf(dataName.type())) { return false; } + return true; + } + + public String toString() { + String msg1 = (subtype == null) ? "" : ", subtypeOf(" + subtype + ")"; + String msg2 = (supertype == null) ? "" : ", supertypeOf(" + supertype + ")"; + return "DataName.FilterdSet(" + mutability + msg1 + msg2 + ")"; } /** @@ -173,55 +192,179 @@ public record DataName(String name, DataName.Type type, boolean mutable, int wei /** * Samples a random {@link DataName} from the filtered set, according to the weights - * of the contained {@link DataName}s. + * of the contained {@link DataName}s, making the sampled {@link DataName} + * available to an inner scope. * - * @return The sampled {@link DataName}. - * @throws UnsupportedOperationException If the type was not constrained with either of - * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. - * @throws RendererException If the set was empty. - */ - public DataName sample() { - DataName n = (DataName)Renderer.getCurrent().sampleName(predicate()); - if (n == null) { - String msg1 = (subtype == null) ? "" : ", subtypeOf(" + subtype + ")"; - String msg2 = (supertype == null) ? "" : ", supertypeOf(" + supertype + ")"; - throw new RendererException("No variable: " + mutability + msg1 + msg2 + "."); - } - return n; - } - - /** - * Counts the number of {@link DataName}s in the filtered set. - * - * @return The number of {@link DataName}s in the filtered set. + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the sampled {@link DataName}. + * @return a token that represents the sampling and inner scope. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public int count() { - return Renderer.getCurrent().countNames(predicate()); + public Token sample(Function function) { + return new NameSampleToken<>(predicate(), null, null, function); } /** - * Checks if there are any {@link DataName}s in the filtered set. + * Samples a random {@link DataName} from the filtered set, according to the weights + * of the contained {@link DataName}s, and makes a hashtag replacement for both + * the name and type of the {@link DataName}, in the current scope. * - * @return Returns {@code true} iff there is at least one {@link DataName} in the filtered set. + *

+ * Note, that the following two do the equivalent: + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name", "type"), + * """ + * #name #type + * """ + * )); + * } + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> transparentScope( + * // The "let" hashtag definitions escape the "transparentScope". + * let("name", dn.name()), + * let("type", dn.type()) + * )), + * """ + * #name #type + * """ + * )); + * } + * + * @param name the key of the hashtag replacement for the {@link DataName} name. + * @param type the key of the hashtag replacement for the {@link DataName} type. + * @return a token that represents the sampling and hashtag replacement definition. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public boolean hasAny() { - return Renderer.getCurrent().hasAnyNames(predicate()); + public Token sampleAndLetAs(String name, String type) { + return new NameSampleToken(predicate(), name, type, n -> Template.transparentScope()); } /** - * Collects all {@link DataName}s in the filtered set. + * Samples a random {@link DataName} from the filtered set, according to the weights + * of the contained {@link DataName}s, and makes a hashtag replacement for the + * name of the {@link DataName}, in the current scope. * + *

+ * Note, that the following two do the equivalent: + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name"), + * """ + * #name + * """ + * )); + * } + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> scope( + * dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> transparentScope( + * // The "let" hashtag definition escape the "transparentScope". + * let("name", dn.name()) + * )), + * """ + * #name + * """ + * )); + * } + * + * @param name the key of the hashtag replacement for the {@link DataName} name. + * @return a token that represents the sampling and hashtag replacement definition. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token sampleAndLetAs(String name) { + return new NameSampleToken(predicate(), name, null, n -> Template.transparentScope()); + } + + /** + * Counts the number of {@link DataName}s in the filtered set, making the count + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the count. + * @return a token that represents the counting and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token count(Function function) { + return new NameCountToken(predicate(), function); + } + + /** + * Checks if there are any {@link DataName}s in the filtered set, making the resulting boolean + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the boolean indicating iff there are any {@link DataName}s in the filtered set. + * @return a token that represents the checking and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token hasAny(Function function) { + return new NameHasAnyToken(predicate(), function); + } + + /** + * Collects all {@link DataName}s in the filtered set, making the collected list + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the list of {@link DataName}. * @return A {@link List} of all {@link DataName}s in the filtered set. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public List toList() { - List list = Renderer.getCurrent().listNames(predicate()); - return list.stream().map(n -> (DataName)n).toList(); + public Token toList(Function, ScopeToken> function) { + return new NamesToListToken<>(predicate(), function); + } + + /** + * Calls the provided {@code function} for each {@link DataName}s in the filtered set, + * making each of these {@link DataName}s available to a separate inner scope. + * + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link DataName}s in the filtered set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(Function function) { + return new NameForEachToken<>(predicate(), null, null, function); + } + + /** + * Calls the provided {@code function} for each {@link DataName}s in the filtered set, + * making each of these {@link DataName}s available to a separate inner scope, and additionally + * setting hashtag replacements for the {@code name} and {@code type} of the respective + * {@link DataName}s. + * + *

+ * Note, to avoid duplication of the {@code name} and {@code type} + * hashtag replacements, the scope created by the provided {@code function} should be + * non-transparent to hashtag replacements, for example {@link Template#scope} or + * {@link Template#hashtagScope}. + * + * @param name the key of the hashtag replacement for each individual {@link DataName} name. + * @param type the key of the hashtag replacement for each individual {@link DataName} type. + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link DataName}s in the filtereds set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(String name, String type, Function function) { + return new NameForEachToken<>(predicate(), name, type, function); } } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java index 8ee2689eb2f..ef5a5df6ce0 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Hook.java @@ -23,59 +23,84 @@ package compiler.lib.template_framework; +import java.util.function.Function; + /** - * {@link Hook}s can be {@link #anchor}ed for a certain scope in a Template, and all nested - * Templates in this scope, and then from within this scope, any Template can - * {@link #insert} code to where the {@link Hook} was {@link #anchor}ed. This can be useful to reach - * "back" or to some outer scope, e.g. while generating code for a method, one can reach out - * to the class scope to insert fields. + * A {@link Hook} can be {@link #anchor}ed for a certain scope ({@link ScopeToken}), and that + * anchoring stays active for any nested scope or nested {@link Template}. With {@link #insert}, + * one can insert a template ({@link TemplateToken}) or scope ({@link ScopeToken}) to where the + * {@link Hook} was {@link #anchor}'ed. If the hook was anchored for multiple outer scopes, the + * innermost is chosen for insertion. * *

+ * This can be useful to reach "back" or to some outer scope, e.g. while generating code for a + * method, one can reach out to the class scope to insert fields. Or one may want to reach back + * to the beginning of a method to insert local variables that should be live for the whole method. + * + *

+ * The choice of {@link ScopeToken} is very important and powerful. + * For example, if you want to insert a {@link DataName} to the scope of an anchor, + * it is important that the scope of the insertion is transparent for {@link DataName}s, + * e.g. using {@link Template#transparentScope}. In most cases, we want {@link DataName}s to escape + * the inserted scope but not the anchor scope, so the anchor scope should be + * non-transparent for {@link DataName}s, e.g. using {@link Template#scope}. * Example: + * + *

* {@snippet lang=java : * var myHook = new Hook("MyHook"); * - * var template1 = Template.make("name", (String name) -> body( - * """ - * public static int #name = 42; - * """ - * )); - * - * var template2 = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * public class Test { * """, * // Anchor the hook here. - * myHook.anchor( + * myHook.anchor(scope( * """ * public static void main(String[] args) { * System.out.println("$field: " + $field) * """, - * // Reach out to where the hook was anchored, and insert the code of template1. - * myHook.insert(template1.asToken($("field"))), + * // Reach out to where the hook was anchored, and insert some code. + * myHook.insert(transparentScope( + * // The field (DataName) escapes because the inserted scope is "transparentScope" + * addDataName($("field"), Primitives.INTS, MUTABLE), + * """ + * public static int $field = 42; + * """ + * )), * """ * } * """ - * ), + * )), * """ * } * """ * )); * } * + *

+ * Note that if we use {@link #insert} with {@link Template#transparentScope}, then + * {@link DataName}s and {@link StructuralName}s escape from the inserted scope to the + * anchor scope, but hashtag replacements and {@link Template#setFuelCost} escape to + * the caller, i.e. from where we inserted the scope. This makes sense if we consider + * {@link DataName}s belonging to the structure of the generated code and the inserted + * scope belonging to the anchor scope. On the other hand, hashtag replacements and + * {@link Template#setFuelCost} rather belong to the code generation that happens + * within the context of a template. + * * @param name The name of the Hook, for debugging purposes only. */ public record Hook(String name) { /** - * Anchor this {@link Hook} for the scope of the provided {@code 'tokens'}. + * Anchor this {@link Hook} for the provided inner scope. * From anywhere inside this scope, even in nested Templates, code can be * {@link #insert}ed back to the location where this {@link Hook} was {@link #anchor}ed. * - * @param tokens A list of tokens, which have the same restrictions as {@link Template#body}. - * @return A {@link Token} that captures the anchoring of the scope and the list of validated {@link Token}s. + * @param innerScope An inner scope, for which the {@link Hook} is anchored. + * @return A {@link Token} that captures the anchoring and the inner scope. */ - public Token anchor(Object... tokens) { - return new HookAnchorToken(this, TokenParser.parse(tokens)); + public Token anchor(ScopeToken innerScope) { + return new HookAnchorToken(this, innerScope); } /** @@ -83,18 +108,31 @@ public record Hook(String name) { * This could be in the same Template, or one nested further out. * * @param templateToken The Template with applied arguments to be inserted at the {@link Hook}. - * @return The {@link Token} which when used inside a {@link Template#body} performs the code insertion into the {@link Hook}. + * @return The {@link Token} which represents the code insertion into the {@link Hook}. */ public Token insert(TemplateToken templateToken) { - return new HookInsertToken(this, templateToken); + return new HookInsertToken(this, Template.transparentScope(templateToken)); } /** - * Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + * Inserts a scope ({@link ScopeToken}) to the innermost location where this {@link Hook} was {@link #anchor}ed. + * This could be in the same Template, or one nested further out. * - * @return If the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope. + * @param scopeToken The scope to be inserted at the {@link Hook}. + * @return The {@link Token} which represents the code insertion into the {@link Hook}. */ - public boolean isAnchored() { - return Renderer.getCurrent().isAnchored(this); + public Token insert(ScopeToken scopeToken) { + return new HookInsertToken(this, scopeToken); + } + + /** + * Checks if the {@link Hook} was {@link Hook#anchor}ed for the current scope or an outer scope, + * and makes the boolean result available to an inner scope. + * + * @param function the function that generates the inner scope given the boolean result. + * @return the token that represents the check and inner scope. + */ + public Token isAnchored(Function function) { + return new HookIsAnchoredToken(this, function); } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java index b025c5ff041..4979365b3d0 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookAnchorToken.java @@ -25,4 +25,7 @@ package compiler.lib.template_framework; import java.util.List; -record HookAnchorToken(Hook hook, List tokens) implements Token {} +/** + * Represents the {@link Hook#anchor} with its inner scope. + */ +record HookAnchorToken(Hook hook, ScopeToken innerScope) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java index de8b60bbf24..a433d472a6e 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookInsertToken.java @@ -23,4 +23,8 @@ package compiler.lib.template_framework; -record HookInsertToken(Hook hook, TemplateToken templateToken) implements Token {} +/** + * Represents the {@link Hook#insert} with the {@link ScopeToken} of the + * scope that is to be inserted. + */ +record HookInsertToken(Hook hook, ScopeToken scopeToken) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java new file mode 100644 index 00000000000..5c7b92ec1fe --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/HookIsAnchoredToken.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents an {@link Hook#isAnchored} query with the function that creates an inner scope + * given the boolean answer. + */ +record HookIsAnchoredToken(Hook hook, Function function) implements Token { + + ScopeToken getScopeToken(boolean isAnchored) { + return function().apply(isAnchored); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java new file mode 100644 index 00000000000..ee18dd440b7 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/LetToken.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents a let (aka hashtag) definition. The hashtag replacement is active for the + * scope ({@link ScopeToken}) that the {@code function} creates, but can escape that + * scope if it is transparent to hashtags. + */ +record LetToken(String key, T value, Function function) implements Token { + + ScopeToken getScopeToken() { + return function().apply(value); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java new file mode 100644 index 00000000000..f0344efdd08 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameCountToken.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the counting of {@link Name}s, and the function that is called + * to create an inner scope given the count. + */ +record NameCountToken( + NameSet.Predicate predicate, + Function function) implements Token { + + ScopeToken getScopeToken(int count) { + return function().apply(count); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java new file mode 100644 index 00000000000..0e629740be1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameForEachToken.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the for-each execution of the provided function and (optional) hashtag replacement + * keys for name and type of each name. + */ +record NameForEachToken( + NameSet.Predicate predicate, + String name, + String type, + Function function) implements Token { + + ScopeToken getScopeToken(Name n) { + return function().apply((N)n); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java new file mode 100644 index 00000000000..a31990af210 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameHasAnyToken.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the check if there is any name and the function that is to + * be called given the boolean value (true iff there are any names). + */ +record NameHasAnyToken( + NameSet.Predicate predicate, + Function function) implements Token { + + ScopeToken getScopeToken(boolean hasAny) { + return function().apply(hasAny); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java new file mode 100644 index 00000000000..0b01f00fcd9 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameSampleToken.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.function.Function; + +/** + * Represents the sampling of {@link Name}s, and the function that is called given + * the sampled name, as well as the (optional) hashtag replacement keys for the + * name and type of the sampled name, which are then available in the inner scope + * created by the provided function. + */ +record NameSampleToken( + NameSet.Predicate predicate, + String name, + String type, + Function function) implements Token { + + ScopeToken getScopeToken(Name n) { + return function().apply((N)n); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java index ef79c33d48a..403dbdc694f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NameSet.java @@ -43,6 +43,7 @@ class NameSet { interface Predicate { boolean check(Name type); + String toString(); // used when sampling fails. } NameSet(NameSet parent) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java new file mode 100644 index 00000000000..40710a01297 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/NamesToListToken.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.function.Function; +import java.util.List; + +/** + * Represents the {@code toList} on a filtered name set, including the collection of the + * names and the creation of the inner scope with the function. + */ +record NamesToListToken( + NameSet.Predicate predicate, + Function, ScopeToken> function) implements Token { + + ScopeToken getScopeToken(List names) { + List castNames = names.stream().map(n -> (N)n).toList(); + return function().apply(castNames); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java index 14adfc81d3f..61ab9ab343c 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Renderer.java @@ -76,7 +76,7 @@ final class Renderer { *

* When using nested templates, the user of the Template Framework may be tempted to first render * the nested template to a {@link String}, and then use this {@link String} as a token in an outer - * {@link Template#body}. This would be a bad pattern: the outer and nested {@link Template} would + * {@link Template#scope}. This would be a bad pattern: the outer and nested {@link Template} would * be rendered separately, and could not interact. For example, the nested {@link Template} would * not have access to the scopes of the outer {@link Template}. The inner {@link Template} could * not access {@link Name}s and {@link Hook}s from the outer {@link Template}. The user might assume @@ -84,8 +84,8 @@ final class Renderer { * be separated. This could lead to unexpected behavior or even bugs. * *

- * Instead, the user should create a {@link TemplateToken} from the inner {@link Template}, and - * use that {@link TemplateToken} in the {@link Template#body} of the outer {@link Template}. + * Instead, the user must create a {@link TemplateToken} from the inner {@link Template}, and + * use that {@link TemplateToken} in the {@link Template#scope} of the outer {@link Template}. * This way, the inner and outer {@link Template}s get rendered together, and the inner {@link Template} * has access to the {@link Name}s and {@link Hook}s of the outer {@link Template}. * @@ -113,7 +113,7 @@ final class Renderer { static Renderer getCurrent() { if (renderer == null) { - throw new RendererException("A Template method such as '$', 'let', 'sample', 'count' etc. was called outside a template rendering."); + throw new RendererException("A Template method such as '$', 'fuel', etc. was called outside a template rendering call."); } return renderer; } @@ -171,26 +171,6 @@ final class Renderer { return currentTemplateFrame.fuel; } - void setFuelCost(float fuelCost) { - currentTemplateFrame.setFuelCost(fuelCost); - } - - Name sampleName(NameSet.Predicate predicate) { - return currentCodeFrame.sampleName(predicate); - } - - int countNames(NameSet.Predicate predicate) { - return currentCodeFrame.countNames(predicate); - } - - boolean hasAnyNames(NameSet.Predicate predicate) { - return currentCodeFrame.hasAnyNames(predicate); - } - - List listNames(NameSet.Predicate predicate) { - return currentCodeFrame.listNames(predicate); - } - /** * Formats values to {@link String} with the goal of using them in Java code. * By default, we use the overrides of {@link Object#toString}. @@ -243,12 +223,16 @@ final class Renderer { } private void renderTemplateToken(TemplateToken templateToken) { + // We need a TemplateFrame in all cases, this ensures that the outermost scope of the template + // is not transparent for hashtags and setFuelCost, and also that the id of the template is + // unique. TemplateFrame templateFrame = TemplateFrame.make(currentTemplateFrame, nextTemplateFrameId++); currentTemplateFrame = templateFrame; templateToken.visitArguments((name, value) -> addHashtagReplacement(name, format(value))); - TemplateBody body = templateToken.instantiate(); - renderTokenList(body.tokens()); + + // If the ScopeToken is transparent to Names, then the Template is transparent to names. + renderScopeToken(templateToken.instantiate()); if (currentTemplateFrame != templateFrame) { throw new RuntimeException("Internal error: TemplateFrame mismatch!"); @@ -256,29 +240,76 @@ final class Renderer { currentTemplateFrame = currentTemplateFrame.parent; } + private void renderScopeToken(ScopeToken st) { + renderScopeToken(st, () -> {}); + } + + private void renderScopeToken(ScopeToken st, Runnable preamble) { + if (!(st instanceof ScopeTokenImpl(List tokens, + boolean isTransparentForNames, + boolean isTransparentForHashtags, + boolean isTransparentForSetFuelCost))) { + throw new RuntimeException("Internal error: could not unpack ScopeTokenImpl."); + } + + // We need the CodeFrame for local names. + CodeFrame outerCodeFrame = currentCodeFrame; + if (!isTransparentForNames) { + currentCodeFrame = CodeFrame.make(currentCodeFrame, false); + } + + // We need to be able to define local hashtag replacements, but still + // see the outer ones. We also need to have the same id for dollar + // replacement as the outer frame. And we need to be able to allow + // local setFuelCost definitions. + TemplateFrame innerTemplateFrame = null; + if (!isTransparentForHashtags || !isTransparentForSetFuelCost) { + innerTemplateFrame = TemplateFrame.makeInnerScope(currentTemplateFrame, + isTransparentForHashtags, + isTransparentForSetFuelCost); + currentTemplateFrame = innerTemplateFrame; + } + + // Allow definition of hashtags and variables to be placed in the nested frames. + preamble.run(); + + // Now render the nested code. + renderTokenList(tokens); + + if (!isTransparentForHashtags || !isTransparentForSetFuelCost) { + if (currentTemplateFrame != innerTemplateFrame) { + throw new RuntimeException("Internal error: TemplateFrame mismatch!"); + } + currentTemplateFrame = currentTemplateFrame.parent; + } + + // Tear down CodeFrame nesting. If no nesting happened, the code is already + // in the currentCodeFrame. + if (!isTransparentForNames) { + outerCodeFrame.addCode(currentCodeFrame.getCode()); + currentCodeFrame = outerCodeFrame; + } + } + private void renderToken(Token token) { switch (token) { case StringToken(String s) -> { renderStringWithDollarAndHashtagReplacements(s); } - case NothingToken() -> { - // Nothing. - } - case HookAnchorToken(Hook hook, List tokens) -> { + case HookAnchorToken(Hook hook, ScopeTokenImpl innerScope) -> { CodeFrame outerCodeFrame = currentCodeFrame; - // We need a CodeFrame to which the hook can insert code. That way, name - // definitions at the hook cannot escape the hookCodeFrame. - CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame); + // We need a CodeFrame to which the hook can insert code. If the nested names + // are to be local, the CodeFrame must be non-transparent for names. + CodeFrame hookCodeFrame = CodeFrame.make(outerCodeFrame, innerScope.isTransparentForNames()); hookCodeFrame.addHook(hook); - // We need a CodeFrame where the tokens can be rendered. That way, name - // definitions from the tokens cannot escape the innerCodeFrame to the - // hookCodeFrame. - CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame); + // We need a CodeFrame where the tokens can be rendered for code that is + // generated inside the anchor scope, but not inserted directly to the hook. + CodeFrame innerCodeFrame = CodeFrame.make(hookCodeFrame, innerScope.isTransparentForNames()); currentCodeFrame = innerCodeFrame; - renderTokenList(tokens); + renderScopeToken(innerScope); // Close the hookCodeFrame and innerCodeFrame. hookCodeFrame code comes before the // innerCodeFrame code from the tokens. @@ -286,20 +317,20 @@ final class Renderer { currentCodeFrame.addCode(hookCodeFrame.getCode()); currentCodeFrame.addCode(innerCodeFrame.getCode()); } - case HookInsertToken(Hook hook, TemplateToken templateToken) -> { + case HookInsertToken(Hook hook, ScopeTokenImpl scopeToken) -> { // Switch to hook CodeFrame. CodeFrame callerCodeFrame = currentCodeFrame; CodeFrame hookCodeFrame = codeFrameForHook(hook); // Use a transparent nested CodeFrame. We need a CodeFrame so that the code generated - // by the TemplateToken can be collected, and hook insertions from it can still - // be made to the hookCodeFrame before the code from the TemplateToken is added to + // by the scopeToken can be collected, and hook insertions from it can still + // be made to the hookCodeFrame before the code from the scopeToken is added to // the hookCodeFrame. // But the CodeFrame must be transparent, so that its name definitions go out to - // the hookCodeFrame, and are not limited to the CodeFrame for the TemplateToken. - currentCodeFrame = CodeFrame.makeTransparentForNames(hookCodeFrame); + // the hookCodeFrame, and are not limited to the CodeFrame for the scopeToken. + currentCodeFrame = CodeFrame.make(hookCodeFrame, true); - renderTemplateToken(templateToken); + renderScopeToken(scopeToken); hookCodeFrame.addCode(currentCodeFrame.getCode()); @@ -307,18 +338,68 @@ final class Renderer { currentCodeFrame = callerCodeFrame; } case TemplateToken templateToken -> { - // Use a nested CodeFrame. - CodeFrame callerCodeFrame = currentCodeFrame; - currentCodeFrame = CodeFrame.make(currentCodeFrame); - renderTemplateToken(templateToken); - - callerCodeFrame.addCode(currentCodeFrame.getCode()); - currentCodeFrame = callerCodeFrame; } case AddNameToken(Name name) -> { currentCodeFrame.addName(name); } + case ScopeToken scopeToken -> { + renderScopeToken(scopeToken); + } + case NameSampleToken nameScopeToken -> { + Name name = currentCodeFrame.sampleName(nameScopeToken.predicate()); + if (name == null) { + throw new RendererException("No Name found for " + nameScopeToken.predicate().toString()); + } + ScopeToken scopeToken = nameScopeToken.getScopeToken(name); + renderScopeToken(scopeToken, () -> { + if (nameScopeToken.name() != null) { + addHashtagReplacement(nameScopeToken.name(), name.name()); + } + if (nameScopeToken.type() != null) { + addHashtagReplacement(nameScopeToken.type(), name.type()); + } + }); + } + case NameForEachToken nameForEachToken -> { + List list = currentCodeFrame.listNames(nameForEachToken.predicate()); + list.stream().forEach(name -> { + ScopeToken scopeToken = nameForEachToken.getScopeToken(name); + renderScopeToken(scopeToken, () -> { + if (nameForEachToken.name() != null) { + addHashtagReplacement(nameForEachToken.name(), name.name()); + } + if (nameForEachToken.type() != null) { + addHashtagReplacement(nameForEachToken.type(), name.type()); + } + }); + }); + } + case NamesToListToken nameToListToken -> { + List list = currentCodeFrame.listNames(nameToListToken.predicate()); + renderScopeToken(nameToListToken.getScopeToken(list)); + } + case NameCountToken nameCountToken -> { + int count = currentCodeFrame.countNames(nameCountToken.predicate()); + renderScopeToken(nameCountToken.getScopeToken(count)); + } + case NameHasAnyToken nameHasAnyToken -> { + boolean hasAny = currentCodeFrame.hasAnyNames(nameHasAnyToken.predicate()); + renderScopeToken(nameHasAnyToken.getScopeToken(hasAny)); + } + case SetFuelCostToken(float fuelCost) -> { + currentTemplateFrame.setFuelCost(fuelCost); + } + case LetToken letToken -> { + ScopeToken scopeToken = letToken.getScopeToken(); + renderScopeToken(scopeToken, () -> { + addHashtagReplacement(letToken.key(), letToken.value()); + }); + } + case HookIsAnchoredToken hookIsAnchoredToken -> { + boolean isAnchored = currentCodeFrame.codeFrameForHook(hookIsAnchoredToken.hook()) != null; + renderScopeToken(hookIsAnchoredToken.getScopeToken(isAnchored)); + } } } @@ -423,10 +504,6 @@ final class Renderer { )); } - boolean isAnchored(Hook hook) { - return currentCodeFrame.codeFrameForHook(hook) != null; - } - private CodeFrame codeFrameForHook(Hook hook) { CodeFrame codeFrame = currentCodeFrame.codeFrameForHook(hook); if (codeFrame == null) { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java similarity index 78% rename from test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java rename to test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java index 440766b3f79..f81215da36b 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateBody.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeToken.java @@ -23,12 +23,8 @@ package compiler.lib.template_framework; -import java.util.List; - /** - * A Template generates a {@link TemplateBody}, which is a list of {@link Token}s, - * which are then later rendered to {@link String}s. - * - * @param tokens The list of {@link Token}s that are later rendered to {@link String}s. + * A {@link ScopeToken} represents a scope in a {@link Template}, which can be + * created with {@link Template#scope}, {@link Template#transparentScope}, and other related methods. */ -public record TemplateBody(List tokens) {} +public sealed interface ScopeToken extends Token permits ScopeTokenImpl {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java new file mode 100644 index 00000000000..df95bd56722 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/ScopeTokenImpl.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, 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 compiler.lib.template_framework; + +import java.util.List; + +/** + * Represents a scope with its tokens. Boolean flags indicate if names, + * hashtag replacements and {@link Template#setFuelCost} are local, or escape to + * outer scopes. + * + *

+ * Note: We want the {@link ScopeToken} to be public, but the internals of the + * record should be private. One way to solve this is with a public interface + * that exposes nothing but its name, and a private implementation via a + * record that allows easy destructuring with pattern matching. + */ +record ScopeTokenImpl(List tokens, + boolean isTransparentForNames, + boolean isTransparentForHashtags, + boolean isTransparentForSetFuelCost) implements ScopeToken, Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/SetFuelCostToken.java similarity index 89% rename from test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java rename to test/hotspot/jtreg/compiler/lib/template_framework/SetFuelCostToken.java index 540eaf1e14c..08e219b2cd9 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/NothingToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/SetFuelCostToken.java @@ -23,4 +23,7 @@ package compiler.lib.template_framework; -record NothingToken() implements Token {} +/** + * Represents the setting of the fuel cost in the current scope. + */ +record SetFuelCostToken(float fuelCost) implements Token {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java b/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java index 866ac6dbfb8..8a1090bc5ab 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/StructuralName.java @@ -24,6 +24,7 @@ package compiler.lib.template_framework; import java.util.List; +import java.util.function.Function; /** * {@link StructuralName}s represent things like method and class names, and can be added to the local @@ -89,16 +90,34 @@ public record StructuralName(String name, StructuralName.Type type, int weight) this(null, null); } + // Wrap the FilteredSet as a Predicate. + private record StructuralNamePredicate(FilteredSet fs) implements NameSet.Predicate { + public boolean check(Name type) { + return fs.check(type); + } + public String toString() { + return fs.toString(); + } + } + NameSet.Predicate predicate() { if (subtype == null && supertype == null) { throw new UnsupportedOperationException("Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); } - return (Name name) -> { - if (!(name instanceof StructuralName structuralName)) { return false; } - if (subtype != null && !structuralName.type().isSubtypeOf(subtype)) { return false; } - if (supertype != null && !supertype.isSubtypeOf(structuralName.type())) { return false; } - return true; - }; + return new StructuralNamePredicate(this); + } + + boolean check(Name name) { + if (!(name instanceof StructuralName structuralName)) { return false; } + if (subtype != null && !structuralName.type().isSubtypeOf(subtype)) { return false; } + if (supertype != null && !supertype.isSubtypeOf(structuralName.type())) { return false; } + return true; + } + + public String toString() { + String msg1 = (subtype == null) ? "" : " subtypeOf(" + subtype + ")"; + String msg2 = (supertype == null) ? "" : " supertypeOf(" + supertype + ")"; + return "StructuralName.FilteredSet(" + msg1 + msg2 + ")"; } /** @@ -146,55 +165,125 @@ public record StructuralName(String name, StructuralName.Type type, int weight) /** * Samples a random {@link StructuralName} from the filtered set, according to the weights - * of the contained {@link StructuralName}s. + * of the contained {@link StructuralName}s, making the sampled {@link StructuralName} + * available to an inner scope. * - * @return The sampled {@link StructuralName}. - * @throws UnsupportedOperationException If the type was not constrained with either of - * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. - * @throws RendererException If the set was empty. - */ - public StructuralName sample() { - StructuralName n = (StructuralName)Renderer.getCurrent().sampleName(predicate()); - if (n == null) { - String msg1 = (subtype == null) ? "" : " subtypeOf(" + subtype + ")"; - String msg2 = (supertype == null) ? "" : " supertypeOf(" + supertype + ")"; - throw new RendererException("No variable:" + msg1 + msg2 + "."); - } - return n; - } - - /** - * Counts the number of {@link StructuralName}s in the filtered set. - * - * @return The number of {@link StructuralName}s in the filtered set. + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the sampled {@link StructuralName}. + * @return a token that represents the sampling and inner scope. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public int count() { - return Renderer.getCurrent().countNames(predicate()); + public Token sample(Function function) { + return new NameSampleToken<>(predicate(), null, null, function); } /** - * Checks if there are any {@link StructuralName}s in the filtered set. + * Samples a random {@link StructuralName} from the filtered set, according to the weights + * of the contained {@link StructuralName}s, and makes a hashtag replacement for both + * the name and type of the {@link StructuralName}, in the current scope. * - * @return Returns {@code true} iff there is at least one {@link StructuralName} in the filtered set. + * @param name the key of the hashtag replacement for the {@link StructuralName} name. + * @param type the key of the hashtag replacement for the {@link StructuralName} type. + * @return a token that represents the sampling and hashtag replacement definition. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public boolean hasAny() { - return Renderer.getCurrent().hasAnyNames(predicate()); + public Token sampleAndLetAs(String name, String type) { + return new NameSampleToken(predicate(), name, type, n -> Template.transparentScope()); } /** - * Collects all {@link StructuralName}s in the filtered set. + * Samples a random {@link StructuralName} from the filtered set, according to the weights + * of the contained {@link StructuralName}s, and makes a hashtag replacement for the + * name of the {@link StructuralName}, in the current scope. * + * @param name the key of the hashtag replacement for the {@link StructuralName} name. + * @return a token that represents the sampling and hashtag replacement definition. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token sampleAndLetAs(String name) { + return new NameSampleToken(predicate(), name, null, n -> Template.transparentScope()); + } + + /** + * Counts the number of {@link StructuralName}s in the filtered set, making the count + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the count. + * @return a token that represents the counting and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token count(Function function) { + return new NameCountToken(predicate(), function); + } + + /** + * Checks if there are any {@link StructuralName}s in the filtered set, making the resulting boolean + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the boolean indicating iff there are any {@link StructuralName}s in the filtered set. + * @return a token that represents the checking and inner scope. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token hasAny(Function function) { + return new NameHasAnyToken(predicate(), function); + } + /** + * Collects all {@link StructuralName}s in the filtered set, making the collected list + * available to an inner scope. + * + * @param function The {@link Function} that creates the inner {@link ScopeToken} given + * the list of {@link StructuralName}. * @return A {@link List} of all {@link StructuralName}s in the filtered set. * @throws UnsupportedOperationException If the type was not constrained with either of * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. */ - public List toList() { - List list = Renderer.getCurrent().listNames(predicate()); - return list.stream().map(n -> (StructuralName)n).toList(); + public Token toList(Function, ScopeToken> function) { + return new NamesToListToken<>(predicate(), function); + } + + /** + * Calls the provided {@code function} for each {@link StructuralName}s in the filtered set, + * making each of these {@link StructuralName}s available to a separate inner scope. + * + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link StructuralName}s in the filtereds set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(Function function) { + return new NameForEachToken<>(predicate(), null, null, function); + } + + /** + * Calls the provided {@code function} for each {@link StructuralName}s in the filtered set, + * making each of these {@link StructuralName}s available to a separate inner scope, and additionally + * setting hashtag replacements for the {@code name} and {@code type} of the respective + * {@link StructuralName}s. + * + *

+ * Note, to avoid duplication of the {@code name} and {@code type} + * hashtag replacements, the scope created by the provided {@code function} should be + * non-transparent to hashtag replacements, for example {@link Template#scope} or + * {@link Template#hashtagScope}. + * + * @param name the key of the hashtag replacement for each individual {@link StructuralName} name. + * @param type the key of the hashtag replacement for each individual {@link StructuralName} type. + * @param function The {@link Function} that is called to create the inner {@link ScopeToken}s + * for each of the {@link StructuralName}s in the filtereds set. + * @return The token representing the for-each execution and the respective inner scopes. + * @throws UnsupportedOperationException If the type was not constrained with either of + * {@link #subtypeOf}, {@link #supertypeOf} or {@link #exactOf}. + */ + public Token forEach(String name, String type, Function function) { + return new NameForEachToken<>(predicate(), name, type, function); } } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java index 57d06e732bb..f245cda0501 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Template.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Template.java @@ -65,7 +65,7 @@ import compiler.lib.ir_framework.TestFramework; * *

* {@snippet lang=java : - * var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body( + * var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> scope( * let("con1", generator.next()), * let("con2", generator.next()), * """ @@ -86,13 +86,13 @@ import compiler.lib.ir_framework.TestFramework; * } * *

- * To get an executable test, we define a {@link Template} that produces a class body with a main method. The Template + * To get an executable test, we define a {@link Template} that produces a class scope with a main method. The Template * takes a list of types, and calls the {@code testTemplate} defined above for each type and operator. We use * the {@link TestFramework} to call our {@code @Test} methods. * *

* {@snippet lang=java : - * var classTemplate = Template.make("types", (List types) -> body( + * var classTemplate = Template.make("types", (List types) -> scope( * let("classpath", comp.getEscapedClassPathOfCompiledClasses()), * """ * package p.xyz; @@ -148,12 +148,12 @@ import compiler.lib.ir_framework.TestFramework; * {@link Template#make(String, Function)}. For each number of arguments there is an implementation * (e.g. {@link Template.TwoArgs} for two arguments). This allows the use of generics for the * {@link Template} argument types which enables type checking of the {@link Template} arguments. - * It is currently only allowed to use up to three arguments. + * It is currently only allowed to use up to three arguments. * *

* A {@link Template} can be rendered to a {@link String} (e.g. {@link Template.ZeroArgs#render()}). * Alternatively, we can generate a {@link Token} (more specifically, a {@link TemplateToken}) with {@code asToken()} - * (e.g. {@link Template.ZeroArgs#asToken()}), and use the {@link Token} inside another {@link Template#body}. + * (e.g. {@link Template.ZeroArgs#asToken()}), and use the {@link Token} inside another {@link Template#scope}. * *

* Ideally, we would have used string templates to inject these Template @@ -161,6 +161,11 @@ import compiler.lib.ir_framework.TestFramework; * hashtag replacements in the {@link String}s: the Template argument names are captured, and * the argument values automatically replace any {@code "#name"} in the {@link String}s. See the different overloads * of {@link #make} for examples. Additional hashtag replacements can be defined with {@link #let}. + * We have decided to keep hashtag replacements constrained to the scope of one Template. They + * do not escape to outer or inner Template uses. If one needs to pass values to inner Templates, + * this can be done with Template arguments. Keeping hashtag replacements local to Templates + * has the benefit that there is no conflict in recursive templates, where outer and inner Templates + * define the same hashtag replacement. * *

* When using nested Templates, there can be collisions with identifiers (e.g. variable names and method names). @@ -176,25 +181,6 @@ import compiler.lib.ir_framework.TestFramework; * {@code #{name}}. * *

- * A {@link TemplateToken} cannot just be used in {@link Template#body}, but it can also be - * {@link Hook#insert}ed to where a {@link Hook} was {@link Hook#anchor}ed earlier (in some outer scope of the code). - * For example, while generating code in a method, one can reach out to the scope of the class, and insert a - * new field, or define a utility method. - * - *

- * A {@link TemplateBinding} allows the recursive use of Templates. With the indirection of such a binding, - * a Template can reference itself. - * - *

- * The writer of recursive {@link Template}s must ensure that this recursion terminates. To unify the - * approach across {@link Template}s, we introduce the concept of {@link #fuel}. Templates are rendered starting - * with a limited amount of {@link #fuel} (default: 100, see {@link #DEFAULT_FUEL}), which is decreased at each - * Template nesting by a certain amount (default: 10, see {@link #DEFAULT_FUEL_COST}). The default fuel for a - * template can be changed when we {@code render()} it (e.g. {@link ZeroArgs#render(float)}) and the default - * fuel cost with {@link #setFuelCost}) when defining the {@link #body(Object...)}. Recursive templates are - * supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero). - * - *

* Code generation can involve keeping track of fields and variables, as well as the scopes in which they * are available, and if they are mutable or immutable. We model fields and variables with {@link DataName}s, * which we can add to the current scope with {@link #addDataName}. We can access the {@link DataName}s with @@ -211,61 +197,70 @@ import compiler.lib.ir_framework.TestFramework; * are not concerned about mutability. * *

- * When working with {@link DataName}s and {@link StructuralName}s, it is important to be aware of the - * relevant scopes, as well as the execution order of the {@link Template} lambdas and the evaluation - * of the {@link Template#body} tokens. When a {@link Template} is rendered, its lambda is invoked. In the - * lambda, we generate the tokens, and create the {@link Template#body}. Once the lambda returns, the - * tokens are evaluated one by one. While evaluating the tokens, the {@link Renderer} might encounter a nested - * {@link TemplateToken}, which in turn triggers the evaluation of that nested {@link Template}, i.e. - * the evaluation of its lambda and later the evaluation of its tokens. It is important to keep in mind - * that the lambda is always executed first, and the tokens are evaluated afterwards. A method like - * {@code dataNames(MUTABLE).exactOf(type).count()} is a method that is executed during the evaluation - * of the lambda. But a method like {@link #addDataName} returns a token, and does not immediately add - * the {@link DataName}. This ensures that the {@link DataName} is only inserted when the tokens are - * evaluated, so that it is inserted at the exact scope where we would expect it. + * Code generation can involve keeping track of scopes in the code (e.g. liveness and availability of + * {@link DataName}s) and of the hashtag replacements in the templates. The {@link ScopeToken} serves + * this purpose, and allows the definition of transparent scopes (e.g. {@link #transparentScope}) and + * non-transparent scopes (e.g. {@link #scope}). + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Scopes and (non-)transparency
hashtag {@link DataName} and {@link StructuralName} {@link #setFuelCost}
{@link #scope} non-transparent non-transparent non-transparent
{@link #hashtagScope} non-transparent transparent transparent
{@link #nameScope} transparent non-transparent transparent
{@link #setFuelCostScope} transparent transparent non-transparent
{@link #transparentScope} transparent transparent transparent
* *

- * Let us look at the following example to better understand the execution order. + * In some cases, we may be deeper nested in templates and scopes, and would like to reach "back" or + * to outer scopes. This is possible with {@link Hook#anchor}ing in some outer scope, and later + * {@link Hook#insert}ing from an inner scope to the scope of the anchoring. For example, while + * generating code in a method, one can reach out to the scope of the class, and insert a new field, + * or define a utility method. * *

- * {@snippet lang=java : - * var testTemplate = Template.make(() -> body( - * // The lambda has just been invoked. - * // We count the DataNames and assign the count to the hashtag replacement "c1". - * let("c1", dataNames(MUTABLE).exactOf(someType).count()), - * // We want to define a DataName "v1", and create a token for it. - * addDataName($("v1"), someType, MUTABLE), - * // We count the DataNames again, but the count does NOT change compared to "c1". - * // This is because the token for "v1" is only evaluated later. - * let("c2", dataNames(MUTABLE).exactOf(someType).count()), - * // Create a nested scope. - * METHOD_HOOK.anchor( - * // We want to define a DataName "v2", which is only valid inside this - * // nested scope. - * addDataName($("v2"), someType, MUTABLE), - * // The count is still not different to "c1". - * let("c3", dataNames(MUTABLE).exactOf(someType).count()), - * // We nest a Template. This creates a TemplateToken, which is later evaluated. - * // By the time the TemplateToken is evaluated, the tokens from above will - * // be already evaluated. Hence, "v1" and "v2" are added by then, and if the - * // "otherTemplate" were to count the DataNames, the count would be increased - * // by 2 compared to "c1". - * otherTemplate.asToken() - * ), - * // After closing the scope, "v2" is no longer available. - * // The count is still the same as "c1", since "v1" is still only a token. - * let("c4", dataNames(MUTABLE).exactOf(someType).count()), - * // We nest another Template. Again, this creates a TemplateToken, which is only - * // evaluated later. By that time, the token for "v1" is evaluated, and so the - * // nested Template would observe an increment in the count. - * anotherTemplate.asToken() - * // By this point, all methods are called, and the tokens generated. - * // The lambda returns the "body", which is all of the tokens that we just - * // generated. After returning from the lambda, the tokens will be evaluated - * // one by one. - * )); - * } - + * A {@link TemplateBinding} allows the recursive use of Templates. With the indirection of such a binding, + * a Template can reference itself. + * + *

+ * The writer of recursive {@link Template}s must ensure that this recursion terminates. To unify the + * approach across {@link Template}s, we introduce the concept of {@link #fuel}. Templates are rendered starting + * with a limited amount of {@link #fuel} (default: 100, see {@link #DEFAULT_FUEL}), which is decreased at each + * Template nesting by a certain amount (default: 10, see {@link #DEFAULT_FUEL_COST}). The default fuel for a + * template can be changed when we {@code render()} it (e.g. {@link ZeroArgs#render(float)}) and the default + * fuel cost with {@link #setFuelCost}) when defining the {@link #scope(Object...)}. Recursive templates are + * supposed to terminate once the {@link #fuel} is depleted (i.e. reaches zero). + * + *

+ * A note from the implementor to the user: We have decided to implement the Template Framework using + * a functional (lambdas) and data-oriented (tokens) model. The consequence is that there are three + * orders in template rendering: (1) the execution order in lambdas, where we usually assemble the + * tokens and pass them to some scope ({@link ScopeToken}) as arguments. (2) the token evaluation + * order, which occurs in the order of how tokens are listed in a scope. By design, the token order + * is the same order as execution in lambdas. To keep the lambda and token order in sync, most of the + * queries about the state of code generation, such as {@link DataName}s and {@link Hook}s cannot + * return the values immediately, but have to be expressed as tokens. If we had a mix of tokens and + * immediate queries, then the immediate queries would "float" by the tokens, because the immediate + * queries are executed during the lambda execution, but the tokens are only executed later. Having + * to express everything as tokens can be a little more cumbersome (e.g. sample requires a lambda + * that captures the {@link DataName}, and sample does not return the {@link DataName} directly). + * But this ensures that reasoning about execution order is relatively straight forward, namely in + * the order of the specified tokens. (3) the final code order is the same as the lambda and token + * order, except when using {@link Hook#insert}, which places the code at the innermost {@link Hook#anchor}. + * *

* More examples for these functionalities can be found in {@code TestTutorial.java}, {@code TestSimple.java}, * and {@code TestAdvanced.java}, which all produce compilable Java code. Additional examples can be found in @@ -281,10 +276,10 @@ public sealed interface Template permits Template.ZeroArgs, /** * A {@link Template} with no arguments. * - * @param function The {@link Supplier} that creates the {@link TemplateBody}. + * @param function The {@link Supplier} that creates the {@link ScopeToken}. */ - record ZeroArgs(Supplier function) implements Template { - TemplateBody instantiate() { + record ZeroArgs(Supplier function) implements Template { + ScopeToken instantiate() { return function.get(); } @@ -324,10 +319,10 @@ public sealed interface Template permits Template.ZeroArgs, * * @param arg1Name The name of the (first) argument, used for hashtag replacements in the {@link Template}. * @param The type of the (first) argument. - * @param function The {@link Function} that creates the {@link TemplateBody} given the template argument. + * @param function The {@link Function} that creates the {@link ScopeToken} given the template argument. */ - record OneArg(String arg1Name, Function function) implements Template { - TemplateBody instantiate(T1 arg1) { + record OneArg(String arg1Name, Function function) implements Template { + ScopeToken instantiate(T1 arg1) { return function.apply(arg1); } @@ -372,10 +367,10 @@ public sealed interface Template permits Template.ZeroArgs, * @param arg2Name The name of the second argument, used for hashtag replacements in the {@link Template}. * @param The type of the first argument. * @param The type of the second argument. - * @param function The {@link BiFunction} that creates the {@link TemplateBody} given the template arguments. + * @param function The {@link BiFunction} that creates the {@link ScopeToken} given the template arguments. */ - record TwoArgs(String arg1Name, String arg2Name, BiFunction function) implements Template { - TemplateBody instantiate(T1 arg1, T2 arg2) { + record TwoArgs(String arg1Name, String arg2Name, BiFunction function) implements Template { + ScopeToken instantiate(T1 arg1, T2 arg2) { return function.apply(arg1, arg2); } @@ -447,10 +442,10 @@ public sealed interface Template permits Template.ZeroArgs, * @param The type of the first argument. * @param The type of the second argument. * @param The type of the third argument. - * @param function The function with three arguments that creates the {@link TemplateBody} given the template arguments. + * @param function The function with three arguments that creates the {@link ScopeToken} given the template arguments. */ - record ThreeArgs(String arg1Name, String arg2Name, String arg3Name, TriFunction function) implements Template { - TemplateBody instantiate(T1 arg1, T2 arg2, T3 arg3) { + record ThreeArgs(String arg1Name, String arg2Name, String arg3Name, TriFunction function) implements Template { + ScopeToken instantiate(T1 arg1, T2 arg2, T3 arg3) { return function.apply(arg1, arg2, arg3); } @@ -496,28 +491,28 @@ public sealed interface Template permits Template.ZeroArgs, /** * Creates a {@link Template} with no arguments. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * *

* Example: * {@snippet lang=java : - * var template = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * Multi-line string or other tokens. * """ * )); * } * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @return A {@link Template} with zero arguments. */ - static Template.ZeroArgs make(Supplier body) { - return new Template.ZeroArgs(body); + static Template.ZeroArgs make(Supplier scope) { + return new Template.ZeroArgs(scope); } /** * Creates a {@link Template} with one argument. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * Good practice but not enforced but not enforced: {@code arg1Name} should match the lambda argument name. * *

@@ -525,7 +520,7 @@ public sealed interface Template permits Template.ZeroArgs, * for use in hashtag replacements, and captured once as lambda argument with the corresponding type * of the generic argument. * {@snippet lang=java : - * var template = Template.make("a", (Integer a) -> body( + * var template = Template.make("a", (Integer a) -> scope( * """ * Multi-line string or other tokens. * We can use the hashtag replacement #a to directly insert the String value of a. @@ -534,18 +529,18 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @param Type of the (first) argument. * @param arg1Name The name of the (first) argument for hashtag replacement. * @return A {@link Template} with one argument. */ - static Template.OneArg make(String arg1Name, Function body) { - return new Template.OneArg<>(arg1Name, body); + static Template.OneArg make(String arg1Name, Function scope) { + return new Template.OneArg<>(arg1Name, scope); } /** * Creates a {@link Template} with two arguments. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * Good practice but not enforced: {@code arg1Name} and {@code arg2Name} should match the lambda argument names. * *

@@ -553,7 +548,7 @@ public sealed interface Template permits Template.ZeroArgs, * for use in hashtag replacements, and captured once as lambda arguments with the corresponding types * of the generic arguments. * {@snippet lang=java : - * var template = Template.make("a", "b", (Integer a, String b) -> body( + * var template = Template.make("a", "b", (Integer a, String b) -> scope( * """ * Multi-line string or other tokens. * We can use the hashtag replacement #a and #b to directly insert the String value of a and b. @@ -562,23 +557,23 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @param Type of the first argument. * @param arg1Name The name of the first argument for hashtag replacement. * @param Type of the second argument. * @param arg2Name The name of the second argument for hashtag replacement. * @return A {@link Template} with two arguments. */ - static Template.TwoArgs make(String arg1Name, String arg2Name, BiFunction body) { - return new Template.TwoArgs<>(arg1Name, arg2Name, body); + static Template.TwoArgs make(String arg1Name, String arg2Name, BiFunction scope) { + return new Template.TwoArgs<>(arg1Name, arg2Name, scope); } /** * Creates a {@link Template} with three arguments. - * See {@link #body} for more details about how to construct a Template with {@link Token}s. + * See {@link #scope} for more details about how to construct a Template with {@link Token}s. * Good practice but not enforced: {@code arg1Name}, {@code arg2Name}, and {@code arg3Name} should match the lambda argument names. * - * @param body The {@link TemplateBody} created by {@link Template#body}. + * @param scope The {@link ScopeToken} created by {@link Template#scope}. * @param Type of the first argument. * @param arg1Name The name of the first argument for hashtag replacement. * @param Type of the second argument. @@ -587,18 +582,35 @@ public sealed interface Template permits Template.ZeroArgs, * @param arg3Name The name of the third argument for hashtag replacement. * @return A {@link Template} with three arguments. */ - static Template.ThreeArgs make(String arg1Name, String arg2Name, String arg3Name, Template.TriFunction body) { - return new Template.ThreeArgs<>(arg1Name, arg2Name, arg3Name, body); + static Template.ThreeArgs make(String arg1Name, String arg2Name, String arg3Name, Template.TriFunction scope) { + return new Template.ThreeArgs<>(arg1Name, arg2Name, arg3Name, scope); } /** - * Creates a {@link TemplateBody} from a list of tokens, which can be {@link String}s, - * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), any {@link Token}, - * or {@link List}s of any of these. + * Creates a {@link ScopeToken} that represents a scope that is completely + * non-transparent, not allowing anything to escape. This + * means that no {@link DataName}, {@link StructuralName}s, hashtag-replacement + * or {@link #setFuelCost} defined inside the scope is available outside. All + * these usages are only local to the defining scope here. + * + *

+ * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

+ * If you require a scope that is either fully transparent (i.e. everything escapes) + * or only restricts a specific kind to not escape, consider using one of the other + * provided scopes: {@link #transparentScope}, {@link #nameScope}, {@link #hashtagScope}, + * or {@link #setFuelCostScope}. A "scope-transparency-matrix" can also be found in + * the interface comment for {@link Template}. + * + *

+ * The most common use of {@link #scope} is in the construction of templates: * *

* {@snippet lang=java : - * var template = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * Multi-line string * """, @@ -608,14 +620,200 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * + *

+ * Note that regardless of the chosen scope for {@code Template.make}, + * hashtag-replacements and {@link #setFuelCost} are always implicitly + * non-transparent (i.e. non-escaping). For example, {@link #let} will + * not escape the template scope even when using {@link #transparentScope}. + * As a default, it is recommended to use {@link #scope} for + * {@code Template.make} since in most cases template scopes align with + * code scopes that are non-transparent for fields, variables, etc. In + * rare cases, where the scope of the template needs to be transparent + * (e.g. because we need to insert a variable or field into an outer scope), + * it is recommended to use {@link #transparentScope}. This allows to make + * {@link DataName}s and {@link StructuralName}s available outside this + * template crossing the template boundary. + * + *

+ * We can also use nested scopes inside of templates: + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> scope( + * // CODE1: some code in the outer scope + * scope( + * // CODE2: some code in the inner scope. Names, hashtags and setFuelCost + * // do not escape the inner scope. + * ), + * // CODE3: more code in the outer scope, names and hashtags from CODE2 are + * // not available anymore because of the non-transparent "scope". + * transparentScope( + * // CODE4: some code in the inner "transparentScope". Names, hashtags and setFuelCost + * // escape the "transparentScope" and are still available after the "transparentScope" + * // closes. + * ) + * // CODE5: we still have access to names and hashtags from CODE4. + * )); + * } + * * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types * (for example {@link Integer}), any {@link Token}, or {@link List}s * of any of these. - * @return The {@link TemplateBody} which captures the list of validated {@link Token}s. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. * @throws IllegalArgumentException if the list of tokens contains an unexpected object. */ - static TemplateBody body(Object... tokens) { - return new TemplateBody(TokenParser.parse(tokens)); + static ScopeToken scope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), false, false, false); + } + + /** + * Creates a {@link ScopeToken} that represents a completely transparent scope. + * This means that {@link DataName}s, {@link StructuralName}s, + * hashtag-replacements and {@link #setFuelCost} declared inside the scope will be available + * in the outer scope. + * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

+ * If you require a scope that is non-transparent (i.e. nothing escapes) or only restricts + * a specific kind to not escape, consider using one of the other provided scopes: + * {@link #scope}, {@link #nameScope}, {@link #hashtagScope}, or {@link #setFuelCostScope}. + * A "scope-transparency-matrix" can also be found in the interface comment for {@link Template}. + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken transparentScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), true, true, true); + } + + /** + * Creates a {@link ScopeToken} that represents a scope that is non-transparent for + * {@link DataName}s and {@link StructuralName}s (i.e. cannot escape), but + * transparent for hashtag-replacements and {@link #setFuelCost} (i.e. available + * in outer scope). + * + *

+ * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

+ * If you require a scope that is transparent or uses a different restriction, consider + * using one of the other provided scopes: {@link #scope}, {@link #transparentScope}, + * {@link #hashtagScope}, or {@link #setFuelCostScope}. A "scope-transparency-matrix" can + * also be found in the interface comment for {@link Template}. + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken nameScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), false, true, true); + } + + /** + * Creates a {@link ScopeToken} that represents a scope that is non-transparent for + * hashtag-replacements (i.e. cannot escape), but transparent for {@link DataName}s + * and {@link StructuralName}s and {@link #setFuelCost} (i.e. available in outer scope). + * + *

+ * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

+ * If you require a scope that is transparent or uses a different restriction, consider + * using one of the other provided scopes: {@link #scope}, {@link #transparentScope}, + * {@link #nameScope}, or {@link #setFuelCostScope}. A "scope-transparency-matrix" can + * also be found in the interface comment for {@link Template}. + * + *

+ * Keeping hashtag-replacements local but letting {@link DataName}s escape can be + * useful in cases like the following, where we may want to reuse the hashtag + * multiple times: + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> scope( + * List.of("a", "b", "c").stream().map(name -> hashtagScope( + * let("name", name), // assumes values: a, b, c + * addDataName(name, PrimitiveType.INTS, MUTABLE), // escapes + * """ + * int #name = 42; + * """ + * )) + * // We still have access to the three DataNames. + * )); + * } + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken hashtagScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), true, false, true); + } + + /** + * Creates a {@link ScopeToken} that represents a scope that is non-transparent for + * {@link #setFuelCost} (i.e. cannot escape), but transparent for hashtag-replacements, + * {@link DataName}s and {@link StructuralName}s (i.e. available in outer scope). + * The scope is formed from a list of tokens, which can be {@link String}s, + * boxed primitive types (for example {@link Integer} or auto-boxed {@code int}), + * any {@link Token}, or {@link List}s of any of these. + * + *

+ * If you require a scope that is transparent or uses a different restriction, consider + * using one of the other provided scopes: {@link #scope}, {@link #transparentScope}, + * {@link #hashtagScope}, or {@link #nameScope}. A "scope-transparency-matrix" can + * also be found in the interface comment for {@link Template}. + * + *

+ * In some cases, it can be helpful to have different {@link #setFuelCost} within + * a single template, depending on the code nesting depth. Example: + * + *

+ * {@snippet lang=java : + * var template = Template.make(() -> scope( + * setFuelCost(1), + * // CODE1: some shallow code, allowing recursive template uses here + * // to use more fuel. + * """ + * for (int i = 0; i < 1000; i++) { + * """, + * setFuelCostScope( + * setFuelCost(100) + * // CODE2: with the for-loop, we already have a deeper nesting + * // depth, and recursive template uses should not get + * // as much fuel as in CODE1. + * ), + * """ + * } + * """ + * // CODE3: we are back in the outer scope of CODE1, and can use + * // more fuel again in nested template uses. setFuelCost + * // is automatically restored to what was set before the + * // inner scope. + * )); + * } + * + * @param tokens A list of tokens, which can be {@link String}s, boxed primitive types + * (for example {@link Integer}), any {@link Token}, or {@link List}s + * of any of these. + * @return The {@link ScopeToken} which captures the list of validated {@link Token}s. + * @throws IllegalArgumentException if the list of tokens contains an unexpected object. + */ + static ScopeToken setFuelCostScope(Object... tokens) { + return new ScopeTokenImpl(TokenParser.parse(tokens), true, true, false); } /** @@ -628,7 +826,7 @@ public sealed interface Template permits Template.ZeroArgs, * with an implicit dollar replacement, and then captures that dollar replacement * using {@link #$} for the use inside a nested template. * {@snippet lang=java : - * var template = Template.make(() -> body( + * var template = Template.make(() -> scope( * """ * int $var = 42; * """, @@ -640,6 +838,9 @@ public sealed interface Template permits Template.ZeroArgs, * @return The dollar replacement for the {@code 'name'}. */ static String $(String name) { + // Note, since the dollar replacements do not change within a template + // and the retrieval has no side effects, we can return the value immediately, + // and do not need a token. return Renderer.getCurrent().$(name); } @@ -648,7 +849,7 @@ public sealed interface Template permits Template.ZeroArgs, * *

* {@snippet lang=java : - * var template = Template.make("a", (Integer a) -> body( + * var template = Template.make("a", (Integer a) -> scope( * let("b", a * 5), * """ * System.out.println("Use a and b with hashtag replacement: #a and #b"); @@ -656,41 +857,50 @@ public sealed interface Template permits Template.ZeroArgs, * )); * } * + *

+ * Note that a {@code let} definition makes the hashtag replacement available + * for anything that follows it, until the the end of the next outer scope + * that is non-transparent for hashtag replacements. Additionally, hashtag + * replacements are limited to the template they were defined in. + * If you want to pass values from an outer to an inner template, this cannot + * be done with hashtags directly. Instead, one has to pass the values via + * template arguments. + * * @param key Name for the hashtag replacement. * @param value The value that the hashtag is replaced with. - * @return A token that does nothing, so that the {@link #let} can easily be put in a list of tokens - * inside a {@link Template#body}. - * @throws RendererException if there is a duplicate hashtag {@code key}. + * @return A token that represents the hashtag replacement definition. */ static Token let(String key, Object value) { - Renderer.getCurrent().addHashtagReplacement(key, value); - return new NothingToken(); + return new LetToken(key, value, v -> transparentScope()); } /** * Define a hashtag replacement for {@code "#key"}, with a specific value, which is also captured - * by the provided {@code function} with type {@code }. + * by the provided {@code function} with type {@code }. While the argument of the lambda that + * captures the value is naturally bounded to the scope of the lambda, the hashtag replacement + * may be bound to the scope or escape it, depending on the choice of scope, see {@link #scope} + * and {@link #transparentScope}. * *

* {@snippet lang=java : - * var template = Template.make("a", (Integer a) -> let("b", a * 2, (Integer b) -> body( - * """ - * System.out.println("Use a and b with hashtag replacement: #a and #b"); - * """, - * "System.out.println(\"Use a and b as capture variables:\"" + a + " and " + b + ");\n" - * ))); + * var template = Template.make("a", (Integer a) -> scope( + * let("b", a * 2, (Integer b) -> scope( + * """ + * System.out.println("Use a and b with hashtag replacement: #a and #b"); + * """, + * "System.out.println(\"Use a and b as capture variables:\"" + a + " and " + b + ");\n" + * )) + * )); * } * * @param key Name for the hashtag replacement. * @param value The value that the hashtag is replaced with. * @param The type of the value. * @param function The function that is applied with the provided {@code value}. - * @return A {@link TemplateBody}. - * @throws RendererException if there is a duplicate hashtag {@code key}. + * @return A {@link Token} representing the hashtag replacement definition and inner scope. */ - static TemplateBody let(String key, T value, Function function) { - Renderer.getCurrent().addHashtagReplacement(key, value); - return function.apply(value); + static Token let(String key, T value, Function function) { + return new LetToken(key, value, function); } /** @@ -702,7 +912,7 @@ public sealed interface Template permits Template.ZeroArgs, /** * The default amount of fuel spent per Template. It is subtracted from the current {@link #fuel} at every * nesting level, and once the {@link #fuel} reaches zero, the nesting is supposed to terminate. Can be changed - * with {@link #setFuelCost(float)} inside {@link #body(Object...)}. + * with {@link #setFuelCost(float)} inside {@link #scope(Object...)}. */ float DEFAULT_FUEL_COST = 10.0f; @@ -721,7 +931,7 @@ public sealed interface Template permits Template.ZeroArgs, *

* {@snippet lang=java : * var binding = new TemplateBinding>(); - * var template = Template.make("depth", (Integer depth) -> body( + * var template = Template.make("depth", (Integer depth) -> scope( * setFuelCost(5.0f), * let("fuel", fuel()), * """ @@ -737,6 +947,9 @@ public sealed interface Template permits Template.ZeroArgs, * @return The amount of fuel left for nested Template use. */ static float fuel() { + // Note, since the fuel amount does not change within a template + // and the retrieval has no side effects, we can return the value immediately, + // and do not need a token. return Renderer.getCurrent().fuel(); } @@ -745,16 +958,17 @@ public sealed interface Template permits Template.ZeroArgs, * {@link Template#DEFAULT_FUEL_COST}. * * @param fuelCost The amount of fuel used for the current Template. - * @return A token for convenient use in {@link Template#body}. + * @return A token for convenient use in {@link Template#scope}. */ static Token setFuelCost(float fuelCost) { - Renderer.getCurrent().setFuelCost(fuelCost); - return new NothingToken(); + return new SetFuelCostToken(fuelCost); } /** - * Add a {@link DataName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}. + * Add a {@link DataName} in the current {@link #scope}. + * If the current scope is transparent to {@link DataName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addDataName} until the end of that non-transparent scope. * * @param name The name of the {@link DataName}, i.e. the {@link String} used in code. * @param type The type of the {@link DataName}. @@ -779,8 +993,10 @@ public sealed interface Template permits Template.ZeroArgs, } /** - * Add a {@link DataName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1. + * Add a {@link DataName} in the current {@link #scope}, with a {@code weight} of 1. + * If the current scope is transparent to {@link DataName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addDataName} until the end of that non-transparent scope. * * @param name The name of the {@link DataName}, i.e. the {@link String} used in code. * @param type The type of the {@link DataName}. @@ -804,8 +1020,10 @@ public sealed interface Template permits Template.ZeroArgs, } /** - * Add a {@link StructuralName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}. + * Add a {@link StructuralName} in the current {@link #scope}. + * If the current scope is transparent to {@link StructuralName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addStructuralName} until the end of that non-transparent scope. * * @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code. * @param type The type of the {@link StructuralName}. @@ -822,8 +1040,10 @@ public sealed interface Template permits Template.ZeroArgs, } /** - * Add a {@link StructuralName} in the current scope, that is the innermost of either - * {@link Template#body} or {@link Hook#anchor}, with a {@code weight} of 1. + * Add a {@link StructuralName} in the current {@link #scope}, with a {@code weight} of 1. + * If the current scope is transparent to {@link StructuralName}s, it escapes to the next + * outer scope that is non-transparent, and is available for everything that follows + * the {@code addStructuralName} until the end of that non-transparent scope. * * @param name The name of the {@link StructuralName}, i.e. the {@link String} used in code. * @param type The type of the {@link StructuralName}. diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java index cf8c4afb321..04305dff02f 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateFrame.java @@ -27,38 +27,96 @@ import java.util.HashMap; import java.util.Map; /** - * The {@link TemplateFrame} is the frame for a {@link Template}, i.e. the corresponding - * {@link TemplateToken}. It ensures that each template use has its own unique {@link #id} - * used to deconflict names using {@link Template#$}. It also has a set of hashtag - * replacements, which combine the key-value pairs from the template argument and the - * {@link Template#let} definitions. The {@link #parent} relationship provides a trace - * for the use chain of templates. The {@link #fuel} is reduced over this chain, to give - * a heuristic on how much time is spent on the code from the template corresponding to - * the frame, and to give a termination criterion to avoid nesting templates too deeply. + * The {@link TemplateFrame} keeps track of the nested hashtag replacements available + * inside the {@link Template}, as well as the unique id of the {@link Template} use, + * and how much fuel is available for recursive {@link Template} calls. The name of + * the {@link TemplateFrame} indicates that it corresponds to the structure of the + * {@link Template}, whereas the {@link CodeFrame} corresponds to the structure of + * the generated code. * *

- * See also {@link CodeFrame} for more explanations about the frames. + * The unique id is used to deconflict names using {@link Template#$}. + * + *

+ * A {@link Template} can have multiple {@link TemplateFrame}s, if there are nested + * scopes. The outermost {@link TemplateFrame} determines the id of the {@link Template} + * use and performs the subtraction of fuel from the outer {@link Template}. Inner + * {@link TemplateFrame}s ensure the correct availability of hashtag replacement and + * {@link Template#setFuelCost} definitions, so that they are local to their scope and + * nested scopes, and only escape if the scope is transparent. + * + *

+ * The hashtag replacements are a set of key-value pairs from the template arguments + * and queries such as {@link Template#let} definitions. Each {@link TemplateFrame} + * has such a set of hashtag replacements, and implicitly provides access to the + * hashtag replacements of the outer {@link TemplateFrame}s, up to the outermost + * of the current {@link Template}. If a hashtag replacement is added in a scope, + * we have to traverse to outer scopes until we find one that is not transparent + * for hashtags (at most it is the frame of the Template), and insert it there. + * The hashtag replacent is local to that frame, and accessible for any frames nested + * inside it, but not inside other Templates. The hashtag replacement disappears once + * the corresponding scope is exited, i.e. the frame removed. + * + *

+ * The {@link #parent} relationship provides a trace for the use chain of templates and + * their inner scopes. The {@link #fuel} is reduced over this chain to give a heuristic + * on how deeply nested the code is at a given point, correlating to the runtime that + * would be spent if the code was executed. The idea is that once the fuel is depleated, + * we do not want to nest more deeply, so that there is a reasonable chance that the + * execution of the generated code can terminate. + * + *

+ * The {@link TemplateFrame} thus implements the hashtag and {@link Template#setFuelCost} + * non-transparency aspect of {@link ScopeToken}. + * + *

+ * See also {@link CodeFrame} for more explanations about the frames. Note, that while + * {@link TemplateFrame} always nests inward, even with {@link Hook#insert}, the + * {@link CodeFrame} can also jump to the {@link Hook#anchor} {@link CodeFrame} when + * using {@link Hook#insert}. */ class TemplateFrame { final TemplateFrame parent; + private final boolean isInnerScope; private final int id; private final Map hashtagReplacements = new HashMap<>(); final float fuel; private float fuelCost; + private final boolean isTransparentForHashtag; + private final boolean isTransparentForFuel; public static TemplateFrame makeBase(int id, float fuel) { - return new TemplateFrame(null, id, fuel, 0.0f); + return new TemplateFrame(null, false, id, fuel, 0.0f, false, false); } public static TemplateFrame make(TemplateFrame parent, int id) { - return new TemplateFrame(parent, id, parent.fuel - parent.fuelCost, Template.DEFAULT_FUEL_COST); + float fuel = parent.fuel - parent.fuelCost; + return new TemplateFrame(parent, false, id, fuel, Template.DEFAULT_FUEL_COST, false, false); } - private TemplateFrame(TemplateFrame parent, int id, float fuel, float fuelCost) { + public static TemplateFrame makeInnerScope(TemplateFrame parent, + boolean isTransparentForHashtag, + boolean isTransparentForFuel) { + // We keep the id of the parent, so that we have the same dollar replacements. + // And we subtract no fuel, but forward the cost. + return new TemplateFrame(parent, true, parent.id, parent.fuel, parent.fuelCost, + isTransparentForHashtag, isTransparentForFuel); + } + + private TemplateFrame(TemplateFrame parent, + boolean isInnerScope, + int id, + float fuel, + float fuelCost, + boolean isTransparentForHashtag, + boolean isTransparentForFuel) { this.parent = parent; + this.isInnerScope = isInnerScope; this.id = id; this.fuel = fuel; this.fuelCost = fuelCost; + this.isTransparentForHashtag = isTransparentForHashtag; + this.isTransparentForFuel = isTransparentForFuel; } public String $(String name) { @@ -78,8 +136,15 @@ class TemplateFrame { if (!Renderer.isValidHashtagOrDollarName(key)) { throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'."); } - if (hashtagReplacements.putIfAbsent(key, value) != null) { - throw new RendererException("Duplicate hashtag replacement for #" + key); + String previous = findHashtagReplacementInScopes(key); + if (previous != null) { + throw new RendererException("Duplicate hashtag replacement for #" + key + ". " + + "previous: " + previous + ", new: " + value); + } + if (isTransparentForHashtag) { + parent.addHashtagReplacement(key, value); + } else { + hashtagReplacements.put(key, value); } } @@ -87,13 +152,27 @@ class TemplateFrame { if (!Renderer.isValidHashtagOrDollarName(key)) { throw new RendererException("Is not a valid hashtag replacement name: '" + key + "'."); } - if (hashtagReplacements.containsKey(key)) { - return hashtagReplacements.get(key); + String value = findHashtagReplacementInScopes(key); + if (value != null) { + return value; } throw new RendererException("Missing hashtag replacement for #" + key); } + private String findHashtagReplacementInScopes(String key) { + if (hashtagReplacements.containsKey(key)) { + return hashtagReplacements.get(key); + } + if (!isInnerScope) { + return null; + } + return parent.findHashtagReplacementInScopes(key); + } + void setFuelCost(float fuelCost) { this.fuelCost = fuelCost; + if (isTransparentForFuel) { + parent.setFuelCost(fuelCost); + } } } diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java index 47262f152d4..ffbfcfdf2d0 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TemplateToken.java @@ -49,7 +49,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return zeroArgs.instantiate(); } @@ -74,7 +74,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return oneArgs.instantiate(arg1); } @@ -104,7 +104,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return twoArgs.instantiate(arg1, arg2); } @@ -138,7 +138,7 @@ public sealed abstract class TemplateToken implements Token } @Override - public TemplateBody instantiate() { + public ScopeToken instantiate() { return threeArgs.instantiate(arg1, arg2, arg3); } @@ -150,7 +150,7 @@ public sealed abstract class TemplateToken implements Token } } - abstract TemplateBody instantiate(); + abstract ScopeToken instantiate(); @FunctionalInterface interface ArgumentVisitor { diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java index 0e9f9b272c5..6e9d5f7650a 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/Token.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/Token.java @@ -24,16 +24,25 @@ package compiler.lib.template_framework; /** - * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * The {@link Template#scope} and {@link Hook#anchor} are given a list of tokens, which are either * {@link Token}s or {@link String}s or some permitted boxed primitives. */ public sealed interface Token permits StringToken, - TemplateToken, - TemplateToken.ZeroArgs, - TemplateToken.OneArg, - TemplateToken.TwoArgs, - TemplateToken.ThreeArgs, - HookAnchorToken, - HookInsertToken, - AddNameToken, - NothingToken {} + TemplateToken, + TemplateToken.ZeroArgs, + TemplateToken.OneArg, + TemplateToken.TwoArgs, + TemplateToken.ThreeArgs, + HookAnchorToken, + HookInsertToken, + HookIsAnchoredToken, + AddNameToken, + NameSampleToken, + NameForEachToken, + NamesToListToken, + NameCountToken, + NameHasAnyToken, + LetToken, + ScopeToken, + ScopeTokenImpl, + SetFuelCostToken {} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java index 0c335bd4fb8..bee6246bdc5 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/TokenParser.java @@ -31,7 +31,7 @@ import java.util.List; * Helper class for {@link Token}, to keep the parsing methods package private. * *

- * The {@link Template#body} and {@link Hook#anchor} are given a list of tokens, which are either + * The {@link Template#scope} and {@link Hook#anchor} are given a list of tokens, which are either * {@link Token}s or {@link String}s or some permitted boxed primitives. These are then parsed * and all non-{@link Token}s are converted to {@link StringToken}s. The parsing also flattens * {@link List}s. diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java index 360937c8f7f..43ab16af415 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Expression.java @@ -33,7 +33,7 @@ import java.util.stream.Collectors; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; /** * {@link Expression}s model Java expressions, that have a list of arguments with specified @@ -357,7 +357,7 @@ public class Expression { } tokens.add(strings.getLast()); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( tokens )); return template.asToken(); diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java index 46a9d5bbabe..c0db3d51545 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -33,7 +33,7 @@ import compiler.lib.generators.RestrictableGenerator; import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; /** * The {@link PrimitiveType} models Java's primitive types, and provides a set @@ -190,7 +190,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType { * @return a TemplateToken that holds all the {@code LibraryRNG} class. */ public static TemplateToken generateLibraryRNG() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ public static class LibraryRNG { private static final Random RANDOM = Utils.getRandomInstance(); diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java index 5194b75af43..a9db9285b78 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/TestFrameworkClass.java @@ -30,7 +30,7 @@ import compiler.lib.ir_framework.TestFramework; import compiler.lib.compile_framework.CompileFramework; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; /** @@ -51,7 +51,7 @@ public final class TestFrameworkClass { private TestFrameworkClass() {} /** - * This method renders a list of {@code testTemplateTokens} into the body of a class + * This method renders a list of {@code testTemplateTokens} into the scope of a class * and generates a {@code main} method which launches the {@link TestFramework} * to run the generated tests. * @@ -81,7 +81,7 @@ public final class TestFrameworkClass { final Set imports, final String classpath, final List testTemplateTokens) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("packageName", packageName), let("className", className), let("classpath", classpath), @@ -96,7 +96,7 @@ public final class TestFrameworkClass { public class #className { // --- CLASS_HOOK insertions start --- """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ // --- CLASS_HOOK insertions end --- public static void main(String[] vmFlags) { @@ -108,7 +108,7 @@ public final class TestFrameworkClass { // --- LIST OF TESTS start --- """, testTemplateTokens - ), + )), """ // --- LIST OF TESTS end --- } diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java index 62e474ecb2c..5d20ce659b9 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestAliasingFuzzer.java @@ -61,7 +61,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.generators.Generators; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; @@ -333,7 +333,7 @@ public class TestAliasingFuzzer { } public TemplateToken index(String invar0, String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("con", con), let("ivScale", ivScale), let("invar0Scale", invar0Scale), @@ -349,7 +349,7 @@ public class TestAliasingFuzzer { // MemorySegment need to be long-addressed, otherwise there can be int-overflow // in the index, and that prevents RangeCheck Elimination and Vectorization. public TemplateToken indexLong(String invar0, String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("con", con), let("ivScale", ivScale), let("invar0Scale", invar0Scale), @@ -365,7 +365,7 @@ public class TestAliasingFuzzer { // Mirror the IndexForm from the generator to the test. public static TemplateToken generateIndexForm() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ private static final Random RANDOM = Utils.getRandomInstance(); @@ -610,7 +610,7 @@ public class TestAliasingFuzzer { for (int i = 0; i < indexFormNames.length; i++) { indexFormNames[i] = $("index" + i); } - return body( + return scope( """ // --- $test start --- """, @@ -662,7 +662,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateArrayField(String name, MyType type) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / type.byteSize()), let("name", name), let("type", type), @@ -676,7 +676,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateMemorySegmentField(String name, MyType type) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / type.byteSize()), let("byteSize", containerByteSize), let("name", name), @@ -698,7 +698,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIndexField(String name, IndexForm form) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("name", name), let("form", form.generate()), """ @@ -709,7 +709,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestFields(String[] invarRest, String[] containerNames, String[] indexFormNames) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("ivType", isLongIvType ? "long" : "int"), """ // invarRest fields: @@ -741,7 +741,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerInitArray(String name) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / containerElementType.byteSize()), let("name", name), """ @@ -753,7 +753,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerInitMemorySegment(String name) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("size", containerByteSize / containerElementType.byteSize()), let("name", name), """ @@ -765,7 +765,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerInit(String[] containerNames) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Init containers from original data: """, @@ -784,7 +784,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerAliasingAssignment(int i, String name1, String name2, String iterations) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("i", i), let("name1", name1), let("name2", name2), @@ -798,7 +798,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateContainerAliasing(String[] containerNames, String iterations) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Container aliasing: """, @@ -832,7 +832,7 @@ public class TestAliasingFuzzer { if (accessIndexForm.length != 2) { throw new RuntimeException("not yet implemented"); } - var templateSplitRanges = Template.make(() -> body( + var templateSplitRanges = Template.make(() -> scope( let("size", size), """ int middle = RANDOM.nextInt(#size / 3, #size * 2 / 3); @@ -865,7 +865,7 @@ public class TestAliasingFuzzer { """ )); - var templateWholeRanges = Template.make(() -> body( + var templateWholeRanges = Template.make(() -> scope( let("size", size), """ var r0 = new IndexForm.Range(0, #size); @@ -873,7 +873,7 @@ public class TestAliasingFuzzer { """ )); - var templateRandomRanges = Template.make(() -> body( + var templateRandomRanges = Template.make(() -> scope( let("size", size), """ int lo0 = RANDOM.nextInt(0, #size * 3 / 4); @@ -883,7 +883,7 @@ public class TestAliasingFuzzer { """ )); - var templateSmallOverlapRanges = Template.make(() -> body( + var templateSmallOverlapRanges = Template.make(() -> scope( // Idea: same size ranges, with size "range". A small overlap, // so that bad runtime checks would create wrong results. let("size", size), @@ -907,7 +907,7 @@ public class TestAliasingFuzzer { // -> safe with rnd = size/10 )); - var templateAnyRanges = Template.make(() -> body( + var templateAnyRanges = Template.make(() -> scope( switch(RANDOM.nextInt(4)) { case 0 -> templateSplitRanges.asToken(); case 1 -> templateWholeRanges.asToken(); @@ -917,7 +917,7 @@ public class TestAliasingFuzzer { } )); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Generate ranges: """, @@ -941,7 +941,7 @@ public class TestAliasingFuzzer { // We want there to be at least 1000 iterations. final int minIvRange = ivStrideAbs * 1000; - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("containerByteSize", containerByteSize), """ // Compute loop bounds and loop invariants. @@ -949,7 +949,7 @@ public class TestAliasingFuzzer { int ivHi = ivLo + #containerByteSize; """, IntStream.range(0, indexFormNames.length).mapToObj(i -> - Template.make(() -> body( + Template.make(() -> scope( let("i", i), let("form", indexFormNames[i]), """ @@ -990,7 +990,7 @@ public class TestAliasingFuzzer { """, IntStream.range(0, indexFormNames.length).mapToObj(i1 -> IntStream.range(0, i1).mapToObj(i2 -> - Template.make(() -> body( + Template.make(() -> scope( let("i1", i1), let("i2", i2), // i1 < i2 or i1 > i2 @@ -1021,7 +1021,7 @@ public class TestAliasingFuzzer { private TemplateToken generateCallMethod(String output, String methodName, String containerPrefix) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("output", output), let("methodName", methodName), "var #output = #methodName(", @@ -1034,7 +1034,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRules() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( switch (containerKind) { case ContainerKind.ARRAY -> generateIRRulesArray(); @@ -1094,7 +1094,7 @@ public class TestAliasingFuzzer { // Regular array-accesses are vectorized quite predictably, and we can create nice // IR rules - even for cases where we do not expect vectorization. private TemplateToken generateIRRulesArray() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("T", containerElementType.letter()), switch (accessScenario) { case COPY_LOAD_STORE -> @@ -1145,7 +1145,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRulesMemorySegmentAtIndex() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Unfortunately, there are some issues that prevent RangeCheck elimination. // The cases are currently quite unpredictable, so we cannot create any IR @@ -1158,7 +1158,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRulesMemorySegmentLongAdrStride() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Unfortunately, there are some issues that prevent RangeCheck elimination. // The cases are currently quite unpredictable, so we cannot create any IR @@ -1169,7 +1169,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateIRRulesMemorySegmentLongAdrScale() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ // Unfortunately, there are some issues that prevent RangeCheck elimination. // The cases are currently quite unpredictable, so we cannot create any IR @@ -1180,7 +1180,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestMethod(String methodName, String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("methodName", methodName), let("containerElementType", containerElementType), let("ivStrideAbs", ivStrideAbs), @@ -1230,7 +1230,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestLoopIterationArray(String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("type", containerElementType), switch (accessScenario) { case COPY_LOAD_STORE -> @@ -1245,7 +1245,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestLoopIterationMemorySegmentAtIndex(String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("type0", accessType[0]), let("type1", accessType[1]), let("type0Layout", accessType[0].layout()), @@ -1265,7 +1265,7 @@ public class TestAliasingFuzzer { } private TemplateToken generateTestLoopIterationMemorySegmentLongAdr(String[] invarRest) { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( let("type0", accessType[0]), let("type1", accessType[1]), let("type0Layout", accessType[0].layout()), diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java index c5a4528f63d..784f1ded065 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestAdvanced.java @@ -43,7 +43,7 @@ import compiler.lib.generators.RestrictableGenerator; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; /** @@ -96,7 +96,7 @@ public class TestAdvanced { // - The GOLD value is computed at the beginning, hopefully by the interpreter. // - The test method is eventually compiled, and the values are verified by the // check method. - var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> body( + var testTemplate = Template.make("typeName", "operator", "generator", (String typeName, String operator, MyGenerator generator) -> scope( let("con1", generator.next()), let("con2", generator.next()), """ @@ -116,7 +116,7 @@ public class TestAdvanced { )); // Template for the Class. - var classTemplate = Template.make("types", (List types) -> body( + var classTemplate = Template.make("types", (List types) -> scope( let("classpath", comp.getEscapedClassPathOfCompiledClasses()), """ package p.xyz; diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java index c21d2492fc7..6a0a2d3786a 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestExpressions.java @@ -40,7 +40,7 @@ import java.util.Set; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import compiler.lib.template_framework.library.Expression; import compiler.lib.template_framework.library.Operations; @@ -78,7 +78,7 @@ public class TestExpressions { // precision results from some operators. We only compare the results if we know that the // result is deterministically the same. TemplateToken expressionToken = expression.asToken(expression.argumentTypes.stream().map(t -> t.con()).toList()); - return body( + return scope( let("returnType", expression.returnType), """ @Test diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java index a04a5771cb4..b1f5f74e682 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestPrimitiveTypes.java @@ -41,7 +41,8 @@ import java.util.HashMap; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; import static compiler.lib.template_framework.Template.dataNames; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; @@ -77,7 +78,7 @@ public class TestPrimitiveTypes { Map tests = new HashMap<>(); // The boxing tests check if we can autobox with "boxedTypeName". - var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + var boxingTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope( let("CON1", type.con()), let("CON2", type.con()), let("Boxed", type.boxedTypeName()), @@ -99,7 +100,7 @@ public class TestPrimitiveTypes { } // Integral and Float types have a size. Also test if "isFloating" is correct. - var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> body( + var integralFloatTemplate = Template.make("name", "type", (String name, PrimitiveType type) -> scope( let("size", type.byteSize()), let("isFloating", type.isFloating()), """ @@ -129,27 +130,31 @@ public class TestPrimitiveTypes { // Finally, test the type by creating some DataNames (variables), and sampling // from them. There should be no cross-over between the types. - var variableTemplate = Template.make("type", (PrimitiveType type) -> body( + // IMPORTANT: since we are adding the DataName via an inserted Template, we + // must chose a "transparentScope", so that the DataName escapes. If we + // instead chose "scope", the test would fail, because it later + // finds no DataNames when we sample. + var variableTemplate = Template.make("type", (PrimitiveType type) -> transparentScope( let("CON", type.con()), - addDataName($("var"), type, MUTABLE), + addDataName($("var"), type, MUTABLE), // escapes the Template """ #type $var = #CON; """ )); - var sampleTemplate = Template.make("type", (PrimitiveType type) -> body( - let("var", dataNames(MUTABLE).exactOf(type).sample().name()), + var sampleTemplate = Template.make("type", (PrimitiveType type) -> scope( let("CON", type.con()), + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("var"), """ #var = #CON; """ )); - var namesTemplate = Template.make(() -> body( + var namesTemplate = Template.make(() -> scope( """ public static void test_names() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( Collections.nCopies(10, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(type -> Hooks.METHOD_HOOK.insert(variableTemplate.asToken(type)) @@ -161,7 +166,7 @@ public class TestPrimitiveTypes { Collections.nCopies(10, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList() ) - ), + )), """ } """ @@ -172,7 +177,7 @@ public class TestPrimitiveTypes { // Test runtime random value generation with LibraryRNG // Runtime random number generation of a given primitive type can be very helpful // when writing tests that require random inputs. - var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> body( + var libraryRNGWithTypeTemplate = Template.make("type", (PrimitiveType type) -> scope( """ { // Fill an array with 1_000 random values. Every type has at least 2 values, @@ -196,7 +201,7 @@ public class TestPrimitiveTypes { """ )); - var libraryRNGTemplate = Template.make(() -> body( + var libraryRNGTemplate = Template.make(() -> scope( // Make sure we instantiate the LibraryRNG class. PrimitiveType.generateLibraryRNG(), // Now we can use it inside the test. @@ -213,7 +218,7 @@ public class TestPrimitiveTypes { // Finally, put all the tests together in a class, and invoke all // tests from the main method. - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ package p.xyz; diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java index e06671ca951..c8afb34e423 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestSimple.java @@ -34,7 +34,7 @@ package template_framework.examples; import compiler.lib.compile_framework.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; public class TestSimple { @@ -61,7 +61,7 @@ public class TestSimple { // Generate a source Java file as String public static String generate() { // Create a Template with two arguments. - var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> body( + var template = Template.make("arg1", "arg2", (Integer arg1, String arg2) -> scope( """ package p.xyz; public class InnerTest { diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java index faa05b29d82..ed542180bad 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestTutorial.java @@ -43,7 +43,9 @@ import compiler.lib.template_framework.Hook; import compiler.lib.template_framework.TemplateBinding; import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.StructuralName; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; +import static compiler.lib.template_framework.Template.hashtagScope; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.$; import static compiler.lib.template_framework.Template.fuel; @@ -68,13 +70,14 @@ public class TestTutorial { comp.addJavaSourceCode("p.xyz.InnerTest2", generateWithTemplateArguments()); comp.addJavaSourceCode("p.xyz.InnerTest3", generateWithHashtagAndDollarReplacements()); comp.addJavaSourceCode("p.xyz.InnerTest3b", generateWithHashtagAndDollarReplacements2()); + comp.addJavaSourceCode("p.xyz.InnerTest3c", generateWithHashtagAndDollarReplacements3()); comp.addJavaSourceCode("p.xyz.InnerTest4", generateWithCustomHooks()); comp.addJavaSourceCode("p.xyz.InnerTest5", generateWithLibraryHooks()); comp.addJavaSourceCode("p.xyz.InnerTest6", generateWithRecursionAndBindingsAndFuel()); comp.addJavaSourceCode("p.xyz.InnerTest7", generateWithDataNamesSimple()); comp.addJavaSourceCode("p.xyz.InnerTest8", generateWithDataNamesForFieldsAndVariables()); - comp.addJavaSourceCode("p.xyz.InnerTest9a", generateWithDataNamesAndScopes1()); - comp.addJavaSourceCode("p.xyz.InnerTest9b", generateWithDataNamesAndScopes2()); + comp.addJavaSourceCode("p.xyz.InnerTest9a", generateWithScopes1()); + comp.addJavaSourceCode("p.xyz.InnerTest9b", generateWithScopes2()); comp.addJavaSourceCode("p.xyz.InnerTest10", generateWithDataNamesForFuzzing()); comp.addJavaSourceCode("p.xyz.InnerTest11", generateWithStructuralNamesForMethods()); @@ -91,6 +94,7 @@ public class TestTutorial { comp.invoke("p.xyz.InnerTest2", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest3", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest3b", "main", new Object[] {}); + comp.invoke("p.xyz.InnerTest3c", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest4", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest5", "main", new Object[] {}); comp.invoke("p.xyz.InnerTest6", "main", new Object[] {}); @@ -105,9 +109,9 @@ public class TestTutorial { // This example shows the use of various Tokens. public static String generateWithListOfTokens() { // A Template is essentially a function / lambda that produces a - // token body, which is a list of Tokens that are concatenated. - var templateClass = Template.make(() -> body( - // The "body" method is filled by a sequence of "Tokens". + // scope, which contains a list of Tokens that are concatenated. + var templateClass = Template.make(() -> scope( + // The "scope" arguments are a sequence of "Tokens". // These can be Strings and multi-line Strings, but also // boxed primitives. """ @@ -141,14 +145,14 @@ public class TestTutorial { // This example shows the use of Templates, with and without arguments. public static String generateWithTemplateArguments() { // A Template with no arguments. - var templateHello = Template.make(() -> body( + var templateHello = Template.make(() -> scope( """ System.out.println("Hello"); """ )); // A Template with a single Integer argument. - var templateCompare = Template.make("arg", (Integer arg) -> body( + var templateCompare = Template.make("arg", (Integer arg) -> scope( "System.out.println(", arg, ");\n", // capture arg via lambda argument "System.out.println(#arg);\n", // capture arg via hashtag replacement "System.out.println(#{arg});\n", // capture arg via hashtag replacement with brackets @@ -156,7 +160,7 @@ public class TestTutorial { // argument values into Strings. However, since these are not (yet) // available, the Template Framework provides two alternative ways of // formatting Strings: - // 1) By appending to the comma-separated list of Tokens passed to body(). + // 1) By appending to the comma-separated list of Tokens passed to scope(). // Appending as a Token works whenever one has a reference to the Object // in Java code. But often, this is rather cumbersome and looks awkward, // given all the additional quotes and commands required. Hence, it @@ -180,7 +184,7 @@ public class TestTutorial { // A Template that creates the body of the Class and main method, and then // uses the two Templates above inside it. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -204,8 +208,16 @@ public class TestTutorial { // Note: hashtag replacements are a workaround for the missing string templates. // If we had string templates, we could just capture the typed lambda // arguments, and use them directly in the String via string templating. + // + // Important: hashtag replacements are always constrained to a single template + // and are not available in any nested templates. Hashtag replacements + // are only there to facilitate string templating within the limited + // scope of a template. You may consider it like a "local variable" + // for code generation purposes only. + // If you need to pass some value to a nested Template, consider using + // a Template argument, and capturing that Template argument. public static String generateWithHashtagAndDollarReplacements() { - var template1 = Template.make("x", (Integer x) -> body( + var template1 = Template.make("x", (Integer x) -> scope( // We have the "#x" hashtag replacement from the argument capture above. // Additionally, we can define "#con" as a hashtag replacement from let: let("con", 3 * x), @@ -219,29 +231,27 @@ public class TestTutorial { """ )); - var template2 = Template.make("x", (Integer x) -> + var template2 = Template.make("x", (Integer x) -> scope( // Sometimes it can be helpful to not just create a hashtag replacement // with let, but also to capture the variable to use it as lambda parameter. - let("y", 11 * x, y -> - body( - """ - System.out.println("T2: #x, #y"); - """, - template1.asToken(y) - ) - ) - ); + let("y", 11 * x, y -> scope( + """ + System.out.println("T2: #x, #y"); + """, + template1.asToken(y) + )) + )); // This template generates an int variable and assigns it a value. // Together with template4, we see that each template has a unique renaming // for a $-name replacement. - var template3 = Template.make("name", "value", (String name, Integer value) -> body( + var template3 = Template.make("name", "value", (String name, Integer value) -> scope( """ int #name = #value; // Note: $var is not #name """ )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( """ // We will define the variable $var: """, @@ -252,7 +262,7 @@ public class TestTutorial { """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( // The Template Framework API only guarantees that every Template use // has a unique ID. When using the Templates, all we need is that // variables from different Template uses do not conflict. But it can @@ -300,7 +310,7 @@ public class TestTutorial { // "INT_CON" and "LONG_CON". public static String generateWithHashtagAndDollarReplacements2() { // Let us define some final static variables of a specific type. - var template1 = Template.make("type", (String type) -> body( + var template1 = Template.make("type", (String type) -> scope( // The type (e.g. "int") is lower case, let us create the upper case "INT_CON" from it. let("TYPE", type.toUpperCase()), """ @@ -309,7 +319,7 @@ public class TestTutorial { )); // Let's write a simple class to demonstrate that this works, i.e. produces compilable code. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -331,50 +341,221 @@ public class TestTutorial { return templateClass.render(); } + // We already have used "scope" multiple times, but not explained it yet. + // So far, we have seen "scope" mostly in the context of Template scopes, but they + // can be used in many contexts as we will see below. They can also be used on + // their own and in the use of "let", as we will show right now. + // + // Scopes are even more relevant for DataNames and Structural names. + // See: generateWithDataNamesForFieldsAndVariables + // See: generateWithScopes1 + // See: generateWithScopes2 + public static String generateWithHashtagAndDollarReplacements3() { + + var template1 = Template.make(() -> scope( + // We can use scopes to limit the liveness of hashtag replacements. + scope( + let("x", 3), // does not escape + """ + static int v1_3 = #x; + """ + ), + scope( + let("x", 5), // does not escape + """ + static int v1_5 = #x; + """ + ), + // Using "scope" does not just limit the liveness / availability + // of hashtag replacements, but also of DataNames, StructuralNames, + // and setFuelCost. We can use "hashtagScope" to only limit hashtag + // replacements. + hashtagScope( + let("x", 7), // does not escape + """ + static int v1_7 = #x; + """ + ), + // Using "transparentScope" means the scope is transparent, and the hashtag + // replacements escape the scope. + transparentScope( + let("x", 11), // escapes the "transparentScope". + """ + static int v1_11a = #x; + """ + ), + // The hashtag replacement from the "transparentScope" escaped, and is + // still available. + """ + static int v1_11b = #x; + """ + )); + + var template2 = Template.make("x", (Integer x) -> scope( + // We can map a list of values to a list of scopes. Using a scope that is + // non-transparent for hashtag replacements means that we can reuse the same + // hashtag key when looping / streaming over multiple values. + List.of(3, 5, 7).stream().map(y -> scope( + let("y", y), // does not escape -> allows reuse of hashtag key "y". + """ + static int v2_#{x}_#{y} = #x * #y; + """ + )).toList() + )); + + var template3 = Template.make("x", (Integer x) -> scope( + // When using a "let" that captures the value in a lambda argument, we have + // to choose what kind of scope we generate. In most cases "scope" or + // "hashtagScope" are the best, because they limit the hashtag replacement + // of "y" to the same scope as the lambda argument. + let("y", x * 11, y -> scope( + """ + static int v3a_#{x} = #y; + """ + )), + // But in rare cases, we may want "y" and some nested "z" to escape. + let("y", x * 11, y -> transparentScope( + let("z", y * 2), + """ + static int v3b_#{x} = #y - #z; + """ + )), + // Because of the "transparentScope", "y" and "z" have escaped. + """ + static int v3c_#{x} = #y - #z; + """, + // Side note: We can simulate a "let" without lambda with a "let" that has a lambda. + // That is not very useful, but a similar trick can be used for other queries, that + // only provide a lambda version, and where we only want to use the hashtag replacement. + // + // Below we see the standard use of "let", where we add a hashtag replacement for "a" + // for the rest of the enclosing scope. We then also use a lambda version of "let" + // with a transparent scope, which means that "b" escapes that scope and is also + // available in the enclosing scope. In the implementation of the framework, we + // actually use a "transparentScope", so the standard "let" is really just syntactic + // sugar for the lambda "let" with "transparentScope". + let("a", -x), + let("b", -x, b -> transparentScope()), + """ + static int v3d_#{x} = #a + #b; + """ + )); + + // Let's write a simple class to demonstrate that this works, i.e. produces compilable code. + var templateClass = Template.make(() -> scope( + """ + package p.xyz; + + public class InnerTest3c { + """, + template1.asToken(), + template2.asToken(1), + template2.asToken(2), + template3.asToken(2), + """ + public static void main() { + if (v1_3 != 3 || + v1_5 != 5 || + v1_7 != 7 || + v1_11a != 11 || + v1_11b != 11 || + v2_1_3 != 3 || + v2_1_5 != 5 || + v2_1_7 != 7 || + v2_2_3 != 6 || + v2_2_5 != 10 || + v2_2_7 != 14 || + v3a_2 != 22 || + v3b_2 != -22 || + v3c_2 != -22 || + v3d_2 != -4) { + throw new RuntimeException("Wrong result!"); + } + } + } + """ + )); + + // Render templateClass to String. + return templateClass.render(); + } + // In this example, we look at the use of Hooks. They allow us to reach back, to outer // scopes. For example, we can reach out from inside a method body to a hook anchored at // the top of the class, and insert a field. + // + // When we insert to a hook, we have 3 relevant scopes: + // - Anchor scope: the scope defined at "hook.anchor(scope(...))" + // - Insertion scope: the scope that is inserted, see "hook.insert(scope(...))" + // - Caller scope: the scope we insert from. + // + // The choice of transparency of an insertion scope (the scope that is inserted) is quite + // important. A common use case is to insert a DataName. + // See: generateWithDataNamesForFieldsAndVariables + // See: generateWithScopes1 + // See: generateWithScopes2 public static String generateWithCustomHooks() { // We can define a custom hook. // Note: generally we prefer using the pre-defined CLASS_HOOK and METHOD_HOOK from the library, // whenever possible. See also the example after this one. var myHook = new Hook("MyHook"); - var template1 = Template.make("name", "value", (String name, Integer value) -> body( + var template1 = Template.make("name", "value", (String name, Integer value) -> scope( """ public static int #name = #value; """ )); - var template2 = Template.make("x", (Integer x) -> body( + var template2 = Template.make("x", (Integer x) -> scope( """ - // Let us go back to where we anchored the hook with anchor() and define a field named $field there. - // Note that in the Java code we have not defined anchor() on the hook, yet. But since it's a lambda - // expression, it is not evaluated, yet! Eventually, anchor() will be evaluated before insert() in - // this example. + // Let us go back to where we anchored the hook with anchor() (see 'templateClass' below) and define a field + // named $field1 there. """, - myHook.insert(template1.asToken($("field"), x)), + myHook.insert(scope( // <- insertion scope + """ + public static int $field1 = #x; + """ + // Note that we were able to use the dollar replacement "$field1" and the hashtag + // replacement "#x" inside the scope that is inserted to myHook. + )), """ - System.out.println("$field: " + $field); - if ($field != #x) { throw new RuntimeException("Wrong value!"); } + // We can do that by inserting a scope like above, or by inserting a template, like below. + // + // Which method is used is up to the user. General guidance is if the same code may also + // be inserted elsewhere, one should lean towards inserting templates. But in many cases + // it is nice to see the inserted code directly, and to be able to use hashtag replacements + // from the outer scope directly, without having to route them via template arguments, + // as we have to do below. + """, + // <- caller scope + myHook.insert(template1.asToken($("field2"), x)), + """ + System.out.println("$field1: " + $field1); + System.out.println("$field2: " + $field2); + if ($field1 != #x) { throw new RuntimeException("Wrong value 1!"); } + if ($field2 != #x) { throw new RuntimeException("Wrong value 2!"); } """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest4 { """, // We anchor a Hook outside the main method, but inside the Class. - // Anchoring a Hook creates a scope, spanning the braces of the - // "anchor" call. Any Hook.insert that happens inside this scope - // goes to the top of that scope. - myHook.anchor( + // Anchoring a Hook requires the definition of an inner scope, + // aka the "anchor scope", spanning the braces of the "anchor" call. + // Any Hook.insert that happens inside this scope goes to the top of + // that scope. + myHook.anchor(scope( // <- anchor scope // Any Hook.insert goes here. // - // <-------- field_X = 5 ------------------+ - // <-------- field_Y = 7 -------------+ | + // <-------- field1_X = 5 -----------------+ + // field2_X = 5 | + // | + // <-------- field1_Y = 7 ------------+ | + // field2_Y = 7 | | // | | """ public static void main() { @@ -384,7 +565,7 @@ public class TestTutorial { """ } """ - ), // The Hook scope ends here. + )), // The Hook scope ends here. """ } """ @@ -408,46 +589,54 @@ public class TestTutorial { // there is a class scope inside another class scope. Similarly, we can nest lambda bodies // inside method bodies, so also METHOD_HOOK can be used in such a "re-entrant" way. public static String generateWithLibraryHooks() { - var templateStaticField = Template.make("name", "value", (String name, Integer value) -> body( - """ - static { System.out.println("Defining static field #name"); } - public static int #name = #value; - """ - )); - var templateLocalVariable = Template.make("name", "value", (String name, Integer value) -> body( - """ - System.out.println("Defining local variable #name"); - int #name = #value; - """ - )); - - var templateMethodBody = Template.make(() -> body( + var templateMethodBody = Template.make(() -> scope( """ // Let's define a local variable $var and a static field $field. - """, - Hooks.CLASS_HOOK.insert(templateStaticField.asToken($("field"), 5)), - Hooks.METHOD_HOOK.insert(templateLocalVariable.asToken($("var"), 11)), - """ + // Since we are inserting them at the anchor before the code below, + // they will already be available: System.out.println("$field: " + $field); System.out.println("$var: " + $var); + """, + Hooks.CLASS_HOOK.insert(scope( + """ + static { System.out.println("Defining static field $field"); } + public static int $field = 5; + """ + )), + Hooks.METHOD_HOOK.insert(scope( + """ + System.out.println("Defining local variable $var"); + int $var = 11; + """ + )), + """ if ($field * $var != 55) { throw new RuntimeException("Wrong value!"); } """ + // Note: we have used "scope" for the "insert" scope. This is fine here as + // we are only working with code and hashtags, but not with DataNames. If + // we were to also "addDataName" inside the insert scope, we would have to + // make sure that the scope is transparent for DataNames, so that they can + // escape to the anchor scope, and can be available to the caller of the + // insertion. One might want to use "transparentScope" for the insertion scope. + // See: generateWithDataNamesForFieldsAndVariables. + // See: generateWithScopes1 + // See: generateWithScopes2 )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest5 { """, // Class Hook for fields. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, // Method Hook for local variables, and earlier computations. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ // This is the beginning of the "main" method body. System.out.println("Welcome to main!"); @@ -457,7 +646,7 @@ public class TestTutorial { System.out.println("Going to call other..."); other(); """ - ), + )), """ } @@ -465,7 +654,7 @@ public class TestTutorial { """, // Have a separate method hook for other, so that it can insert // its own local variables. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ System.out.println("Welcome to other!"); """, @@ -473,11 +662,11 @@ public class TestTutorial { """ System.out.println("Done with other."); """ - ), + )), """ } """ - ), + )), """ } """ @@ -493,7 +682,7 @@ public class TestTutorial { public static String generateWithRecursionAndBindingsAndFuel() { // Binding allows the use of template1 inside of template1, via the binding indirection. var binding1 = new TemplateBinding>(); - var template1 = Template.make("depth", (Integer depth) -> body( + var template1 = Template.make("depth", (Integer depth) -> scope( let("fuel", fuel()), """ System.out.println("At depth #depth with fuel #fuel."); @@ -514,7 +703,7 @@ public class TestTutorial { )); binding1.bind(template1); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -561,6 +750,12 @@ public class TestTutorial { // // To get started, we show an example where all DataNames have the same type, and where // all Names are mutable. For simplicity, our type represents the primitive int type. + // + // Note: the template library contains a lot of types that model the Java types, + // such as primitive types ({@code PrimitiveType}). The following examples + // give insight into how those types work. If you are just interested in + // how to use the predefined types, then you can find other examples in + // {@code examples/TestPrimitiveTypes.java}. private record MySimpleInt() implements DataName.Type { // The type is only subtype of itself. This is relevant when sampling or weighing // DataNames, because we do not just sample from the given type, but also its subtypes. @@ -577,31 +772,25 @@ public class TestTutorial { private static final MySimpleInt mySimpleInt = new MySimpleInt(); // In this example, we generate 3 fields, and add their names to the - // current scope. In a nested Template, we can then sample one of these - // DataNames, which gives us one of the fields. We increment that randomly - // chosen field. At the end, we print all three fields. + // current scope. We can then sample some of these DataNames, which + // gives us one of those fields each time. We increment those randomly + // chosen fields. At the end, we print all three fields. public static String generateWithDataNamesSimple() { - var templateMain = Template.make(() -> body( - // Sample a random DataName, i.e. field, and assign its name to - // the hashtag replacement "#f". - // We are picking a mutable DataName, because we are not just - // reading but also writing to the field. - let("f", dataNames(MUTABLE).exactOf(mySimpleInt).sample().name()), - """ - // Let us now sample a random field #f, and increment it. - #f += 42; - """ - )); - - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( // Let us define the names for the three fields. - // We can then sample from these names in a nested Template. // We make all DataNames mutable, and with the same weight of 1, // so that they have equal probability of being sampled. // Note: the default weight is 1, so we can also omit the weight. + // + // Also note that DataNames are only available once they are defined: + // + // Nothing defined, yet: dataNames() = {} addDataName($("f1"), mySimpleInt, MUTABLE, 1), + // Only now dataNames() contains f1: dataNames() = {f1} addDataName($("f2"), mySimpleInt, MUTABLE, 1), + // dataNames() = {f1, f2} addDataName($("f3"), mySimpleInt, MUTABLE), // omit weight, default is 1. + // dataNames() = {f1, f2, f3} """ package p.xyz; @@ -612,18 +801,35 @@ public class TestTutorial { public static int $f3 = 0; public static void main() { - // Let us now call the nested template that samples - // a random field and increments it. + // Let us now sample a random field and assign its name to + // the hashtag replacement "a". """, - templateMain.asToken(), + dataNames(MUTABLE).exactOf(mySimpleInt).sampleAndLetAs("a"), + """ + // We can now access the field, and increment it. + #a += 42; + // If we are also interested in the type of the field, we can do: + """, + dataNames(MUTABLE).exactOf(mySimpleInt).sampleAndLetAs("b", "bType"), + """ + #b += 7; + // In some cases, we may want to capture the DataName directly, which + // requires capturing the value in a lambda that creates an inner scope: + """, + dataNames(MUTABLE).exactOf(mySimpleInt).sample((DataName dn) -> scope( + let("c", dn.name()), + """ + #c += 12; + """ + )), """ // Now, we can print all three fields, and see which - // one was incremented. + // ones were incremented. System.out.println("f1: " + $f1); System.out.println("f2: " + $f2); System.out.println("f3: " + $f3); - // We have two zeros, and one 42. - if ($f1 + $f2 + $f3 != 42) { throw new RuntimeException("wrong result!"); } + // Make sure they add up to the correct sum. + if ($f1 + $f2 + $f3 != 42 + 7 + 12) { throw new RuntimeException("wrong result!"); } } } """ @@ -662,8 +868,15 @@ public class TestTutorial { public static String generateWithDataNamesForFieldsAndVariables() { // Define a static field. - var templateStaticField = Template.make("type", (DataName.Type type) -> body( - addDataName($("field"), type, MUTABLE), + // Note: it is very important that we use a "transparentScope" for the template here, + // so that the DataName can escape to outer scopes, so that it is available to + // everything that follows the DataName definition in the outer scope. + // (We could also use "hashtagScope", since those are also transparent for + // names. But it is not great style, because template boundaries are + // non-transparent for hashtags and setFuelCost anyway. So we might as + // well just use "transparentScope".) + var templateStaticField = Template.make("type", (DataName.Type type) -> transparentScope( + addDataName($("field"), type, MUTABLE), // escapes template because of "transparentScope" // Note: since we have overridden MyPrimitive::toString, we can use // the type directly as "#type" in the template, which then // gets hashtag replaced with "int" or "long". @@ -673,8 +886,10 @@ public class TestTutorial { )); // Define a local variable. - var templateLocalVariable = Template.make("type", (DataName.Type type) -> body( - addDataName($("var"), type, MUTABLE), + // Note: it is very important that we use a "transparentScope" for the template here, + // so that the DataName can escape to outer scopes. + var templateLocalVariable = Template.make("type", (DataName.Type type) -> transparentScope( + addDataName($("var"), type, MUTABLE), // escapes template because of "transparentScope" """ #type $var = 0; """ @@ -682,8 +897,8 @@ public class TestTutorial { // Sample a random field or variable, from those that are available at // the current scope. - var templateSample = Template.make("type", (DataName.Type type) -> body( - let("name", dataNames(MUTABLE).exactOf(type).sample().name()), + var templateSample = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("name"), // Note: we could also sample from MUTABLE_OR_IMMUTABLE, we will // cover the concept of mutability in an example further down. """ @@ -692,18 +907,36 @@ public class TestTutorial { )); // Check how many fields and variables are available at the current scope. - var templateStatus = Template.make(() -> body( - let("ints", dataNames(MUTABLE).exactOf(myInt).count()), - let("longs", dataNames(MUTABLE).exactOf(myLong).count()), - // Note: we could also count the MUTABLE_OR_IMMUTABLE, we will - // cover the concept of mutability in an example further down. + var templateStatus = Template.make(() -> scope( + dataNames(MUTABLE).exactOf(myInt).count(ints -> scope( + dataNames(MUTABLE).exactOf(myLong).count(longs -> scope( + // We have now captured the values as Java variables, and can + // use them inside the scope in some "let" definitions. + let("ints", ints), + let("longs", longs), + // Note: we could also count the MUTABLE_OR_IMMUTABLE, we will + // cover the concept of mutability in an example further down. + """ + System.out.println("Status: #ints ints, #longs longs."); + """ + )) + )), + // In a real code generation case, we would most likely want to + // have the count as a Java variable so that one can take conditional + // action based on the value. For that we have to capture the count + // with a lambda and inner scope as above. If we only need to have + // the count as a hashtag replacement, we can also use the following + // trick: + dataNames(MUTABLE).exactOf(myInt).count(c -> transparentScope(let("ints", c))), + dataNames(MUTABLE).exactOf(myLong).count(c -> transparentScope(let("longs", c))), + // Because of the "transparentScope", the hashtag replacements escape. """ System.out.println("Status: #ints ints, #longs longs."); """ )); // Definition of the main method body. - var templateMain = Template.make(() -> body( + var templateMain = Template.make(() -> scope( """ System.out.println("Starting inside main..."); """, @@ -736,7 +969,7 @@ public class TestTutorial { // Definition of another method's body. It is in the same class // as the main method, so it has access to the same static fields. - var templateOther = Template.make(() -> body( + var templateOther = Template.make(() -> scope( """ System.out.println("Starting inside other..."); """, @@ -755,19 +988,19 @@ public class TestTutorial { )); // Finally, we put it all together in a class. - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest8 { """, // Class Hook for fields. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, // Method Hook for local variables. - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ // This is the beginning of the "main" method body. System.out.println("Welcome to main!"); @@ -777,7 +1010,7 @@ public class TestTutorial { System.out.println("Going to call other..."); other(); """ - ), + )), """ } @@ -785,7 +1018,7 @@ public class TestTutorial { """, // Have a separate method hook for other, where it could insert // its own local variables (but happens not to). - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( """ System.out.println("Welcome to other!"); """, @@ -793,11 +1026,11 @@ public class TestTutorial { """ System.out.println("Done with other."); """ - ), + )), """ } """ - ), + )), """ } """ @@ -807,83 +1040,119 @@ public class TestTutorial { return templateClass.render(); } - // Let us have a closer look at how DataNames interact with scopes created by - // Templates and Hooks. Additionally, we see how the execution order of the - // lambdas and token evaluation affects the availability of DataNames. - // - // We inject the results directly into verification inside the code, so it - // is relatively simple to see what the expected results are. - // - // For simplicity, we define a simple "list" function. It collects all - // field and variable names, and immediately returns the comma separated - // list of the names. We can use that to visualize the available names - // at any point. - public static String listNames() { - return "{" + String.join(", ", dataNames(MUTABLE).exactOf(myInt).toList() - .stream().map(DataName::name).toList()) + "}"; - } + public static String generateWithScopes1() { - // Even simpler: count the available variables and return the count immediately. - public static int countNames() { - return dataNames(MUTABLE).exactOf(myInt).count(); - } - - // Having defined these helper methods, let us start with the first example. - // You should start reading this example bottom-up, starting at - // templateClass, then going to templateMain and last to templateInner. - public static String generateWithDataNamesAndScopes1() { - - var templateInner = Template.make(() -> body( - // We just got called from the templateMain. All tokens from there - // are already evaluated, so "v1" is now available: - let("l1", listNames()), + // For the examples below, we need a convenient way of asserting the state + // of the available DataNames. + var templateVerify = Template.make("count", "hasAny", "toList", (Integer count, Boolean hasAny, String toList) -> scope( + dataNames(MUTABLE).exactOf(myInt).count(c -> transparentScope(let("count2", c))), + dataNames(MUTABLE).exactOf(myInt).hasAny(h -> transparentScope(let("hasAny2", h))), + dataNames(MUTABLE).exactOf(myInt).toList(list -> transparentScope( + let("toList2", String.join(", ", list.stream().map(DataName::name).toList())) + )), """ - if (!"{v1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{v1}' but was '#l1'"); } + if (#count != #count2 || + #hasAny != #hasAny2 || + !"#toList".equals("#toList2")) { + throw new RuntimeException("verify failed"); + } """ )); - var templateMain = Template.make(() -> body( - // So far, no names were defined. We expect "c1" to be zero. - let("c1", countNames()), - """ - if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } - """, - // We now add a local variable "v1" to the scope of this templateMain. - // This only generates a token, and does not immediately add the name. - // The name is only added once we evaluate the tokens, and arrive at - // this particular token. + var templateMain = Template.make(() -> scope( + "// Start with nothing:\n", + templateVerify.asToken(0, false, ""), + "// Add v1:\n", addDataName("v1", myInt, MUTABLE), - // We count again with "c2". The variable "v1" is at this point still - // in token form, hence it is not yet made available while executing - // the template lambda of templateMain. - let("c2", countNames()), + "int v1 = 1;\n", + "// Check that it is visible:\n", + templateVerify.asToken(1, true, "v1"), + "// Add v2:\n", + addDataName("v2", myInt, MUTABLE), + "int v2 = 2;\n", + "// Check that both are visible:\n", + templateVerify.asToken(2, true, "v1, v2"), + + "// Create a local scope:\n", + "{\n", scope( // for consistency, we model the code and template scope together. + "// Add v3:\n", + addDataName("v3", myInt, MUTABLE), + "int v3 = 3;\n", + "// Check that all are visible:\n", + templateVerify.asToken(3, true, "v1, v2, v3") + ), "}\n", + "// But after the scope, v3 is no longer available:\n", + templateVerify.asToken(2, true, "v1, v2"), + + "// Now let's create a list of variables.\n", + List.of(4, 5, 6).stream().map(i -> hashtagScope( + // The hashtagScope allows hashtag replacements to be local, + // and DataNames to escape, so we can use them afterwards. + let("i", i), + addDataName("v" + i, myInt, MUTABLE), + "int v#i = #i;\n" + )).toList(), + templateVerify.asToken(5, true, "v1, v2, v4, v5, v6"), + + "// Let's multiply all variables by a factor of 2, using forEach:\n", + dataNames(MUTABLE).exactOf(myInt).forEach(dn -> scope( + let("v", dn.name()), + "#v *= 2;\n" + )), + "// We can also capture the name (v) and type of the DataName:\n", + dataNames(MUTABLE).exactOf(myInt).forEach("v", "type", dn -> scope( + "#v *= 2;\n" + )), + "// Yet another option is using toList, but here that is more cumbersome:\n", + dataNames(MUTABLE).exactOf(myInt).toList(list -> scope( + list.stream().map(dn -> scope( + let("v", dn.name()), + "#v *= 2;\n" + )).toList() + )), + """ - if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } + // We verify the result again. """, - // But now we call an inner Template. This is added as a TemplateToken. - // This means it is not evaluated immediately, but only once we evaluate - // the tokens. By that time, all tokens from above are already evaluated - // and we see that "v1" is available. - templateInner.asToken() + templateVerify.asToken(5, true, "v1, v2, v4, v5, v6"), + """ + if (v1 != 1 * 8 || + v2 != 2 * 8 || + v4 != 4 * 8 || + v5 != 5 * 8 || + v6 != 6 * 8) { + throw new RuntimeException("wrong value!"); + } + """, + + "// Let us copy each variable:\n", + dataNames(MUTABLE).exactOf(myInt).forEach("v", "type", dn -> hashtagScope( + // Note that we need a hashtagScope here, so that we can reuse "v" and + // "type" as hashtag replacements in each iteration, but still let the + // copied DataNames escape. + addDataName(dn.name() + "_copy", myInt, MUTABLE), + "#type #{v}_copy = #v;\n" + )), + templateVerify.asToken(10, true, "v1, v2, v4, v5, v6, v1_copy, v2_copy, v4_copy, v5_copy, v6_copy") )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest9a { """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( templateMain.asToken() - ), + )), """ } """ - ), + )), """ } """ @@ -893,111 +1162,129 @@ public class TestTutorial { return templateClass.render(); } - // Now that we understand this simple example, we go to a more complicated one - // where we use Hook.insert. Just as above, you should read this example - // bottom-up, starting at templateClass. - public static String generateWithDataNamesAndScopes2() { + public static String generateWithScopes2() { - var templateFields = Template.make(() -> body( - // We were just called from templateMain. But the code is not - // generated into the main scope, rather into the class scope - // out in templateClass. - // Let us now add a field "f1". - addDataName("f1", myInt, MUTABLE), - // And let's also generate the code for it. + // In this section, we will look at some subtle facts about the behavior of + // transparent scopes around hook insertion. This is intended for expert users + // so feel free to skip it until you extensively use hook insertion. + // More info can also be found in the Javadocs of the Hook class. + + // Helper method to check that the expected DataNames are available. + var templateVerify = Template.make("toList", (String toList) -> scope( + dataNames(MUTABLE).exactOf(myInt).toList(list -> transparentScope( + let("toList2", String.join(", ", list.stream().map(DataName::name).toList())) + )), """ - public static int f1 = 42; - """, - // But why is this DataName now available inside the scope of - // templateInner? Does that not mean that "f1" escapes this - // templateFields here? Yes it does! - // For normal template nesting, the names do not escape the - // scope of the nested template. But this here is no normal - // template nesting, rather it is an insertion into a Hook, - // and we treat those differently. We make the scope of the - // inserted templateFields transparent, so that any added - // DataNames are added to the scope of the Hook we just - // inserted into, i.e. the CLASS_HOOK. This is very important, - // if we did not make that scope transparent, we could not - // add any DataNames to the class scope anymore, and we could - // not add any fields that would be available in the class - // scope. - Hooks.METHOD_HOOK.anchor( - // We now create a separate scope. This one is not the - // template scope from above, and it is not transparent. - // Hence, "f2" will not be available outside of this + if (!"#toList".equals("#toList2")) { + throw new RuntimeException("verify failed: '#toList' vs '#toList2'."); + } + """ + )); + + var myHook = new Hook("MyHook"); + + var templateMain = Template.make(() -> scope( + // Start with nothing: + templateVerify.asToken(""), + addDataName("v1", myInt, MUTABLE), + templateVerify.asToken("v1"), + // Non-transparent hook anchor: + myHook.anchor(scope( + templateVerify.asToken("v1"), + addDataName("v2", myInt, MUTABLE), + templateVerify.asToken("v1, v2"), + // Insert a non-transparent scope: nothing escapes. + myHook.insert(scope( + // Note that at the anchor insertion point, v2 is not yet + // available, because it is added after the anchoring. + templateVerify.asToken("v1"), + let("x3", 42), + addDataName("v3", myInt, MUTABLE), + templateVerify.asToken("v1, v3") + )), + // Note: x3 and v3 do not escape. + let("x3", 7), // we can define it again. + templateVerify.asToken("v1, v2"), + // While not letting hashtags escape may be helpful, it is probably + // not very helpful if the DataNames don't escape. For example, if + // we are inserting some variable at an outer scope, we would like + // it to be available for the rest of the scope. + // That's where a transparent scope can be helpful. + myHook.insert(transparentScope( + // At the anchoring, still only v1 is available. + templateVerify.asToken("v1"), + let("x4", 42), // escapes to caller scope + addDataName("v4", myInt, MUTABLE), // escapes to anchor scope + templateVerify.asToken("v1, v4") + )), + // x4 escapes to the caller out here, and not to the anchor scope. + "// x4: #x4\n", + // And v4 escapes to the anchor scope, which is available from here too. + // Interesting detail: the ordering in the list indicates that v1 + // is from the outermost scope of the template, v4 is located at the + // anchor scope, and v2 is located inside the anchor scope, and + // thus comes last. + templateVerify.asToken("v1, v4, v2"), + // In most practical cases we probably don't want to let the hashtag + // escape, because they just represent something local. So we can + // use a hashtagScope, so that DataNames escape, but not hashtags. + myHook.insert(hashtagScope( + // Note: both v1 and v4 are now available at the anchoring, since + // v1 was inserted outside the anchoring scope, and v4 was just + // inserted to the anchoring scope. + templateVerify.asToken("v1, v4"), + let("x5", 42), // local, does not escape. + addDataName("v5", myInt, MUTABLE), // escapes to anchor scope + templateVerify.asToken("v1, v4, v5") + )), + let("x5", 7), // we can define it again. + templateVerify.asToken("v1, v4, v5, v2") + )), + // We left the non-transparent anchoring scope which does not let anything escape + templateVerify.asToken("v1"), + + // Let us now do something that probably should never be done. But still + // we want to demonstrate it for educational purposes: transparent anchoring + // scopes. + myHook.anchor(transparentScope( + templateVerify.asToken("v1"), + // For one, this means that DataName escape the scope directly. + addDataName("v6", myInt, MUTABLE), + templateVerify.asToken("v1, v6"), + // But also if we insert to the anchoring scope, DataNames don't just + // escape from the anchoring scope, but further out to the enclosing // scope. - addDataName("f2", myInt, MUTABLE), - // And let's also generate the code for it. - """ - public static int f2 = 666; - """ - // Similarly, if we called any nested Template here, - // and added DataNames inside, this would happen inside - // nested scopes that are not transparent. If one wanted - // to add names to the CLASS_HOOK from there, one would - // have to do another Hook.insert, and make sure that - // the names are added from the outermost scope of that - // inserted Template, because only that outermost scope - // is transparent to the CLASS_HOOK. - ) + myHook.insert(transparentScope( + templateVerify.asToken("v1, v6"), + addDataName("v7", myInt, MUTABLE), + templateVerify.asToken("v1, v6, v7") + )), + templateVerify.asToken("v1, v6, v7"), + let("x6", 42) // escapes the anchor scope + )), + // We left the transparent anchoring scope which lets the DataNames and + // hashtags escape. + "// x6: #x6\n", + templateVerify.asToken("v1, v6, v7") )); - var templateInner = Template.make(() -> body( - // We just got called from the templateMain. All tokens from there - // are already evaluated, so there should be some fields available. - // We can see field "f1". - let("l1", listNames()), - """ - if (!"{f1}".equals("#l1")) { throw new RuntimeException("l1 should have been '{f1}' but was '#l1'"); } - """ - // Now go and have a look at templateFields, to understand how that - // field was added, and why not any others. - )); - - var templateMain = Template.make(() -> body( - // So far, no names were defined. We expect "c1" to be zero. - let("c1", countNames()), - """ - if (#c1 != 0) { throw new RuntimeException("c1 was not zero but #c1"); } - """, - // We would now like to add some fields to the class scope, out in the - // templateClass. This creates a token, which is only evaluated after - // the completion of the templateMain lambda. Before you go and look - // at templateFields, just assume that it does add some fields, and - // continue reading in templateMain. - Hooks.CLASS_HOOK.insert(templateFields.asToken()), - // We count again with "c2". The fields we wanted to add above are not - // yet available, because the token is not yet evaluated. Hence, we - // still only count zero names. - let("c2", countNames()), - """ - if (#c2 != 0) { throw new RuntimeException("c2 was not zero but #c2"); } - """, - // Now we call an inner Template. This also creates a token, and so it - // is not evaluated immediately. And by the time this token is evaluated - // the tokens from above are already evaluated, and so the fields should - // be available. Go have a look at templateInner now. - templateInner.asToken() - )); - - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; public class InnerTest9b { """, - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( """ public static void main() { """, - Hooks.METHOD_HOOK.anchor( + Hooks.METHOD_HOOK.anchor(scope( templateMain.asToken() - ), + )), """ } """ - ), + )), """ } """ @@ -1006,8 +1293,6 @@ public class TestTutorial { // Render templateClass to String. return templateClass.render(); } - - // There are two more concepts to understand more deeply with DataNames. // // One is the use of mutable and immutable DataNames. @@ -1045,38 +1330,40 @@ public class TestTutorial { private static final List myClassList = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); public static String generateWithDataNamesForFuzzing() { - var templateStaticField = Template.make("type", "mutable", (DataName.Type type, Boolean mutable) -> body( - addDataName($("field"), type, mutable ? MUTABLE : IMMUTABLE), + // This template is used to insert a DataName (field) into an outer scope, hence we must use + // "transparentScope" instead of "scope". + var templateStaticField = Template.make("type", "mutable", (DataName.Type type, Boolean mutable) -> transparentScope( + addDataName($("field"), type, mutable ? MUTABLE : IMMUTABLE), // Escapes the template. let("isFinal", mutable ? "" : "final"), """ public static #isFinal #type $field = new #type(); """ )); - var templateLoad = Template.make("type", (DataName.Type type) -> body( + var templateLoad = Template.make("type", (DataName.Type type) -> scope( // We only load from the field, so we do not need a mutable one, // we can load from final and non-final fields. // We want to find any field from which we can read the value and store // it in our variable v of our given type. Hence, we can take a field // of the given type or any subtype thereof. - let("field", dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(type).sample().name()), + dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(type).sampleAndLetAs("field"), """ #type $v = #field; System.out.println("#field: " + $v); """ )); - var templateStore = Template.make("type", (DataName.Type type) -> body( + var templateStore = Template.make("type", (DataName.Type type) -> scope( // We are storing to a field, so it better be non-final, i.e. mutable. // We want to store a new instance of our given type to a field. This // field must be of the given type or any supertype. - let("field", dataNames(MUTABLE).supertypeOf(type).sample().name()), + dataNames(MUTABLE).supertypeOf(type).sampleAndLetAs("field"), """ #field = new #type(); """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -1094,7 +1381,7 @@ public class TestTutorial { // addDataName is restricted to the scope of the templateStaticField. But // with the insertion to CLASS_HOOK, the addDataName goes through the scope // of the templateStaticField out to the scope of the CLASS_HOOK. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( myClassList.stream().map(c -> (Object)Hooks.CLASS_HOOK.insert(templateStaticField.asToken(c, true)) ).toList(), @@ -1118,7 +1405,7 @@ public class TestTutorial { """ } """ - ), + )), """ } """ @@ -1126,7 +1413,6 @@ public class TestTutorial { // Render templateClass to String. return templateClass.render(); - } // "DataNames" are useful for modeling fields and variables. They hold data, @@ -1165,9 +1451,9 @@ public class TestTutorial { public static String generateWithStructuralNamesForMethods() { // Define a method, which takes two ints, returns the result of op. - var templateMethod = Template.make("op", (String op) -> body( + var templateMethod = Template.make("op", (String op) -> transparentScope( // Register the method name, so we can later sample. - addStructuralName($("methodName"), myMethodType), + addStructuralName($("methodName"), myMethodType), // escapes the template because of "transparentScope" """ public static int $methodName(int a, int b) { return a #op b; @@ -1175,16 +1461,16 @@ public class TestTutorial { """ )); - var templateSample = Template.make(() -> body( + var templateSample = Template.make(() -> scope( // Sample a random method, and retrieve its name. - let("methodName", structuralNames().exactOf(myMethodType).sample().name()), + structuralNames().exactOf(myMethodType).sampleAndLetAs("methodName"), """ System.out.println("Calling #methodName with inputs 7 and 11"); System.out.println(" result: " + #methodName(7, 11)); """ )); - var templateClass = Template.make(() -> body( + var templateClass = Template.make(() -> scope( """ package p.xyz; @@ -1192,7 +1478,7 @@ public class TestTutorial { // Let us define some methods that we can sample from later. """, // We must anchor a CLASS_HOOK here, and insert the method definitions to that hook. - Hooks.CLASS_HOOK.anchor( + Hooks.CLASS_HOOK.anchor(scope( // If we directly nest the templateMethod, then the addStructuralName goes to the nested // scope, and is not available at the class scope, i.e. it is not visible // for sampleStructuralName outside of the templateMethod. @@ -1218,7 +1504,7 @@ public class TestTutorial { } } """ - ) + )) )); // Render templateClass to String. diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java index 813f2976ef2..01b49db2c01 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/examples/TestWithTestFrameworkClass.java @@ -43,7 +43,7 @@ import compiler.lib.generators.Generators; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; import compiler.lib.template_framework.library.Hooks; @@ -82,7 +82,7 @@ public class TestWithTestFrameworkClass { // Generate a source Java file as String public static String generate(CompileFramework comp) { // A simple template that adds a comment. - var commentTemplate = Template.make(() -> body( + var commentTemplate = Template.make(() -> scope( """ // Comment inserted from test method to class hook. """ @@ -103,7 +103,7 @@ public class TestWithTestFrameworkClass { // - The test method makes use of hashtag replacements (#con2 and #op). // - The Check method verifies the results of the test method with the // GOLD value. - var testTemplate = Template.make("op", (String op) -> body( + var testTemplate = Template.make("op", (String op) -> scope( let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()), let("con1", Generators.G.ints().next()), let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()), diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java index 2dac740dd93..b34538c39c1 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestExpression.java @@ -38,7 +38,7 @@ import java.util.Set; import compiler.lib.template_framework.DataName; import compiler.lib.template_framework.Template; import compiler.lib.template_framework.TemplateToken; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import compiler.lib.template_framework.library.CodeGenerationDataNameType; import compiler.lib.template_framework.library.Expression; @@ -93,7 +93,7 @@ public class TestExpression { Expression e3 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, "]"); Expression e4 = Expression.make(myTypeA, "[", myTypeA, ",", myTypeB, ",", myTypeA1, ",", myTypeA, "]"); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1.toString(), "yy\n", "xx", e2.toString(), "yy\n", "xx", e3.toString(), "yy\n", @@ -141,7 +141,7 @@ public class TestExpression { Expression e3e1 = e3.nest(0, e1); Expression e4e5 = e4.nest(1, e5); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1e1.toString(), "yy\n", "xx", e2e1.toString(), "yy\n", "xx", e3e1.toString(), "yy\n", @@ -184,7 +184,7 @@ public class TestExpression { // Alternating pattern Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5); - var template = Template.make(() -> body( + var template = Template.make(() -> scope( "xx", e1e2.toString(), "yy\n", "xx", e1ex.toString(), "yy\n", "xx", e1e4.toString(), "yy\n", diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java index fe267a3ff63..577542e085b 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestFormat.java @@ -39,7 +39,7 @@ import compiler.lib.compile_framework.*; import compiler.lib.generators.*; import compiler.lib.verify.*; import compiler.lib.template_framework.Template; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; import static compiler.lib.template_framework.Template.let; public class TestFormat { @@ -84,7 +84,7 @@ public class TestFormat { private static String generate(List list) { // Generate 2 "get" methods, one that formats via "let" (hashtag), the other via direct token. - var template1 = Template.make("info", (FormatInfo info) -> body( + var template1 = Template.make("info", (FormatInfo info) -> scope( let("id", info.id()), let("type", info.type()), let("value", info.value()), @@ -95,7 +95,7 @@ public class TestFormat { )); // For each FormatInfo in list, generate the "get" methods inside InnerTest class. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( """ package p.xyz; public class InnerTest { diff --git a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java index 35d020b6080..9be74d232a7 100644 --- a/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java +++ b/test/hotspot/jtreg/testlibrary_tests/template_framework/tests/TestTemplate.java @@ -44,7 +44,11 @@ import compiler.lib.template_framework.StructuralName; import compiler.lib.template_framework.Hook; import compiler.lib.template_framework.TemplateBinding; import compiler.lib.template_framework.RendererException; -import static compiler.lib.template_framework.Template.body; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.transparentScope; +import static compiler.lib.template_framework.Template.nameScope; +import static compiler.lib.template_framework.Template.hashtagScope; +import static compiler.lib.template_framework.Template.setFuelCostScope; import static compiler.lib.template_framework.Template.$; import static compiler.lib.template_framework.Template.let; import static compiler.lib.template_framework.Template.fuel; @@ -121,41 +125,56 @@ public class TestTemplate { // The following tests all pass, i.e. have no errors during rendering. testSingleLine(); testMultiLine(); - testBodyTokens(); + testBasicTokens(); testWithOneArgument(); testWithTwoArguments(); testWithThreeArguments(); - testNested(); - testHookSimple(); + testNestedTemplates(); + testHookSimple1(); + testHookSimple2(); + testHookSimple3(); testHookIsAnchored(); testHookNested(); testHookWithNestedTemplates(); testHookRecursion(); testDollar(); - testLet(); + testLet1(); + testLet2(); testDollarAndHashtagBrackets(); testSelector(); testRecursion(); testFuel(); testFuelCustom(); + testFuelAndScopes(); + testDataNames0a(); + testDataNames0b(); + testDataNames0c(); + testDataNames0d(); testDataNames1(); testDataNames2(); testDataNames3(); testDataNames4(); testDataNames5(); + testDataNames6(); + testStructuralNames0(); testStructuralNames1(); testStructuralNames2(); + testStructuralNames3(); + testStructuralNames4(); + testStructuralNames5(); + testStructuralNames6(); testListArgument(); + testNestedScopes1(); + testNestedScopes2(); + testTemplateScopes(); + testHookAndScopes1(); + testHookAndScopes2(); + testHookAndScopes3(); // The following tests should all fail, with an expected exception and message. expectRendererException(() -> testFailingNestedRendering(), "Nested render not allowed."); expectRendererException(() -> $("name"), "A Template method such as"); - expectRendererException(() -> let("x","y"), "A Template method such as"); expectRendererException(() -> fuel(), "A Template method such as"); - expectRendererException(() -> setFuelCost(1.0f), "A Template method such as"); - expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), "A Template method such as"); - expectRendererException(() -> dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).sample(), "A Template method such as"); - expectRendererException(() -> (new Hook("abc")).isAnchored(), "A Template method such as"); expectRendererException(() -> testFailingDollarName1(), "Is not a valid '$' name: ''."); expectRendererException(() -> testFailingDollarName2(), "Is not a valid '$' name: '#abc'."); expectRendererException(() -> testFailingDollarName3(), "Is not a valid '$' name: 'abc#'."); @@ -178,20 +197,31 @@ public class TestTemplate { expectRendererException(() -> testFailingDollarHashtagName3(), "Is not a valid '#' replacement pattern: '#' in '#$name'."); expectRendererException(() -> testFailingDollarHashtagName4(), "Is not a valid '$' replacement pattern: '$' in '$#name'."); expectRendererException(() -> testFailingHook(), "Hook 'Hook1' was referenced but not found!"); - expectRendererException(() -> testFailingSample1(), "No variable: MUTABLE, subtypeOf(int), supertypeOf(int)."); + expectRendererException(() -> testFailingSample1a(), "No Name found for DataName.FilterdSet(MUTABLE, subtypeOf(int), supertypeOf(int))"); + expectRendererException(() -> testFailingSample1b(), "No Name found for StructuralName.FilteredSet( subtypeOf(StructuralA) supertypeOf(StructuralA))"); expectRendererException(() -> testFailingHashtag1(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag2(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag3(), "Duplicate hashtag replacement for #a"); expectRendererException(() -> testFailingHashtag4(), "Missing hashtag replacement for #a"); + expectRendererException(() -> testFailingHashtag5(), "Missing hashtag replacement for #a"); expectRendererException(() -> testFailingBinding1(), "Duplicate 'bind' not allowed."); expectRendererException(() -> testFailingBinding2(), "Cannot 'get' before 'bind'."); - expectIllegalArgumentException(() -> body(null), "Unexpected tokens: null"); - expectIllegalArgumentException(() -> body("x", null), "Unexpected token: null"); - expectIllegalArgumentException(() -> body(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> scope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> scope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> scope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> transparentScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> transparentScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> transparentScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> nameScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> nameScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> nameScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> hashtagScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> hashtagScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> hashtagScope(new Hook("Hook1")), "Unexpected token:"); + expectIllegalArgumentException(() -> setFuelCostScope(null), "Unexpected tokens: null"); + expectIllegalArgumentException(() -> setFuelCostScope("x", null), "Unexpected token: null"); + expectIllegalArgumentException(() -> setFuelCostScope(new Hook("Hook1")), "Unexpected token:"); Hook hook1 = new Hook("Hook1"); - expectIllegalArgumentException(() -> hook1.anchor(null), "Unexpected tokens: null"); - expectIllegalArgumentException(() -> hook1.anchor("x", null), "Unexpected token: null"); - expectIllegalArgumentException(() -> hook1.anchor(hook1), "Unexpected token:"); expectIllegalArgumentException(() -> testFailingAddDataName1(), "Unexpected mutability: MUTABLE_OR_IMMUTABLE"); expectIllegalArgumentException(() -> testFailingAddDataName2(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddDataName3(), "Unexpected weight: "); @@ -199,7 +229,8 @@ public class TestTemplate { expectIllegalArgumentException(() -> testFailingAddStructuralName1(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddStructuralName2(), "Unexpected weight: "); expectIllegalArgumentException(() -> testFailingAddStructuralName3(), "Unexpected weight: "); - expectUnsupportedOperationException(() -> testFailingSample2(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + expectUnsupportedOperationException(() -> testFailingSample2a(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); + expectUnsupportedOperationException(() -> testFailingSample2b(), "Must first call 'subtypeOf', 'supertypeOf', or 'exactOf'."); expectRendererException(() -> testFailingAddNameDuplication1(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication2(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication3(), "Duplicate name:"); @@ -208,16 +239,23 @@ public class TestTemplate { expectRendererException(() -> testFailingAddNameDuplication6(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication7(), "Duplicate name:"); expectRendererException(() -> testFailingAddNameDuplication8(), "Duplicate name:"); + expectRendererException(() -> testFailingScope1(), "Duplicate hashtag replacement for #x. previous: x1, new: x2"); + expectRendererException(() -> testFailingScope2(), "Duplicate hashtag replacement for #x. previous: x1, new: x2"); + expectRendererException(() -> testFailingScope3(), "Duplicate hashtag replacement for #x. previous: a, new: b"); + expectRendererException(() -> testFailingScope4(), "Duplicate hashtag replacement for #x. previous: a, new: b"); + expectRendererException(() -> testFailingScope5(), "Duplicate name:"); + expectRendererException(() -> testFailingScope6(), "Duplicate name:"); + expectRendererException(() -> testFailingScope7(), "Duplicate name:"); } public static void testSingleLine() { - var template = Template.make(() -> body("Hello World!")); + var template = Template.make(() -> scope("Hello World!")); String code = template.render(); checkEQ(code, "Hello World!"); } public static void testMultiLine() { - var template = Template.make(() -> body( + var template = Template.make(() -> scope( """ Code on more than a single line @@ -232,10 +270,10 @@ public class TestTemplate { checkEQ(code, expected); } - public static void testBodyTokens() { - // We can fill the body with Objects of different types, and they get concatenated. - // Lists get flattened into the body. - var template = Template.make(() -> body( + public static void testBasicTokens() { + // We can fill the scope with Objects of different types, and they get concatenated. + // Lists get flattened into the scope. + var template = Template.make(() -> scope( "start ", Integer.valueOf(1), 1, Long.valueOf(2), 2L, @@ -250,31 +288,31 @@ public class TestTemplate { public static void testWithOneArgument() { // Capture String argument via String name. - var template1 = Template.make("a", (String a) -> body("start #a end")); + var template1 = Template.make("a", (String a) -> scope("start #a end")); checkEQ(template1.render("x"), "start x end"); checkEQ(template1.render("a"), "start a end"); checkEQ(template1.render("" ), "start end"); // Capture String argument via typed lambda argument. - var template2 = Template.make("a", (String a) -> body("start ", a, " end")); + var template2 = Template.make("a", (String a) -> scope("start ", a, " end")); checkEQ(template2.render("x"), "start x end"); checkEQ(template2.render("a"), "start a end"); checkEQ(template2.render("" ), "start end"); // Capture Integer argument via String name. - var template3 = Template.make("a", (Integer a) -> body("start #a end")); + var template3 = Template.make("a", (Integer a) -> scope("start #a end")); checkEQ(template3.render(0 ), "start 0 end"); checkEQ(template3.render(22 ), "start 22 end"); checkEQ(template3.render(444), "start 444 end"); // Capture Integer argument via templated lambda argument. - var template4 = Template.make("a", (Integer a) -> body("start ", a, " end")); + var template4 = Template.make("a", (Integer a) -> scope("start ", a, " end")); checkEQ(template4.render(0 ), "start 0 end"); checkEQ(template4.render(22 ), "start 22 end"); checkEQ(template4.render(444), "start 444 end"); // Test Strings with backslashes: - var template5 = Template.make("a", (String a) -> body("start #a " + a + " end")); + var template5 = Template.make("a", (String a) -> scope("start #a " + a + " end")); checkEQ(template5.render("/"), "start / / end"); checkEQ(template5.render("\\"), "start \\ \\ end"); checkEQ(template5.render("\\\\"), "start \\\\ \\\\ end"); @@ -282,25 +320,25 @@ public class TestTemplate { public static void testWithTwoArguments() { // Capture 2 String arguments via String names. - var template1 = Template.make("a1", "a2", (String a1, String a2) -> body("start #a1 #a2 end")); + var template1 = Template.make("a1", "a2", (String a1, String a2) -> scope("start #a1 #a2 end")); checkEQ(template1.render("x", "y"), "start x y end"); checkEQ(template1.render("a", "b"), "start a b end"); checkEQ(template1.render("", "" ), "start end"); // Capture 2 String arguments via typed lambda arguments. - var template2 = Template.make("a1", "a2", (String a1, String a2) -> body("start ", a1, " ", a2, " end")); + var template2 = Template.make("a1", "a2", (String a1, String a2) -> scope("start ", a1, " ", a2, " end")); checkEQ(template2.render("x", "y"), "start x y end"); checkEQ(template2.render("a", "b"), "start a b end"); checkEQ(template2.render("", "" ), "start end"); // Capture 2 Integer arguments via String names. - var template3 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start #a1 #a2 end")); + var template3 = Template.make("a1", "a2", (Integer a1, Integer a2) -> scope("start #a1 #a2 end")); checkEQ(template3.render(0, 1 ), "start 0 1 end"); checkEQ(template3.render(22, 33 ), "start 22 33 end"); checkEQ(template3.render(444, 555), "start 444 555 end"); // Capture 2 Integer arguments via templated lambda arguments. - var template4 = Template.make("a1", "a2", (Integer a1, Integer a2) -> body("start ", a1, " ", a2, " end")); + var template4 = Template.make("a1", "a2", (Integer a1, Integer a2) -> scope("start ", a1, " ", a2, " end")); checkEQ(template4.render(0, 1 ), "start 0 1 end"); checkEQ(template4.render(22, 33 ), "start 22 33 end"); checkEQ(template4.render(444, 555), "start 444 555 end"); @@ -308,46 +346,46 @@ public class TestTemplate { public static void testWithThreeArguments() { // Capture 3 String arguments via String names. - var template1 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start #a1 #a2 #a3 end")); + var template1 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> scope("start #a1 #a2 #a3 end")); checkEQ(template1.render("x", "y", "z"), "start x y z end"); checkEQ(template1.render("a", "b", "c"), "start a b c end"); checkEQ(template1.render("", "", "" ), "start end"); // Capture 3 String arguments via typed lambda arguments. - var template2 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + var template2 = Template.make("a1", "a2", "a3", (String a1, String a2, String a3) -> scope("start ", a1, " ", a2, " ", a3, " end")); checkEQ(template1.render("x", "y", "z"), "start x y z end"); checkEQ(template1.render("a", "b", "c"), "start a b c end"); checkEQ(template1.render("", "", "" ), "start end"); // Capture 3 Integer arguments via String names. - var template3 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start #a1 #a2 #a3 end")); + var template3 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> scope("start #a1 #a2 #a3 end")); checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); // Capture 2 Integer arguments via templated lambda arguments. - var template4 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> body("start ", a1, " ", a2, " ", a3, " end")); + var template4 = Template.make("a1", "a2", "a3", (Integer a1, Integer a2, Integer a3) -> scope("start ", a1, " ", a2, " ", a3, " end")); checkEQ(template3.render(0, 1 , 2 ), "start 0 1 2 end"); checkEQ(template3.render(22, 33 , 44 ), "start 22 33 44 end"); checkEQ(template3.render(444, 555, 666), "start 444 555 666 end"); } - public static void testNested() { - var template1 = Template.make(() -> body("proton")); + public static void testNestedTemplates() { + var template1 = Template.make(() -> scope("proton")); - var template2 = Template.make("a1", "a2", (String a1, String a2) -> body( + var template2 = Template.make("a1", "a2", (String a1, String a2) -> scope( "electron #a1\n", "neutron #a2\n" )); - var template3 = Template.make("a1", "a2", (String a1, String a2) -> body( + var template3 = Template.make("a1", "a2", (String a1, String a2) -> scope( "Universe ", template1.asToken(), " {\n", template2.asToken("up", "down"), template2.asToken(a1, a2), "}\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( template3.asToken("low", "high"), "{\n", template3.asToken("42", "24"), @@ -374,19 +412,19 @@ public class TestTemplate { checkEQ(code, expected); } - public static void testHookSimple() { + public static void testHookSimple1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body("Hello\n")); + var template1 = Template.make(() -> scope("Hello\n")); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", - hook1.anchor( + hook1.anchor(scope( "World\n", // Note: "Hello" from the template below will be inserted // above "World" above. hook1.insert(template1.asToken()) - ), + )), "}" )); @@ -400,21 +438,85 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testHookSimple2() { + var hook1 = new Hook("Hook1"); + + var template2 = Template.make(() -> scope( + "{\n", + hook1.anchor(scope( + "World\n", + // Note: "Hello" from the scope below will be inserted + // above "World" above. + hook1.insert(scope( + "Hello\n" + )) + )), + "}" + )); + + String code = template2.render(); + String expected = + """ + { + Hello + World + }"""; + checkEQ(code, expected); + } + + public static void testHookSimple3() { + var hook1 = new Hook("Hook1"); + + // Ensure that insert inside insert really goes first. + var template = Template.make(() -> scope( + "{\n", + hook1.anchor(scope( + "Outer Insert\n" + )), + ">Anchor\n" + )), + "}" + )); + + String code = template.render(); + String expected = + """ + { + Inner Insert + Outer Insert + Anchor + }"""; + checkEQ(code, expected); + } + public static void testHookIsAnchored() { var hook1 = new Hook("Hook1"); - var template0 = Template.make(() -> body("isAnchored: ", hook1.isAnchored(), "\n")); + var template0 = Template.make(() -> scope("t0 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n")); - var template1 = Template.make(() -> body("Hello\n", template0.asToken())); + var template1 = Template.make(() -> scope("Hello\n", template0.asToken())); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), - hook1.anchor( + hook1.anchor(scope( "World\n", + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), - hook1.insert(template1.asToken()) - ), + hook1.insert(template1.asToken()), + hook1.insert(scope("Beautiful\n", template0.asToken())), + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n" + )), + "t2 isAnchored: ", hook1.isAnchored(a -> scope(a)), "\n", template0.asToken(), "}" )); @@ -423,12 +525,18 @@ public class TestTemplate { String expected = """ { - isAnchored: false + t2 isAnchored: false + t0 isAnchored: false Hello - isAnchored: true + t0 isAnchored: true + Beautiful + t0 isAnchored: true World - isAnchored: true - isAnchored: false + t2 isAnchored: true + t0 isAnchored: true + t2 isAnchored: true + t2 isAnchored: false + t0 isAnchored: false }"""; checkEQ(code, expected); } @@ -436,36 +544,41 @@ public class TestTemplate { public static void testHookNested() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); // Test nested use of hooks in the same template. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", - hook1.anchor(), // empty + hook1.anchor(scope()), // empty "zero\n", - hook1.anchor( + hook1.anchor(scope( template1.asToken("one"), template1.asToken("two"), hook1.insert(template1.asToken("intoHook1a")), hook1.insert(template1.asToken("intoHook1b")), + hook1.insert(scope("y 1 y\n")), + hook1.insert(scope("y 2 y\n")), template1.asToken("three"), - hook1.anchor( + hook1.anchor(scope( template1.asToken("four"), hook1.insert(template1.asToken("intoHook1c")), + hook1.insert(scope("y 3 y\n")), template1.asToken("five") - ), + )), template1.asToken("six"), - hook1.anchor(), // empty + hook1.anchor(scope()), // empty template1.asToken("seven"), hook1.insert(template1.asToken("intoHook1d")), + hook1.insert(scope("y 4 y\n")), template1.asToken("eight"), - hook1.anchor( + hook1.anchor(scope( template1.asToken("nine"), hook1.insert(template1.asToken("intoHook1e")), + hook1.insert(scope("y 5 y\n")), template1.asToken("ten") - ), + )), template1.asToken("eleven") - ), + )), "}" )); @@ -476,17 +589,22 @@ public class TestTemplate { zero x intoHook1a x x intoHook1b x + y 1 y + y 2 y x intoHook1d x + y 4 y x one x x two x x three x x intoHook1c x + y 3 y x four x x five x x six x x seven x x eight x x intoHook1e x + y 5 y x nine x x ten x x eleven x @@ -498,30 +616,30 @@ public class TestTemplate { var hook1 = new Hook("Hook1"); var hook2 = new Hook("Hook2"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); - var template2 = Template.make("b", (String b) -> body( + var template2 = Template.make("b", (String b) -> scope( "{\n", template1.asToken(b + "A"), hook1.insert(template1.asToken(b + "B")), hook2.insert(template1.asToken(b + "C")), template1.asToken(b + "D"), - hook1.anchor( + hook1.anchor(scope( template1.asToken(b + "E"), hook1.insert(template1.asToken(b + "F")), hook2.insert(template1.asToken(b + "G")), template1.asToken(b + "H"), - hook2.anchor( + hook2.anchor(scope( template1.asToken(b + "I"), hook1.insert(template1.asToken(b + "J")), hook2.insert(template1.asToken(b + "K")), template1.asToken(b + "L") - ), + )), template1.asToken(b + "M"), hook1.insert(template1.asToken(b + "N")), hook2.insert(template1.asToken(b + "O")), template1.asToken(b + "O") - ), + )), template1.asToken(b + "P"), hook1.insert(template1.asToken(b + "Q")), hook2.insert(template1.asToken(b + "R")), @@ -530,18 +648,18 @@ public class TestTemplate { )); // Test use of hooks across templates. - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "base-A\n", - hook1.anchor( + hook1.anchor(scope( "base-B\n", - hook2.anchor( + hook2.anchor(scope( "base-C\n", template2.asToken("sub-"), "base-D\n" - ), + )), "base-E\n" - ), + )), "base-F\n", "}\n" )); @@ -586,32 +704,32 @@ public class TestTemplate { public static void testHookRecursion() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x #a x\n")); - var template2 = Template.make("b", (String b) -> body( + var template2 = Template.make("b", (String b) -> scope( "<\n", template1.asToken(b + "A"), hook1.insert(template1.asToken(b + "B")), // sub-B is rendered before template2. template1.asToken(b + "C"), "inner-hook-start\n", - hook1.anchor( + hook1.anchor(scope( "inner-hook-end\n", template1.asToken(b + "E"), hook1.insert(template1.asToken(b + "E")), template1.asToken(b + "F") - ), + )), ">\n" )); // Test use of hooks across templates. - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "hook-start\n", - hook1.anchor( + hook1.anchor(scope( "hook-end\n", hook1.insert(template2.asToken("sub-")), "base-C\n" - ), + )), "base-D\n", "}\n" )); @@ -642,16 +760,16 @@ public class TestTemplate { public static void testDollar() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body("x $name #a x\n")); + var template1 = Template.make("a", (String a) -> scope("x $name #a x\n")); - var template2 = Template.make("a", (String a) -> body( + var template2 = Template.make("a", (String a) -> scope( "{\n", "y $name #a y\n", template1.asToken($("name")), "}\n" )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", "$name\n", "$name", "\n", @@ -660,11 +778,11 @@ public class TestTemplate { template1.asToken("name"), // does not capture -> literal "$name" template1.asToken("$name"), // does not capture -> literal "$name" template1.asToken($("name")), // capture replacement name "name_1" - hook1.anchor( + hook1.anchor(scope( "$name\n" - ), + )), "break\n", - hook1.anchor( + hook1.anchor(scope( "one\n", hook1.insert(template1.asToken($("name"))), "two\n", @@ -672,7 +790,7 @@ public class TestTemplate { "three\n", hook1.insert(template2.asToken($("name"))), "four\n" - ), + )), "}\n" )); @@ -704,10 +822,10 @@ public class TestTemplate { checkEQ(code, expected); } - public static void testLet() { + public static void testLet1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("a", (String a) -> body( + var template1 = Template.make("a", (String a) -> scope( "{\n", "y #a y\n", let("b", "<" + a + ">"), @@ -715,25 +833,25 @@ public class TestTemplate { "}\n" )); - var template2 = Template.make("a", (Integer a) -> let("b", a * 10, b -> - body( + var template2 = Template.make("a", (Integer a) -> scope( + let("b", a * 10, b -> scope( let("c", b * 3), "abc = #a #b #c\n" - ) + )) )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", let("x", "abc"), template1.asToken("alpha"), "break\n", "x1 = #x\n", - hook1.anchor( + hook1.anchor(transparentScope( // transparentScope allows hashtags to escape "x2 = #x\n", // leaks inside template1.asToken("beta"), let("y", "one"), "y1 = #y\n" - ), + )), "break\n", "y2 = #y\n", // leaks outside "break\n", @@ -766,8 +884,30 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testLet2() { + var template = Template.make(() -> scope( + "outer {\n", + let("x", "x1", x -> scope( + "x: #x ", x, ".\n" + )), + let("x", "x2"), // definition above is limited to its scope + "x: #x\n", + "} outer\n" + )); + + String code = template.render(); + String expected = + """ + outer { + x: x1 x1. + x: x2 + } outer + """; + checkEQ(code, expected); + } + public static void testDollarAndHashtagBrackets() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("xyz", "abc"), let("xyz_", "def"), let("xyz_klm", "ghi"), @@ -792,19 +932,19 @@ public class TestTemplate { } public static void testSelector() { - var template1 = Template.make("a", (String a) -> body( + var template1 = Template.make("a", (String a) -> scope( "<\n", "x #a x\n", ">\n" )); - var template2 = Template.make("a", (String a) -> body( + var template2 = Template.make("a", (String a) -> scope( "<\n", "y #a y\n", ">\n" )); - var template3 = Template.make("a", (Integer a) -> body( + var template3 = Template.make("a", (Integer a) -> scope( "[\n", "z #a z\n", // Select which template should be used: @@ -813,7 +953,7 @@ public class TestTemplate { "]\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "{\n", template3.asToken(-1), "break\n", @@ -865,7 +1005,7 @@ public class TestTemplate { // Binding allows use of template1 inside template1, via the Binding indirection. var binding1 = new TemplateBinding>(); - var template1 = Template.make("i", (Integer i) -> body( + var template1 = Template.make("i", (Integer i) -> scope( "[ #i\n", // We cannot yet use the template1 directly, as it is being defined. // So we use binding1 instead. @@ -874,7 +1014,7 @@ public class TestTemplate { )); binding1.bind(template1); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "{\n", // Now, we can use template1 normally, as it is already defined. template1.asToken(3), @@ -902,7 +1042,7 @@ public class TestTemplate { } public static void testFuel() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("f", fuel()), "<#f>\n" @@ -910,7 +1050,7 @@ public class TestTemplate { // Binding allows use of template2 inside template2, via the Binding indirection. var binding2 = new TemplateBinding>(); - var template2 = Template.make("i", (Integer i) -> body( + var template2 = Template.make("i", (Integer i) -> scope( let("f", fuel()), "[ #i #f\n", @@ -920,7 +1060,7 @@ public class TestTemplate { )); binding2.bind(template2); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "{\n", template2.asToken(3), "}\n" @@ -948,7 +1088,7 @@ public class TestTemplate { } public static void testFuelCustom() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( setFuelCost(2.0f), let("f", fuel()), @@ -957,7 +1097,7 @@ public class TestTemplate { // Binding allows use of template2 inside template2, via the Binding indirection. var binding2 = new TemplateBinding>(); - var template2 = Template.make("i", (Integer i) -> body( + var template2 = Template.make("i", (Integer i) -> scope( setFuelCost(3.0f), let("f", fuel()), @@ -968,7 +1108,7 @@ public class TestTemplate { )); binding2.bind(template2); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( setFuelCost(5.0f), let("f", fuel()), @@ -1002,46 +1142,277 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testFuelAndScopes() { + var readFuelTemplate = Template.make(() -> scope( + let("f", fuel()), + "<#f>\n" + )); + + var template = Template.make(() -> scope( + let("f", fuel()), + "{#f}\n", + readFuelTemplate.asToken(), + + "scope:\n", + setFuelCost(1.0f), + scope( + readFuelTemplate.asToken(), + setFuelCost(2.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "transparentScope:\n", + setFuelCost(4.0f), + transparentScope( + readFuelTemplate.asToken(), + setFuelCost(8.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "nameScope:\n", + setFuelCost(16.0f), + nameScope( + readFuelTemplate.asToken(), + setFuelCost(32.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "hashtagScope:\n", + setFuelCost(64.0f), + hashtagScope( + readFuelTemplate.asToken(), + setFuelCost(128.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken(), + + "setFuelCostScope:\n", + setFuelCost(256.0f), + setFuelCostScope( + readFuelTemplate.asToken(), + setFuelCost(512.0f), + readFuelTemplate.asToken() + ), + readFuelTemplate.asToken() + )); + + String code = template.render(1000.0f); + String expected = + """ + {1000.0f} + <990.0f> + scope: + <999.0f> + <998.0f> + <999.0f> + transparentScope: + <996.0f> + <992.0f> + <992.0f> + nameScope: + <984.0f> + <968.0f> + <968.0f> + hashtagScope: + <936.0f> + <872.0f> + <872.0f> + setFuelCostScope: + <744.0f> + <488.0f> + <744.0f> + """; + checkEQ(code, expected); + } + + public static void testDataNames0a() { + var template = Template.make(() -> scope( + // When a DataName is added, it is immediately available afterwards. + // This may seem trivial, but it requires that either both "add" and + // "sample" happen in lambda execution, or in token evaluation. + // Otherwise, one can float above the other, and lead to unintuitive + // behavior. + addDataName("x", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("v"), + "sample: #v." + )); + + String code = template.render(); + checkEQ(code, "sample: x."); + } + + public static void testDataNames0b() { + // Test that the scope keeps local DataNames only for the scope, but that + // we can see DataNames of outer scopes. + var template = Template.make(() -> scope( + // Outer scope DataName: + addDataName("outerInt", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + let("name1", dn.name()), + "sample: #name1.\n", + // We can also see the outer DataName: + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("name2"), + "sample: #name2.\n", + // Local DataName: + addDataName("innerLong", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myLong).sampleAndLetAs("name3"), + "sample: #name3.\n" + )), + // We can still see the outer scope DataName: + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("name4"), + "sample: #name4.\n", + // But we cannot see the DataNames that are local to the inner scope. + // So here, we will always see "outerLong", and never "innerLong". + addDataName("outerLong", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myLong).sampleAndLetAs("name5"), + "sample: #name5.\n" + )); + + String code = template.render(); + String expected = + """ + sample: outerInt. + sample: outerInt. + sample: innerLong. + sample: outerInt. + sample: outerLong. + """; + checkEQ(code, expected); + } + + public static void testDataNames0c() { + // Test that hashtag replacements that are local to inner scopes are + // only visible to inner scopes, but dollar replacements are the same + // for the whole Template. + var template = Template.make(() -> scope( + let("global", "GLOBAL"), + "g: #global. $a\n", + // Create a dummy DataName so we don't get an exception from sample. + addDataName("x", myInt, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + "g: #global. $b\n", + let("local", "LOCAL1"), + "l: #local. $c\n" + )), + "g: #global. $d\n", + // Open the scope again just to see if we can create the local again there. + dataNames(MUTABLE).exactOf(myInt).sample((DataName dn) -> scope( + "g: #global. $e\n", + let("local", "LOCAL2"), + "l: #local. $f\n" + )), + // We can now use the "local" hashtag replacement again, since it + // was previously only defined in an inner scope. + let("local", "LOCAL3"), + "g: #global. $g\n", + "l: #local. $h\n" + )); + + String code = template.render(); + String expected = + """ + g: GLOBAL. a_1 + g: GLOBAL. b_1 + l: LOCAL1. c_1 + g: GLOBAL. d_1 + g: GLOBAL. e_1 + l: LOCAL2. f_1 + g: GLOBAL. g_1 + l: LOCAL3. h_1 + """; + checkEQ(code, expected); + } + + public static void testDataNames0d() { + var template = Template.make(() -> scope( + addDataName("x", myInt, MUTABLE), + addDataName("y", myInt, MUTABLE), + addDataName("z", myInt, MUTABLE), + addDataName("a", myLong, MUTABLE), + addDataName("b", myLong, MUTABLE), + addDataName("c", myLong, MUTABLE), + dataNames(MUTABLE).exactOf(myInt).forEach((DataName dn) -> scope( + let("name", dn.name()), + let("type", dn.type()), + "listI: #name #type.\n" + )), + dataNames(MUTABLE).exactOf(myLong).forEach((DataName dn) -> scope( + let("name", dn.name()), + let("type", dn.type()), + "listL: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + listI: x int. + listI: y int. + listI: z int. + listL: a long. + listL: b long. + listL: c long. + """; + checkEQ(code, expected); + } public static void testDataNames1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "[", - dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).hasAny(), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, MUTABLE), + // Note: the scope of the template must be transparentScope, so that the addDataName can escape. + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, MUTABLE), // escapes "define #type #name\n", template1.asToken() )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( "<\n", hook1.insert(template2.asToken($("name"), myInt)), "$name = 5\n", ">\n" )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "{\n", template1.asToken(), - hook1.anchor( + hook1.anchor(scope( template1.asToken(), "something\n", - template3.asToken(), + template3.asToken(), // name_4 is inserted to hook1 "more\n", template1.asToken(), "more\n", - template2.asToken($("name"), myInt), + template2.asToken($("name"), myInt), // name_1 escapes "more\n", + template1.asToken(), + "extra\n", + hook1.insert(scope( + addDataName($("extra1"), myInt, MUTABLE), // does not escape + "$extra1 = 666\n" + )), + hook1.insert(transparentScope( + addDataName($("extra2"), myInt, MUTABLE), // escapes + "$extra2 = 42\n" + )), template1.asToken() - ), + )), + // But no names escape to down here, because the anchor scope is "scope". + "final:\n", template1.asToken(), "}\n" )); @@ -1053,6 +1424,8 @@ public class TestTemplate { [false, 0, names: {}] define int name_4 [true, 1, names: {name_4}] + extra1_1 = 666 + extra2_1 = 42 [false, 0, names: {}] something < @@ -1064,7 +1437,10 @@ public class TestTemplate { define int name_1 [true, 2, names: {name_4, name_1}] more - [true, 1, names: {name_4}] + [true, 2, names: {name_4, name_1}] + extra + [true, 3, names: {name_4, extra2_1, name_1}] + final: [false, 0, names: {}] } """; @@ -1074,17 +1450,19 @@ public class TestTemplate { public static void testDataNames2() { var hook1 = new Hook("Hook1"); - var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> scope( " #mutability: [", - dataNames(mutability).exactOf(myInt).hasAny(), + dataNames(mutability).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(mutability).exactOf(myInt).count(), + dataNames(mutability).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(mutability).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", template0.asToken(type, MUTABLE), template0.asToken(type, IMMUTABLE), @@ -1092,51 +1470,51 @@ public class TestTemplate { "]\n" )); - var template2 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, MUTABLE), + var template2 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, MUTABLE), // escapes "define mutable #type #name\n", template1.asToken(type) )); - var template3 = Template.make("name", "type", (String name, DataName.Type type) -> body( - addDataName(name, type, IMMUTABLE), + var template3 = Template.make("name", "type", (String name, DataName.Type type) -> transparentScope( + addDataName(name, type, IMMUTABLE), // escapes "define immutable #type #name\n", template1.asToken(type) )); - var template4 = Template.make("type", (DataName.Type type) -> body( + var template4 = Template.make("type", (DataName.Type type) -> scope( "{ $store\n", hook1.insert(template2.asToken($("name"), type)), "$name = 5\n", "} $store\n" )); - var template5 = Template.make("type", (DataName.Type type) -> body( + var template5 = Template.make("type", (DataName.Type type) -> scope( "{ $load\n", hook1.insert(template3.asToken($("name"), type)), "blackhole($name)\n", "} $load\n" )); - var template6 = Template.make("type", (DataName.Type type) -> body( - let("v", dataNames(MUTABLE).exactOf(type).sample().name()), + var template6 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "#v = 7\n", "} $sample\n" )); - var template7 = Template.make("type", (DataName.Type type) -> body( - let("v", dataNames(MUTABLE_OR_IMMUTABLE).exactOf(type).sample().name()), + var template7 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "blackhole(#v)\n", "} $sample\n" )); - var template8 = Template.make(() -> body( + var template8 = Template.make(() -> scope( "class $X {\n", template1.asToken(myInt), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myInt), "start with immutable\n", template5.asToken(myInt), @@ -1148,7 +1526,7 @@ public class TestTemplate { "then store to it\n", template6.asToken(myInt), template1.asToken(myInt) - ), + )), template1.asToken(myInt), "}\n" )); @@ -1174,7 +1552,7 @@ public class TestTemplate { IMMUTABLE: [true, 1, names: {name_10}] MUTABLE_OR_IMMUTABLE: [true, 2, names: {name_10, name_21}] ] - begin body_1 + begin scope_1 [int: MUTABLE: [false, 0, names: {}] IMMUTABLE: [false, 0, names: {}] @@ -1219,17 +1597,19 @@ public class TestTemplate { public static void testDataNames3() { var hook1 = new Hook("Hook1"); - var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> body( + var template0 = Template.make("type", "mutability", (DataName.Type type, DataName.Mutability mutability) -> scope( " #mutability: [", - dataNames(mutability).exactOf(myInt).hasAny(), + dataNames(mutability).exactOf(myInt).hasAny(h -> scope(h)), ", ", - dataNames(mutability).exactOf(myInt).count(), + dataNames(mutability).exactOf(myInt).count(c -> scope(c)), ", names: {", - String.join(", ", dataNames(mutability).exactOf(myInt).toList().stream().map(DataName::name).toList()), + dataNames(mutability).exactOf(myInt).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}]\n" )); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", template0.asToken(type, MUTABLE), template0.asToken(type, IMMUTABLE), @@ -1237,11 +1617,11 @@ public class TestTemplate { "]\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "class $Y {\n", template1.asToken(myInt), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myInt), "define mutable $v1\n", addDataName($("v1"), myInt, MUTABLE), @@ -1249,7 +1629,7 @@ public class TestTemplate { "define immutable $v2\n", addDataName($("v2"), myInt, IMMUTABLE), template1.asToken(myInt) - ), + )), template1.asToken(myInt), "}\n" )); @@ -1263,7 +1643,7 @@ public class TestTemplate { IMMUTABLE: [false, 0, names: {}] MUTABLE_OR_IMMUTABLE: [false, 0, names: {}] ] - begin body_1 + begin scope_1 [int: MUTABLE: [false, 0, names: {}] IMMUTABLE: [false, 0, names: {}] @@ -1294,47 +1674,62 @@ public class TestTemplate { public static void testDataNames4() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (DataName.Type type) -> body( + var template1 = Template.make("type", (DataName.Type type) -> scope( "[#type:\n", " exact: ", - dataNames(MUTABLE).exactOf(type).hasAny(), + dataNames(MUTABLE).exactOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).exactOf(type).count(), + dataNames(MUTABLE).exactOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).exactOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}\n", " subtype: ", - dataNames(MUTABLE).subtypeOf(type).hasAny(), + dataNames(MUTABLE).subtypeOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).subtypeOf(type).count(), + dataNames(MUTABLE).subtypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).subtypeOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).subtypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), + "}\n", " supertype: ", - dataNames(MUTABLE).supertypeOf(type).hasAny(), + dataNames(MUTABLE).supertypeOf(type).hasAny(h -> scope(h)), ", ", - dataNames(MUTABLE).supertypeOf(type).count(), + dataNames(MUTABLE).supertypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", dataNames(MUTABLE).supertypeOf(type).toList().stream().map(DataName::name).toList()), + dataNames(MUTABLE).supertypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(DataName::name).toList()) + )), "}\n", "]\n" )); List types = List.of(myClassA, myClassA1, myClassA2, myClassA11, myClassB); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "DataNames:\n", types.stream().map(t -> template1.asToken(t)).toList() )); - var template3 = Template.make("type", (DataName.Type type) -> body( - let("name", dataNames(MUTABLE).subtypeOf(type).sample()), - "Sample #type: #name\n" + var template3 = Template.make("type", (DataName.Type type) -> scope( + dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name1"), + "Sample #type: #name1\n", + dataNames(MUTABLE).subtypeOf(type).sampleAndLetAs("name2", "type2"), + "Sample #type: #name2 #type2\n", + dataNames(MUTABLE).subtypeOf(type).sample((DataName dn) -> scope( + let("name3", dn.name()), + let("type3", dn.type()), + let("dn", dn), // format the whole DataName with toString + "Sample #type: #name3 #type3 #dn\n" + )) )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "class $W {\n", template2.asToken(), - hook1.anchor( + hook1.anchor(scope( "Create name for myClassA11, should be visible for the super classes\n", addDataName($("v1"), myClassA11, MUTABLE), template3.asToken(myClassA11), @@ -1345,7 +1740,7 @@ public class TestTemplate { template3.asToken(myClassA11), template3.asToken(myClassA1), template2.asToken() - ), + )), template2.asToken(), "}\n" )); @@ -1381,12 +1776,22 @@ public class TestTemplate { supertype: false, 0, {} ] Create name for myClassA11, should be visible for the super classes - Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA11: v1_1 + Sample myClassA11: v1_1 myClassA11 + Sample myClassA11: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: v1_1 + Sample myClassA1: v1_1 myClassA11 + Sample myClassA1: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA: v1_1 + Sample myClassA: v1_1 myClassA11 + Sample myClassA: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] Create name for myClassA, should never be visible for the sub classes - Sample myClassA11: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] - Sample myClassA1: DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA11: v1_1 + Sample myClassA11: v1_1 myClassA11 + Sample myClassA11: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] + Sample myClassA1: v1_1 + Sample myClassA1: v1_1 myClassA11 + Sample myClassA1: v1_1 myClassA11 DataName[name=v1_1, type=myClassA11, mutable=true, weight=1] DataNames: [myClassA: exact: true, 1, {v2_1} @@ -1449,49 +1854,61 @@ public class TestTemplate { var hook1 = new Hook("Hook1"); var hook2 = new Hook("Hook2"); - // It is safe in separate Hook scopes. - var template1 = Template.make(() -> body( - hook1.anchor( + // It is safe in separate scopes. + var template1 = Template.make(() -> scope( + scope( addDataName("name1", myInt, MUTABLE) ), - hook1.anchor( + scope( addDataName("name1", myInt, MUTABLE) - ) + ), + nameScope( + addDataName("name1", myInt, MUTABLE) + ), + nameScope( + addDataName("name1", myInt, MUTABLE) + ), + hook1.anchor(scope( + addDataName("name1", myInt, MUTABLE) + )), + hook1.anchor(scope( + addDataName("name1", myInt, MUTABLE) + )) )); // It is safe in separate Template scopes. - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( addDataName("name2", myInt, MUTABLE) )); - var template3 = Template.make(() -> body( + var template3 = Template.make(() -> scope( template2.asToken(), template2.asToken() )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( // The following is not safe, it would collide // with (1), because it would be inserted to the // hook1.anchor in template5, and hence be available // inside the scope where (1) is available. // See: testFailingAddNameDuplication8 // addDataName("name", myInt, MUTABLE), - hook2.anchor( + hook2.anchor(scope( // (2) This one is added second. Since it is // inside the hook2.anchor, it does not go // out to the hook1.anchor, and is not // available inside the scope of (1). addDataName("name3", myInt, MUTABLE) - ) + )) )); - var template5 = Template.make(() -> body( - hook1.anchor( + var template5 = Template.make(() -> scope( + hook1.anchor(scope( // (1) this is the first one we add. addDataName("name3", myInt, MUTABLE) - ) + )) )); // Put it all together into a single test. - var template6 = Template.make(() -> body( + var template6 = Template.make(() -> scope( template1.asToken(), template3.asToken(), template5.asToken() @@ -1502,31 +1919,128 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testDataNames6() { + var template = Template.make(() -> scope( + addDataName("x", myInt, IMMUTABLE), + "int x = 5;\n", + // A DataName can be captured, and used to define a new one with the same type. + // It is important that the new DataName can escape the hashtagScope, so we have + // access to it later. + // Using "scope", it does not escape. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> scope( + addDataName("a", dn.type(), MUTABLE), + let("v1", "a"), + "int #v1 = x + 1;\n" + )), + // Using "transparentScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> transparentScope( + addDataName("b", dn.type(), MUTABLE), + let("v2", "b"), + "int #v2 = x + 2;\n" + )), + // Using "nameScope", it does not escape. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> nameScope( + addDataName("c", dn.type(), MUTABLE), + let("v3", "c"), + "int #v3 = x + 3;\n" + )), + // Using "hashtagScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> hashtagScope( + addDataName("d", dn.type(), MUTABLE), + let("v4", "d"), + "int #v4 = x + 4;\n" + )), + // Using "setFuelCostScope", it is available. + dataNames(IMMUTABLE).exactOf(myInt).sample(dn -> setFuelCostScope( + addDataName("e", dn.type(), MUTABLE), + let("v5", "e"), + "int #v5 = x + 5;\n" + )), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).forEach("name", "type", dn -> scope( + "available1: #name #type.\n" + )), + dataNames(MUTABLE_OR_IMMUTABLE).exactOf(myInt).forEach("name", "type", dn -> hashtagScope( + "available2: #name #type.\n" + )), + // Check that hashtags escape correctly too. + "hashtag v2: #v2.\n", + "hashtag v3: #v3.\n", + "hashtag v5: #v5.\n", + let("v1", "aaa"), + let("v4", "ddd") + )); + + String code = template.render(); + String expected = + """ + int x = 5; + int a = x + 1; + int b = x + 2; + int c = x + 3; + int d = x + 4; + int e = x + 5; + available1: x int. + available1: b int. + available1: d int. + available1: e int. + available2: x int. + available2: b int. + available2: d int. + available2: e int. + hashtag v2: b. + hashtag v3: c. + hashtag v5: e. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames0() { + var template = Template.make(() -> scope( + // When a StructuralName is added, it is immediately available afterwards. + // This may seem trivial, but it requires that either both "add" and + // "sample" happen in lambda execution, or in token evaluation. + // Otherwise, one can float above the other, and lead to unintuitive + // behavior. + addStructuralName("x", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).sampleAndLetAs("v"), + "sample: #v." + )); + + String code = template.render(); + checkEQ(code, "sample: x."); + } + public static void testStructuralNames1() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (StructuralName.Type type) -> body( + var template1 = Template.make("type", (StructuralName.Type type) -> scope( "[#type:\n", " exact: ", - structuralNames().exactOf(type).hasAny(), + structuralNames().exactOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().exactOf(type).count(), + structuralNames().exactOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", " subtype: ", - structuralNames().subtypeOf(type).hasAny(), + structuralNames().subtypeOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().subtypeOf(type).count(), + structuralNames().subtypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().subtypeOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().subtypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", " supertype: ", - structuralNames().supertypeOf(type).hasAny(), + structuralNames().supertypeOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().supertypeOf(type).count(), + structuralNames().supertypeOf(type).count(c -> scope(c)), ", {", - String.join(", ", structuralNames().supertypeOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().supertypeOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}\n", "]\n" )); @@ -1536,20 +2050,28 @@ public class TestTemplate { myStructuralTypeA2, myStructuralTypeA11, myStructuralTypeB); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "StructuralNames:\n", types.stream().map(t -> template1.asToken(t)).toList() )); - var template3 = Template.make("type", (StructuralName.Type type) -> body( - let("name", structuralNames().subtypeOf(type).sample()), - "Sample #type: #name\n" + var template3 = Template.make("type", (StructuralName.Type type) -> scope( + structuralNames().subtypeOf(type).sampleAndLetAs("name1"), + "Sample #type: #name1\n", + structuralNames().subtypeOf(type).sampleAndLetAs("name2", "type2"), + "Sample #type: #name2 #type2\n", + structuralNames().subtypeOf(type).sample((StructuralName sn) -> scope( + let("name3", sn.name()), + let("type3", sn.type()), + let("sn", sn), // format the whole StructuralName with toString + "Sample #type: #name3 #type3 #sn\n" + )) )); - var template4 = Template.make(() -> body( + var template4 = Template.make(() -> scope( "class $Q {\n", template2.asToken(), - hook1.anchor( + hook1.anchor(scope( "Create name for myStructuralTypeA11, should be visible for the supertypes\n", addStructuralName($("v1"), myStructuralTypeA11), template3.asToken(myStructuralTypeA11), @@ -1560,7 +2082,7 @@ public class TestTemplate { template3.asToken(myStructuralTypeA11), template3.asToken(myStructuralTypeA1), template2.asToken() - ), + )), template2.asToken(), "}\n" )); @@ -1596,12 +2118,22 @@ public class TestTemplate { supertype: false, 0, {} ] Create name for myStructuralTypeA11, should be visible for the supertypes - Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA11: v1_1 + Sample StructuralA11: v1_1 StructuralA11 + Sample StructuralA11: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: v1_1 + Sample StructuralA1: v1_1 StructuralA11 + Sample StructuralA1: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA: v1_1 + Sample StructuralA: v1_1 StructuralA11 + Sample StructuralA: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] Create name for myStructuralTypeA, should never be visible for the subtypes - Sample StructuralA11: StructuralName[name=v1_1, type=StructuralA11, weight=1] - Sample StructuralA1: StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA11: v1_1 + Sample StructuralA11: v1_1 StructuralA11 + Sample StructuralA11: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] + Sample StructuralA1: v1_1 + Sample StructuralA1: v1_1 StructuralA11 + Sample StructuralA1: v1_1 StructuralA11 StructuralName[name=v1_1, type=StructuralA11, weight=1] StructuralNames: [StructuralA: exact: true, 1, {v2_1} @@ -1662,41 +2194,43 @@ public class TestTemplate { public static void testStructuralNames2() { var hook1 = new Hook("Hook1"); - var template1 = Template.make("type", (StructuralName.Type type) -> body( + var template1 = Template.make("type", (StructuralName.Type type) -> scope( "[#type: ", - structuralNames().exactOf(type).hasAny(), + structuralNames().exactOf(type).hasAny(h -> scope(h)), ", ", - structuralNames().exactOf(type).count(), + structuralNames().exactOf(type).count(c -> scope(c)), ", names: {", - String.join(", ", structuralNames().exactOf(type).toList().stream().map(StructuralName::name).toList()), + structuralNames().exactOf(type).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), "}]\n" )); - var template2 = Template.make("name", "type", (String name, StructuralName.Type type) -> body( - addStructuralName(name, type), + var template2 = Template.make("name", "type", (String name, StructuralName.Type type) -> transparentScope( + addStructuralName(name, type), // escapes "define #type #name\n" )); - var template3 = Template.make("type", (StructuralName.Type type) -> body( + var template3 = Template.make("type", (StructuralName.Type type) -> scope( "{ $access\n", hook1.insert(template2.asToken($("name"), type)), "$name = 5\n", "} $access\n" )); - var template4 = Template.make("type", (StructuralName.Type type) -> body( - let("v", structuralNames().exactOf(type).sample().name()), + var template4 = Template.make("type", (StructuralName.Type type) -> scope( + structuralNames().exactOf(type).sampleAndLetAs("v"), "{ $sample\n", "blackhole(#v)\n", "} $sample\n" )); - var template8 = Template.make(() -> body( + var template8 = Template.make(() -> scope( "class $X {\n", template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), - hook1.anchor( - "begin $body\n", + hook1.anchor(scope( + "begin $scope\n", template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), "start with A\n", @@ -1711,7 +2245,7 @@ public class TestTemplate { template4.asToken(myStructuralTypeB), template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB) - ), + )), template1.asToken(myStructuralTypeA), template1.asToken(myStructuralTypeB), "}\n" @@ -1725,7 +2259,7 @@ public class TestTemplate { [StructuralB: false, 0, names: {}] define StructuralA name_6 define StructuralB name_11 - begin body_1 + begin scope_1 [StructuralA: false, 0, names: {}] [StructuralB: false, 0, names: {}] start with A @@ -1755,16 +2289,269 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testStructuralNames3() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> scope( + let("name1", sn.name()), + "sn1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> nameScope( + // We cannot use "let" here (at least not easily), otherwise we get + // a duplicate hashtag replacement. It would probably be better style + // to use a "let", but we are just checking that "nameScope" works + // for reuse of names. + "sn2: ", sn.name(), ".\n", + // But for testing, we still do a "let", just with different key. + // (This is probably bad practice, we just do this for testing) + let("name2_" + sn.name(), sn.name()), + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + // Same issue with hashtags as with "nameScope". + "sn3: ", sn.name(), ".\n", + let("name3_" + sn.name(), sn.name()), + // Using the same name for each would lead to duplicates, + // so we have to modify the name here. + addStructuralName("x_" + sn.name(), myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> hashtagScope( + let("name4", sn.name()), + "sn4: #name4.\n", + // Same issue with duplicate names as with "transparentScope". + addStructuralName("y_" + sn.name(), myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> setFuelCostScope( + // Same issue with hashtags as with "nameScope". + "sn5: ", sn.name(), ".\n", + let("name5_" + sn.name(), sn.name()), + // Same issue with duplicate names as with "transparentScope". + addStructuralName("z_" + sn.name(), myStructuralTypeA) + )), + "sn2: #name2_a #name2_b.\n", // hashtags escaped + "sn3: #name3_a #name3_b.\n", // hashtags escaped + "sn5: #name5_a #name5_b #name5_x_a #name5_x_b.\n", // hashtags escaped + let("name1", "shouldBeOK1"), // hashtag did not escape + let("name4", "shouldBeOk4") // hashtag did not escape + )); + + String code = template.render(); + String expected = + """ + sn1: a. + sn1: b. + sn2: a. + sn2: b. + sn3: a. + sn3: b. + sn4: a. + sn4: b. + sn4: x_a. + sn4: x_b. + sn5: a. + sn5: b. + sn5: x_a. + sn5: x_b. + sn5: y_a. + sn5: y_b. + sn5: y_x_a. + sn5: y_x_b. + sn2: a b. + sn3: a b. + sn5: a b x_a x_b. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames4() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + let("name1", list.size()), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> nameScope( + let("name2", list.size()), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> transparentScope( + let("name3", list.size()), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> hashtagScope( + let("name4", list.size()), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).toList(list -> setFuelCostScope( + let("name5", list.size()), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: 2. + list2: 2. + list3: 2. + list4: 3. + list5: 4. + list2: 2. + list3: 2. + list5: 4. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames5() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).count(c -> scope( + let("name1", c), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> nameScope( + let("name2", c), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> transparentScope( + let("name3", c), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> hashtagScope( + let("name4", c), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).count(c -> setFuelCostScope( + let("name5", c), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: 2. + list2: 2. + list3: 2. + list4: 3. + list5: 4. + list2: 2. + list3: 2. + list5: 4. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + + public static void testStructuralNames6() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> scope( + let("name1", h), + "list1: #name1.\n", + addStructuralName("scope_garbage1", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> nameScope( + let("name2", h), + "list2: #name2.\n", + addStructuralName("scope_garbage2", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> transparentScope( + let("name3", h), + "list3: #name3.\n", + addStructuralName("x", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> hashtagScope( + let("name4", h), + "list4: #name4.\n", + addStructuralName("y", myStructuralTypeA) + )), + structuralNames().exactOf(myStructuralTypeA).hasAny(h -> setFuelCostScope( + let("name5", h), + "list5: #name5.\n", + addStructuralName("z", myStructuralTypeA) + )), + "list2: #name2.\n", // hashtag escaped + "list3: #name3.\n", // hashtag escaped + "list5: #name5.\n", // hashtag escaped + let("name1", "shouldBeOk4"), // hashtag did not escape + let("name4", "shouldBeOk4"), // hashtag did not escape + structuralNames().exactOf(myStructuralTypeA).forEach("name", "type", sn -> scope( + "available: #name #type.\n" + )) + )); + + String code = template.render(); + String expected = + """ + list1: true. + list2: true. + list3: true. + list4: true. + list5: true. + list2: true. + list3: true. + list5: true. + available: a StructuralA. + available: b StructuralA. + available: x StructuralA. + available: y StructuralA. + available: z StructuralA. + """; + checkEQ(code, expected); + } + record MyItem(DataName.Type type, String op) {} public static void testListArgument() { - var template1 = Template.make("item", (MyItem item) -> body( + var template1 = Template.make("item", (MyItem item) -> scope( let("type", item.type()), let("op", item.op()), "#type apply #op\n" )); - var template2 = Template.make("list", (List list) -> body( + var template2 = Template.make("list", (List list) -> scope( "class $Z {\n", // Use template1 for every item in the list. list.stream().map(item -> template1.asToken(item)).toList(), @@ -1797,12 +2584,746 @@ public class TestTemplate { checkEQ(code, expected); } + public static void testNestedScopes1() { + var listDataNames = Template.make(() -> scope( + "dataNames: {", + dataNames(MUTABLE).exactOf(myInt).forEach("name", "type", (DataName dn) -> scope( + "#name #type; " + )), + "}\n" + )); + + var template = Template.make("x", (String x) -> scope( + "$start\n", + addDataName("vx", myInt, MUTABLE), + "x: #x.\n", + listDataNames.asToken(), + // A "transparentScope" nesting essencially does nothing but create + // a list of tokens. It passes through names and hashtags. + "open transparentScope:\n", + transparentScope( + "$transparentScope\n", + let("y", "YYY"), + addDataName("vy", myInt, MUTABLE), + "x: #x.\n", + "y: #y.\n", + listDataNames.asToken() + ), + "close transparentScope.\n", + "x: #x.\n", + "y: #y.\n", + listDataNames.asToken(), + // A "hashtagScope" nesting makes hashtags local, but names + // escape the nesting. + "open hashtagScope:\n", + hashtagScope( + "$hashtagScope\n", + let("z", "ZZZ1"), + "z: #z.\n", + addDataName("vz", myInt, MUTABLE), + listDataNames.asToken() + ), + "close hashtagScope.\n", + let("z", "ZZZ2"), // we can define it again outside. + "z: #z.\n", + listDataNames.asToken(), + // We can also use hashtagScopes for loops. + List.of("a", "b", "c").stream().map(str -> hashtagScope( + "$hashtagScope\n", + let("str", str), // the hashtag is local to every element + "str: #str.\n", + addDataName("v_" + str, myInt, MUTABLE), + listDataNames.asToken() + )).toList(), + "finish str list.\n", + listDataNames.asToken(), + // A "nameScope" nesting makes names local, but hashtags + // escape the nesting. + "open nameScope:\n", + nameScope( + "$nameScope\n", + let("p", "PPP"), + "p: #p.\n", + addDataName("vp", myInt, MUTABLE), + listDataNames.asToken() + ), + "close hashtagScope.\n", + "p: #p.\n", + listDataNames.asToken(), + // A "scope" nesting makes names and hashtags local + "open scope:\n", + scope( + "$scope\n", + let("q", "QQQ1"), + "q: #q.\n", + addDataName("vq", myInt, MUTABLE), + listDataNames.asToken() + ), + "close scope.\n", + let("q", "QQQ2"), + "q: #q.\n", + listDataNames.asToken(), + // A "setFuelCostScope" nesting behaves the same as "transparentScope", as we are not using fuel here. + "open setFuelCostScope:\n", + setFuelCostScope( + "$setFuelCostScope\n", + let("r", "RRR"), + "r: #r.\n", + addDataName("vr", myInt, MUTABLE), + listDataNames.asToken() + ), + "close setFuelCostScope.\n", + "r: #r.\n", + listDataNames.asToken() + + )); + + String code = template.render("XXX"); + String expected = + """ + start_1 + x: XXX. + dataNames: {vx int; } + open transparentScope: + transparentScope_1 + x: XXX. + y: YYY. + dataNames: {vx int; vy int; } + close transparentScope. + x: XXX. + y: YYY. + dataNames: {vx int; vy int; } + open hashtagScope: + hashtagScope_1 + z: ZZZ1. + dataNames: {vx int; vy int; vz int; } + close hashtagScope. + z: ZZZ2. + dataNames: {vx int; vy int; vz int; } + hashtagScope_1 + str: a. + dataNames: {vx int; vy int; vz int; v_a int; } + hashtagScope_1 + str: b. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; } + hashtagScope_1 + str: c. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + finish str list. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open nameScope: + nameScope_1 + p: PPP. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vp int; } + close hashtagScope. + p: PPP. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open scope: + scope_1 + q: QQQ1. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vq int; } + close scope. + q: QQQ2. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; } + open setFuelCostScope: + setFuelCostScope_1 + r: RRR. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vr int; } + close setFuelCostScope. + r: RRR. + dataNames: {vx int; vy int; vz int; v_a int; v_b int; v_c int; vr int; } + """; + checkEQ(code, expected); + } + + public static void testNestedScopes2() { + var listDataNames = Template.make(() -> scope( + "dataNames: {", + dataNames(MUTABLE).exactOf(myInt).forEach("name", "type", (DataName dn) -> scope( + "#name #type; " + )), + "}\n" + )); + + var template = Template.make(() -> scope( + // Define some global variables. + List.of("a", "b", "c").stream().map(str -> hashtagScope( + let("var", "g_" + str), + addDataName("g_" + str, myInt, MUTABLE), + "def global #var.\n" + )).toList(), + listDataNames.asToken(), + scope( + "open scope:\n", + // Define some variables. + List.of("i", "j", "k").stream().map(str -> hashtagScope( + let("var", "v_" + str), + addDataName("v_" + str, myInt, MUTABLE), + "def #var.\n" + )).toList(), + listDataNames.asToken(), + scope( + "open inner scope:\n", + addDataName("v_local", myInt, MUTABLE), + "def v_local.\n", + listDataNames.asToken(), + "close inner scope.\n" + ), + listDataNames.asToken(), + "close scope.\n" + ), + listDataNames.asToken() + )); + + String code = template.render(); + String expected = + """ + def global g_a. + def global g_b. + def global g_c. + dataNames: {g_a int; g_b int; g_c int; } + open scope: + def v_i. + def v_j. + def v_k. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; } + open inner scope: + def v_local. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; v_local int; } + close inner scope. + dataNames: {g_a int; g_b int; g_c int; v_i int; v_j int; v_k int; } + close scope. + dataNames: {g_a int; g_b int; g_c int; } + """; + checkEQ(code, expected); + } + + public static void testTemplateScopes() { + var statusTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n", + let("fuel", fuel()), + "fuel: #fuel\n" + )); + + var scopeTemplate = Template.make(() -> scope( + "scope:\n", + let("local", "inner scope"), + addStructuralName("x", myStructuralTypeA), + statusTemplate.asToken(), + setFuelCost(50) + )); + + var transparentScopeTemplate = Template.make(() -> transparentScope( + "transparentScope:\n", + let("local", "inner flag"), + addStructuralName("y", myStructuralTypeA), // should escape + statusTemplate.asToken(), + setFuelCost(50) + )); + + var template = Template.make(() -> scope( + setFuelCost(1), + let("local", "root"), + addStructuralName("a", myStructuralTypeA), + statusTemplate.asToken(), + scopeTemplate.asToken(), + statusTemplate.asToken(), + transparentScopeTemplate.asToken(), + statusTemplate.asToken() + )); + + String code = template.render(); + String expected = + """ + {a} + fuel: 99.0f + scope: + {a, x} + fuel: 89.0f + {a} + fuel: 99.0f + transparentScope: + {a, y} + fuel: 89.0f + {a, y} + fuel: 99.0f + """; + checkEQ(code, expected); + } + + public static void testHookAndScopes1() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var insertScopeTemplate = Template.make("name", (String name) -> scope( + let("local", "insert scope garbage"), + addStructuralName(name, myStructuralTypeA), + "inserted scope: #name\n", + listNamesTemplate.asToken() + )); + + var insertTransparentScopeTemplate = Template.make("name", (String name) -> transparentScope( + let("local", "insert transparentScope garbage"), + addStructuralName(name, myStructuralTypeA), + "inserted transparentScope: #name\n", + listNamesTemplate.asToken() + )); + + var probeTemplate = Template.make(() -> scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )); + + var template = Template.make(() -> scope( + "scope:\n", + hook1.anchor(scope( + let("local", "scope garbage"), + addStructuralName("x1a", myStructuralTypeA), + "scope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x1b")), + "scope after insert scope:\n", + listNamesTemplate.asToken(), + "scope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x1c")), + "scope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "scope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after scope:\n", + listNamesTemplate.asToken(), + + "transparentScope:\n", + hook1.anchor(transparentScope( + let("transparentScope2", "abc"), + addStructuralName("x2a", myStructuralTypeA), + "transparentScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x2b")), + "transparentScope after insert scope:\n", + listNamesTemplate.asToken(), + "transparentScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x2c")), + "transparentScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "transparentScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after transparentScope:\n", + listNamesTemplate.asToken(), + "transparentScope2: #transparentScope2\n", + + "hashtagScope:\n", + hook1.anchor(hashtagScope( + let("local", "hashtagScope garbage"), + addStructuralName("x3a", myStructuralTypeA), + "hashtagScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x3b")), + "hashtagScope after insert scope:\n", + listNamesTemplate.asToken(), + "hashtagScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x3c")), + "hashtagScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "hashtagScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after hashtagScope:\n", + listNamesTemplate.asToken(), + + "nameScope:\n", + hook1.anchor(nameScope( + let("transparentScope4", "abcde"), + addStructuralName("x4a", myStructuralTypeA), + "nameScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertScopeTemplate.asToken("x4b")), + "nameScope after insert scope:\n", + listNamesTemplate.asToken(), + "nameScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(insertTransparentScopeTemplate.asToken("x4c")), + "nameScope after insert transparentScope:\n", + listNamesTemplate.asToken(), + "nameScope insert probe.\n", + hook1.insert(probeTemplate.asToken()) + )), + "after nameScope:\n", + listNamesTemplate.asToken(), + "transparentScope4: #transparentScope4\n", + + let("local", "outer garbage") + )); + + String code = template.render(); + String expected = + """ + scope: + inserted scope: x1b + {x1b} + inserted transparentScope: x1c + {x1c} + inserted probe: + {x1c} + scope before insert scope: + {x1a} + scope after insert scope: + {x1a} + scope before insert transparentScope: + {x1a} + scope after insert transparentScope: + {x1c, x1a} + scope insert probe. + after scope: + {} + transparentScope: + inserted scope: x2b + {x2a, x2b} + inserted transparentScope: x2c + {x2a, x2c} + inserted probe: + {x2a, x2c} + transparentScope before insert scope: + {x2a} + transparentScope after insert scope: + {x2a} + transparentScope before insert transparentScope: + {x2a} + transparentScope after insert transparentScope: + {x2a, x2c} + transparentScope insert probe. + after transparentScope: + {x2a, x2c} + transparentScope2: abc + hashtagScope: + inserted scope: x3b + {x2a, x2c, x3a, x3b} + inserted transparentScope: x3c + {x2a, x2c, x3a, x3c} + inserted probe: + {x2a, x2c, x3a, x3c} + hashtagScope before insert scope: + {x2a, x2c, x3a} + hashtagScope after insert scope: + {x2a, x2c, x3a} + hashtagScope before insert transparentScope: + {x2a, x2c, x3a} + hashtagScope after insert transparentScope: + {x2a, x2c, x3a, x3c} + hashtagScope insert probe. + after hashtagScope: + {x2a, x2c, x3a, x3c} + nameScope: + inserted scope: x4b + {x2a, x2c, x3a, x3c, x4b} + inserted transparentScope: x4c + {x2a, x2c, x3a, x3c, x4c} + inserted probe: + {x2a, x2c, x3a, x3c, x4c} + nameScope before insert scope: + {x2a, x2c, x3a, x3c, x4a} + nameScope after insert scope: + {x2a, x2c, x3a, x3c, x4a} + nameScope before insert transparentScope: + {x2a, x2c, x3a, x3c, x4a} + nameScope after insert transparentScope: + {x2a, x2c, x3a, x3c, x4c, x4a} + nameScope insert probe. + after nameScope: + {x2a, x2c, x3a, x3c} + transparentScope4: abcde + """; + checkEQ(code, expected); + } + + public static void testHookAndScopes2() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var template = Template.make(() -> scope( + "scope:\n", + hook1.anchor(scope( + let("local0", "scope garbage"), + let("local1", "LOCAL1"), + addStructuralName("x1a", myStructuralTypeA), + + "scope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(scope( + let("local2", "insert scope garbage"), + let("name", "x1b"), + addStructuralName("x1b", myStructuralTypeA), // does NOT escape to anchor scope + "inserted scope: #name\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert scope:\n", + listNamesTemplate.asToken(), + + "scope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(transparentScope( + let("nameTransparentScope", "x1c"), // escapes to caller + addStructuralName("x1c", myStructuralTypeA), // escapes to anchor scope + "inserted transparentScope: #nameTransparentScope\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert transparentScope:\n", + "nameTransparentScope: #nameTransparentScope\n", + listNamesTemplate.asToken(), + + "scope before insert nameScope:\n", + listNamesTemplate.asToken(), + hook1.insert(nameScope( + let("nameNameScope", "x1d"), // escapes to caller + addStructuralName("x1d", myStructuralTypeA), // does NOT escape to anchor scope + "inserted nameScope: #nameNameScope\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert nameScope:\n", + "nameNameScope: #nameNameScope\n", + listNamesTemplate.asToken(), + + "scope before insert hashtagScope:\n", + listNamesTemplate.asToken(), + hook1.insert(hashtagScope( + let("local2", "insert hashtagScope garbage"), + let("name", "x1e"), // escapes to caller + addStructuralName("x1e", myStructuralTypeA), // escapes to anchor scope + "inserted hashtagScope: #name\n", + "local1: #local1\n", + listNamesTemplate.asToken() + )), + "scope after insert hashtagScope:\n", + listNamesTemplate.asToken(), + + "scope insert probe.\n", + hook1.insert(scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )) + )), + "after scope:\n", + listNamesTemplate.asToken(), + + let("name", "name garbage"), + let("local0", "outer garbage 0"), + let("local1", "outer garbage 1"), + let("local2", "outer garbage 2"), + let("nameTransparentScope", "outer garbage nameTransparentScope"), + let("nameNameScope", "outer garbage nameNameScope") + )); + + String code = template.render(); + String expected = + """ + scope: + inserted scope: x1b + local1: LOCAL1 + {x1b} + inserted transparentScope: x1c + local1: LOCAL1 + {x1c} + inserted nameScope: x1d + local1: LOCAL1 + {x1c, x1d} + inserted hashtagScope: x1e + local1: LOCAL1 + {x1c, x1e} + inserted probe: + {x1c, x1e} + scope before insert scope: + {x1a} + scope after insert scope: + {x1a} + scope before insert transparentScope: + {x1a} + scope after insert transparentScope: + nameTransparentScope: x1c + {x1c, x1a} + scope before insert nameScope: + {x1c, x1a} + scope after insert nameScope: + nameNameScope: x1d + {x1c, x1a} + scope before insert hashtagScope: + {x1c, x1a} + scope after insert hashtagScope: + {x1c, x1e, x1a} + scope insert probe. + after scope: + {} + """; + checkEQ(code, expected); + } + + // Analogue to testHookAndScopes2, but with "transparentScope" instead of "scope". + public static void testHookAndScopes3() { + Hook hook1 = new Hook("Hook1"); + + var listNamesTemplate = Template.make(() -> scope( + "{", + structuralNames().exactOf(myStructuralTypeA).toList(list -> scope( + String.join(", ", list.stream().map(StructuralName::name).toList()) + )), + "}\n" + )); + + var template = Template.make(() -> scope( + "transparentScope:\n", + hook1.anchor(transparentScope( + let("global0", "transparentScope garbage"), + let("global1", "GLOBAL1"), + addStructuralName("x1a", myStructuralTypeA), + + "transparentScope before insert scope:\n", + listNamesTemplate.asToken(), + hook1.insert(scope( + let("local2", "insert scope garbage"), + let("name", "x1b"), + addStructuralName("x1b", myStructuralTypeA), // does NOT escape to anchor scope + "inserted scope: #name\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert scope:\n", + listNamesTemplate.asToken(), + + "transparentScope before insert transparentScope:\n", + listNamesTemplate.asToken(), + hook1.insert(transparentScope( + let("nameTransparentScope", "x1c"), // escapes to caller + addStructuralName("x1c", myStructuralTypeA), // escapes to anchor scope + "inserted transparentScope: #nameTransparentScope\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert transparentScope:\n", + "nameTransparentScope: #nameTransparentScope\n", + listNamesTemplate.asToken(), + + "transparentScope before insert nameScope:\n", + listNamesTemplate.asToken(), + hook1.insert(nameScope( + let("nameNameScope", "x1d"), // escapes to caller + addStructuralName("x1d", myStructuralTypeA), // does NOT escape to anchor scope + "inserted nameScope: #nameNameScope\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert nameScope:\n", + "nameNameScope: #nameNameScope\n", + listNamesTemplate.asToken(), + + "transparentScope before insert hashtagScope:\n", + listNamesTemplate.asToken(), + hook1.insert(hashtagScope( + let("local2", "insert hashtagScope garbage"), + let("name", "x1e"), // escapes to caller + addStructuralName("x1e", myStructuralTypeA), // escapes to anchor scope + "inserted hashtagScope: #name\n", + "global1: #global1\n", + listNamesTemplate.asToken() + )), + "transparentScope after insert hashtagScope:\n", + listNamesTemplate.asToken(), + + "transparentScope insert probe.\n", + hook1.insert(scope( + "inserted probe:\n", + listNamesTemplate.asToken() + )) + )), + "after transparentScope:\n", + listNamesTemplate.asToken(), + """ + global0: #global0 + global1: #global1 + nameTransparentScope: #nameTransparentScope + nameNameScope: #nameNameScope + """, + let("name", "name garbage"), + let("local2", "outer garbage 2") + )); + + String code = template.render(); + String expected = + """ + transparentScope: + inserted scope: x1b + global1: GLOBAL1 + {x1a, x1b} + inserted transparentScope: x1c + global1: GLOBAL1 + {x1a, x1c} + inserted nameScope: x1d + global1: GLOBAL1 + {x1a, x1c, x1d} + inserted hashtagScope: x1e + global1: GLOBAL1 + {x1a, x1c, x1e} + inserted probe: + {x1a, x1c, x1e} + transparentScope before insert scope: + {x1a} + transparentScope after insert scope: + {x1a} + transparentScope before insert transparentScope: + {x1a} + transparentScope after insert transparentScope: + nameTransparentScope: x1c + {x1a, x1c} + transparentScope before insert nameScope: + {x1a, x1c} + transparentScope after insert nameScope: + nameNameScope: x1d + {x1a, x1c} + transparentScope before insert hashtagScope: + {x1a, x1c} + transparentScope after insert hashtagScope: + {x1a, x1c, x1e} + transparentScope insert probe. + after transparentScope: + {x1a, x1c, x1e} + global0: transparentScope garbage + global1: GLOBAL1 + nameTransparentScope: x1c + nameNameScope: x1d + """; + checkEQ(code, expected); + } + public static void testFailingNestedRendering() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "alpha\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "beta\n", // Nested "render" call not allowed! template1.render(), @@ -1813,63 +3334,63 @@ public class TestTemplate { } public static void testFailingDollarName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("")) // empty string not allowed )); String code = template1.render(); } public static void testFailingDollarName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("#abc")) // "#" character not allowed )); String code = template1.render(); } public static void testFailingDollarName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $("abc#")) // "#" character not allowed )); String code = template1.render(); } public static void testFailingDollarName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("x", $(null)) // Null input to dollar )); String code = template1.render(); } public static void testFailingDollarName5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarName6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf$" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarName7() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf$1" // Bad pattern after dollar )); String code = template1.render(); } public static void testFailingDollarName8() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "abc$$abc" // empty dollar name )); String code = template1.render(); } public static void testFailingLetName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let(null, $("abc")) // Null input for hashtag name )); String code = template1.render(); @@ -1877,20 +3398,20 @@ public class TestTemplate { public static void testFailingHashtagName1() { // Empty Template argument - var template1 = Template.make("", (String x) -> body( + var template1 = Template.make("", (String x) -> scope( )); String code = template1.render("abc"); } public static void testFailingHashtagName2() { // "#" character not allowed in template argument - var template1 = Template.make("abc#abc", (String x) -> body( + var template1 = Template.make("abc#abc", (String x) -> scope( )); String code = template1.render("abc"); } public static void testFailingHashtagName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Empty let hashtag name not allowed let("", "abc") )); @@ -1898,7 +3419,7 @@ public class TestTemplate { } public static void testFailingHashtagName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // "#" character not allowed in let hashtag name let("xyz#xyz", "abc") )); @@ -1906,56 +3427,56 @@ public class TestTemplate { } public static void testFailingHashtagName5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#" // empty hashtag name )); String code = template1.render(); } public static void testFailingHashtagName6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf#" // empty hashtag name )); String code = template1.render(); } public static void testFailingHashtagName7() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "asdf#1" // Bad pattern after hashtag )); String code = template1.render(); } public static void testFailingHashtagName8() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "abc##abc" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#$" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$#" // empty dollar name )); String code = template1.render(); } public static void testFailingDollarHashtagName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "#$name" // empty hashtag name )); String code = template1.render(); } public static void testFailingDollarHashtagName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "$#name" // empty dollar name )); String code = template1.render(); @@ -1964,11 +3485,11 @@ public class TestTemplate { public static void testFailingHook() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "alpha\n" )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( "beta\n", // Use hook without hook1.anchor hook1.insert(template1.asToken()), @@ -1978,20 +3499,40 @@ public class TestTemplate { String code = template2.render(); } - public static void testFailingSample1() { - var template1 = Template.make(() -> body( - // No variable added yet. - let("v", dataNames(MUTABLE).exactOf(myInt).sample().name()), + public static void testFailingSample1a() { + var template1 = Template.make(() -> scope( + // No DataName added yet. + dataNames(MUTABLE).exactOf(myInt).sampleAndLetAs("v"), "v is #v\n" )); String code = template1.render(); } - public static void testFailingSample2() { - var template1 = Template.make(() -> body( + public static void testFailingSample1b() { + var template1 = Template.make(() -> scope( + // No StructuralName added yet. + structuralNames().exactOf(myStructuralTypeA).sampleAndLetAs("v"), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingSample2a() { + var template1 = Template.make(() -> scope( // no type restriction - let("v", dataNames(MUTABLE).sample().name()), + dataNames(MUTABLE).sampleAndLetAs("v"), + "v is #v\n" + )); + + String code = template1.render(); + } + + public static void testFailingSample2b() { + var template1 = Template.make(() -> scope( + // no type restriction + structuralNames().sampleAndLetAs("v"), "v is #v\n" )); @@ -2000,7 +3541,7 @@ public class TestTemplate { public static void testFailingHashtag1() { // Duplicate hashtag definition from arguments. - var template1 = Template.make("a", "a", (String _, String _) -> body( + var template1 = Template.make("a", "a", (String _, String _) -> scope( "nothing\n" )); @@ -2008,7 +3549,7 @@ public class TestTemplate { } public static void testFailingHashtag2() { - var template1 = Template.make("a", (String _) -> body( + var template1 = Template.make("a", (String _) -> scope( // Duplicate hashtag name let("a", "x"), "nothing\n" @@ -2018,7 +3559,7 @@ public class TestTemplate { } public static void testFailingHashtag3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( let("a", "x"), // Duplicate hashtag name let("a", "y"), @@ -2029,7 +3570,7 @@ public class TestTemplate { } public static void testFailingHashtag4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Missing hashtag name definition "#a\n" )); @@ -2037,9 +3578,20 @@ public class TestTemplate { String code = template1.render(); } + public static void testFailingHashtag5() { + var template1 = Template.make(() -> scope( + "use before definition: #a\n", + // let is a token, and is only evaluated after + // the string above, and so the string above fails. + let("a", "x") + )); + + String code = template1.render(); + } + public static void testFailingBinding1() { var binding = new TemplateBinding(); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "nothing\n" )); binding.bind(template1); @@ -2049,7 +3601,7 @@ public class TestTemplate { public static void testFailingBinding2() { var binding = new TemplateBinding(); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( "nothing\n", // binding was never bound. binding.get() @@ -2059,7 +3611,7 @@ public class TestTemplate { } public static void testFailingAddDataName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // Must pick either MUTABLE or IMMUTABLE. addDataName("name", myInt, MUTABLE_OR_IMMUTABLE) )); @@ -2067,7 +3619,7 @@ public class TestTemplate { } public static void testFailingAddDataName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, 0) )); @@ -2075,7 +3627,7 @@ public class TestTemplate { } public static void testFailingAddDataName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, -1) )); @@ -2083,7 +3635,7 @@ public class TestTemplate { } public static void testFailingAddDataName4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addDataName("name", myInt, MUTABLE, 1001) )); @@ -2091,7 +3643,7 @@ public class TestTemplate { } public static void testFailingAddStructuralName1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, 0) )); @@ -2099,7 +3651,7 @@ public class TestTemplate { } public static void testFailingAddStructuralName2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, -1) )); @@ -2107,7 +3659,7 @@ public class TestTemplate { } public static void testFailingAddStructuralName3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( // weight out of bounds [0..1000] addStructuralName("name", myStructuralTypeA, 1001) )); @@ -2116,7 +3668,7 @@ public class TestTemplate { // Duplicate name in the same scope, name identical -> expect RendererException. public static void testFailingAddNameDuplication1() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myInt, MUTABLE) )); @@ -2125,7 +3677,7 @@ public class TestTemplate { // Duplicate name in the same scope, names have different mutability -> expect RendererException. public static void testFailingAddNameDuplication2() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myInt, IMMUTABLE) )); @@ -2134,7 +3686,7 @@ public class TestTemplate { // Duplicate name in the same scope, names have different type -> expect RendererException. public static void testFailingAddNameDuplication3() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), addDataName("name", myLong, MUTABLE) )); @@ -2143,7 +3695,7 @@ public class TestTemplate { // Duplicate name in the same scope, name identical -> expect RendererException. public static void testFailingAddNameDuplication4() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addStructuralName("name", myStructuralTypeA), addStructuralName("name", myStructuralTypeA) )); @@ -2152,7 +3704,7 @@ public class TestTemplate { // Duplicate name in the same scope, names have different type -> expect RendererException. public static void testFailingAddNameDuplication5() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addStructuralName("name", myStructuralTypeA), addStructuralName("name", myStructuralTypeB) )); @@ -2161,10 +3713,10 @@ public class TestTemplate { // Duplicate name in inner Template, name identical -> expect RendererException. public static void testFailingAddNameDuplication6() { - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE) )); - var template2 = Template.make(() -> body( + var template2 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), template1.asToken() )); @@ -2175,11 +3727,11 @@ public class TestTemplate { public static void testFailingAddNameDuplication7() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( + var template1 = Template.make(() -> scope( addDataName("name", myInt, MUTABLE), - hook1.anchor( + hook1.anchor(scope( addDataName("name", myInt, MUTABLE) - ) + )) )); String code = template1.render(); } @@ -2188,19 +3740,94 @@ public class TestTemplate { public static void testFailingAddNameDuplication8() { var hook1 = new Hook("Hook1"); - var template1 = Template.make(() -> body( - addDataName("name", myInt, MUTABLE) + var template1 = Template.make(() -> transparentScope( + addDataName("name", myInt, MUTABLE) // escapes )); - var template2 = Template.make(() -> body( - hook1.anchor( + var template2 = Template.make(() -> scope( + hook1.anchor(scope( addDataName("name", myInt, MUTABLE), hook1.insert(template1.asToken()) - ) + )) )); String code = template2.render(); } + public static void testFailingScope1() { + var template = Template.make(() -> scope( + transparentScope( + let("x", "x1") // escapes + ), + let("x", "x2") // second definition + )); + String code = template.render(); + } + + public static void testFailingScope2() { + var template = Template.make(() -> scope( + nameScope( + let("x", "x1") // escapes + ), + let("x", "x2") // second definition + )); + String code = template.render(); + } + + public static void testFailingScope3() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + let("x", sn.name()) // leads to duplicate hashtag + )) + )); + String code = template.render(); + } + + public static void testFailingScope4() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> nameScope( + let("x", sn.name()) // leads to duplicate hashtag + )) + )); + String code = template.render(); + } + + public static void testFailingScope5() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> transparentScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + + public static void testFailingScope6() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> hashtagScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + + public static void testFailingScope7() { + var template = Template.make(() -> scope( + addStructuralName("a", myStructuralTypeA), + addStructuralName("b", myStructuralTypeA), + structuralNames().exactOf(myStructuralTypeA).forEach(sn -> setFuelCostScope( + addStructuralName("x", myStructuralTypeA) // leads to duplicate name + )) + )); + String code = template.render(); + } + public static void expectRendererException(FailingTest test, String errorPrefix) { try { test.run(); From ad38a1253ae3ff92f7e0cf0fbc4d4726957b1443 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Thu, 20 Nov 2025 10:19:57 +0000 Subject: [PATCH 040/114] 8371557: java/net/httpclient/http3/H3RequestRejectedTest.java: javax.net.ssl.SSLHandshakeException: local endpoint (wildcard) and remote endpoint (loopback) ports conflict Reviewed-by: jpai --- .../jdk/java/net/httpclient/http3/H3RequestRejectedTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java b/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java index 361a78fdd47..1731ddae833 100644 --- a/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java +++ b/test/jdk/java/net/httpclient/http3/H3RequestRejectedTest.java @@ -50,6 +50,7 @@ import static java.net.http.HttpOption.H3_DISCOVERY; import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; import static java.net.http.HttpRequest.BodyPublishers; import static java.nio.charset.StandardCharsets.US_ASCII; +import static jdk.httpclient.test.lib.common.HttpServerAdapters.createClientBuilderForH3; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -100,7 +101,7 @@ class H3RequestRejectedTest { */ @Test void testAlwaysRejected() throws Exception { - try (final HttpClient client = HttpClient.newBuilder() + try (final HttpClient client = createClientBuilderForH3() .sslContext(sslContext).proxy(NO_PROXY).version(HTTP_3) .build()) { @@ -134,7 +135,7 @@ class H3RequestRejectedTest { */ @Test void testRejectedRequest() throws Exception { - try (final HttpClient client = HttpClient.newBuilder().sslContext(sslContext) + try (final HttpClient client = createClientBuilderForH3().sslContext(sslContext) .proxy(NO_PROXY).version(HTTP_3) .build()) { From c419dda4e99c3b72fbee95b93159db2e23b994b6 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Thu, 20 Nov 2025 11:37:07 +0000 Subject: [PATCH 041/114] 8372163: G1: Remove unused G1HeapRegion::remove_code_root Reviewed-by: tschatzl --- src/hotspot/share/gc/g1/g1HeapRegion.cpp | 4 ---- src/hotspot/share/gc/g1/g1HeapRegion.hpp | 1 - 2 files changed, 5 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.cpp b/src/hotspot/share/gc/g1/g1HeapRegion.cpp index b1eeb333d8d..361e19d4be5 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.cpp @@ -307,10 +307,6 @@ void G1HeapRegion::add_code_root(nmethod* nm) { rem_set()->add_code_root(nm); } -void G1HeapRegion::remove_code_root(nmethod* nm) { - rem_set()->remove_code_root(nm); -} - void G1HeapRegion::code_roots_do(NMethodClosure* blk) const { rem_set()->code_roots_do(blk); } diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.hpp b/src/hotspot/share/gc/g1/g1HeapRegion.hpp index 17ec3055b52..fe915b0dafe 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.hpp @@ -543,7 +543,6 @@ public: // Routines for managing a list of code roots (attached to the // this region's RSet) that point into this heap region. void add_code_root(nmethod* nm); - void remove_code_root(nmethod* nm); // Applies blk->do_nmethod() to each of the entries in // the code roots list for this region From 7b11bd1b1d8dbc9bedcd8cf14e78c8f5eb06a71f Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Thu, 20 Nov 2025 13:39:49 +0000 Subject: [PATCH 042/114] 8372047: ClassTransform.transformingMethodBodies andThen composes incorrectly Reviewed-by: asotona --- .../classfile/impl/TransformImpl.java | 18 ++--- test/jdk/jdk/classfile/TransformTests.java | 74 +++++++++++++++---- 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java index 23387fcb098..4cb0517ec76 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/TransformImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -120,9 +120,9 @@ public final class TransformImpl { @Override public ClassTransform andThen(ClassTransform next) { - if (next instanceof ClassMethodTransform cmt) - return new ClassMethodTransform(transform.andThen(cmt.transform), - mm -> filter.test(mm) && cmt.filter.test(mm)); + // Optimized for shared _ -> true filter in ClassTransform.transformingMethods(MethodTransform) + if (next instanceof ClassMethodTransform(var nextTransform, var nextFilter) && filter == nextFilter) + return new ClassMethodTransform(transform.andThen(nextTransform), filter); else return UnresolvedClassTransform.super.andThen(next); } @@ -143,9 +143,9 @@ public final class TransformImpl { @Override public ClassTransform andThen(ClassTransform next) { - if (next instanceof ClassFieldTransform cft) - return new ClassFieldTransform(transform.andThen(cft.transform), - mm -> filter.test(mm) && cft.filter.test(mm)); + // Optimized for shared _ -> true filter in ClassTransform.transformingFields(FieldTransform) + if (next instanceof ClassFieldTransform(var nextTransform, var nextFilter) && filter == nextFilter) + return new ClassFieldTransform(transform.andThen(nextTransform), filter); else return UnresolvedClassTransform.super.andThen(next); } @@ -208,8 +208,8 @@ public final class TransformImpl { @Override public MethodTransform andThen(MethodTransform next) { - return (next instanceof TransformImpl.MethodCodeTransform mct) - ? new TransformImpl.MethodCodeTransform(xform.andThen(mct.xform)) + return (next instanceof MethodCodeTransform(var nextXform)) + ? new TransformImpl.MethodCodeTransform(xform.andThen(nextXform)) : UnresolvedMethodTransform.super.andThen(next); } diff --git a/test/jdk/jdk/classfile/TransformTests.java b/test/jdk/jdk/classfile/TransformTests.java index b78da8b4311..351aa1ae35a 100644 --- a/test/jdk/jdk/classfile/TransformTests.java +++ b/test/jdk/jdk/classfile/TransformTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, 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 @@ -23,25 +23,15 @@ /* * @test - * @bug 8335935 8336588 + * @bug 8335935 8336588 8372047 * @summary Testing ClassFile transformations. * @run junit TransformTests */ -import java.lang.classfile.ClassBuilder; -import java.lang.classfile.ClassElement; -import java.lang.classfile.ClassFile; -import java.lang.classfile.ClassModel; -import java.lang.classfile.ClassTransform; -import java.lang.classfile.CodeBuilder; -import java.lang.classfile.CodeElement; -import java.lang.classfile.CodeModel; -import java.lang.classfile.CodeTransform; -import java.lang.classfile.FieldModel; -import java.lang.classfile.FieldTransform; -import java.lang.classfile.Label; -import java.lang.classfile.MethodModel; -import java.lang.classfile.MethodTransform; +import java.lang.classfile.*; +import java.lang.classfile.attribute.AnnotationDefaultAttribute; +import java.lang.classfile.attribute.ConstantValueAttribute; +import java.lang.classfile.attribute.SourceDebugExtensionAttribute; import java.lang.classfile.instruction.BranchInstruction; import java.lang.classfile.instruction.ConstantInstruction; import java.lang.classfile.instruction.LabelTarget; @@ -54,8 +44,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import helpers.ByteArrayClassLoader; +import jdk.internal.classfile.impl.TransformImpl; import org.junit.jupiter.api.Test; import static java.lang.classfile.ClassFile.*; @@ -334,4 +326,54 @@ class TransformTests { return "foo"; } } + + @Test + void testFilteringTransformChaining() { + var cf = ClassFile.of(); + var clazz = cf.parse(cf.build(ClassDesc.of("Test"), clb -> clb + .withField("one", CD_int, fb -> fb.with(ConstantValueAttribute.of(1))) + .withField("two", CD_int, fb -> fb.with(ConstantValueAttribute.of(2))) + .withMethod("one", MTD_void, 0, mb -> mb.with(AnnotationDefaultAttribute.of(AnnotationValue.ofInt(1))).withCode(CodeBuilder::return_)) + .withMethod("two", MTD_void, 0, mb -> mb.with(AnnotationDefaultAttribute.of(AnnotationValue.ofInt(2))).withCode(CodeBuilder::return_)))); + + AtomicBoolean oneFieldCalled = new AtomicBoolean(false); + var oneFieldTransform = new TransformImpl.ClassFieldTransform((fb, fe) -> { + if (fe instanceof ConstantValueAttribute cv) { + assertEquals(1, ((Integer) cv.constant().constantValue()), "Should only transform one"); + } + oneFieldCalled.set(true); + fb.with(fe); + }, fm -> fm.fieldName().equalsString("one")); + AtomicBoolean twoFieldCalled = new AtomicBoolean(false); + var twoFieldTransform = new TransformImpl.ClassFieldTransform((fb, fe) -> { + if (fe instanceof ConstantValueAttribute cv) { + assertEquals(2, ((Integer) cv.constant().constantValue()), "Should only transform two"); + } + twoFieldCalled.set(true); + fb.with(fe); + }, fm -> fm.fieldName().equalsString("two")); + cf.transformClass(clazz, oneFieldTransform.andThen(twoFieldTransform)); + assertTrue(oneFieldCalled.get(), "Field one not transformed"); + assertTrue(twoFieldCalled.get(), "Field two not transformed"); + + AtomicBoolean oneMethodCalled = new AtomicBoolean(false); + var oneMethodTransform = ClassTransform.transformingMethods(mm -> mm.methodName().equalsString("one"), (mb, me) -> { + if (me instanceof AnnotationDefaultAttribute ada) { + assertEquals(1, ((AnnotationValue.OfInt) ada.defaultValue()).intValue(), "Should only transform one"); + } + oneMethodCalled.set(true); + mb.with(me); + }); + AtomicBoolean twoMethodCalled = new AtomicBoolean(false); + var twoMethodTransform = ClassTransform.transformingMethods(mm -> mm.methodName().equalsString("two"), (mb, me) -> { + if (me instanceof AnnotationDefaultAttribute ada) { + assertEquals(2, ((AnnotationValue.OfInt) ada.defaultValue()).intValue(), "Should only transform two"); + } + twoMethodCalled.set(true); + mb.with(me); + }); + cf.transformClass(clazz, oneMethodTransform.andThen(twoMethodTransform)); + assertTrue(oneMethodCalled.get(), "Method one not transformed"); + assertTrue(twoMethodCalled.get(), "Method two not transformed"); + } } From f125c76f5b53d90a09f58c22d6def7d843feaa50 Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Thu, 20 Nov 2025 14:09:55 +0000 Subject: [PATCH 043/114] 8247690: RunTest does not support running of JTREG manual tests Reviewed-by: erikj --- doc/testing.html | 2 ++ doc/testing.md | 4 ++++ make/RunTests.gmk | 10 ++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/doc/testing.html b/doc/testing.html index b9838735e4f..31f4fbd1778 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -535,6 +535,8 @@ failure. This helps to reproduce intermittent test failures. Defaults to

REPORT

Use this report style when reporting test results (sent to JTReg as -report). Defaults to files.

+

MANUAL

+

Set to true to execute manual tests only.

Gtest keywords

REPEAT

The number of times to repeat the tests diff --git a/doc/testing.md b/doc/testing.md index 0144610a5bf..b95f59de9fd 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -512,6 +512,10 @@ helps to reproduce intermittent test failures. Defaults to 0. Use this report style when reporting test results (sent to JTReg as `-report`). Defaults to `files`. +#### MANUAL + +Set to `true` to execute manual tests only. + ### Gtest keywords #### REPEAT diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 947389f64f9..21eb178343e 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -206,7 +206,7 @@ $(eval $(call ParseKeywordVariable, JTREG, \ SINGLE_KEYWORDS := JOBS TIMEOUT_FACTOR FAILURE_HANDLER_TIMEOUT \ TEST_MODE ASSERT VERBOSE RETAIN TEST_THREAD_FACTORY JVMTI_STRESS_AGENT \ MAX_MEM RUN_PROBLEM_LISTS RETRY_COUNT REPEAT_COUNT MAX_OUTPUT REPORT \ - AOT_JDK $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ + AOT_JDK MANUAL $(CUSTOM_JTREG_SINGLE_KEYWORDS), \ STRING_KEYWORDS := OPTIONS JAVA_OPTIONS VM_OPTIONS KEYWORDS \ EXTRA_PROBLEM_LISTS LAUNCHER_OPTIONS \ $(CUSTOM_JTREG_STRING_KEYWORDS), \ @@ -911,7 +911,13 @@ define SetupRunJtregTestBody -vmoption:-Dtest.boot.jdk="$$(BOOT_JDK)" \ -vmoption:-Djava.io.tmpdir="$$($1_TEST_TMP_DIR)" - $1_JTREG_BASIC_OPTIONS += -automatic -ignore:quiet + $1_JTREG_BASIC_OPTIONS += -ignore:quiet + + ifeq ($$(JTREG_MANUAL), true) + $1_JTREG_BASIC_OPTIONS += -manual + else + $1_JTREG_BASIC_OPTIONS += -automatic + endif # Make it possible to specify the JIB_DATA_DIR for tests using the # JIB Artifact resolver From b9ee9541cffb6c5a737b08a69ae04472b3bcab3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20=C3=96sterlund?= Date: Thu, 20 Nov 2025 14:33:40 +0000 Subject: [PATCH 044/114] 8371200: ZGC: C2 allocation deopt race Reviewed-by: aboldtch, stefank --- src/hotspot/share/gc/z/zBarrier.inline.hpp | 4 -- src/hotspot/share/gc/z/zBarrierSet.cpp | 20 ---------- src/hotspot/share/gc/z/zGeneration.cpp | 42 ++++++++++++-------- src/hotspot/share/gc/z/zRelocate.cpp | 45 ++++++++++++++++------ src/hotspot/share/gc/z/zRelocate.hpp | 1 + 5 files changed, 60 insertions(+), 52 deletions(-) diff --git a/src/hotspot/share/gc/z/zBarrier.inline.hpp b/src/hotspot/share/gc/z/zBarrier.inline.hpp index b5923f01628..766a6eb8e4c 100644 --- a/src/hotspot/share/gc/z/zBarrier.inline.hpp +++ b/src/hotspot/share/gc/z/zBarrier.inline.hpp @@ -86,10 +86,6 @@ inline void ZBarrier::self_heal(ZBarrierFastPath fast_path, volatile zpointer* p assert(ZPointer::is_remapped(heal_ptr), "invariant"); for (;;) { - if (ptr == zpointer::null) { - assert(!ZVerifyOops || !ZHeap::heap()->is_in(uintptr_t(p)) || !ZHeap::heap()->is_old(p), "No raw null in old"); - } - assert_transition_monotonicity(ptr, heal_ptr); // Heal diff --git a/src/hotspot/share/gc/z/zBarrierSet.cpp b/src/hotspot/share/gc/z/zBarrierSet.cpp index 87f93043bdf..643eba1947e 100644 --- a/src/hotspot/share/gc/z/zBarrierSet.cpp +++ b/src/hotspot/share/gc/z/zBarrierSet.cpp @@ -223,27 +223,7 @@ void ZBarrierSet::on_slowpath_allocation_exit(JavaThread* thread, oop new_obj) { // breaks that promise. Take a few steps in the interpreter instead, which has // no such assumptions about where an object resides. deoptimize_allocation(thread); - return; } - - if (!ZGeneration::young()->is_phase_mark_complete()) { - return; - } - - if (!page->is_relocatable()) { - return; - } - - if (ZRelocate::compute_to_age(age) != ZPageAge::old) { - return; - } - - // If the object is young, we have to still be careful that it isn't racingly - // about to get promoted to the old generation. That causes issues when null - // pointers are supposed to be coloured, but the JIT is a bit sloppy and - // reinitializes memory with raw nulls. We detect this situation and detune - // rather than relying on the JIT to never be sloppy with redundant initialization. - deoptimize_allocation(thread); } void ZBarrierSet::print_on(outputStream* st) const { diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index d1680b6c336..2b632ef29a9 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -111,6 +111,16 @@ static const ZStatSampler ZSamplerJavaThreads("System", "Java Threads", ZStatUni ZGenerationYoung* ZGeneration::_young; ZGenerationOld* ZGeneration::_old; +class ZRendezvousHandshakeClosure : public HandshakeClosure { +public: + ZRendezvousHandshakeClosure() + : HandshakeClosure("ZRendezvous") {} + + void do_thread(Thread* thread) { + // Does nothing + } +}; + ZGeneration::ZGeneration(ZGenerationId id, ZPageTable* page_table, ZPageAllocator* page_allocator) : _id(id), _page_allocator(page_allocator), @@ -168,11 +178,19 @@ void ZGeneration::free_empty_pages(ZRelocationSetSelector* selector, int bulk) { } void ZGeneration::flip_age_pages(const ZRelocationSetSelector* selector) { - if (is_young()) { - _relocate.flip_age_pages(selector->not_selected_small()); - _relocate.flip_age_pages(selector->not_selected_medium()); - _relocate.flip_age_pages(selector->not_selected_large()); - } + _relocate.flip_age_pages(selector->not_selected_small()); + _relocate.flip_age_pages(selector->not_selected_medium()); + _relocate.flip_age_pages(selector->not_selected_large()); + + // Perform a handshake between flip promotion and running the promotion barrier. This ensures + // that ZBarrierSet::on_slowpath_allocation_exit() observing a young page that was then racingly + // flip promoted, will run any stores without barriers to completion before responding to the + // handshake at the subsequent safepoint poll. This ensures that the flip promotion barriers always + // run after compiled code missing barriers, but before relocate start. + ZRendezvousHandshakeClosure cl; + Handshake::execute(&cl); + + _relocate.barrier_flip_promoted_pages(_relocation_set.flip_promoted_pages()); } static double fragmentation_limit(ZGenerationId generation) { @@ -235,7 +253,9 @@ void ZGeneration::select_relocation_set(bool promote_all) { _relocation_set.install(&selector); // Flip age young pages that were not selected - flip_age_pages(&selector); + if (is_young()) { + flip_age_pages(&selector); + } // Setup forwarding table ZRelocationSetIterator rs_iter(&_relocation_set); @@ -1280,16 +1300,6 @@ bool ZGenerationOld::uses_clear_all_soft_reference_policy() const { return _reference_processor.uses_clear_all_soft_reference_policy(); } -class ZRendezvousHandshakeClosure : public HandshakeClosure { -public: - ZRendezvousHandshakeClosure() - : HandshakeClosure("ZRendezvous") {} - - void do_thread(Thread* thread) { - // Does nothing - } -}; - class ZRendezvousGCThreads: public VM_Operation { public: VMOp_Type type() const { return VMOp_ZRendezvousGCThreads; } diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index 69233da6f54..180ce22b041 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -1322,7 +1322,7 @@ private: public: ZFlipAgePagesTask(const ZArray* pages) - : ZTask("ZPromotePagesTask"), + : ZTask("ZFlipAgePagesTask"), _iter(pages) {} virtual void work() { @@ -1337,16 +1337,6 @@ public: // Figure out if this is proper promotion const bool promotion = to_age == ZPageAge::old; - if (promotion) { - // Before promoting an object (and before relocate start), we must ensure that all - // contained zpointers are store good. The marking code ensures that for non-null - // pointers, but null pointers are ignored. This code ensures that even null pointers - // are made store good, for the promoted objects. - prev_page->object_iterate([&](oop obj) { - ZIterator::basic_oop_iterate_safe(obj, ZBarrier::promote_barrier_on_young_oop_field); - }); - } - // Logging prev_page->log_msg(promotion ? " (flip promoted)" : " (flip survived)"); @@ -1360,7 +1350,7 @@ public: if (promotion) { ZGeneration::young()->flip_promote(prev_page, new_page); - // Defer promoted page registration times the lock is taken + // Defer promoted page registration promoted_pages.push(prev_page); } @@ -1371,11 +1361,42 @@ public: } }; +class ZPromoteBarrierTask : public ZTask { +private: + ZArrayParallelIterator _iter; + +public: + ZPromoteBarrierTask(const ZArray* pages) + : ZTask("ZPromoteBarrierTask"), + _iter(pages) {} + + virtual void work() { + SuspendibleThreadSetJoiner sts_joiner; + + for (ZPage* page; _iter.next(&page);) { + // When promoting an object (and before relocate start), we must ensure that all + // contained zpointers are store good. The marking code ensures that for non-null + // pointers, but null pointers are ignored. This code ensures that even null pointers + // are made store good, for the promoted objects. + page->object_iterate([&](oop obj) { + ZIterator::basic_oop_iterate_safe(obj, ZBarrier::promote_barrier_on_young_oop_field); + }); + + SuspendibleThreadSet::yield(); + } + } +}; + void ZRelocate::flip_age_pages(const ZArray* pages) { ZFlipAgePagesTask flip_age_task(pages); workers()->run(&flip_age_task); } +void ZRelocate::barrier_flip_promoted_pages(const ZArray* pages) { + ZPromoteBarrierTask promote_barrier_task(pages); + workers()->run(&promote_barrier_task); +} + void ZRelocate::synchronize() { _queue.synchronize(); } diff --git a/src/hotspot/share/gc/z/zRelocate.hpp b/src/hotspot/share/gc/z/zRelocate.hpp index d0ddf7deecf..50111f24ee5 100644 --- a/src/hotspot/share/gc/z/zRelocate.hpp +++ b/src/hotspot/share/gc/z/zRelocate.hpp @@ -119,6 +119,7 @@ public: void relocate(ZRelocationSet* relocation_set); void flip_age_pages(const ZArray* pages); + void barrier_flip_promoted_pages(const ZArray* pages); void synchronize(); void desynchronize(); From 45a2fd37f0ebda35789006b4e607422f7c369017 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Thu, 20 Nov 2025 15:15:41 +0000 Subject: [PATCH 045/114] 8325448: Hybrid Public Key Encryption Reviewed-by: mullan, ascarpino, abarashev --- .../com/sun/crypto/provider/DHKEM.java | 361 +++++++---- .../classes/com/sun/crypto/provider/HPKE.java | 588 ++++++++++++++++++ .../com/sun/crypto/provider/SunJCE.java | 2 + .../javax/crypto/spec/HPKEParameterSpec.java | 443 +++++++++++++ .../spec/snippet-files/PackageSnippets.java | 76 +++ .../sun/security/util/SliceableSecretKey.java | 51 ++ .../provider/Cipher/HPKE/Compliance.java | 289 +++++++++ .../provider/Cipher/HPKE/Functions.java | 113 ++++ .../crypto/provider/Cipher/HPKE/KAT9180.java | 126 ++++ .../sun/crypto/provider/DHKEM/Compliance.java | 136 ++-- .../security/provider/all/Deterministic.java | 10 +- .../SliceableSecretKey/SoftSliceable.java | 153 +++++ 12 files changed, 2119 insertions(+), 229 deletions(-) create mode 100644 src/java.base/share/classes/com/sun/crypto/provider/HPKE.java create mode 100644 src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java create mode 100644 src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java create mode 100644 src/java.base/share/classes/sun/security/util/SliceableSecretKey.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java create mode 100644 test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java create mode 100644 test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java index b27320ed24b..c7372a4c2c8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/DHKEM.java @@ -26,26 +26,51 @@ package com.sun.crypto.provider; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.Serial; import java.math.BigInteger; -import java.security.*; -import java.security.interfaces.ECKey; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; -import java.security.interfaces.XECKey; import java.security.interfaces.XECPublicKey; -import java.security.spec.*; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.NamedParameterSpec; +import java.security.spec.XECPrivateKeySpec; +import java.security.spec.XECPublicKeySpec; import java.util.Arrays; import java.util.Objects; -import javax.crypto.*; -import javax.crypto.spec.SecretKeySpec; +import javax.crypto.DecapsulateException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; +import javax.crypto.spec.SecretKeySpec; import sun.security.jca.JCAUtil; -import sun.security.util.*; - -import jdk.internal.access.SharedSecrets; +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.InternalPrivateKey; +import sun.security.util.NamedCurve; +import sun.security.util.SliceableSecretKey; // Implementing DHKEM defined inside https://www.rfc-editor.org/rfc/rfc9180.html, -// without the AuthEncap and AuthDecap functions public class DHKEM implements KEMSpi { private static final byte[] KEM = new byte[] @@ -65,80 +90,86 @@ public class DHKEM implements KEMSpi { private static final byte[] EMPTY = new byte[0]; private record Handler(Params params, SecureRandom secureRandom, - PrivateKey skR, PublicKey pkR) + PrivateKey skS, PublicKey pkS, // sender keys + PrivateKey skR, PublicKey pkR) // receiver keys implements EncapsulatorSpi, DecapsulatorSpi { @Override public KEM.Encapsulated engineEncapsulate(int from, int to, String algorithm) { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); KeyPair kpE = params.generateKeyPair(secureRandom); PrivateKey skE = kpE.getPrivate(); PublicKey pkE = kpE.getPublic(); - byte[] pkEm = params.SerializePublicKey(pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(pkEm, pkRm); - byte[] key = null; + byte[] pkEm = params.serializePublicKey(pkE); + byte[] pkRm = params.serializePublicKey(pkR); try { - byte[] dh = params.DH(skE, pkR); - key = params.ExtractAndExpand(dh, kem_context); - return new KEM.Encapsulated( - new SecretKeySpec(key, from, to - from, algorithm), - pkEm, null); + SecretKey key; + if (skS == null) { + byte[] kem_context = concat(pkEm, pkRm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(pkEm, pkRm, pkSm); + key = params.deriveKey(algorithm, from, to, kem_context, + params.dh(skE, pkR), params.dh(skS, pkR)); + } + return new KEM.Encapsulated(key, pkEm, null); + } catch (UnsupportedOperationException e) { + throw e; } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - // `key` has been cloned into the `SecretKeySpec` within the - // returned `KEM.Encapsulated`, so it can now be cleared. - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public SecretKey engineDecapsulate(byte[] encapsulation, int from, int to, String algorithm) throws DecapsulateException { - Objects.checkFromToIndex(from, to, params.Nsecret); + Objects.checkFromToIndex(from, to, params.nsecret); Objects.requireNonNull(algorithm, "null algorithm"); Objects.requireNonNull(encapsulation, "null encapsulation"); - if (encapsulation.length != params.Npk) { + if (encapsulation.length != params.npk) { throw new DecapsulateException("incorrect encapsulation size"); } - byte[] key = null; try { - PublicKey pkE = params.DeserializePublicKey(encapsulation); - byte[] dh = params.DH(skR, pkE); - byte[] pkRm = params.SerializePublicKey(pkR); - byte[] kem_context = concat(encapsulation, pkRm); - key = params.ExtractAndExpand(dh, kem_context); - return new SecretKeySpec(key, from, to - from, algorithm); + PublicKey pkE = params.deserializePublicKey(encapsulation); + byte[] pkRm = params.serializePublicKey(pkR); + if (pkS == null) { + byte[] kem_context = concat(encapsulation, pkRm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE)); + } else { + byte[] pkSm = params.serializePublicKey(pkS); + byte[] kem_context = concat(encapsulation, pkRm, pkSm); + return params.deriveKey(algorithm, from, to, kem_context, + params.dh(skR, pkE), params.dh(skR, pkS)); + } + } catch (UnsupportedOperationException e) { + throw e; } catch (IOException | InvalidKeyException e) { throw new DecapsulateException("Cannot decapsulate", e); } catch (Exception e) { throw new ProviderException("internal error", e); - } finally { - if (key != null) { - Arrays.fill(key, (byte)0); - } } } @Override public int engineSecretSize() { - return params.Nsecret; + return params.nsecret; } @Override public int engineEncapsulationSize() { - return params.Npk; + return params.npk; } } // Not really a random. For KAT test only. It generates key pair from ikm. public static class RFC9180DeriveKeyPairSR extends SecureRandom { - static final long serialVersionUID = 0L; + @Serial + private static final long serialVersionUID = 0L; private final byte[] ikm; @@ -147,7 +178,7 @@ public class DHKEM implements KEMSpi { this.ikm = ikm; } - public KeyPair derive(Params params) { + private KeyPair derive(Params params) { try { return params.deriveKeyPair(ikm); } catch (Exception e) { @@ -183,9 +214,9 @@ public class DHKEM implements KEMSpi { ; private final int kem_id; - private final int Nsecret; - private final int Nsk; - private final int Npk; + private final int nsecret; + private final int nsk; + private final int npk; private final String kaAlgorithm; private final String keyAlgorithm; private final AlgorithmParameterSpec spec; @@ -193,18 +224,18 @@ public class DHKEM implements KEMSpi { private final byte[] suiteId; - Params(int kem_id, int Nsecret, int Nsk, int Npk, + Params(int kem_id, int nsecret, int nsk, int npk, String kaAlgorithm, String keyAlgorithm, AlgorithmParameterSpec spec, String hkdfAlgorithm) { this.kem_id = kem_id; this.spec = spec; - this.Nsecret = Nsecret; - this.Nsk = Nsk; - this.Npk = Npk; + this.nsecret = nsecret; + this.nsk = nsk; + this.npk = npk; this.kaAlgorithm = kaAlgorithm; this.keyAlgorithm = keyAlgorithm; this.hkdfAlgorithm = hkdfAlgorithm; - suiteId = concat(KEM, I2OSP(kem_id, 2)); + suiteId = concat(KEM, i2OSP(kem_id, 2)); } private boolean isEC() { @@ -224,18 +255,18 @@ public class DHKEM implements KEMSpi { } } - private byte[] SerializePublicKey(PublicKey k) { + private byte[] serializePublicKey(PublicKey k) { if (isEC()) { ECPoint w = ((ECPublicKey) k).getW(); return ECUtil.encodePoint(w, ((NamedCurve) spec).getCurve()); } else { byte[] uArray = ((XECPublicKey) k).getU().toByteArray(); ArrayUtil.reverse(uArray); - return Arrays.copyOf(uArray, Npk); + return Arrays.copyOf(uArray, npk); } } - private PublicKey DeserializePublicKey(byte[] data) + private PublicKey deserializePublicKey(byte[] data) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { KeySpec keySpec; if (isEC()) { @@ -251,29 +282,59 @@ public class DHKEM implements KEMSpi { return KeyFactory.getInstance(keyAlgorithm).generatePublic(keySpec); } - private byte[] DH(PrivateKey skE, PublicKey pkR) + private SecretKey dh(PrivateKey skE, PublicKey pkR) throws NoSuchAlgorithmException, InvalidKeyException { KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); ka.init(skE); ka.doPhase(pkR, true); - return ka.generateSecret(); + return ka.generateSecret("Generic"); } - private byte[] ExtractAndExpand(byte[] dh, byte[] kem_context) - throws NoSuchAlgorithmException, InvalidKeyException { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey eae_prk = LabeledExtract(hkdf, suiteId, EAE_PRK, dh); - try { - return LabeledExpand(hkdf, suiteId, eae_prk, SHARED_SECRET, - kem_context, Nsecret); - } finally { - if (eae_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + // The final shared secret derivation of either the encapsulator + // or the decapsulator. The key slicing is implemented inside. + // Throws UOE if a slice of the key cannot be found. + private SecretKey deriveKey(String alg, int from, int to, + byte[] kem_context, SecretKey... dhs) + throws NoSuchAlgorithmException { + if (from == 0 && to == nsecret) { + return extractAndExpand(kem_context, alg, dhs); + } else { + // First get shared secrets in "Generic" and then get a slice + // of it in the requested algorithm. + var fullKey = extractAndExpand(kem_context, "Generic", dhs); + if ("RAW".equalsIgnoreCase(fullKey.getFormat())) { + byte[] km = fullKey.getEncoded(); + if (km == null) { + // Should not happen if format is "RAW" + throw new UnsupportedOperationException("Key extract failed"); + } else { + try { + return new SecretKeySpec(km, from, to - from, alg); + } finally { + Arrays.fill(km, (byte)0); + } + } + } else if (fullKey instanceof SliceableSecretKey ssk) { + return ssk.slice(alg, from, to); + } else { + throw new UnsupportedOperationException("Cannot extract key"); } } } + private SecretKey extractAndExpand(byte[] kem_context, String alg, SecretKey... dhs) + throws NoSuchAlgorithmException { + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, EAE_PRK); + for (var dh : dhs) builder.addIKM(dh); + try { + return kdf.deriveKey(alg, + labeledExpand(builder, suiteId, SHARED_SECRET, kem_context, nsecret)); + } catch (InvalidAlgorithmParameterException e) { + throw new ProviderException(e); + } + } + private PublicKey getPublicKey(PrivateKey sk) throws InvalidKeyException { if (!(sk instanceof InternalPrivateKey)) { @@ -298,45 +359,37 @@ public class DHKEM implements KEMSpi { // For KAT tests only. See RFC9180DeriveKeyPairSR. public KeyPair deriveKeyPair(byte[] ikm) throws Exception { - KDF hkdf = KDF.getInstance(hkdfAlgorithm); - SecretKey dkp_prk = LabeledExtract(hkdf, suiteId, DKP_PRK, ikm); - try { - if (isEC()) { - NamedCurve curve = (NamedCurve) spec; - BigInteger sk = BigInteger.ZERO; - int counter = 0; - while (sk.signum() == 0 || - sk.compareTo(curve.getOrder()) >= 0) { - if (counter > 255) { - throw new RuntimeException(); - } - byte[] bytes = LabeledExpand(hkdf, suiteId, dkp_prk, - CANDIDATE, I2OSP(counter, 1), Nsk); - // bitmask is defined to be 0xFF for P-256 and P-384, - // and 0x01 for P-521 - if (this == Params.P521) { - bytes[0] = (byte) (bytes[0] & 0x01); - } - sk = new BigInteger(1, (bytes)); - counter = counter + 1; + var kdf = KDF.getInstance(hkdfAlgorithm); + var builder = labeledExtract(suiteId, DKP_PRK).addIKM(ikm); + if (isEC()) { + NamedCurve curve = (NamedCurve) spec; + BigInteger sk = BigInteger.ZERO; + int counter = 0; + while (sk.signum() == 0 || sk.compareTo(curve.getOrder()) >= 0) { + if (counter > 255) { + // So unlucky and should not happen + throw new ProviderException("DeriveKeyPairError"); } - PrivateKey k = DeserializePrivateKey(sk.toByteArray()); - return new KeyPair(getPublicKey(k), k); - } else { - byte[] sk = LabeledExpand(hkdf, suiteId, dkp_prk, SK, EMPTY, - Nsk); - PrivateKey k = DeserializePrivateKey(sk); - return new KeyPair(getPublicKey(k), k); - } - } finally { - if (dkp_prk instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); + byte[] bytes = kdf.deriveData(labeledExpand(builder, + suiteId, CANDIDATE, i2OSP(counter, 1), nsk)); + // bitmask is defined to be 0xFF for P-256 and P-384, and 0x01 for P-521 + if (this == Params.P521) { + bytes[0] = (byte) (bytes[0] & 0x01); + } + sk = new BigInteger(1, (bytes)); + counter = counter + 1; } + PrivateKey k = deserializePrivateKey(sk.toByteArray()); + return new KeyPair(getPublicKey(k), k); + } else { + byte[] sk = kdf.deriveData(labeledExpand(builder, + suiteId, SK, EMPTY, nsk)); + PrivateKey k = deserializePrivateKey(sk); + return new KeyPair(getPublicKey(k), k); } } - private PrivateKey DeserializePrivateKey(byte[] data) throws Exception { + private PrivateKey deserializePrivateKey(byte[] data) throws Exception { KeySpec keySpec = isEC() ? new ECPrivateKeySpec(new BigInteger(1, (data)), (NamedCurve) spec) : new XECPrivateKeySpec(spec, data); @@ -359,7 +412,22 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(pk); - return new Handler(params, getSecureRandom(secureRandom), null, pk); + return new Handler(params, getSecureRandom(secureRandom), null, null, null, pk); + } + + // AuthEncap is not public KEM API + public EncapsulatorSpi engineNewAuthEncapsulator(PublicKey pkR, PrivateKey skS, + AlgorithmParameterSpec spec, SecureRandom secureRandom) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (pkR == null || skS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(pkR); + return new Handler(params, getSecureRandom(secureRandom), + skS, params.getPublicKey(skS), null, pkR); } @Override @@ -372,20 +440,34 @@ public class DHKEM implements KEMSpi { throw new InvalidAlgorithmParameterException("no spec needed"); } Params params = paramsFromKey(sk); - return new Handler(params, null, sk, params.getPublicKey(sk)); + return new Handler(params, null, null, null, sk, params.getPublicKey(sk)); } - private Params paramsFromKey(Key k) throws InvalidKeyException { - if (k instanceof ECKey eckey) { - if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) { + // AuthDecap is not public KEM API + public DecapsulatorSpi engineNewAuthDecapsulator( + PrivateKey skR, PublicKey pkS, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (skR == null || pkS == null) { + throw new InvalidKeyException("input key is null"); + } + if (spec != null) { + throw new InvalidAlgorithmParameterException("no spec needed"); + } + Params params = paramsFromKey(skR); + return new Handler(params, null, null, pkS, skR, params.getPublicKey(skR)); + } + + private Params paramsFromKey(AsymmetricKey k) throws InvalidKeyException { + var p = k.getParams(); + if (p instanceof ECParameterSpec ecp) { + if (ECUtil.equals(ecp, CurveDB.P_256)) { return Params.P256; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) { + } else if (ECUtil.equals(ecp, CurveDB.P_384)) { return Params.P384; - } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) { + } else if (ECUtil.equals(ecp, CurveDB.P_521)) { return Params.P521; } - } else if (k instanceof XECKey xkey - && xkey.getParams() instanceof NamedParameterSpec ns) { + } else if (p instanceof NamedParameterSpec ns) { if (ns.getName().equalsIgnoreCase("X25519")) { return Params.X25519; } else if (ns.getName().equalsIgnoreCase("X448")) { @@ -401,8 +483,11 @@ public class DHKEM implements KEMSpi { return o.toByteArray(); } - private static byte[] I2OSP(int n, int w) { - assert n < 256; + // I2OSP(n, w) as defined in RFC 9180 Section 3. + // In DHKEM and HPKE, number is always <65536 + // and converted to at most 2 bytes. + public static byte[] i2OSP(int n, int w) { + assert n < 65536; assert w == 1 || w == 2; if (w == 1) { return new byte[] { (byte) n }; @@ -411,32 +496,32 @@ public class DHKEM implements KEMSpi { } } - private static SecretKey LabeledExtract(KDF hkdf, byte[] suite_id, - byte[] label, byte[] ikm) throws InvalidKeyException { - SecretKeySpec s = new SecretKeySpec(concat(HPKE_V1, suite_id, label, - ikm), "IKM"); - try { - HKDFParameterSpec spec = - HKDFParameterSpec.ofExtract().addIKM(s).extractOnly(); - return hkdf.deriveKey("Generic", spec); - } catch (InvalidAlgorithmParameterException | - NoSuchAlgorithmException e) { - throw new InvalidKeyException(e.getMessage(), e); - } finally { - SharedSecrets.getJavaxCryptoSpecAccess().clearSecretKeySpec(s); - } + // Create a LabeledExtract builder with labels. + // You can add more IKM and salt into the result. + public static HKDFParameterSpec.Builder labeledExtract( + byte[] suiteId, byte[] label) { + return HKDFParameterSpec.ofExtract() + .addIKM(HPKE_V1).addIKM(suiteId).addIKM(label); } - private static byte[] LabeledExpand(KDF hkdf, byte[] suite_id, - SecretKey prk, byte[] label, byte[] info, int L) - throws InvalidKeyException { - byte[] labeled_info = concat(I2OSP(L, 2), HPKE_V1, suite_id, label, - info); - try { - return hkdf.deriveData(HKDFParameterSpec.expandOnly( - prk, labeled_info, L)); - } catch (InvalidAlgorithmParameterException iape) { - throw new InvalidKeyException(iape.getMessage(), iape); - } + // Create a labeled info from info and labels + private static byte[] labeledInfo( + byte[] suiteId, byte[] label, byte[] info, int length) { + return concat(i2OSP(length, 2), HPKE_V1, suiteId, label, info); + } + + // LabeledExpand from a builder + public static HKDFParameterSpec labeledExpand( + HKDFParameterSpec.Builder builder, + byte[] suiteId, byte[] label, byte[] info, int length) { + return builder.thenExpand( + labeledInfo(suiteId, label, info, length), length); + } + + // LabeledExpand from a prk + public static HKDFParameterSpec labeledExpand( + SecretKey prk, byte[] suiteId, byte[] label, byte[] info, int length) { + return HKDFParameterSpec.expandOnly( + prk, labeledInfo(suiteId, label, info, length), length); } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java new file mode 100644 index 00000000000..eee5f59cc75 --- /dev/null +++ b/src/java.base/share/classes/com/sun/crypto/provider/HPKE.java @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2025, 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.crypto.provider; + +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.DecapsulateException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KDF; +import javax.crypto.KEM; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.AsymmetricKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.NamedParameterSpec; +import java.util.Arrays; + +public class HPKE extends CipherSpi { + + private static final byte[] HPKE = new byte[] + {'H', 'P', 'K', 'E'}; + private static final byte[] SEC = new byte[] + {'s', 'e', 'c'}; + private static final byte[] PSK_ID_HASH = new byte[] + {'p', 's', 'k', '_', 'i', 'd', '_', 'h', 'a', 's', 'h'}; + private static final byte[] INFO_HASH = new byte[] + {'i', 'n', 'f', 'o', '_', 'h', 'a', 's', 'h'}; + private static final byte[] SECRET = new byte[] + {'s', 'e', 'c', 'r', 'e', 't'}; + private static final byte[] EXP = new byte[] + {'e', 'x', 'p'}; + private static final byte[] KEY = new byte[] + {'k', 'e', 'y'}; + private static final byte[] BASE_NONCE = new byte[] + {'b', 'a', 's', 'e', '_', 'n', 'o', 'n', 'c', 'e'}; + + private static final int BEGIN = 1; + private static final int EXPORT_ONLY = 2; // init done with aead_id == 65535 + private static final int ENCRYPT_AND_EXPORT = 3; // int done with AEAD + private static final int AFTER_FINAL = 4; // after doFinal, need reinit internal cipher + + private int state = BEGIN; + private Impl impl; + + @Override + protected void engineSetMode(String mode) throws NoSuchAlgorithmException { + throw new NoSuchAlgorithmException(mode); + } + + @Override + protected void engineSetPadding(String padding) throws NoSuchPaddingException { + throw new NoSuchPaddingException(padding); + } + + @Override + protected int engineGetBlockSize() { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getBlockSize(); + } else { + return 0; + } + } + + @Override + protected int engineGetOutputSize(int inputLen) { + if (state == ENCRYPT_AND_EXPORT || state == AFTER_FINAL) { + return impl.aead.cipher.getOutputSize(inputLen); + } else { + return 0; + } + } + + @Override + protected byte[] engineGetIV() { + return (state == BEGIN || impl.kemEncaps == null) + ? null : impl.kemEncaps.clone(); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + impl = new Impl(opmode); + if (!(key instanceof AsymmetricKey ak)) { + throw new InvalidKeyException("Not an asymmetric key"); + } + if (params == null) { + throw new InvalidAlgorithmParameterException( + "HPKEParameterSpec must be provided"); + } else if (params instanceof HPKEParameterSpec hps) { + impl.init(ak, hps, random); + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params type: " + params.getClass()); + } + if (impl.hasEncrypt()) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } else { + state = EXPORT_ONLY; + } + } + + @Override + protected void engineInit(int opmode, Key key, + AlgorithmParameters params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException { + throw new InvalidKeyException("HPKEParameterSpec must be provided"); + } + + // state is ENCRYPT_AND_EXPORT after this call succeeds + private void maybeReinitInternalCipher() { + if (state == BEGIN) { + throw new IllegalStateException("Illegal state: " + state); + } + if (state == EXPORT_ONLY) { + throw new UnsupportedOperationException(); + } + if (state == AFTER_FINAL) { + impl.aead.start(impl.opmode, impl.context.k, impl.context.computeNonce()); + state = ENCRYPT_AND_EXPORT; + } + } + + @Override + protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + maybeReinitInternalCipher(); + return impl.aead.cipher.update(input, inputOffset, inputLen); + } + + @Override + protected int engineUpdate(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException { + maybeReinitInternalCipher(); + return impl.aead.cipher.update( + input, inputOffset, inputLen, output, outputOffset); + } + + @Override + protected void engineUpdateAAD(byte[] src, int offset, int len) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src, offset, len); + } + + @Override + protected void engineUpdateAAD(ByteBuffer src) { + maybeReinitInternalCipher(); + impl.aead.cipher.updateAAD(src); + } + + @Override + protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + if (input == null) { // a bug in doFinal(null, ?, ?) + return impl.aead.cipher.doFinal(); + } else { + return impl.aead.cipher.doFinal(input, inputOffset, inputLen); + } + } + + @Override + protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, + byte[] output, int outputOffset) throws ShortBufferException, + IllegalBlockSizeException, BadPaddingException { + maybeReinitInternalCipher(); + impl.context.IncrementSeq(); + state = AFTER_FINAL; + return impl.aead.cipher.doFinal( + input, inputOffset, inputLen, output, outputOffset); + } + + //@Override + protected SecretKey engineExportKey(String algorithm, byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportKey(algorithm, context, length); + } + } + + //@Override + protected byte[] engineExportData(byte[] context, int length) { + if (state == BEGIN) { + throw new IllegalStateException("State: " + state); + } else { + return impl.context.exportData(context, length); + } + } + + private static class AEAD { + final Cipher cipher; + final int nk, nn, nt; + final int id; + public AEAD(int id) throws InvalidAlgorithmParameterException { + this.id = id; + try { + switch (id) { + case HPKEParameterSpec.AEAD_AES_128_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 16; + } + case HPKEParameterSpec.AEAD_AES_256_GCM -> { + cipher = Cipher.getInstance("AES/GCM/NoPadding"); + nk = 32; + } + case HPKEParameterSpec.AEAD_CHACHA20_POLY1305 -> { + cipher = Cipher.getInstance("ChaCha20-Poly1305"); + nk = 32; + } + case HPKEParameterSpec.EXPORT_ONLY -> { + cipher = null; + nk = -1; + } + default -> throw new InvalidAlgorithmParameterException( + "Unknown aead_id: " + id); + } + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new ProviderException("Internal error", e); + } + nn = 12; nt = 16; + } + + void start(int opmode, SecretKey key, byte[] nonce) { + try { + if (id == HPKEParameterSpec.AEAD_CHACHA20_POLY1305) { + cipher.init(opmode, key, new IvParameterSpec(nonce)); + } else { + cipher.init(opmode, key, new GCMParameterSpec(nt * 8, nonce)); + } + } catch (InvalidAlgorithmParameterException | InvalidKeyException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static class Impl { + + final int opmode; + + HPKEParameterSpec params; + Context context; + AEAD aead; + + byte[] suite_id; + String kdfAlg; + int kdfNh; + + // only used on sender side + byte[] kemEncaps; + + class Context { + final SecretKey k; // null if only export + final byte[] base_nonce; + final SecretKey exporter_secret; + + byte[] seq = new byte[aead.nn]; + + public Context(SecretKey sk, byte[] base_nonce, + SecretKey exporter_secret) { + this.k = sk; + this.base_nonce = base_nonce; + this.exporter_secret = exporter_secret; + } + + SecretKey exportKey(String algorithm, byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveKey(algorithm, DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + byte[] exportData(byte[] exporter_context, int length) { + if (exporter_context == null) { + throw new IllegalArgumentException("Null exporter_context"); + } + try { + var kdf = KDF.getInstance(kdfAlg); + return kdf.deriveData(DHKEM.labeledExpand( + exporter_secret, suite_id, SEC, exporter_context, length)); + } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + // algorithm not accepted by HKDF, length too big or too small + throw new IllegalArgumentException("Invalid input", e); + } + } + + private byte[] computeNonce() { + var result = new byte[aead.nn]; + for (var i = 0; i < result.length; i++) { + result[i] = (byte)(seq[i] ^ base_nonce[i]); + } + return result; + } + + private void IncrementSeq() { + for (var i = seq.length - 1; i >= 0; i--) { + if ((seq[i] & 0xff) == 0xff) { + seq[i] = 0; + } else { + seq[i]++; + return; + } + } + // seq >= (1 << (8*aead.Nn)) - 1 when this method is called + throw new ProviderException("MessageLimitReachedError"); + } + } + + public Impl(int opmode) { + this.opmode = opmode; + } + + public boolean hasEncrypt() { + return params.aead_id() != 65535; + } + + // Section 7.2.1 of RFC 9180 has restrictions on size of psk, psk_id, + // info, and exporter_context (~2^61 for HMAC-SHA256 and ~2^125 for + // HMAC-SHA384 and HMAC-SHA512). This method does not pose any + // restrictions. + public void init(AsymmetricKey key, HPKEParameterSpec p, SecureRandom rand) + throws InvalidKeyException, InvalidAlgorithmParameterException { + if (opmode != Cipher.ENCRYPT_MODE && opmode != Cipher.DECRYPT_MODE) { + throw new UnsupportedOperationException( + "Can only be used for encryption and decryption"); + } + setParams(p); + SecretKey shared_secret; + if (opmode == Cipher.ENCRYPT_MODE) { + if (!(key instanceof PublicKey pk)) { + throw new InvalidKeyException( + "Cannot encrypt with private key"); + } + if (p.encapsulation() != null) { + throw new InvalidAlgorithmParameterException( + "Must not provide key encapsulation message on sender side"); + } + checkMatch(false, pk, params.kem_id()); + KEM.Encapsulated enc; + switch (p.authKey()) { + case null -> { + var e = kem().newEncapsulator(pk, rand); + enc = e.encapsulate(); + } + case PrivateKey skS -> { + checkMatch(true, skS, params.kem_id()); + // AuthEncap not public KEM API but it's internally supported + var e = new DHKEM().engineNewAuthEncapsulator(pk, skS, null, rand); + enc = e.engineEncapsulate(0, e.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with public key"); + } + kemEncaps = enc.encapsulation(); + shared_secret = enc.key(); + } else { + if (!(key instanceof PrivateKey sk)) { + throw new InvalidKeyException("Cannot decrypt with public key"); + } + checkMatch(false, sk, params.kem_id()); + try { + var encap = p.encapsulation(); + if (encap == null) { + throw new InvalidAlgorithmParameterException( + "Must provide key encapsulation message on recipient side"); + } + switch (p.authKey()) { + case null -> { + var d = kem().newDecapsulator(sk); + shared_secret = d.decapsulate(encap); + } + case PublicKey pkS -> { + checkMatch(true, pkS, params.kem_id()); + // AuthDecap not public KEM API but it's internally supported + var d = new DHKEM().engineNewAuthDecapsulator(sk, pkS, null); + shared_secret = d.engineDecapsulate( + encap, 0, d.engineSecretSize(), "Generic"); + } + default -> throw new InvalidAlgorithmParameterException( + "Cannot auth with private key"); + } + } catch (DecapsulateException e) { + throw new InvalidAlgorithmParameterException(e); + } + } + + var usePSK = usePSK(params.psk()); + int mode = params.authKey() == null ? (usePSK ? 1 : 0) : (usePSK ? 3 : 2); + context = keySchedule(mode, shared_secret, + params.info(), + params.psk(), + params.psk_id()); + } + + private static void checkMatch(boolean inSpec, AsymmetricKey k, int kem_id) + throws InvalidKeyException, InvalidAlgorithmParameterException { + var p = k.getParams(); + switch (p) { + case ECParameterSpec ecp -> { + if ((!ECUtil.equals(ecp, CurveDB.P_256) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_256_HKDF_SHA256) + && (!ECUtil.equals(ecp, CurveDB.P_384) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_384_HKDF_SHA384) + && (!ECUtil.equals(ecp, CurveDB.P_521) + || kem_id != HPKEParameterSpec.KEM_DHKEM_P_521_HKDF_SHA512)) { + var name = ECUtil.getCurveName(ecp); + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case NamedParameterSpec ns -> { + var name = ns.getName(); + if ((!name.equalsIgnoreCase("x25519") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256) + && (!name.equalsIgnoreCase("x448") + || kem_id != HPKEParameterSpec.KEM_DHKEM_X448_HKDF_SHA512)) { + throw new InvalidAlgorithmParameterException( + name + " does not match " + kem_id); + } + } + case null, default -> { + var msg = k.getClass() + " does not match " + kem_id; + if (inSpec) { + throw new InvalidAlgorithmParameterException(msg); + } else { + throw new InvalidKeyException(msg); + } + } + } + } + + private KEM kem() { + try { + return KEM.getInstance("DHKEM"); + } catch (NoSuchAlgorithmException e) { + throw new ProviderException("Internal error", e); + } + } + + private void setParams(HPKEParameterSpec p) + throws InvalidAlgorithmParameterException { + params = p; + suite_id = concat( + HPKE, + DHKEM.i2OSP(params.kem_id(), 2), + DHKEM.i2OSP(params.kdf_id(), 2), + DHKEM.i2OSP(params.aead_id(), 2)); + switch (params.kdf_id()) { + case HPKEParameterSpec.KDF_HKDF_SHA256 -> { + kdfAlg = "HKDF-SHA256"; + kdfNh = 32; + } + case HPKEParameterSpec.KDF_HKDF_SHA384 -> { + kdfAlg = "HKDF-SHA384"; + kdfNh = 48; + } + case HPKEParameterSpec.KDF_HKDF_SHA512 -> { + kdfAlg = "HKDF-SHA512"; + kdfNh = 64; + } + default -> throw new InvalidAlgorithmParameterException( + "Unsupported kdf_id: " + params.kdf_id()); + } + aead = new AEAD(params.aead_id()); + } + + private Context keySchedule(int mode, + SecretKey shared_secret, + byte[] info, + SecretKey psk, + byte[] psk_id) { + try { + var psk_id_hash_x = DHKEM.labeledExtract(suite_id, PSK_ID_HASH) + .addIKM(psk_id).extractOnly(); + var info_hash_x = DHKEM.labeledExtract(suite_id, INFO_HASH) + .addIKM(info).extractOnly(); + + // deriveData must and can be called because all info to + // thw builder are just byte arrays. Any KDF impl can handle this. + var kdf = KDF.getInstance(kdfAlg); + var key_schedule_context = concat(new byte[]{(byte) mode}, + kdf.deriveData(psk_id_hash_x), + kdf.deriveData(info_hash_x)); + + var secret_x_builder = DHKEM.labeledExtract(suite_id, SECRET); + if (psk != null) { + secret_x_builder.addIKM(psk); + } + secret_x_builder.addSalt(shared_secret); + var secret_x = kdf.deriveKey("Generic", secret_x_builder.extractOnly()); + + // A new KDF object must be created because secret_x_builder + // might contain provider-specific keys which the previous + // KDF (provider already chosen) cannot handle. + kdf = KDF.getInstance(kdfAlg); + var exporter_secret = kdf.deriveKey("Generic", DHKEM.labeledExpand( + secret_x, suite_id, EXP, key_schedule_context, kdfNh)); + + if (hasEncrypt()) { + // ChaCha20-Poly1305 does not care about algorithm name + var key = kdf.deriveKey("AES", DHKEM.labeledExpand(secret_x, + suite_id, KEY, key_schedule_context, aead.nk)); + // deriveData must be called because we need to increment nonce + var base_nonce = kdf.deriveData(DHKEM.labeledExpand(secret_x, + suite_id, BASE_NONCE, key_schedule_context, aead.nn)); + return new Context(key, base_nonce, exporter_secret); + } else { + return new Context(null, null, exporter_secret); + } + } catch (InvalidAlgorithmParameterException + | NoSuchAlgorithmException | UnsupportedOperationException e) { + throw new ProviderException("Internal error", e); + } + } + } + + private static boolean usePSK(SecretKey psk) { + return psk != null; + } + + private static byte[] concat(byte[]... inputs) { + var o = new ByteArrayOutputStream(); + Arrays.stream(inputs).forEach(o::writeBytes); + return o.toByteArray(); + } +} diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 22d5f17c6e0..4b38bd55809 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -371,6 +371,8 @@ public final class SunJCE extends Provider { ps("Cipher", "PBEWithHmacSHA512/256AndAES_256", "com.sun.crypto.provider.PBES2Core$HmacSHA512_256AndAES_256"); + ps("Cipher", "HPKE", "com.sun.crypto.provider.HPKE"); + /* * Key(pair) Generator engines */ diff --git a/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java b/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java new file mode 100644 index 00000000000..6776ddcdb75 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/spec/HPKEParameterSpec.java @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2025, 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 javax.crypto.spec; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.AsymmetricKey; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; +import java.util.HexFormat; +import java.util.Objects; + +/** + * This immutable class specifies the set of parameters used with a {@code Cipher} for the + * Hybrid Public Key Encryption + * (HPKE) algorithm. HPKE is a public key encryption scheme for encrypting + * arbitrary-sized plaintexts with a recipient's public key. It combines a key + * encapsulation mechanism (KEM), a key derivation function (KDF), and an + * authenticated encryption with additional data (AEAD) cipher. + *

+ * The + * standard algorithm name for the cipher is "HPKE". Unlike most other + * ciphers, HPKE is not expressed as a transformation string of the form + * "algorithm/mode/padding". Therefore, the argument to {@code Cipher.getInstance} + * must be the single algorithm name "HPKE". + *

+ * In HPKE, the sender's {@code Cipher} is always initialized with the + * recipient's public key in {@linkplain Cipher#ENCRYPT_MODE encrypt mode}, + * while the recipient's {@code Cipher} object is initialized with its own + * private key in {@linkplain Cipher#DECRYPT_MODE decrypt mode}. + *

+ * An {@code HPKEParameterSpec} object must be provided at HPKE + * {@linkplain Cipher#init(int, Key, AlgorithmParameterSpec) cipher initialization}. + *

+ * The {@link #of(int, int, int)} static method returns an {@code HPKEParameterSpec} + * object with the specified KEM, KDF, and AEAD algorithm identifiers. + * The terms "KEM algorithm identifiers", "KDF algorithm identifiers", and + * "AEAD algorithm identifiers" refer to their respective numeric values + * (specifically, {@code kem_id}, {@code kdf_id}, and {@code aead_id}) as + * defined in Section 7 + * of RFC 9180 and maintained on the + * IANA HPKE page. + *

+ * Once an {@code HPKEParameterSpec} object is created, additional methods + * are available to generate new {@code HPKEParameterSpec} objects with + * different features: + *

    + *
  • + * Application-supplied information can be provided using the + * {@link #withInfo(byte[])} method by both sides. + *
  • + * To authenticate using a pre-shared key ({@code mode_psk}), the + * pre-shared key and its identifier must be provided using the + * {@link #withPsk(SecretKey, byte[])} method by both sides. + *
  • + * To authenticate using an asymmetric key ({@code mode_auth}), + * the asymmetric keys must be provided using the {@link #withAuthKey(AsymmetricKey)} + * method. Precisely, the sender must call this method with its own private key + * and the recipient must call it with the sender's public key. + *
  • + * To authenticate using both a PSK and an asymmetric key + * ({@code mode_auth_psk}), both {@link #withAuthKey(AsymmetricKey)} and + * {@link #withPsk(SecretKey, byte[])} methods must be called as described above. + *
  • + * In HPKE, a shared secret is negotiated during the KEM step and a key + * encapsulation message must be transmitted from the sender to the recipient + * so that the recipient can recover the shared secret. On the sender side, + * after the cipher is initialized, the key encapsulation message can be + * retrieved using the {@link Cipher#getIV()} method. On the recipient side, + * this message must be supplied as part of an {@code HPKEParameterSpec} + * object obtained from the {@link #withEncapsulation(byte[])} method. + *
+ * For successful interoperability, both sides need to have identical algorithm + * identifiers, and supply identical + * {@code info}, {@code psk}, and {@code psk_id} or matching authentication + * keys if provided. For details about HPKE modes, refer to + * Section 5 + * of RFC 9180. + *

+ * If an HPKE cipher is {@linkplain Cipher#init(int, Key) initialized without + * parameters}, an {@code InvalidKeyException} is thrown. + *

+ * At HPKE cipher initialization, if no HPKE implementation supports the + * provided key type, an {@code InvalidKeyException} is thrown. If the provided + * {@code HPKEParameterSpec} is not accepted by any HPKE implementation, + * an {@code InvalidAlgorithmParameterException} is thrown. For example: + *

    + *
  • An algorithm identifier is unsupported or does not match the provided key type. + *
  • A key encapsulation message is provided on the sender side. + *
  • A key encapsulation message is not provided on the recipient side. + *
  • An attempt to use {@code withAuthKey(key)} is made with an incompatible key. + *
  • An attempt to use {@code withAuthKey(key)} is made but {@code mode_auth} + * or {@code mode_auth_psk} is not supported by the KEM algorithm used. + *
+ * After initialization, both the sender and recipient can process multiple + * messages in sequence with repeated {@code doFinal} calls, optionally preceded + * by one or more {@code updateAAD} and {@code update}. Each {@code doFinal} + * performs a complete HPKE encryption or decryption operation using a distinct + * IV derived from an internal sequence counter, as specified in + * Section 5.2 + * of RFC 9180. On the recipient side, each {@code doFinal} call must correspond + * to exactly one complete ciphertext, and the number and order of calls must + * match those on the sender side. This differs from the direct use of an AEAD + * cipher, where the caller must provide a fresh IV and reinitialize the cipher + * for each message. By managing IVs internally, HPKE allows a single + * initialization to support multiple messages while still ensuring IV + * uniqueness and preserving AEAD security guarantees. + *

+ * This example shows a sender and a recipient using HPKE to securely exchange + * messages with an X25519 key pair. + * {@snippet lang=java class="PackageSnippets" region="hpke-spec-example"} + * + * @implNote This class defines constants for some of the standard algorithm + * identifiers such as {@link #KEM_DHKEM_P_256_HKDF_SHA256}, + * {@link #KDF_HKDF_SHA256}, and {@link #AEAD_AES_128_GCM}. An HPKE {@code Cipher} + * implementation may support all, some, or none of the algorithm identifiers + * defined here. An implementation may also support additional identifiers not + * listed here, including private or experimental values. + * + * @spec https://www.rfc-editor.org/info/rfc9180 + * RFC 9180: Hybrid Public Key Encryption + * @spec security/standard-names.html + * Java Security Standard Algorithm Names + * @since 26 + */ +public final class HPKEParameterSpec implements AlgorithmParameterSpec { + + /** + * KEM algorithm identifier for DHKEM(P-256, HKDF-SHA256) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_256_HKDF_SHA256 = 0x10; + + /** + * KEM algorithm identifier for DHKEM(P-384, HKDF-SHA384) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_384_HKDF_SHA384 = 0x11; + + /** + * KEM algorithm identifier for DHKEM(P-521, HKDF-SHA512) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_P_521_HKDF_SHA512 = 0x12; + + /** + * KEM algorithm identifier for DHKEM(X25519, HKDF-SHA256) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_X25519_HKDF_SHA256 = 0x20; + + /** + * KEM algorithm identifier for DHKEM(X448, HKDF-SHA512) as defined in RFC 9180. + */ + public static final int KEM_DHKEM_X448_HKDF_SHA512 = 0x21; + + /** + * KDF algorithm identifier for HKDF-SHA256 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA256 = 0x1; + + /** + * KDF algorithm identifier for HKDF-SHA384 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA384 = 0x2; + + /** + * KDF algorithm identifier for HKDF-SHA512 as defined in RFC 9180. + */ + public static final int KDF_HKDF_SHA512 = 0x3; + + /** + * AEAD algorithm identifier for AES-128-GCM as defined in RFC 9180. + */ + public static final int AEAD_AES_128_GCM = 0x1; + + /** + * AEAD algorithm identifier for AES-256-GCM as defined in RFC 9180. + */ + public static final int AEAD_AES_256_GCM = 0x2; + + /** + * AEAD algorithm identifier for ChaCha20Poly1305 as defined in RFC 9180. + */ + public static final int AEAD_CHACHA20_POLY1305 = 0x3; + + /** + * AEAD algorithm identifier for Export-only as defined in RFC 9180. + */ + public static final int EXPORT_ONLY = 0xffff; + + private final int kem_id; + private final int kdf_id; + private final int aead_id; + private final byte[] info; // never null, can be empty + private final SecretKey psk; // null if not used + private final byte[] psk_id; // never null, can be empty + private final AsymmetricKey kS; // null if not used + private final byte[] encapsulation; // null if none + + // Note: this constructor does not clone array arguments. + private HPKEParameterSpec(int kem_id, int kdf_id, int aead_id, byte[] info, + SecretKey psk, byte[] psk_id, AsymmetricKey kS, byte[] encapsulation) { + this.kem_id = kem_id; + this.kdf_id = kdf_id; + this.aead_id = aead_id; + this.info = info; + this.psk = psk; + this.psk_id = psk_id; + this.kS = kS; + this.encapsulation = encapsulation; + } + + /** + * A factory method to create a new {@code HPKEParameterSpec} object with + * specified KEM, KDF, and AEAD algorithm identifiers in {@code mode_base} + * mode with an empty {@code info}. + * + * @param kem_id algorithm identifier for KEM, must be between 0 and 65535 (inclusive) + * @param kdf_id algorithm identifier for KDF, must be between 0 and 65535 (inclusive) + * @param aead_id algorithm identifier for AEAD, must be between 0 and 65535 (inclusive) + * @return a new {@code HPKEParameterSpec} object + * @throws IllegalArgumentException if any input value + * is out of range (must be between 0 and 65535, inclusive). + */ + public static HPKEParameterSpec of(int kem_id, int kdf_id, int aead_id) { + if (kem_id < 0 || kem_id > 65535) { + throw new IllegalArgumentException("Invalid kem_id: " + kem_id); + } + if (kdf_id < 0 || kdf_id > 65535) { + throw new IllegalArgumentException("Invalid kdf_id: " + kdf_id); + } + if (aead_id < 0 || aead_id > 65535) { + throw new IllegalArgumentException("Invalid aead_id: " + aead_id); + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + new byte[0], null, new byte[0], null, null); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * {@code info} value. + *

+ * For interoperability, RFC 9180 Section 7.2.1 recommends limiting + * this value to a maximum of 64 bytes. + * + * @param info application-supplied information. + * The contents of the array are copied to protect + * against subsequent modification. + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code info} is {@code null} + * @throws IllegalArgumentException if {@code info} is empty. + */ + public HPKEParameterSpec withInfo(byte[] info) { + Objects.requireNonNull(info); + if (info.length == 0) { + throw new IllegalArgumentException("info is empty"); + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info.clone(), psk, psk_id, kS, encapsulation); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * {@code psk} and {@code psk_id} values. + *

+ * RFC 9180 Section 5.1.2 requires the PSK MUST have at least 32 bytes + * of entropy. For interoperability, RFC 9180 Section 7.2.1 recommends + * limiting the key size and identifier length to a maximum of 64 bytes. + * + * @param psk pre-shared key + * @param psk_id identifier for PSK. The contents of the array are copied + * to protect against subsequent modification. + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code psk} or {@code psk_id} is {@code null} + * @throws IllegalArgumentException if {@code psk} is shorter than 32 bytes + * or {@code psk_id} is empty + */ + public HPKEParameterSpec withPsk(SecretKey psk, byte[] psk_id) { + Objects.requireNonNull(psk); + Objects.requireNonNull(psk_id); + if (psk_id.length == 0) { + throw new IllegalArgumentException("psk_id is empty"); + } + if ("RAW".equalsIgnoreCase(psk.getFormat())) { + // We can only check when psk is extractable. We can only + // check the length and not the real entropy size + var keyBytes = psk.getEncoded(); + assert keyBytes != null; + Arrays.fill(keyBytes, (byte)0); + if (keyBytes.length < 32) { + throw new IllegalArgumentException("psk is too short"); + } + } + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id.clone(), kS, encapsulation); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * key encapsulation message value that will be used by the recipient. + * + * @param encapsulation the key encapsulation message. + * The contents of the array are copied to protect against + * subsequent modification. + * + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code encapsulation} is {@code null} + */ + public HPKEParameterSpec withEncapsulation(byte[] encapsulation) { + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id, kS, + Objects.requireNonNull(encapsulation).clone()); + } + + /** + * Creates a new {@code HPKEParameterSpec} object with the specified + * authentication key value. + *

+ * Note: this method does not check whether the KEM algorithm supports + * {@code mode_auth} or {@code mode_auth_psk}. If the resulting object is + * used to initialize an HPKE cipher with an unsupported mode, an + * {@code InvalidAlgorithmParameterException} will be thrown at that time. + * + * @param kS the authentication key + * @return a new {@code HPKEParameterSpec} object + * @throws NullPointerException if {@code kS} is {@code null} + */ + public HPKEParameterSpec withAuthKey(AsymmetricKey kS) { + return new HPKEParameterSpec(kem_id, kdf_id, aead_id, + info, psk, psk_id, + Objects.requireNonNull(kS), + encapsulation); + } + + /** + * {@return the algorithm identifier for KEM } + */ + public int kem_id() { + return kem_id; + } + + /** + * {@return the algorithm identifier for KDF } + */ + public int kdf_id() { + return kdf_id; + } + + /** + * {@return the algorithm identifier for AEAD } + */ + public int aead_id() { + return aead_id; + } + + /** + * {@return a copy of the application-supplied information, empty if none} + */ + public byte[] info() { + return info.clone(); + } + + /** + * {@return pre-shared key, {@code null} if none} + */ + public SecretKey psk() { + return psk; + } + + /** + * {@return a copy of the identifier for PSK, empty if none} + */ + public byte[] psk_id() { + return psk_id.clone(); + } + + /** + * {@return the key for authentication, {@code null} if none} + */ + public AsymmetricKey authKey() { + return kS; + } + + /** + * {@return a copy of the key encapsulation message, {@code null} if none} + */ + public byte[] encapsulation() { + return encapsulation == null ? null : encapsulation.clone(); + } + + @Override + public String toString() { + return "HPKEParameterSpec{" + + "kem_id=" + kem_id + + ", kdf_id=" + kdf_id + + ", aead_id=" + aead_id + + ", info=" + bytesToString(info) + + ", " + (psk == null + ? (kS == null ? "mode_base" : "mode_auth") + : (kS == null ? "mode_psk" : "mode_auth_psk")) + "}"; + } + + // Returns a human-readable representation of a byte array. + private static String bytesToString(byte[] input) { + if (input.length == 0) { + return "(empty)"; + } else { + for (byte b : input) { + if (b < 0x20 || b > 0x7E || b == '"') { + // Non-ASCII or control characters are hard to read, and + // `"` requires character escaping. If any of these are + // present, return only the HEX representation. + return HexFormat.of().formatHex(input); + } + } + // Otherwise, all characters are printable and safe. + // Return both HEX and ASCII representations. + return HexFormat.of().formatHex(input) + + " (\"" + new String(input, StandardCharsets.US_ASCII) + "\")"; + } + } +} diff --git a/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java b/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java new file mode 100644 index 00000000000..e4074c1c4a9 --- /dev/null +++ b/src/java.base/share/classes/javax/crypto/spec/snippet-files/PackageSnippets.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025, 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. + */ +import javax.crypto.Cipher; +import javax.crypto.spec.HPKEParameterSpec; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Arrays; +import java.util.HexFormat; + +class PackageSnippets { + public static void main(String[] args) throws Exception { + + // @start region="hpke-spec-example" + // Recipient key pair generation + KeyPairGenerator g = KeyPairGenerator.getInstance("X25519"); + KeyPair kp = g.generateKeyPair(); + + // The HPKE sender cipher is initialized with the recipient's public + // key and an HPKEParameterSpec using specified algorithm identifiers + // and application-supplied info. + Cipher senderCipher = Cipher.getInstance("HPKE"); + HPKEParameterSpec ps = HPKEParameterSpec.of( + HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_128_GCM) + .withInfo(HexFormat.of().parseHex("010203040506")); + senderCipher.init(Cipher.ENCRYPT_MODE, kp.getPublic(), ps); + + // Retrieve the key encapsulation message (from the KEM step) from + // the sender. + byte[] kemEncap = senderCipher.getIV(); + + // The HPKE recipient cipher is initialized with its own private key, + // an HPKEParameterSpec using the same algorithm identifiers as used by + // the sender, and the key encapsulation message from the sender. + Cipher recipientCipher = Cipher.getInstance("HPKE"); + HPKEParameterSpec pr = HPKEParameterSpec.of( + HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_128_GCM) + .withInfo(HexFormat.of().parseHex("010203040506")) + .withEncapsulation(kemEncap); + recipientCipher.init(Cipher.DECRYPT_MODE, kp.getPrivate(), pr); + + // Encryption and decryption + byte[] msg = "Hello World".getBytes(StandardCharsets.UTF_8); + byte[] ct = senderCipher.doFinal(msg); + byte[] pt = recipientCipher.doFinal(ct); + + assert Arrays.equals(msg, pt); + // @end + } +} diff --git a/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java b/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java new file mode 100644 index 00000000000..4dc3fe0a3e8 --- /dev/null +++ b/src/java.base/share/classes/sun/security/util/SliceableSecretKey.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025, 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 sun.security.util; + +import javax.crypto.SecretKey; + +/** + * An interface for SecretKeys that support using its slice as a new + * SecretKey. + *

+ * This is mainly used by PKCS #11 implementations that support the + * EXTRACT_KEY_FROM_KEY mechanism even if the key itself is sensitive + * and non-extractable. + */ +public interface SliceableSecretKey { + + /** + * Returns a slice as a new SecretKey. + * + * @param alg the new algorithm name + * @param from the byte offset of the new key in the full key + * @param to the to offset (exclusive) of the new key in the full key + * @return the new key + * @throws ArrayIndexOutOfBoundsException for improper from + * and to values + * @throws UnsupportedOperationException if slicing is not supported + */ + SecretKey slice(String alg, int from, int to); +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java new file mode 100644 index 00000000000..2e10bb23e82 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Compliance.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2025, 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 jdk.test.lib.Asserts; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.spec.NamedParameterSpec; + +import static javax.crypto.spec.HPKEParameterSpec.AEAD_AES_256_GCM; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256; + +/* + * @test + * @bug 8325448 + * @library /test/lib + * @summary HPKE compliance test + */ +public class Compliance { + public static void main(String[] args) throws Exception { + + var kp = KeyPairGenerator.getInstance("X25519").generateKeyPair(); + var info = "info".getBytes(StandardCharsets.UTF_8); + var psk = new SecretKeySpec(new byte[32], "ONE"); + var shortKey = new SecretKeySpec(new byte[31], "ONE"); + var psk_id = "psk_id".getBytes(StandardCharsets.UTF_8); + var emptyKey = new SecretKey() { + public String getAlgorithm() { return "GENERIC"; } + public String getFormat() { return "RAW"; } + public byte[] getEncoded() { return new byte[0]; } + }; + + // HPKEParameterSpec + + // A typical spec + var spec = HPKEParameterSpec.of( + KEM_DHKEM_X25519_HKDF_SHA256, + KDF_HKDF_SHA256, + AEAD_AES_256_GCM); + Asserts.assertEQ(spec.kem_id(), KEM_DHKEM_X25519_HKDF_SHA256); + Asserts.assertEQ(spec.kdf_id(), KDF_HKDF_SHA256); + Asserts.assertEQ(spec.aead_id(), AEAD_AES_256_GCM); + Asserts.assertEQ(spec.authKey(), null); + Asserts.assertEQ(spec.encapsulation(), null); + Asserts.assertEqualsByteArray(spec.info(), new byte[0]); + Asserts.assertEQ(spec.psk(), null); + Asserts.assertEqualsByteArray(spec.psk_id(), new byte[0]); + + // A fake spec but still valid + var specZero = HPKEParameterSpec.of(0, 0, 0); + Asserts.assertEQ(specZero.kem_id(), 0); + Asserts.assertEQ(specZero.kdf_id(), 0); + Asserts.assertEQ(specZero.aead_id(), 0); + Asserts.assertEQ(specZero.authKey(), null); + Asserts.assertEQ(specZero.encapsulation(), null); + Asserts.assertEqualsByteArray(specZero.info(), new byte[0]); + Asserts.assertEQ(specZero.psk(), null); + Asserts.assertEqualsByteArray(specZero.psk_id(), new byte[0]); + + // identifiers + HPKEParameterSpec.of(65535, 65535, 65535); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(-1, 0, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, -1, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, 0, -1)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(65536, 0, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, 65536, 0)); + Asserts.assertThrows(IllegalArgumentException.class, + () -> HPKEParameterSpec.of(0, 0, 65536)); + + // auth key + Asserts.assertTrue(spec.withAuthKey(kp.getPrivate()).authKey() != null); + Asserts.assertTrue(spec.withAuthKey(kp.getPublic()).authKey() != null); + Asserts.assertThrows(NullPointerException.class, () -> spec.withAuthKey(null)); + + // info + Asserts.assertEqualsByteArray(spec.withInfo(info).info(), info); + Asserts.assertThrows(NullPointerException.class, () -> spec.withInfo(null)); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withInfo(new byte[0])); + + // encapsulation + Asserts.assertEqualsByteArray(spec.withEncapsulation(info).encapsulation(), info); + Asserts.assertThrows(NullPointerException.class, () -> spec.withEncapsulation(null)); + Asserts.assertTrue(spec.withEncapsulation(new byte[0]).encapsulation().length == 0); // not emptiness check (yet) + + // psk_id and psk + Asserts.assertEqualsByteArray(spec.withPsk(psk, psk_id).psk().getEncoded(), psk.getEncoded()); + Asserts.assertEqualsByteArray(spec.withPsk(psk, psk_id).psk_id(), psk_id); + Asserts.assertThrows(NullPointerException.class, () -> spec.withPsk(psk, null)); + Asserts.assertThrows(NullPointerException.class, () -> spec.withPsk(null, psk_id)); + Asserts.assertThrows(NullPointerException.class, () -> spec.withPsk(null, null)); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withPsk(psk, new byte[0])); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withPsk(emptyKey, psk_id)); + Asserts.assertThrows(IllegalArgumentException.class, () -> spec.withPsk(shortKey, psk_id)); + + // toString + Asserts.assertTrue(spec.toString().contains("kem_id=32, kdf_id=1, aead_id=2")); + Asserts.assertTrue(spec.toString().contains("info=(empty),")); + Asserts.assertTrue(spec.withInfo(new byte[3]).toString().contains("info=000000,")); + Asserts.assertTrue(spec.withInfo("info".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=696e666f (\"info\"),")); + Asserts.assertTrue(spec.withInfo("\"info\"".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=22696e666f22,")); + Asserts.assertTrue(spec.withInfo("'info'".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=27696e666f27 (\"'info'\"),")); + Asserts.assertTrue(spec.withInfo("i\\n\\f\\o".getBytes(StandardCharsets.UTF_8)) + .toString().contains("info=695c6e5c665c6f (\"i\\n\\f\\o\"),")); + Asserts.assertTrue(spec.toString().contains("mode_base}")); + Asserts.assertTrue(spec.withPsk(psk, psk_id).toString().contains("mode_psk}")); + Asserts.assertTrue(spec.withAuthKey(kp.getPrivate()).toString().contains("mode_auth}")); + Asserts.assertTrue(spec.withAuthKey(kp.getPrivate()).withPsk(psk, psk_id).toString().contains("mode_auth_psk}")); + + var c1 = Cipher.getInstance("HPKE"); + + Asserts.assertThrows(NoSuchAlgorithmException.class, () -> Cipher.getInstance("HPKE/None/NoPadding")); + + // Still at BEGIN, not initialized + Asserts.assertEQ(c1.getIV(), null); + Asserts.assertEQ(c1.getParameters(), null); + Asserts.assertEquals(0, c1.getBlockSize()); + Asserts.assertThrows(IllegalStateException.class, () -> c1.getOutputSize(100)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.update(new byte[1])); + Asserts.assertThrows(IllegalStateException.class, () -> c1.update(new byte[1], 0, 1)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.updateAAD(new byte[1])); + Asserts.assertThrows(IllegalStateException.class, () -> c1.updateAAD(new byte[1], 0, 1)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal()); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal(new byte[1])); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal(new byte[1], 0, 1)); + Asserts.assertThrows(IllegalStateException.class, () -> c1.doFinal(new byte[1], 0, 1, new byte[1024], 0)); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), spec); + var encap = c1.getIV(); + + // Does not support WRAP and UNWRAP mode + Asserts.assertThrows(UnsupportedOperationException.class, + () -> c1.init(Cipher.WRAP_MODE, kp.getPublic(), spec)); + Asserts.assertThrows(UnsupportedOperationException.class, + () -> c1.init(Cipher.UNWRAP_MODE, kp.getPublic(), spec)); + + // Nulls + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, null, spec)); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), (HPKEParameterSpec) null)); + + // Cannot init sender with private key + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPrivate(), spec)); + + // Cannot provide key encap msg to sender + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withEncapsulation(encap))); + + // Cannot init without HPKEParameterSpec + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic())); + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate())); + + // Cannot init with a spec not HPKEParameterSpec + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + NamedParameterSpec.X25519)); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate(), + NamedParameterSpec.X25519)); + + // Cannot init recipient with public key + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPublic(), + spec.withEncapsulation(new byte[32]))); + // Cannot provide key encap msg to sender + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), spec.withEncapsulation(encap))); + // Must provide key encap msg to recipient + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate(), spec)); + + // Unsupported identifiers + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(0, KDF_HKDF_SHA256, AEAD_AES_256_GCM))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(0x200, KDF_HKDF_SHA256, AEAD_AES_256_GCM))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(KEM_DHKEM_X25519_HKDF_SHA256, 4, AEAD_AES_256_GCM))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + HPKEParameterSpec.of(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, 4))); + + // HPKE + checkEncryptDecrypt(kp, spec, spec); + + // extra features + var kp2 = KeyPairGenerator.getInstance("X25519").generateKeyPair(); + checkEncryptDecrypt(kp, + spec.withInfo(info), + spec.withInfo(info)); + checkEncryptDecrypt(kp, + spec.withPsk(psk, psk_id), + spec.withPsk(psk, psk_id)); + checkEncryptDecrypt(kp, + spec.withAuthKey(kp2.getPrivate()), + spec.withAuthKey(kp2.getPublic())); + checkEncryptDecrypt(kp, + spec.withInfo(info).withPsk(psk, psk_id).withAuthKey(kp2.getPrivate()), + spec.withInfo(info).withPsk(psk, psk_id).withAuthKey(kp2.getPublic())); + + // wrong keys + var kpRSA = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + var kpEC = KeyPairGenerator.getInstance("EC").generateKeyPair(); + + Asserts.assertThrows(InvalidKeyException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kpRSA.getPublic(), spec)); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kpEC.getPublic(), spec)); + + // mod_auth, wrong key type + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withAuthKey(kp2.getPublic()))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.DECRYPT_MODE, kp.getPrivate(), + spec.withAuthKey(kp2.getPrivate()))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withAuthKey(kpRSA.getPrivate()))); + Asserts.assertThrows(InvalidAlgorithmParameterException.class, + () -> c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), + spec.withAuthKey(kpEC.getPrivate()))); + } + + static void checkEncryptDecrypt(KeyPair kp, HPKEParameterSpec ps, + HPKEParameterSpec pr) throws Exception { + + var c1 = Cipher.getInstance("HPKE"); + var c2 = Cipher.getInstance("HPKE"); + var aad = "AAD".getBytes(StandardCharsets.UTF_8); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), ps); + Asserts.assertEquals(16, c1.getBlockSize()); + Asserts.assertEquals(116, c1.getOutputSize(100)); + c1.updateAAD(aad); + var ct = c1.doFinal(new byte[2]); + + c2.init(Cipher.DECRYPT_MODE, kp.getPrivate(), + pr.withEncapsulation(c1.getIV())); + Asserts.assertEquals(16, c2.getBlockSize()); + Asserts.assertEquals(84, c2.getOutputSize(100)); + c2.updateAAD(aad); + Asserts.assertEqualsByteArray(c2.doFinal(ct), new byte[2]); + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java new file mode 100644 index 00000000000..9ebf4ce5c09 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/Functions.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025, 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 jdk.test.lib.Asserts; + +import javax.crypto.Cipher; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.spec.ECGenParameterSpec; +import java.util.List; + +import static javax.crypto.spec.HPKEParameterSpec.AEAD_AES_128_GCM; +import static javax.crypto.spec.HPKEParameterSpec.AEAD_AES_256_GCM; +import static javax.crypto.spec.HPKEParameterSpec.AEAD_CHACHA20_POLY1305; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA384; +import static javax.crypto.spec.HPKEParameterSpec.KDF_HKDF_SHA512; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_P_256_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_P_384_HKDF_SHA384; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_P_521_HKDF_SHA512; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256; +import static javax.crypto.spec.HPKEParameterSpec.KEM_DHKEM_X448_HKDF_SHA512; + +/* + * @test + * @bug 8325448 + * @library /test/lib + * @summary HPKE running with different keys + */ +public class Functions { + + record Params(String name, int kem) {} + static List PARAMS = List.of( + new Params("secp256r1", KEM_DHKEM_P_256_HKDF_SHA256), + new Params("secp384r1", KEM_DHKEM_P_384_HKDF_SHA384), + new Params("secp521r1", KEM_DHKEM_P_521_HKDF_SHA512), + new Params("X25519", KEM_DHKEM_X25519_HKDF_SHA256), + new Params("X448", KEM_DHKEM_X448_HKDF_SHA512) + ); + + public static void main(String[] args) throws Exception { + + var msg = "hello".getBytes(StandardCharsets.UTF_8); + var msg2 = "goodbye".getBytes(StandardCharsets.UTF_8); + var info = "info".getBytes(StandardCharsets.UTF_8); + var psk = new SecretKeySpec("K".repeat(32).getBytes(StandardCharsets.UTF_8), "Generic"); + var psk_id = "psk1".getBytes(StandardCharsets.UTF_8); + + for (var param : PARAMS) { + var c1 = Cipher.getInstance("HPKE"); + var c2 = Cipher.getInstance("HPKE"); + var kp = genKeyPair(param.name()); + var kp2 = genKeyPair(param.name()); + for (var kdf : List.of(KDF_HKDF_SHA256, KDF_HKDF_SHA384, KDF_HKDF_SHA512)) { + for (var aead : List.of(AEAD_AES_256_GCM, AEAD_AES_128_GCM, AEAD_CHACHA20_POLY1305)) { + + var params = HPKEParameterSpec.of(param.kem, kdf, aead); + System.out.println(params); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), params); + c2.init(Cipher.DECRYPT_MODE, kp.getPrivate(), params.withEncapsulation(c1.getIV())); + Asserts.assertEqualsByteArray(msg, c2.doFinal(c1.doFinal(msg))); + Asserts.assertEqualsByteArray(msg2, c2.doFinal(c1.doFinal(msg2))); + + c1.init(Cipher.ENCRYPT_MODE, kp.getPublic(), params + .withAuthKey(kp2.getPrivate()) + .withInfo(info) + .withPsk(psk, psk_id)); + c2.init(Cipher.DECRYPT_MODE, kp.getPrivate(), params + .withAuthKey(kp2.getPublic()) + .withInfo(info) + .withPsk(psk, psk_id) + .withEncapsulation(c1.getIV())); + Asserts.assertEqualsByteArray(msg, c2.doFinal(c1.doFinal(msg))); + Asserts.assertEqualsByteArray(msg2, c2.doFinal(c1.doFinal(msg2))); + } + } + } + } + + static KeyPair genKeyPair(String name) throws Exception { + if (name.startsWith("secp")) { + var g = KeyPairGenerator.getInstance("EC"); + g.initialize(new ECGenParameterSpec(name)); + return g.generateKeyPair(); + } else { + return KeyPairGenerator.getInstance(name).generateKeyPair(); + } + } +} diff --git a/test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java new file mode 100644 index 00000000000..f4717f57883 --- /dev/null +++ b/test/jdk/com/sun/crypto/provider/Cipher/HPKE/KAT9180.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025, 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 8325448 + * @summary KAT inside RFC 9180 + * @library /test/lib + * @modules java.base/com.sun.crypto.provider + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.artifacts.Artifact; +import jdk.test.lib.artifacts.ArtifactResolver; +import jdk.test.lib.json.JSONValue; + +import com.sun.crypto.provider.DHKEM; + +import javax.crypto.Cipher; +import javax.crypto.spec.HPKEParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HexFormat; + +/// This test is based on Appendix A (Test Vectors) of +/// [RFC 9180](https://datatracker.ietf.org/doc/html/rfc9180#name-test-vectors) +/// The test data is available as a JSON file at: +/// https://github.com/cfrg/draft-irtf-cfrg-hpke/blob/5f503c564da00b0687b3de75f1dfbdfc4079ad31/test-vectors.json. +/// +/// The JSON file can either be hosted on an artifactory server or +/// provided via a local path with +/// ``` +/// jtreg -Djdk.test.lib.artifacts.rfc9180-test-vectors= KAT9180.java +/// ``` +public class KAT9180 { + + @Artifact( + organization = "jpg.tests.jdk.ietf", + name = "rfc9180-test-vectors", + revision = "5f503c5", + extension = "json", + unpack = false) + private static class RFC_9180_KAT { + } + + + public static void main(String[] args) throws Exception { + var h = HexFormat.of(); + Path archivePath = ArtifactResolver.fetchOne(RFC_9180_KAT.class); + System.out.println("Data path: " + archivePath); + var c1 = Cipher.getInstance("HPKE"); + var c2 = Cipher.getInstance("HPKE"); + var ts = JSONValue.parse(new String(Files.readAllBytes(archivePath), StandardCharsets.UTF_8)); + for (var tg : ts.asArray()) { + var mode = Integer.parseInt(tg.get("mode").asString()); + System.err.print('I'); + var kem_id = Integer.parseInt(tg.get("kem_id").asString()); + var kdf_id = Integer.parseInt(tg.get("kdf_id").asString()); + var aead_id = Integer.parseInt(tg.get("aead_id").asString()); + var ikmR = h.parseHex(tg.get("ikmR").asString()); + var ikmE = h.parseHex(tg.get("ikmE").asString()); + var info = h.parseHex(tg.get("info").asString()); + + var kpR = new DHKEM.RFC9180DeriveKeyPairSR(ikmR).derive(kem_id); + var spec = HPKEParameterSpec.of(kem_id, kdf_id, aead_id).withInfo(info); + var rand = new DHKEM.RFC9180DeriveKeyPairSR(ikmE); + + if (mode == 1 || mode == 3) { + spec = spec.withPsk( + new SecretKeySpec(h.parseHex(tg.get("psk").asString()), "Generic"), + h.parseHex(tg.get("psk_id").asString())); + } + if (mode == 0 || mode == 1) { + c1.init(Cipher.ENCRYPT_MODE, kpR.getPublic(), spec, rand); + c2.init(Cipher.DECRYPT_MODE, kpR.getPrivate(), + spec.withEncapsulation(c1.getIV())); + } else { + var ikmS = h.parseHex(tg.get("ikmS").asString()); + var kpS = new DHKEM.RFC9180DeriveKeyPairSR(ikmS).derive(kem_id); + c1.init(Cipher.ENCRYPT_MODE, kpR.getPublic(), + spec.withAuthKey(kpS.getPrivate()), rand); + c2.init(Cipher.DECRYPT_MODE, kpR.getPrivate(), + spec.withEncapsulation(c1.getIV()).withAuthKey(kpS.getPublic())); + } + var enc = tg.get("encryptions"); + if (enc != null) { + System.err.print('e'); + var count = 0; + for (var p : enc.asArray()) { + var aad = h.parseHex(p.get("aad").asString()); + var pt = h.parseHex(p.get("pt").asString()); + var ct = h.parseHex(p.get("ct").asString()); + c1.updateAAD(aad); + var ct1 = c1.doFinal(pt); + Asserts.assertEqualsByteArray(ct, ct1); + c2.updateAAD(aad); + var pt1 = c2.doFinal(ct); + Asserts.assertEqualsByteArray(pt, pt1); + count++; + } + System.err.print(count); + } + } + } +} diff --git a/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java b/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java index 22c5c89b57b..d8814513b12 100644 --- a/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java +++ b/test/jdk/com/sun/crypto/provider/DHKEM/Compliance.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2025, 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 @@ -31,7 +31,6 @@ * @run main/othervm Compliance */ import jdk.test.lib.Asserts; -import jdk.test.lib.Utils; import javax.crypto.DecapsulateException; import javax.crypto.KEM; @@ -41,9 +40,7 @@ import java.security.*; import java.security.interfaces.ECPublicKey; import java.security.spec.*; import java.util.Arrays; -import java.util.Objects; import java.util.Random; -import java.util.function.Consumer; import com.sun.crypto.provider.DHKEM; @@ -66,12 +63,10 @@ public class Compliance { private static void conform() { new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), new byte[0], new byte[0]); new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), new byte[0], null); - Utils.runAndCheckException( - () -> new KEM.Encapsulated(null, new byte[0], null), - NullPointerException.class); - Utils.runAndCheckException( - () -> new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), null, null), - NullPointerException.class); + Asserts.assertThrows(NullPointerException.class, + () -> new KEM.Encapsulated(null, new byte[0], null)); + Asserts.assertThrows(NullPointerException.class, + () -> new KEM.Encapsulated(new SecretKeySpec(new byte[1], "X"), null, null)); } // basic should and shouldn't behaviors @@ -86,37 +81,33 @@ public class Compliance { KEM.getInstance("DHKEM", (String) null); KEM.getInstance("DHKEM", (Provider) null); KEM kem = KEM.getInstance("DHKEM"); - Utils.runAndCheckException( - () -> KEM.getInstance("OLALA"), - NoSuchAlgorithmException.class); - Utils.runAndCheckException( - () -> KEM.getInstance("DHKEM", "NoWhere"), - NoSuchProviderException.class); - Utils.runAndCheckException( - () -> KEM.getInstance("DHKEM", "SunRsaSign"), - NoSuchAlgorithmException.class); + Asserts.assertThrows(NoSuchAlgorithmException.class, + () -> KEM.getInstance("OLALA")); + Asserts.assertThrows(NoSuchProviderException.class, + () -> KEM.getInstance("DHKEM", "NoWhere")); + Asserts.assertThrows(NoSuchAlgorithmException.class, + () -> KEM.getInstance("DHKEM", "SunRsaSign")); - Utils.runAndCheckException( - () -> kem.newEncapsulator(null), - InvalidKeyException.class); - Utils.runAndCheckException( - () -> kem.newDecapsulator(null), - InvalidKeyException.class); + Asserts.assertThrows(InvalidKeyException.class, + () -> kem.newEncapsulator(null)); + Asserts.assertThrows(InvalidKeyException.class, + () -> kem.newDecapsulator(null)); // Still an EC key, rejected by implementation - Utils.runAndCheckException( - () -> kem.newEncapsulator(badECKey()), - ExChecker.of(InvalidKeyException.class).by(DHKEM.class)); + checkThrownBy(Asserts.assertThrows( + InvalidKeyException.class, + () -> kem.newEncapsulator(badECKey())), + DHKEM.class.getName()); // Not an EC key at all, rejected by framework coz it's not // listed in "SupportedKeyClasses" in SunJCE.java. - Utils.runAndCheckException( - () -> kem.newEncapsulator(kpRSA.getPublic()), - ExChecker.of(InvalidKeyException.class).by(KEM.class.getName() + "$DelayedKEM")); + checkThrownBy(Asserts.assertThrows( + InvalidKeyException.class, + () -> kem.newEncapsulator(kpRSA.getPublic())), + KEM.class.getName() + "$DelayedKEM"); - Utils.runAndCheckException( - () -> kem.newDecapsulator(kpRSA.getPrivate()), - InvalidKeyException.class); + Asserts.assertThrows(InvalidKeyException.class, + () -> kem.newDecapsulator(kpRSA.getPrivate())); kem.newEncapsulator(kpX.getPublic(), null); kem.newEncapsulator(kpX.getPublic(), null, null); @@ -125,15 +116,12 @@ public class Compliance { Asserts.assertEQ(enc1.key().getEncoded().length, e2.secretSize()); Asserts.assertEQ(enc1.key().getAlgorithm(), "AES"); - Utils.runAndCheckException( - () -> e2.encapsulate(-1, 12, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> e2.encapsulate(0, e2.secretSize() + 1, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> e2.encapsulate(0, e2.secretSize(), null), - NullPointerException.class); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> e2.encapsulate(-1, 12, "AES")); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> e2.encapsulate(0, e2.secretSize() + 1, "AES")); + Asserts.assertThrows(NullPointerException.class, + () -> e2.encapsulate(0, e2.secretSize(), null)); KEM.Encapsulated enc = e2.encapsulate(); Asserts.assertEQ(enc.key().getEncoded().length, e2.secretSize()); @@ -162,29 +150,23 @@ public class Compliance { d.secretSize() - 16, d.secretSize(), "AES"); Asserts.assertEQ(encTail.key(), decTail); - Utils.runAndCheckException( - () -> d.decapsulate(null), - NullPointerException.class); - Utils.runAndCheckException( - () -> d.decapsulate(enc.encapsulation(), -1, 12, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize() + 1, "AES"), - IndexOutOfBoundsException.class); - Utils.runAndCheckException( - () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize(), null), - NullPointerException.class); + Asserts.assertThrows(NullPointerException.class, + () -> d.decapsulate(null)); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> d.decapsulate(enc.encapsulation(), -1, 12, "AES")); + Asserts.assertThrows(IndexOutOfBoundsException.class, + () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize() + 1, "AES")); + Asserts.assertThrows(NullPointerException.class, + () -> d.decapsulate(enc.encapsulation(), 0, d.secretSize(), null)); KEM.Encapsulator e3 = kem.newEncapsulator(kpEC.getPublic()); KEM.Encapsulated enc2 = e3.encapsulate(); KEM.Decapsulator d3 = kem.newDecapsulator(kpX.getPrivate()); - Utils.runAndCheckException( - () -> d3.decapsulate(enc2.encapsulation()), - DecapsulateException.class); + Asserts.assertThrows(DecapsulateException.class, + () -> d3.decapsulate(enc2.encapsulation())); - Utils.runAndCheckException( - () -> d3.decapsulate(new byte[100]), - DecapsulateException.class); + Asserts.assertThrows(DecapsulateException.class, + () -> d3.decapsulate(new byte[100])); } static class MySecureRandom extends SecureRandom { @@ -273,34 +255,8 @@ public class Compliance { }; } - // Used by Utils.runAndCheckException. Checks for type and final thrower. - record ExChecker(Class ex, String caller) - implements Consumer { - ExChecker { - Objects.requireNonNull(ex); - } - static ExChecker of(Class ex) { - return new ExChecker(ex, null); - } - ExChecker by(String caller) { - return new ExChecker(ex(), caller); - } - ExChecker by(Class caller) { - return new ExChecker(ex(), caller.getName()); - } - @Override - public void accept(Throwable t) { - if (t == null) { - throw new AssertionError("no exception thrown"); - } else if (!ex.isAssignableFrom(t.getClass())) { - throw new AssertionError("exception thrown is " + t.getClass()); - } else if (caller == null) { - return; - } else if (t.getStackTrace()[0].getClassName().equals(caller)) { - return; - } else { - throw new AssertionError("thrown by " + t.getStackTrace()[0].getClassName()); - } - } + // Ensures `t` is thrown by `caller` + static void checkThrownBy(T t, String caller) { + Asserts.assertEquals(caller, t.getStackTrace()[0].getClassName()); } } diff --git a/test/jdk/sun/security/provider/all/Deterministic.java b/test/jdk/sun/security/provider/all/Deterministic.java index 8fb0e943768..60c56cd1b93 100644 --- a/test/jdk/sun/security/provider/all/Deterministic.java +++ b/test/jdk/sun/security/provider/all/Deterministic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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 @@ -42,6 +42,7 @@ import javax.crypto.KeyGenerator; import javax.crypto.spec.ChaCha20ParameterSpec; import javax.crypto.spec.DHParameterSpec; import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.HPKEParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.SecretKeySpec; @@ -96,6 +97,11 @@ public class Deterministic { key = new SecretKeySpec("isthisakey".getBytes(StandardCharsets.UTF_8), "PBE"); // Some cipher requires salt to be 8 byte long spec = new PBEParameterSpec("saltsalt".getBytes(StandardCharsets.UTF_8), 100); + } else if (alg.equals("HPKE")) { + key = KeyPairGenerator.getInstance("x25519").generateKeyPair().getPublic(); + spec = HPKEParameterSpec.of(HPKEParameterSpec.KEM_DHKEM_X25519_HKDF_SHA256, + HPKEParameterSpec.KDF_HKDF_SHA256, + HPKEParameterSpec.AEAD_AES_256_GCM); } else { key = generateKey(alg.split("/")[0], s.getProvider()); if (!alg.contains("/") || alg.contains("/ECB/")) { @@ -239,6 +245,8 @@ public class Deterministic { return g.generateKey(); } if (s.equals("RSA")) { return generateKeyPair("RSA", 3).getPublic(); + } if (s.equals("HPKE")) { + return generateKeyPair("EC", 3).getPublic(); } else { var g = KeyGenerator.getInstance(s, p); g.init(new SeededSecureRandom(SEED + 4)); diff --git a/test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java b/test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java new file mode 100644 index 00000000000..6340b520a55 --- /dev/null +++ b/test/jdk/sun/security/util/SliceableSecretKey/SoftSliceable.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2025, 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 jdk.test.lib.Asserts; +import sun.security.util.SliceableSecretKey; + +import javax.crypto.KDF; +import javax.crypto.KDFParameters; +import javax.crypto.KDFSpi; +import javax.crypto.KEM; +import javax.crypto.SecretKey; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +/* + * @test + * @bug 8325448 + * @library /test/lib /test/jdk/security/unsignedjce + * @build java.base/javax.crypto.ProviderVerifier + * @modules java.base/sun.security.util + * @run main/othervm SoftSliceable + * @summary Showcase how Sliceable can be used in DHKEM + */ +public class SoftSliceable { + + public static void main(String[] args) throws Exception { + + // Put an HKDF-SHA256 impl that is preferred to the SunJCE one + Security.insertProviderAt(new ProviderImpl(), 1); + + // Just plain KEM calls + var kp = KeyPairGenerator.getInstance("X25519").generateKeyPair(); + var k = KEM.getInstance("DHKEM"); + var e = k.newEncapsulator(kp.getPublic()); + var d = k.newDecapsulator(kp.getPrivate()); + var enc = e.encapsulate(3, 9, "Generic"); + var k2 = d.decapsulate(enc.encapsulation(), 3, 9, "Generic"); + var k2full = d.decapsulate(enc.encapsulation()); + + if (enc.key() instanceof KeyImpl ki1 + && k2 instanceof KeyImpl ki2 + && k2full instanceof KeyImpl ki2full) { + // So the keys do come from the new provider, and + // 1. It has the correct length + Asserts.assertEquals(6, ki1.bytes.length); + // 2. encaps and decaps result in same keys + Asserts.assertEqualsByteArray(ki1.bytes, ki2.bytes); + // 3. The key is the correct slice from the full shared secret + Asserts.assertEqualsByteArray( + Arrays.copyOfRange(ki2full.bytes, 3, 9), ki2.bytes); + } else { + throw new Exception("Unexpected key types"); + } + } + + // A trivial SliceableSecretKey that is non-extractable with getBytes() + public static class KeyImpl implements SecretKey, SliceableSecretKey { + + private final byte[] bytes; + private final String algorithm; + + public KeyImpl(byte[] bytes, String algorithm) { + this.bytes = bytes.clone(); + this.algorithm = algorithm; + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return null; + } + + @Override + public SecretKey slice(String alg, int from, int to) { + return new KeyImpl(Arrays.copyOfRange(bytes, from, to), algorithm); + } + } + + // Our new provider + public static class ProviderImpl extends Provider { + public ProviderImpl() { + super("A", "A", "A"); + put("KDF.HKDF-SHA256", KDFImpl.class.getName()); + } + } + + // Our new HKDF-SHA256 impl that always returns a KeyImpl object + public static class KDFImpl extends KDFSpi { + + public KDFImpl(KDFParameters p) + throws InvalidAlgorithmParameterException { + super(p); + } + + @Override + protected KDFParameters engineGetParameters() { + return null; + } + + @Override + protected SecretKey engineDeriveKey(String alg, AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException { + try { + var kdf = KDF.getInstance("HKDF-SHA256", "SunJCE"); + var bytes = kdf.deriveData(spec); + return new KeyImpl(bytes, alg); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + throw new AssertionError("Cannot happen", e); + } + } + + @Override + protected byte[] engineDeriveData(AlgorithmParameterSpec spec) { + throw new UnsupportedOperationException("Cannot derive data"); + } + } +} From a89018582160a9d876f66925618c8b8f93190e67 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Thu, 20 Nov 2025 15:17:44 +0000 Subject: [PATCH 046/114] 8333727: Use JOpt in jpackage to parse command line 8371384: libapplauncher.so is copied to a wrong location in two step packaging when --install-dir=/usr Reviewed-by: almatvee --- .../share/classes/module-info.java | 3 +- .../jpackage/internal/LinuxAppBundler.java | 41 - .../internal/LinuxBundlingEnvironment.java | 114 ++ .../jpackage/internal/LinuxDebBundler.java | 79 - .../jpackage/internal/LinuxFromOptions.java | 119 ++ .../jpackage/internal/LinuxFromParams.java | 146 -- .../internal/LinuxPackageBundler.java | 88 - .../jdk/jpackage/internal/LinuxPackager.java | 3 +- .../internal/LinuxPackagingPipeline.java | 27 +- .../jpackage/internal/LinuxRpmBundler.java | 80 - .../internal/model/LinuxLauncher.java | 6 +- .../resources/LinuxResources.properties | 4 - .../linux/classes/module-info.java.extra | 9 +- .../jdk/jpackage/internal/MacAppBundler.java | 81 - .../internal/MacBundlingEnvironment.java | 110 ++ .../jdk/jpackage/internal/MacDmgBundler.java | 98 -- .../jdk/jpackage/internal/MacFromOptions.java | 331 ++++ .../jdk/jpackage/internal/MacFromParams.java | 384 ----- .../internal/MacPackagingPipeline.java | 16 +- .../jdk/jpackage/internal/MacPkgBundler.java | 99 -- .../internal/model/MacApplication.java | 38 +- .../resources/MacResources.properties | 8 - .../macosx/classes/module-info.java.extra | 9 +- .../internal/AddLauncherArguments.java | 212 --- .../jpackage/internal/AppImageBundler.java | 168 -- .../jdk/jpackage/internal/AppImageFile.java | 341 ++-- .../jpackage/internal/ApplicationBuilder.java | 41 +- .../internal/ApplicationLayoutUtils.java | 71 - .../jdk/jpackage/internal/Arguments.java | 867 ---------- .../jdk/jpackage/internal/BasicBundlers.java | 86 - .../internal/BuildEnvFromOptions.java | 104 ++ .../jpackage/internal/BuildEnvFromParams.java | 74 - .../jdk/jpackage/internal/BundleParams.java | 72 - .../jdk/jpackage/internal/Bundler.java | 126 -- .../jpackage/internal/BundlerParamInfo.java | 179 --- .../jdk/jpackage/internal/Bundlers.java | 112 -- .../jdk/jpackage/internal/CLIHelp.java | 116 -- .../jdk/jpackage/internal/CfgFile.java | 19 +- .../internal/DefaultBundlingEnvironment.java | 283 ++++ .../jdk/jpackage/internal/DeployParams.java | 362 ----- .../internal/FileAssociationGroup.java | 4 + .../jdk/jpackage/internal/FromOptions.java | 237 +++ .../jdk/jpackage/internal/FromParams.java | 251 --- .../jdk/jpackage/internal/IOUtils.java | 10 +- .../internal/JLinkRuntimeBuilder.java | 10 +- .../internal/JPackageToolProvider.java | 58 - .../jdk/jpackage/internal/LauncherData.java | 307 ---- .../internal/LauncherFromOptions.java | 189 +++ .../jpackage/internal/LauncherFromParams.java | 156 -- .../internal/LauncherStartupInfoBuilder.java | 207 ++- .../jdk/jpackage/internal/OptionUtils.java | 54 + .../jpackage/internal/OptionsTransformer.java | 86 + .../jdk/jpackage/internal/Packager.java | 3 +- .../jpackage/internal/PackagingPipeline.java | 33 +- .../internal/StandardBundlerParam.java | 513 ------ ...bstractBundler.java => TempDirectory.java} | 55 +- .../jdk/jpackage/internal/ValidOptions.java | 187 --- .../internal/cli/AdditionalLauncher.java} | 10 +- .../cli/BundlingOperationModifier.java | 42 + .../cli/BundlingOperationOptionScope.java | 38 + .../internal/cli/CliBundlingEnvironment.java | 47 + .../jpackage/internal/cli/DefaultOptions.java | 191 +++ .../jpackage/internal/cli/HelpFormatter.java | 178 ++ .../jdk/jpackage/internal/cli/I18N.java} | 41 +- .../cli/JOptSimpleOptionsBuilder.java | 817 ++++++++++ .../jdk/jpackage/internal/cli/Main.java | 224 +++ .../internal/cli/MessageFormatUtils.java | 79 + .../jdk/jpackage/internal/cli/Option.java | 54 + .../OptionArrayValueConverter.java} | 24 +- .../internal/cli/OptionIdentifier.java | 53 + .../jdk/jpackage/internal/cli/OptionName.java | 68 + .../OptionScope.java} | 11 +- .../jpackage/internal/cli/OptionSource.java | 68 + .../jdk/jpackage/internal/cli/OptionSpec.java | 180 +++ .../internal/cli/OptionSpecBuilder.java | 475 ++++++ .../cli/OptionSpecMapperOptionScope.java | 157 ++ .../jpackage/internal/cli/OptionValue.java | 190 +++ .../internal/cli/OptionValueConverter.java | 273 ++++ .../cli/OptionValueExceptionFactory.java | 183 +++ .../jdk/jpackage/internal/cli/Options.java | 202 +++ .../internal/cli/OptionsAnalyzer.java | 430 +++++ .../internal/cli/OptionsProcessor.java | 428 +++++ .../cli/StandardAppImageFileOption.java | 246 +++ .../cli/StandardBundlingOperation.java | 173 ++ .../internal/cli/StandardFaOption.java | 111 ++ .../internal/cli/StandardHelpFormatter.java | 398 +++++ .../jpackage/internal/cli/StandardOption.java | 802 +++++++++ .../internal/cli/StandardOptionContext.java | 68 + .../StandardOptionValueExceptionFactory.java | 79 + .../internal/cli/StandardValidator.java | 162 ++ .../internal/cli/StandardValueConverter.java | 83 + .../jpackage/internal/cli/StringToken.java | 57 + .../jdk/jpackage/internal/cli/Utils.java | 109 ++ .../jdk/jpackage/internal/cli/Validator.java | 265 +++ .../jpackage/internal/cli/ValueConverter.java | 68 + .../internal/cli/WithOptionIdentifier.java | 38 + .../cli/WithOptionIdentifierStub.java} | 14 +- .../internal/model/BundlingEnvironment.java | 47 +- .../model/BundlingOperationDescriptor.java | 53 + .../internal/model/ConfigException.java | 7 +- .../internal/model/ExternalApplication.java | 145 +- .../internal/model/JPackageException.java} | 27 +- .../LauncherModularStartupInfoMixin.java | 13 +- .../internal/model/PackagerException.java | 84 - .../internal/model/RuntimeBuilder.java | 2 +- .../resources/HelpResources.properties | 629 ++++---- .../resources/MainResources.properties | 55 +- .../jdk/jpackage/internal/util/FileUtils.java | 14 + .../jpackage/internal/util/SetBuilder.java | 87 + .../share/classes/jdk/jpackage/main/Main.java | 86 +- .../share/classes/module-info.java | 14 +- .../internal/WinBundlingEnvironment.java | 109 ++ .../jdk/jpackage/internal/WinExeBundler.java | 62 +- .../jdk/jpackage/internal/WinFromOpions.java | 115 ++ .../jdk/jpackage/internal/WinFromParams.java | 164 -- .../jdk/jpackage/internal/WinMsiBundler.java | 122 -- .../jdk/jpackage/internal/WinMsiPackager.java | 5 +- .../internal/WinPackagingPipeline.java | 45 +- .../jpackage/internal/model/WinLauncher.java | 10 +- .../resources/WinResources.properties | 5 - .../windows/classes/module-info.java.extra | 9 +- test/jdk/tools/jpackage/TEST.properties | 2 + .../jdk/jpackage/test/AppImageFile.java | 98 +- .../helpers/jdk/jpackage/test/MacHelper.java | 2 +- .../jdk/jpackage/test/PackageType.java | 90 +- test/jdk/tools/jpackage/junit/TEST.properties | 8 +- .../jpackage/internal/AppImageFileTest.java | 678 ++++++-- .../DefaultBundlingEnvironmentTest.java | 58 + .../jpackage/internal/DeployParamsTest.java | 74 - .../LauncherStartupInfoBuilderTest.java | 155 ++ .../internal/PackagingPipelineTest.java | 43 +- .../internal/cli/DefaultOptionsTest.java | 57 + .../internal/cli/ExpectedOptions.java | 94 ++ .../jdk/jpackage/internal/cli/HelpTest.java | 181 +++ .../cli/JOptSimpleOptionsBuilderTest.java | 1428 +++++++++++++++++ .../jdk/jpackage/internal/cli/MainTest.java | 203 +++ .../cli/MockupCliBundlingEnvironment.java | 160 ++ .../internal/cli/OptionIdentifierTest.java | 63 + .../jpackage/internal/cli/OptionNameTest.java | 152 ++ .../cli/OptionSpecMutatorOptionScopeTest.java | 144 ++ .../jpackage/internal/cli/OptionSpecTest.java | 365 +++++ .../cli/OptionValueConverterTest.java | 216 +++ .../cli/OptionValueExceptionFactoryTest.java | 152 ++ .../internal/cli/OptionValueTest.java | 212 +++ .../internal/cli/OptionsProcessorTest.java | 786 +++++++++ .../jpackage/internal/cli/OptionsTest.java | 272 ++++ .../cli/OptionsValidationFailTest.excludes | 46 + .../cli/OptionsValidationFailTest.java | 245 +++ .../cli/StandardBundlingOperationTest.java | 103 ++ .../internal/cli/StandardOptionTest.java | 639 ++++++++ .../internal/cli/StandardValidatorTest.java | 259 +++ .../cli/StandardValueConverterTest.java | 161 ++ .../internal/cli/StringTokenTest.java | 49 + .../jdk/jpackage/internal/cli/TestUtils.java | 179 +++ .../jdk/jpackage/internal/cli/UtilsTest.java | 91 ++ .../jpackage/internal/cli/ValidatorTest.java | 284 ++++ .../jdk/jpackage/internal/cli/help-linux.txt | 197 +++ .../jdk/jpackage/internal/cli/help-macos.txt | 238 +++ .../jpackage/internal/cli/help-windows.txt | 205 +++ .../jpackage/internal/cli/jpackage-options.md | 63 + .../jpackage/share/AppImagePackageTest.java | 6 +- test/jdk/tools/jpackage/share/AsyncTest.java | 222 +++ test/jdk/tools/jpackage/share/ErrorTest.java | 8 +- 163 files changed, 18527 insertions(+), 6692 deletions(-) delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java create mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java create mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java delete mode 100644 src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java create mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java create mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java delete mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java rename src/jdk.jpackage/share/classes/jdk/jpackage/internal/{AbstractBundler.java => TempDirectory.java} (53%) delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java rename src/jdk.jpackage/{macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java => share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java} (76%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java rename src/jdk.jpackage/{macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java => share/classes/jdk/jpackage/internal/cli/I18N.java} (56%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Main.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/MessageFormatUtils.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Option.java rename src/jdk.jpackage/share/classes/jdk/jpackage/internal/{model/BundleCreator.java => cli/OptionArrayValueConverter.java} (68%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionIdentifier.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionName.java rename src/jdk.jpackage/share/classes/jdk/jpackage/internal/{model/BundlingOperation.java => cli/OptionScope.java} (81%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSource.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpec.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecBuilder.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionSpecMapperOptionScope.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionValue.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionValueConverter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionValueExceptionFactory.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Options.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionsAnalyzer.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/OptionsProcessor.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardAppImageFileOption.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardBundlingOperation.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardFaOption.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardHelpFormatter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOption.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOptionContext.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardOptionValueExceptionFactory.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardValidator.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StandardValueConverter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/StringToken.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Utils.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/Validator.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/ValueConverter.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/WithOptionIdentifier.java rename src/jdk.jpackage/{macosx/classes/jdk/jpackage/internal/MacAppImageFileExtras.java => share/classes/jdk/jpackage/internal/cli/WithOptionIdentifierStub.java} (69%) create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/BundlingOperationDescriptor.java rename src/jdk.jpackage/{windows/classes/jdk/jpackage/internal/WinAppBundler.java => share/classes/jdk/jpackage/internal/model/JPackageException.java} (69%) delete mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/PackagerException.java create mode 100644 src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/SetBuilder.java create mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinBundlingEnvironment.java create mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromOpions.java delete mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinFromParams.java delete mode 100644 src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/DefaultBundlingEnvironmentTest.java delete mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/DeployParamsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/LauncherStartupInfoBuilderTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/DefaultOptionsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/ExpectedOptions.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/HelpTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilderTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MainTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MockupCliBundlingEnvironment.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionIdentifierTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionNameTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionSpecMutatorOptionScopeTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionSpecTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionValueConverterTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionValueExceptionFactoryTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionValueTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsProcessorTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.excludes create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/OptionsValidationFailTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardBundlingOperationTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardOptionTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardValidatorTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StandardValueConverterTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/StringTokenTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/TestUtils.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/UtilsTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/ValidatorTest.java create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/help-linux.txt create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/help-macos.txt create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/help-windows.txt create mode 100644 test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/jpackage-options.md create mode 100644 test/jdk/tools/jpackage/share/AsyncTest.java diff --git a/src/jdk.internal.opt/share/classes/module-info.java b/src/jdk.internal.opt/share/classes/module-info.java index ba6987f1ea9..728c2de500d 100644 --- a/src/jdk.internal.opt/share/classes/module-info.java +++ b/src/jdk.internal.opt/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -32,6 +32,7 @@ module jdk.internal.opt { exports jdk.internal.joptsimple to jdk.jlink, jdk.jshell, + jdk.jpackage, jdk.jdeps; exports jdk.internal.opt to jdk.compiler, diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java deleted file mode 100644 index fe8d6bcf34f..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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.jpackage.internal; - -import java.util.Optional; - -public class LinuxAppBundler extends AppImageBundler { - public LinuxAppBundler() { - setAppImageSupplier((params, output) -> { - // Order is important! - var app = LinuxFromParams.APPLICATION.fetchFrom(params); - var env = BuildEnvFromParams.BUILD_ENV.fetchFrom(params); - LinuxPackagingPipeline.build(Optional.empty()) - .excludeDirFromCopying(output.getParent()) - .create().execute(BuildEnv.withAppImageDir(env, output), app); - }); - } -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java new file mode 100644 index 00000000000..cfd8ab391bb --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxBundlingEnvironment.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static java.util.stream.Collectors.toMap; +import static jdk.jpackage.internal.LinuxFromOptions.createLinuxApplication; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_DEB; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_LINUX_RPM; + +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; +import jdk.jpackage.internal.model.LinuxPackage; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.util.Result; + +public class LinuxBundlingEnvironment extends DefaultBundlingEnvironment { + + public LinuxBundlingEnvironment() { + super(build() + .defaultOperation(() -> { + return LazyLoad.SYS_ENV.value().map(LinuxSystemEnvironment::nativePackageType).map(DESCRIPTORS::get); + }) + .bundler(CREATE_LINUX_APP_IMAGE, LinuxBundlingEnvironment::createAppImage) + .bundler(CREATE_LINUX_DEB, LazyLoad::debSysEnv, LinuxBundlingEnvironment::createDebPackage) + .bundler(CREATE_LINUX_RPM, LazyLoad::rpmSysEnv, LinuxBundlingEnvironment::createRpmPackage)); + } + + private static void createDebPackage(Options options, LinuxDebSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions::createLinuxDebPackage, + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxDebPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createRpmPackage(Options options, LinuxRpmSystemEnvironment sysEnv) { + + createNativePackage(options, + LinuxFromOptions::createLinuxRpmPackage, + buildEnv()::create, + LinuxBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + return new LinuxRpmPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createAppImage(Options options) { + + final var app = createLinuxApplication(options); + + createApplicationImage(options, app, LinuxPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(LinuxPackage pkg) { + return LinuxPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions().predefinedAppImageLayout(APPLICATION_LAYOUT); + } + + private static final class LazyLoad { + + static Result debSysEnv() { + return DEB_SYS_ENV; + } + + static Result rpmSysEnv() { + return RPM_SYS_ENV; + } + + private static final Result SYS_ENV = LinuxSystemEnvironment.create(); + + private static final Result DEB_SYS_ENV = LinuxDebSystemEnvironment.create(SYS_ENV); + + private static final Result RPM_SYS_ENV = LinuxRpmSystemEnvironment.create(SYS_ENV); + } + + private static final Map DESCRIPTORS = Stream.of( + CREATE_LINUX_DEB, + CREATE_LINUX_RPM + ).collect(toMap(StandardBundlingOperation::packageType, StandardBundlingOperation::descriptor)); +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java deleted file mode 100644 index 76a08519b48..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - -public class LinuxDebBundler extends LinuxPackageBundler { - - public LinuxDebBundler() { - super(LinuxFromParams.DEB_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("deb.bundler.name"); - } - - @Override - public String getID() { - return "deb"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.DEB_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxDebPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_DEB::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxDebSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java new file mode 100644 index 00000000000..799c92ce2e1 --- /dev/null +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromOptions.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_DEB_MAINTAINER_EMAIL; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_MENU_GROUP; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_DEPENDENCIES; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_PACKAGE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RELEASE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_RPM_LICENSE_TYPE; +import static jdk.jpackage.internal.cli.StandardOption.LINUX_SHORTCUT_HINT; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; +import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; + +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LinuxApplication; +import jdk.jpackage.internal.model.LinuxDebPackage; +import jdk.jpackage.internal.model.LinuxLauncher; +import jdk.jpackage.internal.model.LinuxLauncherMixin; +import jdk.jpackage.internal.model.LinuxRpmPackage; +import jdk.jpackage.internal.model.StandardPackageType; + +final class LinuxFromOptions { + + static LinuxApplication createLinuxApplication(Options options) { + + final var launcherFromOptions = new LauncherFromOptions().faWithDefaultDescription(); + + final var appBuilder = buildApplicationBuilder().create(options, launcherOptions -> { + + final var launcher = launcherFromOptions.create(launcherOptions); + + final var shortcut = LINUX_SHORTCUT_HINT.findIn(launcherOptions); + + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); + + }, (LinuxLauncher linuxLauncher, Launcher launcher) -> { + return LinuxLauncher.create(launcher, linuxLauncher); + }, APPLICATION_LAYOUT); + + appBuilder.launchers().map(LinuxPackagingPipeline::normalizeShortcuts).ifPresent(appBuilder::launchers); + + return LinuxApplication.create(appBuilder.create()); + } + + static LinuxRpmPackage createLinuxRpmPackage(Options options) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_RPM); + + final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); + + LINUX_RPM_LICENSE_TYPE.ifPresentIn(options, pkgBuilder::licenseType); + + return pkgBuilder.create(); + } + + static LinuxDebPackage createLinuxDebPackage(Options options) { + + final var superPkgBuilder = createLinuxPackageBuilder(options, LINUX_DEB); + + final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); + + LINUX_DEB_MAINTAINER_EMAIL.ifPresentIn(options, pkgBuilder::maintainerEmail); + + final var pkg = pkgBuilder.create(); + + // Show warning if license file is missing + if (pkg.licenseFile().isEmpty()) { + Log.verbose(I18N.getString("message.debs-like-licenses")); + } + + return pkg; + } + + private static LinuxPackageBuilder createLinuxPackageBuilder(Options options, StandardPackageType type) { + + final var app = createLinuxApplication(options); + + final var superPkgBuilder = createPackageBuilder(options, app, type); + + final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); + + LINUX_PACKAGE_DEPENDENCIES.ifPresentIn(options, pkgBuilder::additionalDependencies); + LINUX_APP_CATEGORY.ifPresentIn(options, pkgBuilder::category); + LINUX_MENU_GROUP.ifPresentIn(options, pkgBuilder::menuGroupName); + LINUX_RELEASE.ifPresentIn(options, pkgBuilder::release); + LINUX_PACKAGE_NAME.ifPresentIn(options, pkgBuilder::literalName); + + return pkgBuilder; + } + +} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java deleted file mode 100644 index e9d1416b5c3..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxFromParams.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2025, 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.jpackage.internal; - -import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; -import static jdk.jpackage.internal.FromParams.createApplicationBuilder; -import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; -import static jdk.jpackage.internal.FromParams.createPackageBuilder; -import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; -import static jdk.jpackage.internal.FromParams.findLauncherShortcut; -import static jdk.jpackage.internal.LinuxPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.model.StandardPackageType.LINUX_DEB; -import static jdk.jpackage.internal.model.StandardPackageType.LINUX_RPM; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxApplication; -import jdk.jpackage.internal.model.LinuxDebPackage; -import jdk.jpackage.internal.model.LinuxLauncher; -import jdk.jpackage.internal.model.LinuxLauncherMixin; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.StandardPackageType; - -final class LinuxFromParams { - - private static LinuxApplication createLinuxApplication( - Map params) throws ConfigException, IOException { - final var launcherFromParams = new LauncherFromParams(); - - final var app = createApplicationBuilder(params, toFunction(launcherParams -> { - final var launcher = launcherFromParams.create(launcherParams); - final var shortcut = findLauncherShortcut(LINUX_SHORTCUT_HINT, params, launcherParams); - return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(shortcut)); - }), (LinuxLauncher linuxLauncher, Launcher launcher) -> { - return LinuxLauncher.create(launcher, linuxLauncher); - }, APPLICATION_LAYOUT).create(); - return LinuxApplication.create(app); - } - - private static LinuxPackageBuilder createLinuxPackageBuilder( - Map params, StandardPackageType type) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createPackageBuilder(params, app, type); - - final var pkgBuilder = new LinuxPackageBuilder(superPkgBuilder); - - LINUX_PACKAGE_DEPENDENCIES.copyInto(params, pkgBuilder::additionalDependencies); - LINUX_CATEGORY.copyInto(params, pkgBuilder::category); - LINUX_MENU_GROUP.copyInto(params, pkgBuilder::menuGroupName); - RELEASE.copyInto(params, pkgBuilder::release); - LINUX_PACKAGE_NAME.copyInto(params, pkgBuilder::literalName); - - return pkgBuilder; - } - - private static LinuxRpmPackage createLinuxRpmPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_RPM); - - final var pkgBuilder = new LinuxRpmPackageBuilder(superPkgBuilder); - - LICENSE_TYPE.copyInto(params, pkgBuilder::licenseType); - - return pkgBuilder.create(); - } - - private static LinuxDebPackage createLinuxDebPackage( - Map params) throws ConfigException, IOException { - - final var superPkgBuilder = createLinuxPackageBuilder(params, LINUX_DEB); - - final var pkgBuilder = new LinuxDebPackageBuilder(superPkgBuilder); - - MAINTAINER_EMAIL.copyInto(params, pkgBuilder::maintainerEmail); - - final var pkg = pkgBuilder.create(); - - // Show warning if license file is missing - if (pkg.licenseFile().isEmpty()) { - Log.verbose(I18N.getString("message.debs-like-licenses")); - } - - return pkg; - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - LinuxFromParams::createLinuxApplication); - - static final BundlerParamInfo RPM_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxRpmPackage); - - static final BundlerParamInfo DEB_PACKAGE = createPackageBundlerParam( - LinuxFromParams::createLinuxDebPackage); - - private static final BundlerParamInfo LINUX_SHORTCUT_HINT = createStringBundlerParam( - Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId()); - - private static final BundlerParamInfo LINUX_CATEGORY = createStringBundlerParam( - Arguments.CLIOptions.LINUX_CATEGORY.getId()); - - private static final BundlerParamInfo LINUX_PACKAGE_DEPENDENCIES = createStringBundlerParam( - Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId()); - - private static final BundlerParamInfo LINUX_MENU_GROUP = createStringBundlerParam( - Arguments.CLIOptions.LINUX_MENU_GROUP.getId()); - - private static final BundlerParamInfo RELEASE = createStringBundlerParam( - Arguments.CLIOptions.RELEASE.getId()); - - private static final BundlerParamInfo LINUX_PACKAGE_NAME = createStringBundlerParam( - Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo LICENSE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId()); - - private static final BundlerParamInfo MAINTAINER_EMAIL = createStringBundlerParam( - Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId()); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java deleted file mode 100644 index 1f674c0be11..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2019, 2025, 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.jpackage.internal; - -import java.util.Map; -import java.util.Objects; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.util.Result; - -abstract class LinuxPackageBundler extends AbstractBundler { - - LinuxPackageBundler(BundlerParamInfo pkgParam) { - this.pkgParam = Objects.requireNonNull(pkgParam); - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - - // Order is important! - pkgParam.fetchFrom(params); - BuildEnvFromParams.BUILD_ENV.fetchFrom(params); - - LinuxSystemEnvironment sysEnv; - try { - sysEnv = sysEnv().orElseThrow(); - } catch (RuntimeException ex) { - throw ConfigException.rethrowConfigException(ex); - } - - if (!isDefault()) { - Log.verbose(I18N.format( - "message.not-default-bundler-no-dependencies-lookup", - getName())); - } else if (!sysEnv.soLookupAvailable()) { - final String advice; - if ("deb".equals(getID())) { - advice = "message.deb-ldd-not-available.advice"; - } else { - advice = "message.rpm-ldd-not-available.advice"; - } - // Let user know package dependencies will not be generated. - Log.error(String.format("%s\n%s", I18N.getString( - "message.ldd-not-available"), I18N.getString(advice))); - } - - return true; - } - - @Override - public final String getBundleType() { - return "INSTALLER"; - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv().hasValue(); - } - - protected abstract Result sysEnv(); - - private final BundlerParamInfo pkgParam; - - static final Result SYS_ENV = LinuxSystemEnvironment.create(); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java index 806592904d1..af7f5288cc5 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackager.java @@ -40,7 +40,6 @@ import jdk.jpackage.internal.PackagingPipeline.PrimaryTaskID; import jdk.jpackage.internal.PackagingPipeline.TaskID; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.LinuxPackage; -import jdk.jpackage.internal.model.PackagerException; abstract class LinuxPackager implements Consumer { @@ -95,7 +94,7 @@ abstract class LinuxPackager implements Consumer { + // Return "true" if shortcut is not configured for the launcher. + return launcher.shortcut().isEmpty(); + }, (LinuxLauncher launcher) -> { + return launcher.shortcut().flatMap(LauncherShortcut::startupDirectory); + }, (launcher, shortcut) -> { + return LinuxLauncher.create(launcher, new LinuxLauncherMixin.Stub(Optional.of(new LauncherShortcut(shortcut)))); + }); + } + private static void writeLauncherLib( AppImageBuildEnv env) throws IOException { @@ -90,6 +106,15 @@ final class LinuxPackagingPipeline { }); } + private static final ApplicationLayout LINUX_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("bin") + .appDirectory("lib/app") + .runtimeDirectory("lib/runtime") + .desktopIntegrationDirectory("lib") + .appModsDirectory("lib/app/mods") + .contentDirectory("lib") + .create(); + static final LinuxApplicationLayout APPLICATION_LAYOUT = LinuxApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); + LINUX_APPLICATION_LAYOUT, Path.of("lib/libapplauncher.so")); } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java deleted file mode 100644 index c134aa91d6a..00000000000 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.LinuxRpmPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.model.StandardPackageType; -import jdk.jpackage.internal.util.Result; - - -public class LinuxRpmBundler extends LinuxPackageBundler { - - public LinuxRpmBundler() { - super(LinuxFromParams.RPM_PACKAGE); - } - - @Override - public String getName() { - return I18N.getString("rpm.bundler.name"); - } - - @Override - public String getID() { - return "rpm"; - } - - @Override - public Path execute(Map params, Path outputParentDir) throws PackagerException { - - var pkg = LinuxFromParams.RPM_PACKAGE.fetchFrom(params); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(BuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new LinuxRpmPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(LinuxPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - protected Result sysEnv() { - return sysEnv; - } - - @Override - public boolean isDefault() { - return sysEnv.value() - .map(LinuxSystemEnvironment::nativePackageType) - .map(StandardPackageType.LINUX_RPM::equals) - .orElse(false); - } - - private final Result sysEnv = LinuxRpmSystemEnvironment.create(SYS_ENV); -} diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java index c84b5e3bbf5..3c654f604c2 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/model/LinuxLauncher.java @@ -24,6 +24,8 @@ */ package jdk.jpackage.internal.model; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LINUX_LAUNCHER_SHORTCUT; + import java.util.HashMap; import java.util.Map; import jdk.jpackage.internal.util.CompositeProxy; @@ -39,7 +41,7 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { default Map extraAppImageFileData() { Map map = new HashMap<>(); shortcut().ifPresent(shortcut -> { - shortcut.store(SHORTCUT_ID, map::put); + shortcut.store(LINUX_LAUNCHER_SHORTCUT.getName(), map::put); }); return map; } @@ -55,6 +57,4 @@ public interface LinuxLauncher extends Launcher, LinuxLauncherMixin { public static LinuxLauncher create(Launcher launcher, LinuxLauncherMixin mixin) { return CompositeProxy.create(LinuxLauncher.class, launcher, mixin); } - - public static final String SHORTCUT_ID = "linux-shortcut"; } diff --git a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties index a732d02c7d1..6f05b623064 100644 --- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties +++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties @@ -23,10 +23,6 @@ # questions. # # -app.bundler.name=Linux Application Image -deb.bundler.name=DEB Bundle -rpm.bundler.name=RPM Bundle - param.license-type.default=Unknown resource.deb-control-file=DEB control file diff --git a/src/jdk.jpackage/linux/classes/module-info.java.extra b/src/jdk.jpackage/linux/classes/module-info.java.extra index d32314b0429..7bef2286214 100644 --- a/src/jdk.jpackage/linux/classes/module-info.java.extra +++ b/src/jdk.jpackage/linux/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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 @@ -23,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.LinuxAppBundler, - jdk.jpackage.internal.LinuxDebBundler, - jdk.jpackage.internal.LinuxRpmBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.LinuxBundlingEnvironment; diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java deleted file mode 100644 index cce35ece117..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.OUTPUT_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; - -import java.util.Map; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.util.function.ExceptionBox; - -public class MacAppBundler extends AppImageBundler { - public MacAppBundler() { - setAppImageSupplier((params, output) -> { - - // Order is important! - final var app = MacFromParams.APPLICATION.fetchFrom(params); - final BuildEnv env; - - if (StandardBundlerParam.hasPredefinedAppImage(params)) { - env = MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params); - final var pkg = MacPackagingPipeline.createSignAppImagePackage(app, env); - MacPackagingPipeline.build(Optional.of(pkg)).create().execute(env, pkg, output); - } else { - env = BuildEnv.withAppImageDir(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params), output); - MacPackagingPipeline.build(Optional.empty()) - .excludeDirFromCopying(output.getParent()) - .excludeDirFromCopying(OUTPUT_DIR.fetchFrom(params)).create().execute(env, app); - } - - }); - setParamsValidator(MacAppBundler::doValidate); - } - - private static void doValidate(Map params) - throws ConfigException { - - try { - MacFromParams.APPLICATION.fetchFrom(params); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ConfigException cfgEx) { - throw cfgEx; - } else { - throw ex; - } - } - - if (StandardBundlerParam.hasPredefinedAppImage(params)) { - if (!Optional.ofNullable( - SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) { - throw new ConfigException( - I18N.getString("error.app-image.mac-sign.required"), - null); - } - } - } -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java new file mode 100644 index 00000000000..371a3c7307a --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundlingEnvironment.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.MacFromOptions.createMacApplication; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacPackagingPipeline.createSignAppImagePackage; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_DMG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.CREATE_MAC_PKG; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; + +import java.util.Optional; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.MacPackage; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.util.Result; + +public class MacBundlingEnvironment extends DefaultBundlingEnvironment { + + public MacBundlingEnvironment() { + super(build() + .defaultOperation(CREATE_MAC_DMG) + .bundler(SIGN_MAC_APP_IMAGE, MacBundlingEnvironment::signAppImage) + .bundler(CREATE_MAC_APP_IMAGE, MacBundlingEnvironment::createAppImage) + .bundler(CREATE_MAC_DMG, LazyLoad::dmgSysEnv, MacBundlingEnvironment::createDmdPackage) + .bundler(CREATE_MAC_PKG, MacBundlingEnvironment::createPkgPackage)); + } + + private static void createDmdPackage(Options options, MacDmgSystemEnvironment sysEnv) { + createNativePackage(options, + MacFromOptions::createMacDmgPackage, + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); + return new MacDmgPackager(env, pkg, outputDir, sysEnv); + }); + } + + private static void createPkgPackage(Options options) { + createNativePackage(options, + MacFromOptions::createMacPkgPackage, + buildEnv()::create, + MacBundlingEnvironment::buildPipeline, + (env, pkg, outputDir) -> { + Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); + return new MacPkgPackager(env, pkg, outputDir); + }); + } + + private static void signAppImage(Options options) { + + final var app = createMacApplication(options); + + final var env = buildEnv().create(options, app); + + final var pkg = createSignAppImagePackage(app, env); + + buildPipeline(pkg).create().execute(env, pkg, env.appImageDir()); + } + + private static void createAppImage(Options options) { + + final var app = createMacApplication(options); + + createApplicationImage(options, app, MacPackagingPipeline.build(Optional.empty())); + } + + private static PackagingPipeline.Builder buildPipeline(Package pkg) { + return MacPackagingPipeline.build(Optional.of(pkg)); + } + + private static BuildEnvFromOptions buildEnv() { + return new BuildEnvFromOptions() + .predefinedAppImageLayout(APPLICATION_LAYOUT) + .predefinedRuntimeImageLayout(MacPackage::guessRuntimeLayout); + } + + private static final class LazyLoad { + + static Result dmgSysEnv() { + return DMG_SYS_ENV; + } + + private static final Result DMG_SYS_ENV = MacDmgSystemEnvironment.create(); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java deleted file mode 100644 index 0ddb987dbee..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2012, 2025, 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacDmgPackage; -import jdk.jpackage.internal.model.PackagerException; -import jdk.jpackage.internal.util.Result; - -public class MacDmgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("dmg.bundler.name"); - } - - @Override - public String getID() { - return "dmg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - MacFromParams.DMG_PACKAGE.fetchFrom(params); - - //run basic validation to ensure requirements are met - //we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.DMG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-dmg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacDmgPackager(env, pkg, outputDir, sysEnv.orElseThrow()); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return sysEnv.hasValue(); - } - - @Override - public boolean isDefault() { - return true; - } - - private final Result sysEnv = MacDmgSystemEnvironment.create(); -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java new file mode 100644 index 00000000000..074014dede0 --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromOptions.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.FromOptions.buildApplicationBuilder; +import static jdk.jpackage.internal.FromOptions.createPackageBuilder; +import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; +import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; +import static jdk.jpackage.internal.cli.StandardBundlingOperation.SIGN_MAC_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.APPCLASS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_CATEGORY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_IMAGE_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_IDENTIFIER; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_NAME; +import static jdk.jpackage.internal.cli.StandardOption.MAC_BUNDLE_SIGNING_PREFIX; +import static jdk.jpackage.internal.cli.StandardOption.MAC_DMG_CONTENT; +import static jdk.jpackage.internal.cli.StandardOption.MAC_ENTITLEMENTS; +import static jdk.jpackage.internal.cli.StandardOption.MAC_INSTALLER_SIGN_IDENTITY; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEYCHAIN; +import static jdk.jpackage.internal.cli.StandardOption.MAC_SIGNING_KEY_NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; +import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; +import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; +import static jdk.jpackage.internal.util.function.ExceptionBox.rethrowUnchecked; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; +import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; +import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardFaOption; +import jdk.jpackage.internal.model.ApplicationLaunchers; +import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.MacApplication; +import jdk.jpackage.internal.model.MacDmgPackage; +import jdk.jpackage.internal.model.MacFileAssociation; +import jdk.jpackage.internal.model.MacLauncher; +import jdk.jpackage.internal.model.MacPackage; +import jdk.jpackage.internal.model.MacPkgPackage; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.RuntimeLayout; +import jdk.jpackage.internal.util.Result; +import jdk.jpackage.internal.util.function.ExceptionBox; + + +final class MacFromOptions { + + static MacApplication createMacApplication(Options options) { + return createMacApplicationInternal(options).app(); + } + + static MacDmgPackage createMacDmgPackage(Options options) { + + final var app = createMacApplicationInternal(options); + + final var superPkgBuilder = createMacPackageBuilder(options, app, MAC_DMG); + + final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); + + MAC_DMG_CONTENT.ifPresentIn(options, pkgBuilder::dmgContent); + + return pkgBuilder.create(); + } + + static MacPkgPackage createMacPkgPackage(Options options) { + + // + // One of "MacSignTest.testExpiredCertificate" test cases expects + // two error messages about expired certificates in the output: one for + // certificate for signing an app image, another certificate for signing a PKG. + // So creation of a PKG package is a bit messy. + // + + final boolean sign = MAC_SIGN.findIn(options).orElse(false); + final boolean appStore = MAC_APP_STORE.findIn(options).orElse(false); + + final var appResult = Result.create(() -> createMacApplicationInternal(options)); + + final Optional pkgBuilder; + if (appResult.hasValue()) { + final var superPkgBuilder = createMacPackageBuilder(options, appResult.orElseThrow(), MAC_PKG); + pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); + } else { + // Failed to create an app. Is it because of the expired certificate? + rethrowIfNotExpiredCertificateException(appResult); + // Yes, the certificate for signing the app image has expired. + // Keep going, try to create a signing config for the package. + pkgBuilder = Optional.empty(); + } + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_INSTALLER_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; + } else { + domain = StandardCertificateSelector.PKG_INSTALLER; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + if (pkgBuilder.isPresent()) { + pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); + } else { + // + // The certificate for signing the app image has expired. Can not create a + // package because there is no app. + // Try to create a signing config for the package and see if the certificate for + // signing the package is also expired. + // + + final var expiredAppCertException = appResult.firstError().orElseThrow(); + + final var pkgSignConfigResult = Result.create(signingIdentityBuilder::create); + try { + rethrowIfNotExpiredCertificateException(pkgSignConfigResult); + // The certificate for the package signing config is also expired! + } catch (RuntimeException ex) { + // Some error occurred trying to configure the signing config for the package. + // Ignore it, bail out with the first error. + rethrowUnchecked(expiredAppCertException); + } + + Log.error(pkgSignConfigResult.firstError().orElseThrow().getMessage()); + rethrowUnchecked(expiredAppCertException); + } + } + + return pkgBuilder.orElseThrow().create(); + } + + private record ApplicationWithDetails(MacApplication app, Optional externalApp) { + ApplicationWithDetails { + Objects.requireNonNull(app); + Objects.requireNonNull(externalApp); + } + } + + private static ApplicationWithDetails createMacApplicationInternal(Options options) { + + final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacPackage::guessRuntimeLayout); + + predefinedRuntimeLayout.ifPresent(layout -> { + validateRuntimeHasJliLib(layout); + if (MAC_APP_STORE.containsIn(options)) { + validateRuntimeHasNoBinDir(layout); + } + }); + + final var launcherFromOptions = new LauncherFromOptions().faMapper(MacFromOptions::createMacFa); + + final var superAppBuilder = buildApplicationBuilder() + .runtimeLayout(RUNTIME_BUNDLE_LAYOUT) + .predefinedRuntimeLayout(predefinedRuntimeLayout.map(RuntimeLayout::unresolve).orElse(null)) + .create(options, launcherOptions -> { + var launcher = launcherFromOptions.create(launcherOptions); + return MacLauncher.create(launcher); + }, (MacLauncher _, Launcher launcher) -> { + return MacLauncher.create(launcher); + }, APPLICATION_LAYOUT); + + if (PREDEFINED_APP_IMAGE.containsIn(options)) { + // Set the main launcher start up info. + // AppImageFile assumes the main launcher start up info is available when + // it is constructed from Application instance. + // This happens when jpackage signs predefined app image. + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + final var mainLauncherStartupInfo = new MainLauncherStartupInfo(APPCLASS.getFrom(appImageFileOptions)); + final var launchers = superAppBuilder.launchers().orElseThrow(); + final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); + superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); + } + + final var app = superAppBuilder.create(); + + final var appBuilder = new MacApplicationBuilder(app); + + PREDEFINED_APP_IMAGE.findIn(options) + .map(MacBundle::new) + .map(MacBundle::infoPlistFile) + .ifPresent(appBuilder::externalInfoPlistFile); + + ICON.ifPresentIn(options, appBuilder::icon); + MAC_BUNDLE_NAME.ifPresentIn(options, appBuilder::bundleName); + MAC_BUNDLE_IDENTIFIER.ifPresentIn(options, appBuilder::bundleIdentifier); + MAC_APP_CATEGORY.ifPresentIn(options, appBuilder::category); + + final boolean sign; + final boolean appStore; + + if (PREDEFINED_APP_IMAGE.containsIn(options) && OptionUtils.bundlingOperation(options) != SIGN_MAC_APP_IMAGE) { + final var appImageFileOptions = superAppBuilder.externalApplication().orElseThrow().extra(); + sign = MAC_SIGN.getFrom(appImageFileOptions); + appStore = MAC_APP_STORE.getFrom(appImageFileOptions); + } else { + sign = MAC_SIGN.getFrom(options); + appStore = MAC_APP_STORE.getFrom(options); + } + + appBuilder.appStore(appStore); + + if (sign) { + final var signingIdentityBuilder = createSigningIdentityBuilder(options); + MAC_APP_IMAGE_SIGN_IDENTITY.ifPresentIn(options, signingIdentityBuilder::signingIdentity); + MAC_SIGNING_KEY_NAME.findIn(options).ifPresent(userName -> { + final StandardCertificateSelector domain; + if (appStore) { + domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; + } else { + domain = StandardCertificateSelector.APP_IMAGE; + } + + signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); + }); + + final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); + if (appStore) { + signingBuilder.entitlementsResourceName("sandbox.plist"); + } + + app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( + signingBuilder::signingIdentifierPrefix, + () -> { + // Runtime installer does not have the main launcher, use + // 'bundleIdentifier' as the prefix by default. + var bundleIdentifier = appBuilder.create().bundleIdentifier(); + signingBuilder.signingIdentifierPrefix(bundleIdentifier + "."); + }); + MAC_BUNDLE_SIGNING_PREFIX.ifPresentIn(options, signingBuilder::signingIdentifierPrefix); + + MAC_ENTITLEMENTS.ifPresentIn(options, signingBuilder::entitlements); + + appBuilder.signingBuilder(signingBuilder); + } + + return new ApplicationWithDetails(appBuilder.create(), superAppBuilder.externalApplication()); + } + + private static MacPackageBuilder createMacPackageBuilder(Options options, ApplicationWithDetails app, PackageType type) { + + final var builder = new MacPackageBuilder(createPackageBuilder(options, app.app(), type)); + + app.externalApp() + .map(ExternalApplication::extra) + .flatMap(MAC_SIGN::findIn) + .ifPresent(builder::predefinedAppImageSigned); + + PREDEFINED_RUNTIME_IMAGE.findIn(options) + .map(MacBundle::new) + .filter(MacBundle::isValid) + .map(MacBundle::isSigned) + .ifPresent(builder::predefinedAppImageSigned); + + return builder; + } + + private static void rethrowIfNotExpiredCertificateException(Result result) { + final var ex = result.firstError().orElseThrow(); + + if (ex instanceof ExpiredCertificateException) { + return; + } + + if (ex instanceof ExceptionBox box) { + if (box.getCause() instanceof Exception cause) { + rethrowIfNotExpiredCertificateException(Result.ofError(cause)); + } + } + + rethrowUnchecked(ex); + } + + private static SigningIdentityBuilder createSigningIdentityBuilder(Options options) { + final var builder = new SigningIdentityBuilder(); + MAC_SIGNING_KEYCHAIN.findIn(options).map(Path::toString).ifPresent(builder::keychain); + return builder; + } + + private static MacFileAssociation createMacFa(Options options, FileAssociation fa) { + + final var builder = new MacFileAssociationBuilder(); + + StandardFaOption.MAC_CFBUNDLETYPEROLE.ifPresentIn(options, builder::cfBundleTypeRole); + StandardFaOption.MAC_LSHANDLERRANK.ifPresentIn(options, builder::lsHandlerRank); + StandardFaOption.MAC_NSSTORETYPEKEY.ifPresentIn(options, builder::nsPersistentStoreTypeKey); + StandardFaOption.MAC_NSDOCUMENTCLASS.ifPresentIn(options, builder::nsDocumentClass); + StandardFaOption.MAC_LSTYPEISPACKAGE.ifPresentIn(options, builder::lsTypeIsPackage); + StandardFaOption.MAC_LSDOCINPLACE.ifPresentIn(options, builder::lsSupportsOpeningDocumentsInPlace); + StandardFaOption.MAC_UIDOCBROWSER.ifPresentIn(options, builder::uiSupportsDocumentBrowser); + StandardFaOption.MAC_NSEXPORTABLETYPES.ifPresentIn(options, builder::nsExportableTypes); + StandardFaOption.MAC_UTTYPECONFORMSTO.ifPresentIn(options, builder::utTypeConformsTo); + + return builder.create(fa); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java deleted file mode 100644 index 72c33ef6475..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (c) 2025, 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.jpackage.internal; - -import static jdk.jpackage.internal.BundlerParamInfo.createBooleanBundlerParam; -import static jdk.jpackage.internal.BundlerParamInfo.createPathBundlerParam; -import static jdk.jpackage.internal.BundlerParamInfo.createStringBundlerParam; -import static jdk.jpackage.internal.FromParams.createApplicationBuilder; -import static jdk.jpackage.internal.FromParams.createApplicationBundlerParam; -import static jdk.jpackage.internal.FromParams.createPackageBuilder; -import static jdk.jpackage.internal.FromParams.createPackageBundlerParam; -import static jdk.jpackage.internal.MacPackagingPipeline.APPLICATION_LAYOUT; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasJliLib; -import static jdk.jpackage.internal.MacRuntimeValidator.validateRuntimeHasNoBinDir; -import static jdk.jpackage.internal.StandardBundlerParam.DMG_CONTENT; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE; -import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; -import static jdk.jpackage.internal.model.MacPackage.RUNTIME_BUNDLE_LAYOUT; -import static jdk.jpackage.internal.model.StandardPackageType.MAC_DMG; -import static jdk.jpackage.internal.model.StandardPackageType.MAC_PKG; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.function.Predicate; -import jdk.jpackage.internal.ApplicationBuilder.MainLauncherStartupInfo; -import jdk.jpackage.internal.SigningIdentityBuilder.ExpiredCertificateException; -import jdk.jpackage.internal.SigningIdentityBuilder.StandardCertificateSelector; -import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.FileAssociation; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.MacApplication; -import jdk.jpackage.internal.model.MacDmgPackage; -import jdk.jpackage.internal.model.MacFileAssociation; -import jdk.jpackage.internal.model.MacLauncher; -import jdk.jpackage.internal.model.MacPackage; -import jdk.jpackage.internal.model.MacPkgPackage; -import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.RuntimeLayout; -import jdk.jpackage.internal.util.function.ExceptionBox; - - -final class MacFromParams { - - private static MacApplication createMacApplication( - Map params) throws ConfigException, IOException { - - final var predefinedRuntimeLayout = PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacPackage::guessRuntimeLayout); - - if (predefinedRuntimeLayout.isPresent()) { - validateRuntimeHasJliLib(predefinedRuntimeLayout.orElseThrow()); - if (APP_STORE.findIn(params).orElse(false)) { - validateRuntimeHasNoBinDir(predefinedRuntimeLayout.orElseThrow()); - } - } - - final var launcherFromParams = new LauncherFromParams(Optional.of(MacFromParams::createMacFa)); - - final var superAppBuilder = createApplicationBuilder(params, toFunction(launcherParams -> { - var launcher = launcherFromParams.create(launcherParams); - return MacLauncher.create(launcher); - }), (MacLauncher _, Launcher launcher) -> { - return MacLauncher.create(launcher); - }, APPLICATION_LAYOUT, RUNTIME_BUNDLE_LAYOUT, predefinedRuntimeLayout.map(RuntimeLayout::unresolve)); - - if (hasPredefinedAppImage(params)) { - // Set the main launcher start up info. - // AppImageFile assumes the main launcher start up info is available when - // it is constructed from Application instance. - // This happens when jpackage signs predefined app image. - final var mainLauncherStartupInfo = new MainLauncherStartupInfo(superAppBuilder.mainLauncherClassName().orElseThrow()); - final var launchers = superAppBuilder.launchers().orElseThrow(); - final var mainLauncher = ApplicationBuilder.overrideLauncherStartupInfo(launchers.mainLauncher(), mainLauncherStartupInfo); - superAppBuilder.launchers(new ApplicationLaunchers(MacLauncher.create(mainLauncher), launchers.additionalLaunchers())); - } - - final var app = superAppBuilder.create(); - - final var appBuilder = new MacApplicationBuilder(app); - - if (hasPredefinedAppImage(params)) { - appBuilder.externalInfoPlistFile(PREDEFINED_APP_IMAGE.findIn(params).map(MacBundle::new).orElseThrow().infoPlistFile()); - } - - ICON.copyInto(params, appBuilder::icon); - MAC_CF_BUNDLE_NAME.copyInto(params, appBuilder::bundleName); - MAC_CF_BUNDLE_IDENTIFIER.copyInto(params, appBuilder::bundleIdentifier); - APP_CATEGORY.copyInto(params, appBuilder::category); - - final boolean sign; - final boolean appStore; - - if (hasPredefinedAppImage(params) && PACKAGE_TYPE.findIn(params).filter(Predicate.isEqual("app-image")).isEmpty()) { - final var appImageFileExtras = new MacAppImageFileExtras(superAppBuilder.externalApplication().orElseThrow()); - sign = appImageFileExtras.signed(); - appStore = appImageFileExtras.appStore(); - } else { - sign = SIGN_BUNDLE.findIn(params).orElse(false); - appStore = APP_STORE.findIn(params).orElse(false); - } - - appBuilder.appStore(appStore); - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - APP_IMAGE_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_APP_IMAGE; - } else { - domain = StandardCertificateSelector.APP_IMAGE; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - final var signingBuilder = new AppImageSigningConfigBuilder(signingIdentityBuilder); - if (appStore) { - signingBuilder.entitlementsResourceName("sandbox.plist"); - } - - final var bundleIdentifier = appBuilder.create().bundleIdentifier(); - app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( - signingBuilder::signingIdentifierPrefix, - () -> { - // Runtime installer does not have main launcher, so use - // 'bundleIdentifier' as prefix by default. - signingBuilder.signingIdentifierPrefix( - bundleIdentifier + "."); - }); - SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix); - - ENTITLEMENTS.copyInto(params, signingBuilder::entitlements); - - appBuilder.signingBuilder(signingBuilder); - } - - return appBuilder.create(); - } - - private static MacPackageBuilder createMacPackageBuilder( - Map params, MacApplication app, - PackageType type) throws ConfigException { - final var builder = new MacPackageBuilder(createPackageBuilder(params, app, type)); - - PREDEFINED_APP_IMAGE_FILE.findIn(params) - .map(MacAppImageFileExtras::new) - .map(MacAppImageFileExtras::signed) - .ifPresent(builder::predefinedAppImageSigned); - - PREDEFINED_RUNTIME_IMAGE.findIn(params) - .map(MacBundle::new) - .filter(MacBundle::isValid) - .map(MacBundle::isSigned) - .ifPresent(builder::predefinedAppImageSigned); - - return builder; - } - - private static MacDmgPackage createMacDmgPackage( - Map params) throws ConfigException, IOException { - - final var app = APPLICATION.fetchFrom(params); - - final var superPkgBuilder = createMacPackageBuilder(params, app, MAC_DMG); - - final var pkgBuilder = new MacDmgPackageBuilder(superPkgBuilder); - - DMG_CONTENT.copyInto(params, pkgBuilder::dmgContent); - - return pkgBuilder.create(); - } - - private record WithExpiredCertificateException(Optional obj, Optional certEx) { - WithExpiredCertificateException { - if (obj.isEmpty() == certEx.isEmpty()) { - throw new IllegalArgumentException(); - } - } - - static WithExpiredCertificateException of(Callable callable) { - try { - return new WithExpiredCertificateException<>(Optional.of(callable.call()), Optional.empty()); - } catch (ExpiredCertificateException ex) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(ex)); - } catch (ExceptionBox ex) { - if (ex.getCause() instanceof ExpiredCertificateException certEx) { - return new WithExpiredCertificateException<>(Optional.empty(), Optional.of(certEx)); - } - throw ex; - } catch (RuntimeException ex) { - throw ex; - } catch (Throwable t) { - throw ExceptionBox.rethrowUnchecked(t); - } - } - } - - private static MacPkgPackage createMacPkgPackage( - Map params) throws ConfigException, IOException { - - // This is over complicated to make "MacSignTest.testExpiredCertificate" test pass. - - final boolean sign = SIGN_BUNDLE.findIn(params).orElse(false); - final boolean appStore = APP_STORE.findIn(params).orElse(false); - - final var appOrExpiredCertEx = WithExpiredCertificateException.of(() -> { - return APPLICATION.fetchFrom(params); - }); - - final Optional pkgBuilder; - if (appOrExpiredCertEx.obj().isPresent()) { - final var superPkgBuilder = createMacPackageBuilder(params, appOrExpiredCertEx.obj().orElseThrow(), MAC_PKG); - pkgBuilder = Optional.of(new MacPkgPackageBuilder(superPkgBuilder)); - } else { - pkgBuilder = Optional.empty(); - } - - if (sign) { - final var signingIdentityBuilder = createSigningIdentityBuilder(params); - INSTALLER_SIGN_IDENTITY.copyInto(params, signingIdentityBuilder::signingIdentity); - SIGNING_KEY_USER.findIn(params).ifPresent(userName -> { - final StandardCertificateSelector domain; - if (appStore) { - domain = StandardCertificateSelector.APP_STORE_PKG_INSTALLER; - } else { - domain = StandardCertificateSelector.PKG_INSTALLER; - } - - signingIdentityBuilder.certificateSelector(StandardCertificateSelector.create(userName, domain)); - }); - - if (pkgBuilder.isPresent()) { - pkgBuilder.orElseThrow().signingBuilder(signingIdentityBuilder); - } else { - final var expiredPkgCert = WithExpiredCertificateException.of(() -> { - return signingIdentityBuilder.create(); - }).certEx(); - expiredPkgCert.map(ConfigException::getMessage).ifPresent(Log::error); - throw appOrExpiredCertEx.certEx().orElseThrow(); - } - } - - return pkgBuilder.orElseThrow().create(); - } - - private static SigningIdentityBuilder createSigningIdentityBuilder(Map params) { - final var builder = new SigningIdentityBuilder(); - SIGNING_KEYCHAIN.copyInto(params, builder::keychain); - return builder; - } - - private static MacFileAssociation createMacFa(FileAssociation fa, Map params) { - - final var builder = new MacFileAssociationBuilder(); - - FA_MAC_CFBUNDLETYPEROLE.copyInto(params, builder::cfBundleTypeRole); - FA_MAC_LSHANDLERRANK.copyInto(params, builder::lsHandlerRank); - FA_MAC_NSSTORETYPEKEY.copyInto(params, builder::nsPersistentStoreTypeKey); - FA_MAC_NSDOCUMENTCLASS.copyInto(params, builder::nsDocumentClass); - FA_MAC_LSTYPEISPACKAGE.copyInto(params, builder::lsTypeIsPackage); - FA_MAC_LSDOCINPLACE.copyInto(params, builder::lsSupportsOpeningDocumentsInPlace); - FA_MAC_UIDOCBROWSER.copyInto(params, builder::uiSupportsDocumentBrowser); - FA_MAC_NSEXPORTABLETYPES.copyInto(params, builder::nsExportableTypes); - FA_MAC_UTTYPECONFORMSTO.copyInto(params, builder::utTypeConformsTo); - - return toFunction(builder::create).apply(fa); - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam( - MacFromParams::createMacApplication); - - static final BundlerParamInfo DMG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacDmgPackage); - - static final BundlerParamInfo PKG_PACKAGE = createPackageBundlerParam( - MacFromParams::createMacPkgPackage); - - private static final BundlerParamInfo MAC_CF_BUNDLE_NAME = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_NAME.getId()); - - private static final BundlerParamInfo APP_CATEGORY = createStringBundlerParam( - Arguments.CLIOptions.MAC_CATEGORY.getId()); - - private static final BundlerParamInfo ENTITLEMENTS = createPathBundlerParam( - Arguments.CLIOptions.MAC_ENTITLEMENTS.getId()); - - private static final BundlerParamInfo MAC_CF_BUNDLE_IDENTIFIER = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId()); - - private static final BundlerParamInfo SIGN_IDENTIFIER_PREFIX = createStringBundlerParam( - Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId()); - - private static final BundlerParamInfo APP_IMAGE_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo INSTALLER_SIGN_IDENTITY = createStringBundlerParam( - Arguments.CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId()); - - private static final BundlerParamInfo SIGNING_KEY_USER = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId()); - - private static final BundlerParamInfo SIGNING_KEYCHAIN = createStringBundlerParam( - Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId()); - - private static final BundlerParamInfo PACKAGE_TYPE = createStringBundlerParam( - Arguments.CLIOptions.PACKAGE_TYPE.getId()); - - private static final BundlerParamInfo APP_STORE = createBooleanBundlerParam( - Arguments.CLIOptions.MAC_APP_STORE.getId()); - - private static final BundlerParamInfo FA_MAC_CFBUNDLETYPEROLE = createStringBundlerParam( - Arguments.MAC_CFBUNDLETYPEROLE); - - private static final BundlerParamInfo FA_MAC_LSHANDLERRANK = createStringBundlerParam( - Arguments.MAC_LSHANDLERRANK); - - private static final BundlerParamInfo FA_MAC_NSSTORETYPEKEY = createStringBundlerParam( - Arguments.MAC_NSSTORETYPEKEY); - - private static final BundlerParamInfo FA_MAC_NSDOCUMENTCLASS = createStringBundlerParam( - Arguments.MAC_NSDOCUMENTCLASS); - - private static final BundlerParamInfo FA_MAC_LSTYPEISPACKAGE = createBooleanBundlerParam( - Arguments.MAC_LSTYPEISPACKAGE); - - private static final BundlerParamInfo FA_MAC_LSDOCINPLACE = createBooleanBundlerParam( - Arguments.MAC_LSDOCINPLACE); - - private static final BundlerParamInfo FA_MAC_UIDOCBROWSER = createBooleanBundlerParam( - Arguments.MAC_UIDOCBROWSER); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_NSEXPORTABLETYPES = - new BundlerParamInfo<>( - Arguments.MAC_NSEXPORTABLETYPES, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - @SuppressWarnings("unchecked") - private static final BundlerParamInfo> FA_MAC_UTTYPECONFORMSTO = - new BundlerParamInfo<>( - Arguments.MAC_UTTYPECONFORMSTO, - (Class>) (Object) List.class, - null, - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java index b82b20c0c36..51fd15afabb 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -75,7 +75,6 @@ import jdk.jpackage.internal.model.MacFileAssociation; import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.util.FileUtils; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; @@ -268,7 +267,7 @@ final class MacPackagingPipeline { static AppImageTaskAction withBundleLayout(AppImageTaskAction action) { return new AppImageTaskAction<>() { @Override - public void execute(AppImageBuildEnv env) throws IOException, PackagerException { + public void execute(AppImageBuildEnv env) throws IOException { if (!env.envLayout().runtimeDirectory().getName(0).equals(Path.of("Contents"))) { env = LayoutUtils.fromPackagerLayout(env); } @@ -610,11 +609,20 @@ final class MacPackagingPipeline { } @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { delegate.execute(taskAction); } } + private static final ApplicationLayout MAC_APPLICATION_LAYOUT = ApplicationLayout.build() + .launchersDirectory("Contents/MacOS") + .appDirectory("Contents/app") + .runtimeDirectory("Contents/runtime/Contents/Home") + .desktopIntegrationDirectory("Contents/Resources") + .appModsDirectory("Contents/app/mods") + .contentDirectory("Contents") + .create(); + static final MacApplicationLayout APPLICATION_LAYOUT = MacApplicationLayout.create( - ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT, Path.of("Contents/runtime")); + MAC_APPLICATION_LAYOUT, Path.of("Contents/runtime")); } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java deleted file mode 100644 index e827f238db3..00000000000 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.MacPkgPackage; -import jdk.jpackage.internal.model.PackagerException; - -public class MacPkgBundler extends MacBaseInstallerBundler { - - @Override - public String getName() { - return I18N.getString("pkg.bundler.name"); - } - - @Override - public String getID() { - return "pkg"; - } - - @Override - public boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - final var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - // run basic validation to ensure requirements are met - // we are not interested in return code, only possible exception - validateAppImageAndBundeler(params); - - // hdiutil is always available so there's no need - // to test for availability. - - return true; - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - } - - @Override - public Path execute(Map params, - Path outputParentDir) throws PackagerException { - - var pkg = MacFromParams.PKG_PACKAGE.fetchFrom(params); - - Log.verbose(I18N.format("message.building-pkg", pkg.app().name())); - - return Packager.build().outputDir(outputParentDir) - .pkg(pkg) - .env(MacBuildEnvFromParams.BUILD_ENV.fetchFrom(params)) - .pipelineBuilderMutatorFactory((env, _, outputDir) -> { - return new MacPkgPackager(env, pkg, outputDir); - }).execute(MacPackagingPipeline.build(Optional.of(pkg))); - } - - @Override - public boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public boolean isDefault() { - return false; - } - -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java index 04ab7042ac5..cfe10e8a012 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java @@ -25,13 +25,18 @@ package jdk.jpackage.internal.model; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_APP_STORE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_MAIN_CLASS; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.MAC_SIGNED; import java.nio.file.Path; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; +import jdk.jpackage.internal.cli.OptionValue; import jdk.jpackage.internal.util.CompositeProxy; public interface MacApplication extends Application, MacApplicationMixin { @@ -76,7 +81,14 @@ public interface MacApplication extends Application, MacApplicationMixin { @Override default Map extraAppImageFileData() { - return Stream.of(ExtraAppImageFileField.values()).collect(toMap(ExtraAppImageFileField::fieldName, x -> x.asString(this))); + return Stream.of(ExtraAppImageFileField.values()).map(field -> { + return field.findStringValue(this).map(value -> { + return Map.entry(field.fieldName(), value); + }); + }) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } public static MacApplication create(Application app, MacApplicationMixin mixin) { @@ -84,23 +96,31 @@ public interface MacApplication extends Application, MacApplicationMixin { } public enum ExtraAppImageFileField { - SIGNED("signed", app -> Boolean.toString(app.sign())), - APP_STORE("app-store", app -> Boolean.toString(app.appStore())); + SIGNED(MAC_SIGNED, app -> { + return Optional.of(Boolean.toString(app.sign())); + }), + APP_STORE(MAC_APP_STORE, app -> { + return Optional.of(Boolean.toString(app.appStore())); + }), + APP_CLASS(MAC_MAIN_CLASS, app -> { + return app.mainLauncher().flatMap(Launcher::startupInfo).map(LauncherStartupInfo::qualifiedClassName); + }), + ; - ExtraAppImageFileField(String fieldName, Function getter) { - this.fieldName = fieldName; + ExtraAppImageFileField(OptionValue option, Function> getter) { + this.fieldName = option.getName(); this.getter = getter; } - public String fieldName() { + String fieldName() { return fieldName; } - String asString(MacApplication app) { + Optional findStringValue(MacApplication app) { return getter.apply(app); } private final String fieldName; - private final Function getter; + private final Function> getter; } } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index afa71d84d5c..7ce20925439 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -23,12 +23,6 @@ # questions. # # - -app.bundler.name=Mac Application Image -store.bundler.name=Mac App Store Ready Bundler -dmg.bundler.name=Mac DMG Package -pkg.bundler.name=Mac PKG Package - error.invalid-cfbundle-version.advice=Set a compatible 'app-version' value. Valid versions are one to three integers separated by dots. error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate found error.explicit-sign-no-cert.advice=Specify a valid mac-signing-key-user-name and mac-signing-keychain @@ -60,7 +54,6 @@ resource.pkg-background-image=pkg background image resource.pkg-pdf=project definition file resource.launchd-plist-file=launchd plist file - message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it. message.preparing-info-plist=Preparing Info.plist: {0}. message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place. @@ -70,7 +63,6 @@ message.creating-association-with-null-extension=Creating association with null message.ignoring.symlink=Warning: codesign is skipping the symlink {0}. message.already.signed=File already signed: {0}. message.keychain.error=Error: unable to get keychain list. -message.building-bundle=Building Mac App Store Package for {0}. message.invalid-identifier=invalid mac bundle identifier [{0}]. message.invalid-identifier.advice=specify identifier with "--mac-package-identifier". message.building-dmg=Building DMG package for {0}. diff --git a/src/jdk.jpackage/macosx/classes/module-info.java.extra b/src/jdk.jpackage/macosx/classes/module-info.java.extra index 1496167cd4a..e6202dd1156 100644 --- a/src/jdk.jpackage/macosx/classes/module-info.java.extra +++ b/src/jdk.jpackage/macosx/classes/module-info.java.extra @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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 @@ -23,8 +23,5 @@ * questions. */ -provides jdk.jpackage.internal.Bundler with - jdk.jpackage.internal.MacAppBundler, - jdk.jpackage.internal.MacDmgBundler, - jdk.jpackage.internal.MacPkgBundler; - +provides jdk.jpackage.internal.cli.CliBundlingEnvironment with + jdk.jpackage.internal.MacBundlingEnvironment; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java deleted file mode 100644 index 93d037c6a45..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2018, 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; -import java.util.List; -import java.util.Optional; - -import jdk.internal.util.OperatingSystem; - -import jdk.jpackage.internal.Arguments.CLIOptions; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; - -/* - * AddLauncherArguments - * - * Processes a add-launcher properties file to create the Map of - * bundle params applicable to the add-launcher: - * - * BundlerParams p = (new AddLauncherArguments(file)).getLauncherMap(); - * - * A add-launcher is another executable program generated by either the - * create-app-image mode or the create-installer mode. - * The add-launcher may be the same program with different configuration, - * or a completely different program created from the same files. - * - * There may be multiple add-launchers, each created by using the - * command line arg "--add-launcher - * - * The add-launcher properties file may have any of: - * - * appVersion - * description - * module - * main-jar - * main-class - * icon - * arguments - * java-options - * launcher-as-service - * win-console - * win-shortcut - * win-menu - * linux-app-category - * linux-shortcut - * - */ -class AddLauncherArguments { - - private final String name; - private final String filename; - private Map allArgs; - private Map bundleParams; - - AddLauncherArguments(String name, String filename) { - this.name = name; - this.filename = filename; - } - - private void initLauncherMap() { - if (bundleParams != null) { - return; - } - - allArgs = Arguments.getPropertiesFromFile(filename); - allArgs.put(CLIOptions.NAME.getId(), name); - - bundleParams = new HashMap<>(); - String mainJar = getOptionValue(CLIOptions.MAIN_JAR); - String mainClass = getOptionValue(CLIOptions.APPCLASS); - String module = getOptionValue(CLIOptions.MODULE); - - if (module != null && mainClass != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module + "/" + mainClass); - } else if (module != null) { - Arguments.putUnlessNull(bundleParams, CLIOptions.MODULE.getId(), - module); - } else { - Arguments.putUnlessNull(bundleParams, CLIOptions.MAIN_JAR.getId(), - mainJar); - Arguments.putUnlessNull(bundleParams, CLIOptions.APPCLASS.getId(), - mainClass); - } - - Arguments.putUnlessNull(bundleParams, CLIOptions.NAME.getId(), - getOptionValue(CLIOptions.NAME)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.VERSION.getId(), - getOptionValue(CLIOptions.VERSION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.DESCRIPTION.getId(), - getOptionValue(CLIOptions.DESCRIPTION)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(), - getOptionValue(CLIOptions.RELEASE)); - - Arguments.putUnlessNull(bundleParams, CLIOptions.ICON.getId(), - Optional.ofNullable(getOptionValue(CLIOptions.ICON)).map( - Path::of).orElse(null)); - - Arguments.putUnlessNull(bundleParams, - CLIOptions.LAUNCHER_AS_SERVICE.getId(), getOptionValue( - CLIOptions.LAUNCHER_AS_SERVICE)); - - if (OperatingSystem.isWindows()) { - Arguments.putUnlessNull(bundleParams, - CLIOptions.WIN_CONSOLE_HINT.getId(), - getOptionValue(CLIOptions.WIN_CONSOLE_HINT)); - Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.WIN_SHORTCUT_HINT)); - Arguments.putUnlessNull(bundleParams, CLIOptions.WIN_MENU_HINT.getId(), - getOptionValue(CLIOptions.WIN_MENU_HINT)); - } - - if (OperatingSystem.isLinux()) { - Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(), - getOptionValue(CLIOptions.LINUX_CATEGORY)); - Arguments.putUnlessNull(bundleParams, CLIOptions.LINUX_SHORTCUT_HINT.getId(), - getOptionValue(CLIOptions.LINUX_SHORTCUT_HINT)); - } - - // "arguments" and "java-options" even if value is null: - if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) { - String argumentStr = getOptionValue(CLIOptions.ARGUMENTS); - bundleParams.put(CLIOptions.ARGUMENTS.getId(), - Arguments.getArgumentList(argumentStr)); - } - - if (allArgs.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - String jvmargsStr = getOptionValue(CLIOptions.JAVA_OPTIONS); - bundleParams.put(CLIOptions.JAVA_OPTIONS.getId(), - Arguments.getArgumentList(jvmargsStr)); - } - } - - private String getOptionValue(CLIOptions option) { - if (option == null || allArgs == null) { - return null; - } - - String id = option.getId(); - - if (allArgs.containsKey(id)) { - return allArgs.get(id); - } - - return null; - } - - Map getLauncherMap() { - initLauncherMap(); - return bundleParams; - } - - static Map merge( - Map original, - Map additional, String... exclude) { - Map tmp = new HashMap<>(original); - List.of(exclude).forEach(tmp::remove); - - // remove LauncherData from map so it will be re-computed - tmp.remove(LAUNCHER_DATA.getID()); - // remove "application-name" so it will be re-computed - tmp.remove(APP_NAME.getID()); - - if (additional.containsKey(CLIOptions.MODULE.getId())) { - tmp.remove(CLIOptions.MAIN_JAR.getId()); - tmp.remove(CLIOptions.APPCLASS.getId()); - } else if (additional.containsKey(CLIOptions.MAIN_JAR.getId())) { - tmp.remove(CLIOptions.MODULE.getId()); - } - if (additional.containsKey(CLIOptions.ARGUMENTS.getId())) { - // if add launcher properties file contains "arguments", even with - // null value, disregard the "arguments" from command line - tmp.remove(CLIOptions.ARGUMENTS.getId()); - } - if (additional.containsKey(CLIOptions.JAVA_OPTIONS.getId())) { - // same thing for java-options - tmp.remove(CLIOptions.JAVA_OPTIONS.getId()); - } - tmp.putAll(additional); - return tmp; - } - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java deleted file mode 100644 index 192630a5656..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageBundler.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2015, 2025, 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.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.Map; -import java.util.Objects; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; - - -class AppImageBundler extends AbstractBundler { - - @Override - public final String getName() { - return I18N.getString("app.bundler.name"); - } - - @Override - public final String getID() { - return "app"; - } - - @Override - public final String getBundleType() { - return "IMAGE"; - } - - @Override - public final boolean validate(Map params) - throws ConfigException { - try { - Objects.requireNonNull(params); - - if (!params.containsKey(PREDEFINED_APP_IMAGE.getID()) - && !StandardBundlerParam.isRuntimeInstaller(params)) { - LAUNCHER_DATA.fetchFrom(params); - } - - if (paramsValidator != null) { - paramsValidator.validate(params); - } - } catch (RuntimeException re) { - if (re.getCause() instanceof ConfigException) { - throw (ConfigException) re.getCause(); - } else { - throw new ConfigException(re); - } - } - - return true; - } - - @Override - public final Path execute(Map params, - Path outputParentDir) throws PackagerException { - - final var predefinedAppImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - - try { - if (predefinedAppImage == null) { - Path rootDirectory = createRoot(params, outputParentDir); - appImageSupplier.prepareApplicationFiles(params, rootDirectory); - return rootDirectory; - } else { - appImageSupplier.prepareApplicationFiles(params, predefinedAppImage); - return predefinedAppImage; - } - } catch (PackagerException pe) { - throw pe; - } catch (RuntimeException|IOException ex) { - Log.verbose(ex); - throw new PackagerException(ex); - } - } - - @Override - public final boolean supported(boolean runtimeInstaller) { - return true; - } - - @Override - public final boolean isDefault() { - return false; - } - - @FunctionalInterface - static interface AppImageSupplier { - - void prepareApplicationFiles(Map params, - Path root) throws PackagerException, IOException; - } - - final AppImageBundler setAppImageSupplier(AppImageSupplier v) { - appImageSupplier = v; - return this; - } - - final AppImageBundler setParamsValidator(ParamsValidator v) { - paramsValidator = v; - return this; - } - - @FunctionalInterface - interface ParamsValidator { - void validate(Map params) throws ConfigException; - } - - private Path createRoot(Map params, - Path outputDirectory) throws PackagerException, IOException { - - IOUtils.writableOutputDir(outputDirectory); - - String imageName = APP_NAME.fetchFrom(params); - if (OperatingSystem.isMacOS()) { - imageName = imageName + ".app"; - } - - Log.verbose(MessageFormat.format( - I18N.getString("message.creating-app-bundle"), - imageName, outputDirectory.toAbsolutePath())); - - // Create directory structure - Path rootDirectory = outputDirectory.resolve(imageName); - if (Files.exists(rootDirectory)) { - throw new PackagerException("error.root-exists", - rootDirectory.toAbsolutePath().toString()); - } - - Files.createDirectories(rootDirectory); - - return rootDirectory; - } - - private ParamsValidator paramsValidator; - private AppImageSupplier appImageSupplier; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java index 8b8a22edc56..ec8c9a31173 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java @@ -25,148 +25,148 @@ package jdk.jpackage.internal; import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_AS_SERVICE; +import static jdk.jpackage.internal.cli.StandardAppImageFileOption.LAUNCHER_NAME; import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.cli.OptionValue; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.AppImageFileOptionScope; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.InvalidOptionValueException; +import jdk.jpackage.internal.cli.StandardAppImageFileOption.MissingMandatoryOptionException; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.util.XmlUtils; +import jdk.jpackage.internal.util.function.ExceptionBox; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.SAXException; -final class AppImageFile implements ExternalApplication { +final class AppImageFile { AppImageFile(Application app) { - this(new ApplicationData(app)); - } - - private AppImageFile(ApplicationData app) { - - appVersion = app.version(); - launcherName = app.mainLauncherName(); - mainClass = app.mainLauncherMainClassName(); - extra = app.extra; - creatorVersion = getVersion(); - creatorPlatform = getPlatform(); - addLauncherInfos = app.additionalLaunchers; - } - - @Override - public List getAddLaunchers() { - return addLauncherInfos; - } - - @Override - public String getAppVersion() { - return appVersion; - } - - @Override - public String getAppName() { - return launcherName; - } - - @Override - public String getLauncherName() { - return launcherName; - } - - @Override - public String getMainClass() { - return mainClass; - } - - @Override - public Map getExtra() { - return extra; + appVersion = Objects.requireNonNull(app.version()); + extra = Objects.requireNonNull(app.extraAppImageFileData()); + launcherInfos = app.launchers().stream().map(LauncherInfo::new).toList(); } /** - * Saves file with application image info in application image using values - * from this instance. + * Writes the values captured in this instance into the application image info + * file in the given application layout. + *

+ * It is an equivalent to calling + * {@link #save(ApplicationLayout, OperatingSystem)} method with + * {@code OperatingSystem.current()} for the second parameter. + * + * @param appLayout the application layout + * @throws IOException if an I/O error occurs when writing */ void save(ApplicationLayout appLayout) throws IOException { + save(appLayout, OperatingSystem.current()); + } + + /** + * Writes the values captured in this instance into the application image info + * file in the given application layout. + * + * @param appLayout the application layout + * @param os the target OS + * @throws IOException if an I/O error occurs when writing + */ + void save(ApplicationLayout appLayout, OperatingSystem os) throws IOException { XmlUtils.createXml(getPathInAppImage(appLayout), xml -> { xml.writeStartElement("jpackage-state"); - xml.writeAttribute("version", creatorVersion); - xml.writeAttribute("platform", creatorPlatform); + xml.writeAttribute("version", getVersion()); + xml.writeAttribute("platform", getPlatform(os)); xml.writeStartElement("app-version"); xml.writeCharacters(appVersion); xml.writeEndElement(); - xml.writeStartElement("main-launcher"); - xml.writeCharacters(launcherName); - xml.writeEndElement(); - - xml.writeStartElement("main-class"); - xml.writeCharacters(mainClass); - xml.writeEndElement(); - for (var extraKey : extra.keySet().stream().sorted().toList()) { xml.writeStartElement(extraKey); xml.writeCharacters(extra.get(extraKey)); xml.writeEndElement(); } - for (var li : addLauncherInfos) { - xml.writeStartElement("add-launcher"); - xml.writeAttribute("name", li.name()); - xml.writeAttribute("service", Boolean.toString(li.service())); - for (var extraKey : li.extra().keySet().stream().sorted().toList()) { - xml.writeStartElement(extraKey); - xml.writeCharacters(li.extra().get(extraKey)); - xml.writeEndElement(); - } - xml.writeEndElement(); + launcherInfos.getFirst().save(xml, "main-launcher"); + + for (var li : launcherInfos.subList(1, launcherInfos.size())) { + li.save(xml, "add-launcher"); } }); } /** - * Returns path to application image info file. - * @param appLayout - application layout + * Returns the path to the application image info file in the given application layout. + * + * @param appLayout the application layout */ static Path getPathInAppImage(ApplicationLayout appLayout) { return appLayout.appDirectory().resolve(FILENAME); } /** - * Loads application image info from application image. - * @param appImageDir - path at which to resolve the given application layout - * @param appLayout - application layout + * Loads application image info from the specified application layout. + *

+ * It is an equivalent to calling + * {@link #load(ApplicationLayout, OperatingSystem)} method with + * {@code OperatingSystem.current()} for the second parameter. + * + * @param appLayout the application layout */ - static AppImageFile load(Path appImageDir, ApplicationLayout appLayout) throws ConfigException, IOException { - var srcFilePath = getPathInAppImage(appLayout.resolveAt(appImageDir)); + static ExternalApplication load(ApplicationLayout appLayout) { + return load(appLayout, OperatingSystem.current()); + } + + /** + * Loads application image info from the specified application layout and OS. + * + * @param appLayout the application layout + * @param os the OS defining extra properties of the application and + * additional launchers + */ + static ExternalApplication load(ApplicationLayout appLayout, OperatingSystem os) { + Objects.requireNonNull(appLayout); + Objects.requireNonNull(os); + + final var appImageDir = appLayout.rootDirectory(); + final var appImageFilePath = getPathInAppImage(appLayout); + final var relativeAppImageFilePath = appImageDir.relativize(appImageFilePath); + try { - final Document doc = XmlUtils.initDocumentBuilder().parse(Files.newInputStream(srcFilePath)); + final Document doc = XmlUtils.initDocumentBuilder().parse(Files.newInputStream(appImageFilePath)); final XPath xPath = XPathFactory.newInstance().newXPath(); final var isPlatformValid = XmlUtils.queryNodes(doc, xPath, "/jpackage-state/@platform").findFirst().map( - Node::getNodeValue).map(getPlatform()::equals).orElse(false); + Node::getNodeValue).map(getPlatform(os)::equals).orElse(false); if (!isPlatformValid) { throw new InvalidAppImageFileException(); } @@ -177,102 +177,73 @@ final class AppImageFile implements ExternalApplication { throw new InvalidAppImageFileException(); } - final AppImageProperties props; + final var appOptions = AppImageFileOptionScope.APP.parse(appImageFilePath, AppImageProperties.main(doc, xPath), os); + + final var mainLauncherOptions = LauncherElement.MAIN.readAll(doc, xPath).stream().reduce((_, second) -> { + return second; + }).map(launcherProps -> { + return AppImageFileOptionScope.LAUNCHER.parse(appImageFilePath, launcherProps, os); + }).orElseThrow(InvalidAppImageFileException::new); + + final var addLauncherOptions = LauncherElement.ADDITIONAL.readAll(doc, xPath).stream().map(launcherProps -> { + return AppImageFileOptionScope.LAUNCHER.parse(appImageFilePath, launcherProps, os); + }).toList(); + try { - props = AppImageProperties.main(doc, xPath); - } catch (IllegalArgumentException ex) { + return ExternalApplication.create(Options.concat(appOptions, mainLauncherOptions), addLauncherOptions, os); + } catch (NoSuchElementException ex) { throw new InvalidAppImageFileException(ex); } - final var additionalLaunchers = AppImageProperties.launchers(doc, xPath).stream().map(launcherProps -> { - try { - return new LauncherInfo(launcherProps.get("name"), - launcherProps.find("service").map(Boolean::parseBoolean).orElse(false), launcherProps.getExtra()); - } catch (IllegalArgumentException ex) { - throw new InvalidAppImageFileException(ex); - } - }).toList(); - - return new AppImageFile(new ApplicationData(props.get("app-version"), props.get("main-launcher"), - props.get("main-class"), props.getExtra(), additionalLaunchers)); - } catch (XPathExpressionException ex) { // This should never happen as XPath expressions should be correct - throw new RuntimeException(ex); + throw ExceptionBox.rethrowUnchecked(ex); } catch (SAXException ex) { - // Exception reading input XML (probably malformed XML) - throw new IOException(ex); + // Malformed input XML + throw new JPackageException(I18N.format("error.malformed-app-image-file", relativeAppImageFilePath, appImageDir), ex); } catch (NoSuchFileException ex) { - throw I18N.buildConfigException("error.foreign-app-image", appImageDir).create(); - } catch (InvalidAppImageFileException ex) { + // Don't save the original exception as its error message is redundant. + throw new JPackageException(I18N.format("error.missing-app-image-file", relativeAppImageFilePath, appImageDir)); + } catch (InvalidAppImageFileException|InvalidOptionValueException|MissingMandatoryOptionException ex) { // Invalid input XML - throw I18N.buildConfigException("error.invalid-app-image", appImageDir, srcFilePath).create(); + throw new JPackageException(I18N.format("error.invalid-app-image-file", relativeAppImageFilePath, appImageDir), ex); + } catch (IOException ex) { + throw new JPackageException(I18N.format("error.reading-app-image-file", relativeAppImageFilePath, appImageDir), ex); } } - static boolean getBooleanExtraFieldValue(String fieldId, ExternalApplication appImageFile) { - Objects.requireNonNull(fieldId); - Objects.requireNonNull(appImageFile); - return Optional.ofNullable(appImageFile.getExtra().get(fieldId)).map(Boolean::parseBoolean).orElse(false); - } - static String getVersion() { return System.getProperty("java.version"); } - static String getPlatform() { - return PLATFORM_LABELS.get(OperatingSystem.current()); + static String getPlatform(OperatingSystem os) { + return Objects.requireNonNull(PLATFORM_LABELS.get(Objects.requireNonNull(os))); } + private static final class AppImageProperties { - private AppImageProperties(Map data, Set stdKeys) { - this.data = data; - this.stdKeys = stdKeys; + + static Map main(Document xml, XPath xPath) throws XPathExpressionException { + return queryProperties(xml.getDocumentElement(), xPath, MAIN_PROPERTIES_XPATH_QUERY); } - static AppImageProperties main(Document xml, XPath xPath) throws XPathExpressionException { - final var data = queryProperties(xml.getDocumentElement(), xPath, MAIN_PROPERTIES_XPATH_QUERY); - return new AppImageProperties(data, MAIN_ELEMENT_NAMES); - } + static Map launcher(Element launcherNode, XPath xPath) throws XPathExpressionException { + final var attrData = XmlUtils.toStream(launcherNode.getAttributes()) + .collect(toUnmodifiableMap(Node::getNodeName, Node::getNodeValue)); - static AppImageProperties launcher(Element addLauncherNode, XPath xPath) throws XPathExpressionException { - final var attrData = XmlUtils.toStream(addLauncherNode.getAttributes()) - .collect(toMap(Node::getNodeName, Node::getNodeValue)); - - final var extraData = queryProperties(addLauncherNode, xPath, LAUNCHER_PROPERTIES_XPATH_QUERY); + final var extraData = queryProperties(launcherNode, xPath, LAUNCHER_PROPERTIES_XPATH_QUERY); final Map data = new HashMap<>(attrData); data.putAll(extraData); - return new AppImageProperties(data, LAUNCHER_ATTR_NAMES); - } - - static List launchers(Document xml, XPath xPath) throws XPathExpressionException { - return XmlUtils.queryNodes(xml, xPath, "/jpackage-state/add-launcher") - .map(Element.class::cast).map(toFunction(e -> { - return launcher(e, xPath); - })).toList(); - } - - String get(String name) { - return find(name).orElseThrow(InvalidAppImageFileException::new); - } - - Optional find(String name) { - return Optional.ofNullable(data.get(name)); - } - - Map getExtra() { - Map extra = new HashMap<>(data); - stdKeys.forEach(extra::remove); - return extra; + return data; } private static Map queryProperties(Element e, XPath xPath, String xpathExpr) throws XPathExpressionException { return XmlUtils.queryNodes(e, xPath, xpathExpr) .map(Element.class::cast) - .collect(toMap(Node::getNodeName, selectedElement -> { + .collect(toUnmodifiableMap(Node::getNodeName, selectedElement -> { return selectedElement.getTextContent(); }, (a, b) -> b)); } @@ -285,13 +256,14 @@ final class AppImageFile implements ExternalApplication { return String.format("*[(%s) and not(*)]", otherElementNames); } - private final Map data; - private final Set stdKeys; - - private static final Set LAUNCHER_ATTR_NAMES = Set.of("name", "service"); + private static final Set LAUNCHER_ATTR_NAMES = Stream.of( + LAUNCHER_NAME + ).map(OptionValue::getName).collect(toUnmodifiableSet()); private static final String LAUNCHER_PROPERTIES_XPATH_QUERY = xpathQueryForExtraProperties(LAUNCHER_ATTR_NAMES); - private static final Set MAIN_ELEMENT_NAMES = Set.of("app-version", "main-launcher", "main-class"); + private static final Set MAIN_ELEMENT_NAMES = Stream.of( + APP_VERSION + ).map(OptionValue::getName).collect(toUnmodifiableSet()); private static final String MAIN_PROPERTIES_XPATH_QUERY; static { @@ -301,40 +273,64 @@ final class AppImageFile implements ExternalApplication { MAIN_PROPERTIES_XPATH_QUERY = String.format("%s|/jpackage-state/%s", nonEmptyMainElements, xpathQueryForExtraProperties(Stream.concat(MAIN_ELEMENT_NAMES.stream(), - Stream.of("add-launcher")).collect(toSet()))); + Stream.of("main-launcher", "add-launcher")).collect(toUnmodifiableSet()))); } } - private record ApplicationData(String version, String mainLauncherName, String mainLauncherMainClassName, - Map extra, List additionalLaunchers) { - ApplicationData { - Objects.requireNonNull(version); - Objects.requireNonNull(mainLauncherName); - Objects.requireNonNull(mainLauncherMainClassName); - Objects.requireNonNull(extra); - Objects.requireNonNull(additionalLaunchers); + private enum LauncherElement { + MAIN("main-launcher"), + ADDITIONAL("add-launcher"); - for (final var property : List.of(version, mainLauncherName, mainLauncherMainClassName)) { - if (property.isBlank()) { - throw new IllegalArgumentException(); - } + LauncherElement(String elementName) { + this.elementName = Objects.requireNonNull(elementName); + } + + List> readAll(Document xml, XPath xPath) throws XPathExpressionException { + return XmlUtils.queryNodes(xml, xPath, "/jpackage-state/" + elementName + "[@name]") + .map(Element.class::cast).map(toFunction(e -> { + return AppImageProperties.launcher(e, xPath); + })).toList(); + } + + private final String elementName; + } + + private record LauncherInfo(String name, Map properties) { + LauncherInfo { + Objects.requireNonNull(name); + Objects.requireNonNull(properties); + } + + LauncherInfo(Launcher launcher) { + this(launcher.name(), properties(launcher)); + } + + void save(XMLStreamWriter xml, String elementName) throws IOException, XMLStreamException { + xml.writeStartElement(elementName); + xml.writeAttribute("name", name()); + for (var key : properties().keySet().stream().sorted().toList()) { + xml.writeStartElement(key); + xml.writeCharacters(properties().get(key)); + xml.writeEndElement(); } + xml.writeEndElement(); } - ApplicationData(Application app) { - this(app, app.mainLauncher().orElseThrow()); - } + private static Map properties(Launcher launcher) { + List> standardProps = new ArrayList<>(); + if (launcher.isService()) { + standardProps.add(Map.entry(LAUNCHER_AS_SERVICE.getName(), Boolean.TRUE.toString())); + } - private ApplicationData(Application app, Launcher mainLauncher) { - this(app.version(), mainLauncher.name(), mainLauncher.startupInfo().orElseThrow().qualifiedClassName(), - app.extraAppImageFileData(), app.additionalLaunchers().stream().map(launcher -> { - return new LauncherInfo(launcher.name(), launcher.isService(), - launcher.extraAppImageFileData()); - }).toList()); + return Stream.concat( + standardProps.stream(), + launcher.extraAppImageFileData().entrySet().stream() + ).collect(toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); } } + private static class InvalidAppImageFileException extends RuntimeException { InvalidAppImageFileException() { @@ -347,13 +343,10 @@ final class AppImageFile implements ExternalApplication { private static final long serialVersionUID = 1L; } + private final String appVersion; - private final String launcherName; - private final String mainClass; private final Map extra; - private final List addLauncherInfos; - private final String creatorVersion; - private final String creatorPlatform; + private final List launcherInfos; private static final String FILENAME = ".jpackage.xml"; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java index 76a5fc1a50c..e9324f65671 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationBuilder.java @@ -35,12 +35,13 @@ import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLaunchers; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExternalApplication; -import jdk.jpackage.internal.model.ExternalApplication.LauncherInfo; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.Launcher; import jdk.jpackage.internal.model.LauncherIcon; import jdk.jpackage.internal.model.LauncherStartupInfo; @@ -55,11 +56,9 @@ final class ApplicationBuilder { final var launchersAsList = Optional.ofNullable(launchers).map( ApplicationLaunchers::asList).orElseGet(List::of); - final var launcherCount = launchersAsList.size(); - - if (launcherCount != launchersAsList.stream().map(Launcher::name).distinct().count()) { - throw buildConfigException("ERR_NoUniqueName").create(); - } + launchersAsList.stream().collect(Collectors.toMap(Launcher::name, x -> x, (a, b) -> { + throw new JPackageException(I18N.format("error.launcher-duplicate-name", a.name())); + })); final String effectiveName; if (name != null) { @@ -88,25 +87,8 @@ final class ApplicationBuilder { return this; } - ApplicationBuilder initFromExternalApplication(ExternalApplication app, - Function mapper) { - - externalApp = Objects.requireNonNull(app); - - if (version == null) { - version = app.getAppVersion(); - } - if (name == null) { - name = app.getAppName(); - } - runtimeBuilder = null; - - var mainLauncherInfo = new LauncherInfo(app.getLauncherName(), false, Map.of()); - - launchers = new ApplicationLaunchers( - mapper.apply(mainLauncherInfo), - app.getAddLaunchers().stream().map(mapper).toList()); - + ApplicationBuilder externalApplication(ExternalApplication v) { + externalApp = v; return this; } @@ -123,15 +105,6 @@ final class ApplicationBuilder { return Optional.ofNullable(externalApp); } - Optional mainLauncherClassName() { - return launchers() - .map(ApplicationLaunchers::mainLauncher) - .flatMap(Launcher::startupInfo) - .map(LauncherStartupInfo::qualifiedClassName).or(() -> { - return externalApplication().map(ExternalApplication::getMainClass); - }); - } - ApplicationBuilder appImageLayout(AppImageLayout v) { appImageLayout = v; return this; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java deleted file mode 100644 index 0ea72ae160c..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayoutUtils.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2025, 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.jpackage.internal; - -import java.nio.file.Path; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ApplicationLayout; - - -final class ApplicationLayoutUtils { - - public static final ApplicationLayout PLATFORM_APPLICATION_LAYOUT; - - private static final ApplicationLayout WIN_APPLICATION_LAYOUT = ApplicationLayout.build() - .setAll("") - .appDirectory("app") - .runtimeDirectory("runtime") - .appModsDirectory(Path.of("app", "mods")) - .create(); - - private static final ApplicationLayout MAC_APPLICATION_LAYOUT = ApplicationLayout.build() - .launchersDirectory("Contents/MacOS") - .appDirectory("Contents/app") - .runtimeDirectory("Contents/runtime/Contents/Home") - .desktopIntegrationDirectory("Contents/Resources") - .appModsDirectory("Contents/app/mods") - .contentDirectory("Contents") - .create(); - - private static final ApplicationLayout LINUX_APPLICATION_LAYOUT = ApplicationLayout.build() - .launchersDirectory("bin") - .appDirectory("lib/app") - .runtimeDirectory("lib/runtime") - .desktopIntegrationDirectory("lib") - .appModsDirectory("lib/app/mods") - .contentDirectory("lib") - .create(); - - static { - switch (OperatingSystem.current()) { - case WINDOWS -> PLATFORM_APPLICATION_LAYOUT = WIN_APPLICATION_LAYOUT; - case MACOS -> PLATFORM_APPLICATION_LAYOUT = MAC_APPLICATION_LAYOUT; - case LINUX -> PLATFORM_APPLICATION_LAYOUT = LINUX_APPLICATION_LAYOUT; - default -> { - throw new UnsupportedOperationException(); - } - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java deleted file mode 100644 index f0323bbd841..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java +++ /dev/null @@ -1,867 +0,0 @@ -/* - * Copyright (c) 2018, 2025, 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.jpackage.internal; - -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; -import java.io.IOException; -import java.io.Reader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.ResourceBundle; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Arguments - * - * This class encapsulates and processes the command line arguments, - * in effect, implementing all the work of jpackage tool. - * - * The primary entry point, processArguments(): - * Processes and validates command line arguments, constructing DeployParams. - * Validates the DeployParams, and generate the BundleParams. - * Generates List of Bundlers from BundleParams valid for this platform. - * Executes each Bundler in the list. - */ -public class Arguments { - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.MainResources"); - - private static final String FA_EXTENSIONS = "extension"; - private static final String FA_CONTENT_TYPE = "mime-type"; - private static final String FA_DESCRIPTION = "description"; - private static final String FA_ICON = "icon"; - - // Mac specific file association keys - // String - public static final String MAC_CFBUNDLETYPEROLE = "mac.CFBundleTypeRole"; - public static final String MAC_LSHANDLERRANK = "mac.LSHandlerRank"; - public static final String MAC_NSSTORETYPEKEY = "mac.NSPersistentStoreTypeKey"; - public static final String MAC_NSDOCUMENTCLASS = "mac.NSDocumentClass"; - // Boolean - public static final String MAC_LSTYPEISPACKAGE = "mac.LSTypeIsPackage"; - public static final String MAC_LSDOCINPLACE = "mac.LSSupportsOpeningDocumentsInPlace"; - public static final String MAC_UIDOCBROWSER = "mac.UISupportsDocumentBrowser"; - // Array of strings - public static final String MAC_NSEXPORTABLETYPES = "mac.NSExportableTypes"; - public static final String MAC_UTTYPECONFORMSTO = "mac.UTTypeConformsTo"; - - // regexp for parsing args (for example, for additional launchers) - private static Pattern pattern = Pattern.compile( - "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++"); - - private DeployParams deployParams = null; - - private int pos = 0; - private List argList = null; - - private List allOptions = null; - - private boolean hasMainJar = false; - private boolean hasMainClass = false; - private boolean hasMainModule = false; - public boolean userProvidedBuildRoot = false; - - private String buildRoot = null; - private String mainJarPath = null; - - private boolean runtimeInstaller = false; - - private List addLaunchers = null; - - private static final Map argIds = new HashMap<>(); - private static final Map argShortIds = new HashMap<>(); - - static { - // init maps for parsing arguments - (EnumSet.allOf(CLIOptions.class)).forEach(option -> { - argIds.put(option.getIdWithPrefix(), option); - if (option.getShortIdWithPrefix() != null) { - argShortIds.put(option.getShortIdWithPrefix(), option); - } - }); - } - - private static final InheritableThreadLocal instance = - new InheritableThreadLocal(); - - public Arguments(String[] args) { - instance.set(this); - - argList = new ArrayList(args.length); - for (String arg : args) { - argList.add(arg); - } - - pos = 0; - - deployParams = new DeployParams(); - - allOptions = new ArrayList<>(); - - addLaunchers = new ArrayList<>(); - } - - // CLIOptions is public for DeployParamsTest - public enum CLIOptions { - PACKAGE_TYPE("type", "t", OptionCategories.PROPERTY, () -> { - var type = popArg(); - context().deployParams.setTargetFormat(type); - setOptionValue("type", type); - }), - - INPUT ("input", "i", OptionCategories.PROPERTY, () -> { - setOptionValue("input", popArg()); - }), - - OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> { - var path = Path.of(popArg()); - setOptionValue("dest", path); - }), - - DESCRIPTION ("description", OptionCategories.PROPERTY), - - VENDOR ("vendor", OptionCategories.PROPERTY), - - APPCLASS ("main-class", OptionCategories.PROPERTY, () -> { - context().hasMainClass = true; - setOptionValue("main-class", popArg()); - }), - - NAME ("name", "n", OptionCategories.PROPERTY), - - VERBOSE ("verbose", OptionCategories.PROPERTY, () -> { - setOptionValue("verbose", true); - Log.setVerbose(); - }), - - RESOURCE_DIR("resource-dir", - OptionCategories.PROPERTY, () -> { - String resourceDir = popArg(); - setOptionValue("resource-dir", resourceDir); - }), - - DMG_CONTENT ("mac-dmg-content", OptionCategories.PROPERTY, () -> { - List content = getArgumentList(popArg()); - content.forEach(a -> setOptionValue("mac-dmg-content", a)); - }), - - ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> { - List arguments = getArgumentList(popArg()); - setOptionValue("arguments", arguments); - }), - - JLINK_OPTIONS ("jlink-options", OptionCategories.PROPERTY, () -> { - List options = getArgumentList(popArg()); - setOptionValue("jlink-options", options); - }), - - ICON ("icon", OptionCategories.PROPERTY), - - COPYRIGHT ("copyright", OptionCategories.PROPERTY), - - LICENSE_FILE ("license-file", OptionCategories.PROPERTY), - - VERSION ("app-version", OptionCategories.PROPERTY), - - RELEASE ("linux-app-release", OptionCategories.PROPERTY), - - ABOUT_URL ("about-url", OptionCategories.PROPERTY), - - JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> { - List args = getArgumentList(popArg()); - args.forEach(a -> setOptionValue("java-options", a)); - }), - - APP_CONTENT ("app-content", OptionCategories.PROPERTY, () -> { - getArgumentList(popArg()).forEach( - a -> setOptionValue("app-content", a)); - }), - - FILE_ASSOCIATIONS ("file-associations", - OptionCategories.PROPERTY, () -> { - Map args = new HashMap<>(); - - // load .properties file - Map initialMap = getPropertiesFromFile(popArg()); - - putUnlessNull(args, StandardBundlerParam.FA_EXTENSIONS.getID(), - initialMap.get(FA_EXTENSIONS)); - - putUnlessNull(args, StandardBundlerParam.FA_CONTENT_TYPE.getID(), - initialMap.get(FA_CONTENT_TYPE)); - - putUnlessNull(args, StandardBundlerParam.FA_DESCRIPTION.getID(), - initialMap.get(FA_DESCRIPTION)); - - putUnlessNull(args, StandardBundlerParam.FA_ICON.getID(), - initialMap.get(FA_ICON)); - - // Mac extended file association arguments - putUnlessNull(args, MAC_CFBUNDLETYPEROLE, - initialMap.get(MAC_CFBUNDLETYPEROLE)); - - putUnlessNull(args, MAC_LSHANDLERRANK, - initialMap.get(MAC_LSHANDLERRANK)); - - putUnlessNull(args, MAC_NSSTORETYPEKEY, - initialMap.get(MAC_NSSTORETYPEKEY)); - - putUnlessNull(args, MAC_NSDOCUMENTCLASS, - initialMap.get(MAC_NSDOCUMENTCLASS)); - - putUnlessNull(args, MAC_LSTYPEISPACKAGE, - initialMap.get(MAC_LSTYPEISPACKAGE)); - - putUnlessNull(args, MAC_LSDOCINPLACE, - initialMap.get(MAC_LSDOCINPLACE)); - - putUnlessNull(args, MAC_UIDOCBROWSER, - initialMap.get(MAC_UIDOCBROWSER)); - - putUnlessNull(args, MAC_NSEXPORTABLETYPES, - initialMap.get(MAC_NSEXPORTABLETYPES)); - - putUnlessNull(args, MAC_UTTYPECONFORMSTO, - initialMap.get(MAC_UTTYPECONFORMSTO)); - - ArrayList> associationList = - new ArrayList>(); - - associationList.add(args); - - // check that we really add _another_ value to the list - setOptionValue("file-associations", associationList); - - }), - - ADD_LAUNCHER ("add-launcher", - OptionCategories.PROPERTY, () -> { - String spec = popArg(); - String name = null; - String filename = spec; - if (spec.contains("=")) { - String[] values = spec.split("=", 2); - name = values[0]; - filename = values[1]; - } - context().addLaunchers.add( - new AddLauncherArguments(name, filename)); - }), - - TEMP_ROOT ("temp", OptionCategories.PROPERTY, () -> { - context().buildRoot = popArg(); - context().userProvidedBuildRoot = true; - setOptionValue("temp", context().buildRoot); - }), - - INSTALL_DIR ("install-dir", OptionCategories.PROPERTY), - - PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY), - - PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY), - - MAIN_JAR ("main-jar", OptionCategories.PROPERTY, () -> { - context().mainJarPath = popArg(); - context().hasMainJar = true; - setOptionValue("main-jar", context().mainJarPath); - }), - - MODULE ("module", "m", OptionCategories.MODULAR, () -> { - context().hasMainModule = true; - setOptionValue("module", popArg()); - }), - - ADD_MODULES ("add-modules", OptionCategories.MODULAR), - - MODULE_PATH ("module-path", "p", OptionCategories.MODULAR), - - LAUNCHER_AS_SERVICE ("launcher-as-service", OptionCategories.PROPERTY, () -> { - setOptionValue("launcher-as-service", true); - }), - - MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> { - setOptionValue("mac-sign", true); - }), - - MAC_APP_STORE ("mac-app-store", OptionCategories.PLATFORM_MAC, () -> { - setOptionValue("mac-app-store", true); - }), - - MAC_CATEGORY ("mac-app-category", OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_NAME ("mac-package-name", OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_IDENTIFIER("mac-package-identifier", - OptionCategories.PLATFORM_MAC), - - MAC_BUNDLE_SIGNING_PREFIX ("mac-package-signing-prefix", - OptionCategories.PLATFORM_MAC), - - MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name", - OptionCategories.PLATFORM_MAC), - - MAC_APP_IMAGE_SIGN_IDENTITY ("mac-app-image-sign-identity", - OptionCategories.PLATFORM_MAC), - - MAC_INSTALLER_SIGN_IDENTITY ("mac-installer-sign-identity", - OptionCategories.PLATFORM_MAC), - - MAC_SIGNING_KEYCHAIN ("mac-signing-keychain", - OptionCategories.PLATFORM_MAC), - - MAC_ENTITLEMENTS ("mac-entitlements", OptionCategories.PLATFORM_MAC), - - WIN_HELP_URL ("win-help-url", OptionCategories.PLATFORM_WIN), - - WIN_UPDATE_URL ("win-update-url", OptionCategories.PLATFORM_WIN), - - WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, - createArgumentWithOptionalValueAction("win-menu")), - - WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN), - - WIN_SHORTCUT_HINT ("win-shortcut", OptionCategories.PLATFORM_WIN, - createArgumentWithOptionalValueAction("win-shortcut")), - - WIN_SHORTCUT_PROMPT ("win-shortcut-prompt", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-shortcut-prompt", true); - }), - - WIN_PER_USER_INSTALLATION ("win-per-user-install", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-per-user-install", false); - }), - - WIN_DIR_CHOOSER ("win-dir-chooser", - OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-dir-chooser", true); - }), - - WIN_UPGRADE_UUID ("win-upgrade-uuid", - OptionCategories.PLATFORM_WIN), - - WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> { - setOptionValue("win-console", true); - }), - - LINUX_BUNDLE_NAME ("linux-package-name", - OptionCategories.PLATFORM_LINUX), - - LINUX_DEB_MAINTAINER ("linux-deb-maintainer", - OptionCategories.PLATFORM_LINUX), - - LINUX_CATEGORY ("linux-app-category", - OptionCategories.PLATFORM_LINUX), - - LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type", - OptionCategories.PLATFORM_LINUX), - - LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps", - OptionCategories.PLATFORM_LINUX), - - LINUX_SHORTCUT_HINT ("linux-shortcut", OptionCategories.PLATFORM_LINUX, - createArgumentWithOptionalValueAction("linux-shortcut")), - - LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX); - - private final String id; - private final String shortId; - private final OptionCategories category; - private final Runnable action; - private static Arguments argContext; - - private CLIOptions(String id, OptionCategories category) { - this(id, null, category, null); - } - - private CLIOptions(String id, String shortId, - OptionCategories category) { - this(id, shortId, category, null); - } - - private CLIOptions(String id, - OptionCategories category, Runnable action) { - this(id, null, category, action); - } - - private CLIOptions(String id, String shortId, - OptionCategories category, Runnable action) { - this.id = id; - this.shortId = shortId; - this.action = action; - this.category = category; - } - - public static Arguments context() { - return instance.get(); - } - - public String getId() { - return this.id; - } - - String getIdWithPrefix() { - return "--" + this.id; - } - - String getShortIdWithPrefix() { - return this.shortId == null ? null : "-" + this.shortId; - } - - void execute() { - if (action != null) { - action.run(); - } else { - defaultAction(); - } - } - - private void defaultAction() { - context().deployParams.addBundleArgument(id, popArg()); - } - - private static void setOptionValue(String option, Object value) { - context().deployParams.addBundleArgument(option, value); - } - - private static String popArg() { - nextArg(); - return (context().pos >= context().argList.size()) ? - "" : context().argList.get(context().pos); - } - - private static String getArg() { - return (context().pos >= context().argList.size()) ? - "" : context().argList.get(context().pos); - } - - private static void nextArg() { - context().pos++; - } - - private static void prevArg() { - Objects.checkIndex(context().pos, context().argList.size()); - context().pos--; - } - - private static boolean hasNextArg() { - return context().pos < context().argList.size(); - } - - private static Runnable createArgumentWithOptionalValueAction(String option) { - Objects.requireNonNull(option); - return () -> { - nextArg(); - if (hasNextArg()) { - var value = getArg(); - if (value.startsWith("-")) { - prevArg(); - setOptionValue(option, true); - } else { - setOptionValue(option, value); - } - } else { - setOptionValue(option, true); - } - }; - } - } - - enum OptionCategories { - MODULAR, - PROPERTY, - PLATFORM_MAC, - PLATFORM_WIN, - PLATFORM_LINUX; - } - - public boolean processArguments() { - try { - // parse cmd line - String arg; - CLIOptions option; - for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) { - arg = CLIOptions.getArg(); - if ((option = toCLIOption(arg)) != null) { - // found a CLI option - allOptions.add(option); - option.execute(); - } else { - throw new PackagerException("ERR_InvalidOption", arg); - } - } - - // display error for arguments that are not supported - // for current configuration. - - validateArguments(); - - List> launchersAsMap = - new ArrayList<>(); - - for (AddLauncherArguments sl : addLaunchers) { - launchersAsMap.add(sl.getLauncherMap()); - } - - deployParams.addBundleArgument( - StandardBundlerParam.ADD_LAUNCHERS.getID(), - launchersAsMap); - - // at this point deployParams should be already configured - - deployParams.validate(); - - BundleParams bp = deployParams.getBundleParams(); - - // validate name(s) - ArrayList usedNames = new ArrayList(); - usedNames.add(bp.getName()); // add main app name - - for (AddLauncherArguments sl : addLaunchers) { - Map slMap = sl.getLauncherMap(); - String slName = - (String) slMap.get(Arguments.CLIOptions.NAME.getId()); - if (slName == null) { - throw new PackagerException("ERR_NoAddLauncherName"); - } - // same rules apply to additional launcher names as app name - DeployParams.validateName(slName, false); - for (String usedName : usedNames) { - if (slName.equals(usedName)) { - throw new PackagerException("ERR_NoUniqueName"); - } - } - usedNames.add(slName); - } - - generateBundle(bp.getBundleParamsAsMap()); - return true; - } catch (Exception e) { - Log.verbose(e); - String msg1 = e.getMessage(); - Log.fatalError(msg1); - if (e.getCause() != null && e.getCause() != e) { - String msg2 = e.getCause().getMessage(); - if (msg2 != null && !msg1.contains(msg2)) { - Log.fatalError(msg2); - } - } - return false; - } - } - - private void validateArguments() throws PackagerException { - String type = deployParams.getTargetFormat(); - String ptype = (type != null) ? type : "default"; - boolean imageOnly = deployParams.isTargetAppImage(); - boolean hasAppImage = allOptions.contains( - CLIOptions.PREDEFINED_APP_IMAGE); - boolean hasRuntime = allOptions.contains( - CLIOptions.PREDEFINED_RUNTIME_IMAGE); - boolean installerOnly = !imageOnly && hasAppImage; - boolean isMac = OperatingSystem.isMacOS(); - runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage && - !hasMainModule && !hasMainJar; - - for (CLIOptions option : allOptions) { - if (!ValidOptions.checkIfSupported(option)) { - // includes option valid only on different platform - throw new PackagerException("ERR_UnsupportedOption", - option.getIdWithPrefix()); - } - if ((imageOnly && !isMac) || (imageOnly && !hasAppImage && isMac)) { - if (!ValidOptions.checkIfImageSupported(option)) { - throw new PackagerException("ERR_InvalidTypeOption", - option.getIdWithPrefix(), type); - } - } else if (imageOnly && hasAppImage && isMac) { // Signing app image - if (!ValidOptions.checkIfSigningSupported(option)) { - throw new PackagerException( - "ERR_InvalidOptionWithAppImageSigning", - option.getIdWithPrefix()); - } - } else if (installerOnly || runtimeInstaller) { - if (!ValidOptions.checkIfInstallerSupported(option)) { - if (runtimeInstaller) { - throw new PackagerException("ERR_NoInstallerEntryPoint", - option.getIdWithPrefix()); - } else { - throw new PackagerException("ERR_InvalidTypeOption", - option.getIdWithPrefix(), ptype); - } - } - } - } - if (hasRuntime) { - if (hasAppImage) { - // note --runtime-image is only for image or runtime installer. - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.PREDEFINED_APP_IMAGE.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.ADD_MODULES)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.ADD_MODULES.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.JLINK_OPTIONS)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(), - CLIOptions.JLINK_OPTIONS.getIdWithPrefix()); - } - } - if (allOptions.contains(CLIOptions.MAC_SIGNING_KEY_NAME) && - allOptions.contains(CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.MAC_SIGNING_KEY_NAME.getIdWithPrefix(), - CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getIdWithPrefix()); - } - if (allOptions.contains(CLIOptions.MAC_SIGNING_KEY_NAME) && - allOptions.contains(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY)) { - throw new PackagerException("ERR_MutuallyExclusiveOptions", - CLIOptions.MAC_SIGNING_KEY_NAME.getIdWithPrefix(), - CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getIdWithPrefix()); - } - if (isMac && (imageOnly || "dmg".equals(type)) && - allOptions.contains(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY)) { - throw new PackagerException("ERR_InvalidTypeOption", - CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getIdWithPrefix(), - type); - } - if (allOptions.contains(CLIOptions.DMG_CONTENT) - && !("dmg".equals(type))) { - throw new PackagerException("ERR_InvalidTypeOption", - CLIOptions.DMG_CONTENT.getIdWithPrefix(), ptype); - } - if (hasMainJar && hasMainModule) { - throw new PackagerException("ERR_BothMainJarAndModule"); - } - if (imageOnly && !hasAppImage && !hasMainJar && !hasMainModule) { - throw new PackagerException("ERR_NoEntryPoint"); - } - } - - private jdk.jpackage.internal.Bundler getPlatformBundler() { - boolean appImage = deployParams.isTargetAppImage(); - String type = deployParams.getTargetFormat(); - String bundleType = (appImage ? "IMAGE" : "INSTALLER"); - - for (jdk.jpackage.internal.Bundler bundler : - Bundlers.createBundlersInstance().getBundlers(bundleType)) { - if (type == null) { - if (bundler.isDefault()) { - return bundler; - } - } else { - if (appImage || type.equalsIgnoreCase(bundler.getID())) { - return bundler; - } - } - } - return null; - } - - private void generateBundle(Map params) - throws PackagerException { - - // the temp dir needs to be fetched from the params early, - // to prevent each copy of the params (such as may be used for - // additional launchers) from generating a separate temp dir when - // the default is used (the default is a new temp directory) - // The bundler.cleanup() below would not otherwise be able to - // clean these extra (and unneeded) temp directories. - StandardBundlerParam.TEMP_ROOT.fetchFrom(params); - - // determine what bundler to run - jdk.jpackage.internal.Bundler bundler = getPlatformBundler(); - - if (bundler == null || !bundler.supported(runtimeInstaller)) { - String type = Optional.ofNullable(bundler).map(Bundler::getID).orElseGet( - () -> deployParams.getTargetFormat()); - throw new PackagerException("ERR_InvalidInstallerType", type); - } - - Map localParams = new HashMap<>(params); - try { - Path result = executeBundler(bundler, params, localParams); - if (result == null) { - throw new PackagerException("MSG_BundlerFailed", - bundler.getID(), bundler.getName()); - } - Log.verbose(MessageFormat.format( - I18N.getString("message.bundle-created"), - bundler.getName())); - } catch (ConfigException e) { - Log.verbose(e); - if (e.getAdvice() != null) { - throw new PackagerException(e, "MSG_BundlerConfigException", - bundler.getName(), e.getMessage(), e.getAdvice()); - } else { - throw new PackagerException(e, - "MSG_BundlerConfigExceptionNoAdvice", - bundler.getName(), e.getMessage()); - } - } catch (RuntimeException re) { - Log.verbose(re); - throw new PackagerException(re, "MSG_BundlerRuntimeException", - bundler.getName(), re.toString()); - } finally { - if (userProvidedBuildRoot) { - Log.verbose(MessageFormat.format( - I18N.getString("message.debug-working-directory"), - (Path.of(buildRoot)).toAbsolutePath().toString())); - } else { - // always clean up the temporary directory created - // when --temp option not used. - bundler.cleanup(localParams); - } - } - } - - private static Path executeBundler(Bundler bundler, Map params, - Map localParams) throws ConfigException, PackagerException { - try { - bundler.validate(localParams); - return bundler.execute(localParams, StandardBundlerParam.OUTPUT_DIR.fetchFrom(params)); - } catch (ConfigException|PackagerException ex) { - throw ex; - } catch (RuntimeException ex) { - if (ex.getCause() instanceof ConfigException cfgEx) { - throw cfgEx; - } else if (ex.getCause() instanceof PackagerException pkgEx) { - throw pkgEx; - } else { - throw ex; - } - } - } - - static CLIOptions toCLIOption(String arg) { - CLIOptions option; - if ((option = argIds.get(arg)) == null) { - option = argShortIds.get(arg); - } - return option; - } - - static Map getPropertiesFromFile(String filename) { - Map map = new HashMap<>(); - // load properties file - Properties properties = new Properties(); - try (Reader reader = Files.newBufferedReader(Path.of(filename))) { - properties.load(reader); - } catch (IOException e) { - Log.error("Exception: " + e.getMessage()); - } - - for (final String name: properties.stringPropertyNames()) { - map.put(name, properties.getProperty(name)); - } - - return map; - } - - static List getArgumentList(String inputString) { - List list = new ArrayList<>(); - if (inputString == null || inputString.isEmpty()) { - return list; - } - - // The "pattern" regexp attempts to abide to the rule that - // strings are delimited by whitespace unless surrounded by - // quotes, then it is anything (including spaces) in the quotes. - Matcher m = pattern.matcher(inputString); - while (m.find()) { - String s = inputString.substring(m.start(), m.end()).trim(); - // Ensure we do not have an empty string. trim() will take care of - // whitespace only strings. The regex preserves quotes and escaped - // chars so we need to clean them before adding to the List - if (!s.isEmpty()) { - list.add(unquoteIfNeeded(s)); - } - } - return list; - } - - static void putUnlessNull(Map params, - String param, Object value) { - if (value != null) { - params.put(param, value); - } - } - - private static String unquoteIfNeeded(String in) { - if (in == null) { - return null; - } - - if (in.isEmpty()) { - return ""; - } - - // Use code points to preserve non-ASCII chars - StringBuilder sb = new StringBuilder(); - int codeLen = in.codePointCount(0, in.length()); - int quoteChar = -1; - for (int i = 0; i < codeLen; i++) { - int code = in.codePointAt(i); - if (code == '"' || code == '\'') { - // If quote is escaped make sure to copy it - if (i > 0 && in.codePointAt(i - 1) == '\\') { - sb.deleteCharAt(sb.length() - 1); - sb.appendCodePoint(code); - continue; - } - if (quoteChar != -1) { - if (code == quoteChar) { - // close quote, skip char - quoteChar = -1; - } else { - sb.appendCodePoint(code); - } - } else { - // opening quote, skip char - quoteChar = code; - } - } else { - sb.appendCodePoint(code); - } - } - return sb.toString(); - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java deleted file mode 100644 index 7f444fe7337..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2014, 2019, 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.jpackage.internal; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.ServiceLoader; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * BasicBundlers - * - * A basic bundlers collection that loads the default bundlers. - * Loads the common bundlers. - *

    - *
  • Windows file image
  • - *
  • Mac .app
  • - *
  • Linux file image
  • - *
  • Windows MSI
  • - *
  • Windows EXE
  • - *
  • Mac DMG
  • - *
  • Mac PKG
  • - *
  • Linux DEB
  • - *
  • Linux RPM
  • - * - *
- */ -public class BasicBundlers implements Bundlers { - - boolean defaultsLoaded = false; - - private final Collection bundlers = new CopyOnWriteArrayList<>(); - - @Override - public Collection getBundlers() { - return Collections.unmodifiableCollection(bundlers); - } - - @Override - public Collection getBundlers(String type) { - if (type == null) return Collections.emptySet(); - switch (type) { - case "NONE": - return Collections.emptySet(); - case "ALL": - return getBundlers(); - default: - return Arrays.asList(getBundlers().stream() - .filter(b -> type.equalsIgnoreCase(b.getBundleType())) - .toArray(Bundler[]::new)); - } - } - - // Loads bundlers from the META-INF/services direct - @Override - public void loadBundlersFromServices(ClassLoader cl) { - ServiceLoader loader = ServiceLoader.load(Bundler.class, cl); - for (Bundler aLoader : loader) { - bundlers.add(aLoader); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java new file mode 100644 index 00000000000..8faabe97a3d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromOptions.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR; +import static jdk.jpackage.internal.cli.StandardOption.TEMP_ROOT; +import static jdk.jpackage.internal.cli.StandardOption.VERBOSE; + +import java.nio.file.Path; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.RuntimeLayout; + +final class BuildEnvFromOptions { + + BuildEnvFromOptions() { + predefinedRuntimeImageLayout(RuntimeLayout.DEFAULT); + } + + BuildEnvFromOptions predefinedAppImageLayout(Function v) { + predefinedAppImageLayout = v; + return this; + } + + BuildEnvFromOptions predefinedAppImageLayout(ApplicationLayout v) { + return predefinedAppImageLayout(path -> v.resolveAt(path)); + } + + BuildEnvFromOptions predefinedRuntimeImageLayout(Function v) { + predefinedRuntimeImageLayout = v; + return this; + } + + BuildEnvFromOptions predefinedRuntimeImageLayout(RuntimeLayout v) { + return predefinedRuntimeImageLayout(path -> v.resolveAt(path)); + } + + BuildEnv create(Options options, Application app) { + return create(options, app, Optional.empty()); + } + + BuildEnv create(Options options, Package pkg) { + return create(options, pkg.app(), Optional.of(pkg)); + } + + private BuildEnv create(Options options, Application app, Optional pkg) { + Objects.requireNonNull(options); + Objects.requireNonNull(app); + Objects.requireNonNull(pkg); + Objects.requireNonNull(predefinedAppImageLayout); + Objects.requireNonNull(predefinedRuntimeImageLayout); + + final var builder = new BuildEnvBuilder(TEMP_ROOT.getFrom(options)); + + RESOURCE_DIR.ifPresentIn(options, builder::resourceDir); + VERBOSE.ifPresentIn(options, builder::verbose); + + if (app.isRuntime()) { + var path = PREDEFINED_RUNTIME_IMAGE.getFrom(options); + builder.appImageLayout(predefinedRuntimeImageLayout.apply(path)); + } else if (PREDEFINED_APP_IMAGE.containsIn(options)) { + var path = PREDEFINED_APP_IMAGE.getFrom(options); + builder.appImageLayout(predefinedAppImageLayout.apply(path)); + } else { + pkg.ifPresentOrElse(builder::appImageDirFor, () -> { + builder.appImageDirFor(app); + }); + } + + return builder.create(); + } + + private Function predefinedAppImageLayout; + private Function predefinedRuntimeImageLayout; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java deleted file mode 100644 index 6fb1a342fbf..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BuildEnvFromParams.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2025, 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.jpackage.internal; - -import static jdk.jpackage.internal.ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.TEMP_ROOT; -import static jdk.jpackage.internal.StandardBundlerParam.VERBOSE; - -import java.nio.file.Path; -import java.util.Map; -import java.util.function.Function; -import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.RuntimeLayout; - -final class BuildEnvFromParams { - - static BuildEnv create(Map params, - Function predefinedAppImageLayoutProvider, - Function predefinedRuntimeImageLayoutProvider) throws ConfigException { - - final var builder = new BuildEnvBuilder(TEMP_ROOT.fetchFrom(params)); - - RESOURCE_DIR.copyInto(params, builder::resourceDir); - VERBOSE.copyInto(params, builder::verbose); - - final var app = FromParams.APPLICATION.findIn(params).orElseThrow(); - - final var pkg = FromParams.getCurrentPackage(params); - - if (app.isRuntime()) { - var layout = predefinedRuntimeImageLayoutProvider.apply(PREDEFINED_RUNTIME_IMAGE.findIn(params).orElseThrow()); - builder.appImageLayout(layout); - } else if (StandardBundlerParam.hasPredefinedAppImage(params)) { - var layout = predefinedAppImageLayoutProvider.apply(PREDEFINED_APP_IMAGE.findIn(params).orElseThrow()); - builder.appImageLayout(layout); - } else if (pkg.isPresent()) { - builder.appImageDirFor(pkg.orElseThrow()); - } else { - builder.appImageDirFor(app); - } - - return builder.create(); - } - - static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam(BuildEnv.class, params -> { - return create(params, PLATFORM_APPLICATION_LAYOUT::resolveAt, RuntimeLayout.DEFAULT::resolveAt); - }); -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java deleted file mode 100644 index 937a3655e8b..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2012, 2022, 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.jpackage.internal; - -import java.util.HashMap; -import java.util.Map; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; - -public class BundleParams { - - protected final Map params; - - /** - * create a new bundle with all default values - */ - public BundleParams() { - params = new HashMap<>(); - } - - /** - * Create a bundle params with a copy of the params - * @param params map of initial parameters to be copied in. - */ - public BundleParams(Map params) { - this.params = new HashMap<>(params); - } - - public void addAllBundleParams(Map params) { - this.params.putAll(params); - } - - // NOTE: we do not care about application parameters here - // as they will be embedded into jar file manifest and - // java launcher will take care of them! - - public Map getBundleParamsAsMap() { - return new HashMap<>(params); - } - - public String getName() { - return APP_NAME.fetchFrom(params); - } - - private void putUnlessNull(String param, Object value) { - if (value != null) { - params.put(param, value); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java deleted file mode 100644 index 0d29677e826..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.PackagerException; - -/** - * Bundler - * - * The basic interface implemented by all Bundlers. - */ -public interface Bundler { - /** - * @return User Friendly name of this bundler. - */ - String getName(); - - /** - * @return Command line identifier of the bundler. Should be unique. - */ - String getID(); - - /** - * @return The bundle type of the bundle that is created by this bundler. - */ - String getBundleType(); - - /** - * Determines if this bundler will execute with the given parameters. - * - * @param params The parameters to be validate. Validation may modify - * the map, so if you are going to be using the same map - * across multiple bundlers you should pass in a deep copy. - * @return true if valid - * @throws ConfigException If the configuration params are incorrect. The - * exception may contain advice on how to modify the params map - * to make it valid. - */ - public boolean validate(Map params) - throws ConfigException; - - /** - * Creates a bundle from existing content. - * - * If a call to {@link #validate(java.util.Map)} date} returns true with - * the parameters map, then you can expect a valid output. - * However if an exception was thrown out of validate or it returned - * false then you should not expect sensible results from this call. - * It may or may not return a value, and it may or may not throw an - * exception. But any output should not be considered valid or sane. - * - * @param params The Bundle parameters, - * Keyed by the id from the ParamInfo. Execution may - * modify the map, so if you are going to be using the - * same map across multiple bundlers you should pass - * in a deep copy. - * @param outputParentDir - * The parent dir that the returned bundle will be placed in. - * @return The resulting bundled file - * - * For a bundler that produces a single artifact file this will be the - * location of that artifact (.exe file, .deb file, etc) - * - * For a bundler that produces a specific directory format output this will - * be the location of that specific directory (.app file, etc). - * - * For a bundler that produce multiple files, this will be a parent - * directory of those files (linux and windows images), whose name is not - * relevant to the result. - * - * @throws java.lang.IllegalArgumentException for any of the following - * reasons: - *
    - *
  • A required parameter is not found in the params list, for - * example missing the main class.
  • - *
  • A parameter has the wrong type of an object, for example a - * String where a File is required
  • - *
  • Bundler specific incompatibilities with the parameters, for - * example a bad version number format or an application id with - * forward slashes.
  • - *
- */ - public Path execute(Map params, - Path outputParentDir) throws PackagerException; - - /** - * Removes temporary files that are used for bundling. - */ - public void cleanup(Map params); - - /** - * Returns "true" if this bundler is supported on current platform. - */ - public boolean supported(boolean runtimeInstaller); - - /** - * Returns "true" if this bundler is he default for the current platform. - */ - public boolean isDefault(); -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java deleted file mode 100644 index 81030b18f6d..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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.jpackage.internal; - -import java.nio.file.Path; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import jdk.jpackage.internal.util.function.ThrowingFunction; - -/** - * BundlerParamInfo - * - * A BundlerParamInfo encapsulates an individual bundler parameter of type . - * - * @param id The command line and hashmap name of the parameter - * - * @param valueType Type of the parameter - * - * @param defaultValueFunction If the value is not set, and no fallback value is found, the - * parameter uses the value returned by the producer. - * - * @param stringConverter An optional string converter for command line arguments. - */ -record BundlerParamInfo(String id, Class valueType, - Function, T> defaultValueFunction, - BiFunction, T> stringConverter) { - - BundlerParamInfo { - Objects.requireNonNull(id); - Objects.requireNonNull(valueType); - } - - static BundlerParamInfo createStringBundlerParam(String id) { - return new BundlerParamInfo<>(id, String.class, null, null); - } - - static BundlerParamInfo createBooleanBundlerParam(String id) { - return new BundlerParamInfo<>(id, Boolean.class, null, BundlerParamInfo::toBoolean); - } - - static BundlerParamInfo createPathBundlerParam(String id) { - return new BundlerParamInfo<>(id, Path.class, null, BundlerParamInfo::toPath); - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - static BundlerParamInfo createBundlerParam(String id, Class valueType, - ThrowingFunction, U> valueCtor) { - return new BundlerParamInfo(id, valueType, ThrowingFunction.toFunction(valueCtor), null); - } - - static BundlerParamInfo createBundlerParam(Class valueType, - ThrowingFunction, U> valueCtor) { - return createBundlerParam(valueType.getName(), valueType, valueCtor); - } - - static boolean toBoolean(String value, Map params) { - if (value == null || "null".equalsIgnoreCase(value)) { - return false; - } else { - return Boolean.valueOf(value); - } - } - - static Path toPath(String value, Map params) { - return Path.of(value); - } - - String getID() { - return id; - } - - Class getValueType() { - return valueType; - } - - /** - * Returns true if value was not provided on command line for this parameter. - * - * @param params - params from which value will be fetch - * @return true if value was not provided on command line, false otherwise - */ - boolean getIsDefaultValue(Map params) { - Object o = params.get(getID()); - if (o != null) { - return false; // We have user provided value - } - - if (params.containsKey(getID())) { - return false; // explicit nulls are allowed for provided value - } - - return true; - } - - Function, T> getDefaultValueFunction() { - return defaultValueFunction; - } - - BiFunction, T> getStringConverter() { - return stringConverter; - } - - final T fetchFrom(Map params) { - return fetchFrom(params, true); - } - - @SuppressWarnings("unchecked") - final T fetchFrom(Map params, - boolean invokeDefault) { - Object o = params.get(getID()); - if (o instanceof String && getStringConverter() != null) { - return getStringConverter().apply((String) o, params); - } - - Class klass = getValueType(); - if (klass.isInstance(o)) { - return (T) o; - } - if (o != null) { - throw new IllegalArgumentException("Param " + getID() - + " should be of type " + getValueType() - + " but is a " + o.getClass()); - } - if (params.containsKey(getID())) { - // explicit nulls are allowed - return null; - } - - if (invokeDefault && (getDefaultValueFunction() != null)) { - T result = getDefaultValueFunction().apply(params); - if (result != null) { - params.put(getID(), result); - } - return result; - } - - // ultimate fallback - return null; - } - - Optional findIn(Map params) { - if (params.containsKey(getID())) { - return Optional.of(fetchFrom(params, true)); - } else { - return Optional.empty(); - } - } - - void copyInto(Map params, Consumer consumer) { - findIn(params).ifPresent(consumer); - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java deleted file mode 100644 index 955952d563d..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2014, 2019, 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.jpackage.internal; - -import java.util.Collection; -import java.util.Iterator; -import java.util.ServiceLoader; - -/** - * Bundlers - * - * The interface implemented by BasicBundlers - */ -public interface Bundlers { - - /** - * This convenience method will call - * {@link #createBundlersInstance(ClassLoader)} - * with the classloader that this Bundlers is loaded from. - * - * @return an instance of Bundlers loaded and configured from - * the current ClassLoader. - */ - public static Bundlers createBundlersInstance() { - return createBundlersInstance(Bundlers.class.getClassLoader()); - } - - /** - * This convenience method will automatically load a Bundlers instance - * from either META-INF/services or the default - * {@link BasicBundlers} if none are found in - * the services meta-inf. - * - * After instantiating the bundlers instance it will load the default - * bundlers via {@link #loadDefaultBundlers()} as well as requesting - * the services loader to load any other bundelrs via - * {@link #loadBundlersFromServices(ClassLoader)}. - - * - * @param servicesClassLoader the classloader to search for - * META-INF/service registered bundlers - * @return an instance of Bundlers loaded and configured from - * the specified ClassLoader - */ - public static Bundlers createBundlersInstance( - ClassLoader servicesClassLoader) { - ServiceLoader bundlersLoader = - ServiceLoader.load(Bundlers.class, servicesClassLoader); - Bundlers bundlers = null; - Iterator iter = bundlersLoader.iterator(); - if (iter.hasNext()) { - bundlers = iter.next(); - } - if (bundlers == null) { - bundlers = new BasicBundlers(); - } - - bundlers.loadBundlersFromServices(servicesClassLoader); - return bundlers; - } - - /** - * Returns all of the preconfigured, requested, and manually - * configured bundlers loaded with this instance. - * - * @return a read-only collection of the requested bundlers - */ - Collection getBundlers(); - - /** - * Returns all of the preconfigured, requested, and manually - * configured bundlers loaded with this instance that are of - * a specific BundleType, such as disk images, installers, or - * remote installers. - * - * @return a read-only collection of the requested bundlers - */ - Collection getBundlers(String type); - - /** - * Loads bundlers from the META-INF/services directly. - * - * This method is called from the - * {@link #createBundlersInstance(ClassLoader)} - * and {@link #createBundlersInstance()} methods. - */ - void loadBundlersFromServices(ClassLoader cl); - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java deleted file mode 100644 index 7790ebb3ebb..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2018, 2025, 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.jpackage.internal; - -import jdk.internal.util.OperatingSystem; - -import java.util.ResourceBundle; -import java.io.File; -import java.text.MessageFormat; - - -/** - * CLIHelp - * - * Generate and show the command line interface help message(s). - */ -public class CLIHelp { - - private static final ResourceBundle I18N = ResourceBundle.getBundle( - "jdk.jpackage.internal.resources.HelpResources"); - - // generates --help for jpackage's CLI - public static void showHelp(boolean noArgs) { - - if (noArgs) { - Log.info(I18N.getString("MSG_Help_no_args")); - } else { - OperatingSystem platform = OperatingSystem.current(); - String types; - String pLaunchOptions; - String pInstallOptions; - String pInstallDir; - String pAppImageDescription; - String pSignSampleUsage; - String pAppContentNote; - switch (platform) { - case MACOS: - types = "{\"app-image\", \"dmg\", \"pkg\"}"; - pLaunchOptions = I18N.getString("MSG_Help_mac_launcher"); - pInstallOptions = I18N.getString("MSG_Help_mac_install"); - pInstallDir - = I18N.getString("MSG_Help_mac_linux_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_mac_app_image"); - pSignSampleUsage - = I18N.getString("MSG_Help_mac_sign_sample_usage"); - pAppContentNote - = I18N.getString("MSG_Help_mac_app_content_note"); - break; - case LINUX: - types = "{\"app-image\", \"rpm\", \"deb\"}"; - pLaunchOptions = ""; - pInstallOptions = I18N.getString("MSG_Help_linux_install"); - pInstallDir - = I18N.getString("MSG_Help_mac_linux_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - case WINDOWS: - types = "{\"app-image\", \"exe\", \"msi\"}"; - pLaunchOptions = I18N.getString("MSG_Help_win_launcher"); - pInstallOptions = I18N.getString("MSG_Help_win_install"); - pInstallDir - = I18N.getString("MSG_Help_win_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - default: - types = "{\"app-image\", \"exe\", \"msi\", \"rpm\", \"deb\", \"pkg\", \"dmg\"}"; - pLaunchOptions = I18N.getString("MSG_Help_win_launcher") - + I18N.getString("MSG_Help_mac_launcher"); - pInstallOptions = I18N.getString("MSG_Help_win_install") - + I18N.getString("MSG_Help_linux_install") - + I18N.getString("MSG_Help_mac_install"); - pInstallDir - = I18N.getString("MSG_Help_default_install_dir"); - pAppImageDescription - = I18N.getString("MSG_Help_default_app_image"); - pSignSampleUsage = ""; - pAppContentNote = ""; - break; - } - Log.info(MessageFormat.format(I18N.getString("MSG_Help"), - File.pathSeparator, types, pLaunchOptions, - pInstallOptions, pInstallDir, pAppImageDescription, - pSignSampleUsage, pAppContentNote)); - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java index 6b4ba3c4410..9cb9fb5cba0 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CfgFile.java @@ -30,7 +30,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.Objects; import java.util.stream.Stream; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; @@ -47,10 +47,14 @@ final class CfgFile { CfgFile(Application app, Launcher launcher) { startupInfo = launcher.startupInfo().orElseThrow(); outputFileName = launcher.executableName() + ".cfg"; - version = app.version(); + version = Objects.requireNonNull(app.version()); } void create(ApplicationLayout appLayout) throws IOException { + Objects.requireNonNull(appLayout); + + Objects.requireNonNull(startupInfo.qualifiedClassName()); + List> content = new ArrayList<>(); final var refs = new Referencies(appLayout); @@ -58,7 +62,7 @@ final class CfgFile { content.add(Map.entry("[Application]", SECTION_TAG)); if (startupInfo instanceof LauncherModularStartupInfo modularStartupInfo) { - content.add(Map.entry("app.mainmodule", modularStartupInfo.moduleName() + content.add(Map.entry("app.mainmodule", Objects.requireNonNull(modularStartupInfo.moduleName()) + "/" + startupInfo.qualifiedClassName())); } else if (startupInfo instanceof LauncherJarStartupInfo jarStartupInfo) { Path mainJarPath = refs.appDirectory().resolve(jarStartupInfo.jarPath()); @@ -67,16 +71,13 @@ final class CfgFile { content.add(Map.entry("app.mainjar", mainJarPath)); } else { content.add(Map.entry("app.classpath", mainJarPath)); - } - - if (!jarStartupInfo.isJarWithMainClass()) { content.add(Map.entry("app.mainclass", startupInfo.qualifiedClassName())); } } else { throw new UnsupportedOperationException(); } - for (var value : Optional.ofNullable(startupInfo.classPath()).orElseGet(List::of)) { + for (var value : startupInfo.classPath()) { content.add(Map.entry("app.classpath", refs.appDirectory().resolve(value).toString())); } @@ -88,7 +89,7 @@ final class CfgFile { "java-options", "-Djpackage.app-version=" + version)); // add user supplied java options if there are any - for (var value : Optional.ofNullable(startupInfo.javaOptions()).orElseGet(List::of)) { + for (var value : startupInfo.javaOptions()) { content.add(Map.entry("java-options", value)); } @@ -98,7 +99,7 @@ final class CfgFile { content.add(Map.entry("java-options", refs.appModsDirectory())); } - var arguments = Optional.ofNullable(startupInfo.defaultParameters()).orElseGet(List::of); + var arguments = startupInfo.defaultParameters(); if (!arguments.isEmpty()) { content.add(Map.entry("[ArgOptions]", SECTION_TAG)); for (var value : arguments) { diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java new file mode 100644 index 00000000000..0bf7d6f41b2 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DefaultBundlingEnvironment.java @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static java.util.stream.Collectors.toMap; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.cli.CliBundlingEnvironment; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; +import jdk.jpackage.internal.model.AppImagePackageType; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; +import jdk.jpackage.internal.model.JPackageException; +import jdk.jpackage.internal.model.Package; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.StandardPackageType; +import jdk.jpackage.internal.util.Result; + +class DefaultBundlingEnvironment implements CliBundlingEnvironment { + + DefaultBundlingEnvironment(Builder builder) { + this(Optional.ofNullable(builder.defaultOperationSupplier), builder.bundlers); + } + + DefaultBundlingEnvironment(Optional>> defaultOperationSupplier, + Map>>> bundlers) { + + this.bundlers = bundlers.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> { + return new CachingSupplier<>(e.getValue()); + })); + + this.defaultOperationSupplier = Objects.requireNonNull(defaultOperationSupplier).map(CachingSupplier::new); + } + + + static final class Builder { + + Builder defaultOperation(Supplier> v) { + defaultOperationSupplier = v; + return this; + } + + Builder defaultOperation(StandardBundlingOperation v) { + return defaultOperation(() -> Optional.of(v.descriptor())); + } + + Builder bundler(StandardBundlingOperation op, Supplier>> bundlerSupplier) { + bundlers.put(Objects.requireNonNull(op.descriptor()), Objects.requireNonNull(bundlerSupplier)); + return this; + } + + Builder bundler(StandardBundlingOperation op, + Supplier> sysEnvResultSupplier, BiConsumer bundler) { + return bundler(op, createBundlerSupplier(sysEnvResultSupplier, bundler)); + } + + Builder bundler(StandardBundlingOperation op, Consumer bundler) { + Objects.requireNonNull(bundler); + return bundler(op, () -> Result.ofValue(bundler)); + } + + private Supplier> defaultOperationSupplier; + private final Map>>> bundlers = new HashMap<>(); + } + + + static Builder build() { + return new Builder(); + } + + static Supplier>> createBundlerSupplier( + Supplier> sysEnvResultSupplier, BiConsumer bundler) { + Objects.requireNonNull(sysEnvResultSupplier); + Objects.requireNonNull(bundler); + return () -> { + return sysEnvResultSupplier.get().map(sysEnv -> { + return options -> { + bundler.accept(options, sysEnv); + }; + }); + }; + } + + static void createApplicationImage(Options options, Application app, PackagingPipeline.Builder pipelineBuilder) { + Objects.requireNonNull(options); + Objects.requireNonNull(app); + Objects.requireNonNull(pipelineBuilder); + + final var outputDir = OptionUtils.outputDir(options).resolve(app.appImageDirName()); + + IOUtils.writableOutputDir(outputDir.getParent()); + + final var env = new BuildEnvFromOptions() + .predefinedAppImageLayout(app.asApplicationLayout().orElseThrow()) + .create(options, app); + + Log.verbose(I18N.format("message.creating-app-bundle", outputDir.getFileName(), outputDir.toAbsolutePath().getParent())); + + if (Files.exists(outputDir)) { + throw new JPackageException(I18N.format("error.root-exists", outputDir.toAbsolutePath())); + } + + pipelineBuilder.excludeDirFromCopying(outputDir.getParent()) + .create().execute(BuildEnv.withAppImageDir(env, outputDir), app); + } + + static void createNativePackage(Options options, + Function createPackage, + BiFunction createBuildEnv, + PackagingPipeline.Builder pipelineBuilder, + Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) { + + Objects.requireNonNull(pipelineBuilder); + createNativePackage(options, createPackage, createBuildEnv, _ -> pipelineBuilder, pipelineBuilderMutatorFactory); + } + + static void createNativePackage(Options options, + Function createPackage, + BiFunction createBuildEnv, + Function createPipelineBuilder, + Packager.PipelineBuilderMutatorFactory pipelineBuilderMutatorFactory) { + + Objects.requireNonNull(options); + Objects.requireNonNull(createPackage); + Objects.requireNonNull(createBuildEnv); + Objects.requireNonNull(createPipelineBuilder); + Objects.requireNonNull(pipelineBuilderMutatorFactory); + + var pkg = Objects.requireNonNull(createPackage.apply(options)); + + Packager.build().pkg(pkg) + .outputDir(OptionUtils.outputDir(options)) + .env(Objects.requireNonNull(createBuildEnv.apply(options, pkg))) + .pipelineBuilderMutatorFactory(pipelineBuilderMutatorFactory) + .execute(Objects.requireNonNull(createPipelineBuilder.apply(pkg))); + } + + @Override + public Optional defaultOperation() { + return defaultOperationSupplier.flatMap(Supplier::get); + } + + @Override + public void createBundle(BundlingOperationDescriptor op, Options cmdline) { + final var bundler = getBundlerSupplier(op).get().orElseThrow(); + Optional permanentWorkDirectory = Optional.empty(); + try (var tempDir = new TempDirectory(cmdline)) { + if (!tempDir.deleteOnClose()) { + permanentWorkDirectory = Optional.of(tempDir.path()); + } + bundler.accept(tempDir.options()); + + var packageType = OptionUtils.bundlingOperation(cmdline).packageType(); + + Log.verbose(I18N.format("message.bundle-created", I18N.getString(bundleTypeDescription(packageType, op.os())))); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } finally { + permanentWorkDirectory.ifPresent(workDir -> { + Log.verbose(I18N.format("message.debug-working-directory", workDir.toAbsolutePath())); + }); + } + } + + @Override + public Collection configurationErrors(BundlingOperationDescriptor op) { + return getBundlerSupplier(op).get().errors(); + } + + private Supplier>> getBundlerSupplier(BundlingOperationDescriptor op) { + return Optional.ofNullable(bundlers.get(op)).orElseThrow(NoSuchElementException::new); + } + + private String bundleTypeDescription(PackageType type, OperatingSystem os) { + switch (type) { + case StandardPackageType stdType -> { + switch (stdType) { + case WIN_MSI -> { + return "bundle-type.win-msi"; + } + case WIN_EXE -> { + return "bundle-type.win-exe"; + } + case LINUX_DEB -> { + return "bundle-type.linux-deb"; + } + case LINUX_RPM -> { + return "bundle-type.linux-rpm"; + } + case MAC_DMG -> { + return "bundle-type.mac-dmg"; + } + case MAC_PKG -> { + return "bundle-type.mac-pkg"; + } + default -> { + throw new AssertionError(); + } + } + } + case AppImagePackageType appImageType -> { + switch (os) { + case WINDOWS -> { + return "bundle-type.win-app"; + } + case LINUX -> { + return "bundle-type.linux-app"; + } + case MACOS -> { + return "bundle-type.mac-app"; + } + default -> { + throw new AssertionError(); + } + } + } + default -> { + throw new AssertionError(); + } + } + } + + + private static final class CachingSupplier implements Supplier { + + CachingSupplier(Supplier getter) { + this.getter = Objects.requireNonNull(getter); + } + + @Override + public T get() { + return cachedValue.updateAndGet(v -> { + return Optional.ofNullable(v).orElseGet(getter); + }); + } + + private final Supplier getter; + private final AtomicReference cachedValue = new AtomicReference<>(); + } + + + private final Map>>> bundlers; + private final Optional>> defaultOperationSupplier; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java deleted file mode 100644 index d7b4052d34a..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (c) 2011, 2025, 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.jpackage.internal; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.InvalidPathException; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import java.util.stream.Stream; -import jdk.jpackage.internal.model.PackagerException; - -/** - * DeployParams - * - * This class is generated and used in Arguments.processArguments() as - * intermediate step in generating the BundleParams and ultimately the Bundles - */ -public class DeployParams { - - String targetFormat = null; // means default type for this platform - - // raw arguments to the bundler - Map bundlerArguments = new LinkedHashMap<>(); - - static class Template { - Path in; - Path out; - - Template(Path in, Path out) { - this.in = in; - this.out = out; - } - } - - // we need to expand as in some cases - // (most notably jpackage) - // we may get "." as filename and assumption is we include - // everything in the given folder - // (IOUtils.copyfiles() have recursive behavior) - List expandFileset(Path root) throws IOException { - List files = new LinkedList<>(); - if (!Files.isSymbolicLink(root)) { - if (Files.isDirectory(root)) { - try (Stream stream = Files.list(root)) { - List children = stream.toList(); - if (children != null && children.size() > 0) { - children.forEach(f -> { - try { - files.addAll(expandFileset(f)); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - } else { - // Include empty folders - files.add(root); - } - } - } else { - files.add(root); - } - } - return files; - } - - static void validateName(String s, boolean forApp) - throws PackagerException { - - String exceptionKey = forApp ? - "ERR_InvalidAppName" : "ERR_InvalidSLName"; - - if (s == null) { - if (forApp) { - return; - } else { - throw new PackagerException(exceptionKey); - } - } - if (s.length() == 0 || s.charAt(s.length() - 1) == '\\') { - throw new PackagerException(exceptionKey, s); - } - try { - // name must be valid path element for this file system - Path p = Path.of(s); - // and it must be a single name element in a path - if (p.getNameCount() != 1) { - throw new PackagerException(exceptionKey, s); - } - } catch (InvalidPathException ipe) { - throw new PackagerException(ipe, exceptionKey, s); - } - - for (int i = 0; i < s.length(); i++) { - char a = s.charAt(i); - // We check for ASCII codes first which we accept. If check fails, - // check if it is acceptable extended ASCII or unicode character. - if (a < ' ' || a > '~') { - // Accept anything else including special chars like copyright - // symbols. Note: space will be included by ASCII check above, - // but other whitespace like tabs or new line will be rejected. - if (Character.isISOControl(a) || - Character.isWhitespace(a)) { - throw new PackagerException(exceptionKey, s); - } - } else if (a == '"' || a == '%') { - throw new PackagerException(exceptionKey, s); - } - } - } - - @SuppressWarnings("unchecked") - public void validate() throws PackagerException { - boolean hasModule = (bundlerArguments.get( - Arguments.CLIOptions.MODULE.getId()) != null); - boolean hasAppImage = (bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()) != null); - boolean hasMain = (bundlerArguments.get( - Arguments.CLIOptions.MAIN_JAR.getId()) != null); - boolean hasRuntimeImage = (bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()) != null); - boolean hasInput = (bundlerArguments.get( - Arguments.CLIOptions.INPUT.getId()) != null); - boolean hasModulePath = (bundlerArguments.get( - Arguments.CLIOptions.MODULE_PATH.getId()) != null); - boolean hasMacAppStore = (bundlerArguments.get( - Arguments.CLIOptions.MAC_APP_STORE.getId()) != null); - boolean runtimeInstaller = !isTargetAppImage() && - !hasAppImage && !hasModule && !hasMain && hasRuntimeImage; - - if (isTargetAppImage()) { - // Module application requires --runtime-image or --module-path - if (hasModule) { - if (!hasModulePath && !hasRuntimeImage && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--runtime-image or --module-path"); - } - } else { - if (!hasInput && !hasAppImage) { - throw new PackagerException("error.no-input-parameter"); - } - } - } else { - if (!runtimeInstaller) { - if (hasModule) { - if (!hasModulePath && !hasRuntimeImage && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--runtime-image, --module-path or --app-image"); - } - } else { - if (!hasInput && !hasAppImage) { - throw new PackagerException("ERR_MissingArgument", - "--input or --app-image"); - } - } - } - } - - // if bundling non-modular image, or installer without app-image - // then we need some resources and a main class - if (!hasModule && !hasAppImage && !runtimeInstaller && !hasMain) { - throw new PackagerException("ERR_MissingArgument", "--main-jar"); - } - - String name = (String)bundlerArguments.get( - Arguments.CLIOptions.NAME.getId()); - validateName(name, true); - - // Validate app image if set - String appImage = (String)bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()); - if (appImage != null) { - Path appImageDir = Path.of(appImage); - if (!Files.exists(appImageDir) - || appImageDir.toFile().list() == null - || appImageDir.toFile().list().length == 0) { - throw new PackagerException("ERR_AppImageNotExist", appImage); - } - } - - // Validate temp dir - String root = (String)bundlerArguments.get( - Arguments.CLIOptions.TEMP_ROOT.getId()); - if (root != null && Files.exists(Path.of(root))) { - try (Stream stream = Files.walk(Path.of(root), 1)) { - Path [] contents = stream.toArray(Path[]::new); - // contents.length > 1 because Files.walk(path) includes path - if (contents != null && contents.length > 1) { - throw new PackagerException( - "ERR_BuildRootInvalid", root); - } - } catch (IOException ioe) { - throw new PackagerException(ioe); - } - } - - // Validate resource dir - String resources = (String)bundlerArguments.get( - Arguments.CLIOptions.RESOURCE_DIR.getId()); - if (resources != null) { - if (!(Files.exists(Path.of(resources)))) { - throw new PackagerException( - "message.resource-dir-does-not-exist", - Arguments.CLIOptions.RESOURCE_DIR.getId(), resources); - } - } - - // Validate predefined runtime dir - String runtime = (String)bundlerArguments.get( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()); - if (runtime != null) { - if (!(Files.exists(Path.of(runtime)))) { - throw new PackagerException( - "message.runtime-image-dir-does-not-exist", - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), - runtime); - } - } - - - // Validate license file if set - String license = (String)bundlerArguments.get( - Arguments.CLIOptions.LICENSE_FILE.getId()); - if (license != null) { - if (!(Files.exists(Path.of(license)))) { - throw new PackagerException("ERR_LicenseFileNotExit"); - } - } - - // Validate icon file if set - String icon = (String)bundlerArguments.get( - Arguments.CLIOptions.ICON.getId()); - if (icon != null) { - if (!(Files.exists(Path.of(icon)))) { - throw new PackagerException("ERR_IconFileNotExit", - Path.of(icon).toAbsolutePath().toString()); - } - } - - - if (hasMacAppStore) { - // Validate jlink-options if mac-app-store is set - Object jlinkOptions = bundlerArguments.get( - Arguments.CLIOptions.JLINK_OPTIONS.getId()); - if (jlinkOptions instanceof List) { - List options = (List) jlinkOptions; - if (!options.contains("--strip-native-commands")) { - throw new PackagerException( - "ERR_MissingJLinkOptMacAppStore", - "--strip-native-commands"); - } - } - } - } - - void setTargetFormat(String t) { - targetFormat = t; - } - - String getTargetFormat() { - return targetFormat; - } - - boolean isTargetAppImage() { - return ("app-image".equals(targetFormat)); - } - - private static final Set multi_args = new TreeSet<>(Arrays.asList( - StandardBundlerParam.JAVA_OPTIONS.getID(), - StandardBundlerParam.ARGUMENTS.getID(), - StandardBundlerParam.MODULE_PATH.getID(), - StandardBundlerParam.ADD_MODULES.getID(), - StandardBundlerParam.LIMIT_MODULES.getID(), - StandardBundlerParam.FILE_ASSOCIATIONS.getID(), - StandardBundlerParam.DMG_CONTENT.getID(), - StandardBundlerParam.APP_CONTENT.getID(), - StandardBundlerParam.JLINK_OPTIONS.getID() - )); - - @SuppressWarnings("unchecked") - public void addBundleArgument(String key, Object value) { - // special hack for multi-line arguments - if (multi_args.contains(key)) { - Object existingValue = bundlerArguments.get(key); - if (existingValue instanceof String && value instanceof String) { - String delim = "\n\n"; - if (key.equals(StandardBundlerParam.MODULE_PATH.getID())) { - delim = File.pathSeparator; - } else if ( - key.equals(StandardBundlerParam.DMG_CONTENT.getID()) || - key.equals(StandardBundlerParam.APP_CONTENT.getID()) || - key.equals(StandardBundlerParam.ADD_MODULES.getID())) { - delim = ","; - } - bundlerArguments.put(key, existingValue + delim + value); - } else if (existingValue instanceof List && value instanceof List) { - ((List)existingValue).addAll((List)value); - } else if (existingValue instanceof Map && - value instanceof String && ((String)value).contains("=")) { - String[] mapValues = ((String)value).split("=", 2); - ((Map)existingValue).put(mapValues[0], mapValues[1]); - } else { - bundlerArguments.put(key, value); - } - } else { - bundlerArguments.put(key, value); - } - } - - BundleParams getBundleParams() { - BundleParams bundleParams = new BundleParams(); - - // check for collisions - TreeSet keys = new TreeSet<>(bundlerArguments.keySet()); - keys.retainAll(bundleParams.getBundleParamsAsMap().keySet()); - - if (!keys.isEmpty()) { - throw new RuntimeException("Deploy Params and Bundler Arguments " - + "overlap in the following values:" + keys.toString()); - } - - bundleParams.addAllBundleParams(bundlerArguments); - - return bundleParams; - } - - @Override - public String toString() { - return "DeployParams {" + "output: " + "}"; - } - -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java index 349a09f237c..ed89ffe1ef6 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FileAssociationGroup.java @@ -111,6 +111,10 @@ final record FileAssociationGroup(List items) { return this; } + Optional description() { + return Optional.ofNullable(description); + } + Builder mimeTypes(Collection v) { mimeTypes = Set.copyOf(v); return this; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java new file mode 100644 index 00000000000..6b74cab4e65 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromOptions.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.ApplicationBuilder.normalizeIcons; +import static jdk.jpackage.internal.JLinkRuntimeBuilder.ensureBaseModuleInModulePath; +import static jdk.jpackage.internal.OptionUtils.isRuntimeInstaller; +import static jdk.jpackage.internal.cli.StandardOption.ABOUT_URL; +import static jdk.jpackage.internal.cli.StandardOption.ADDITIONAL_LAUNCHERS; +import static jdk.jpackage.internal.cli.StandardOption.ADD_MODULES; +import static jdk.jpackage.internal.cli.StandardOption.APP_CONTENT; +import static jdk.jpackage.internal.cli.StandardOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardOption.COPYRIGHT; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.INPUT; +import static jdk.jpackage.internal.cli.StandardOption.INSTALL_DIR; +import static jdk.jpackage.internal.cli.StandardOption.JLINK_OPTIONS; +import static jdk.jpackage.internal.cli.StandardOption.LICENSE_FILE; +import static jdk.jpackage.internal.cli.StandardOption.MODULE_PATH; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.RESOURCE_DIR; +import static jdk.jpackage.internal.cli.StandardOption.VENDOR; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.model.Application; +import jdk.jpackage.internal.model.ApplicationLaunchers; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherModularStartupInfo; +import jdk.jpackage.internal.model.PackageType; +import jdk.jpackage.internal.model.RuntimeLayout; + +final class FromOptions { + + static ApplicationBuilderBuilder buildApplicationBuilder() { + return new ApplicationBuilderBuilder(); + } + + static PackageBuilder createPackageBuilder(Options options, Application app, PackageType type) { + + final var builder = new PackageBuilder(app, type); + + NAME.ifPresentIn(options, builder::name); + DESCRIPTION.ifPresentIn(options, builder::description); + APP_VERSION.ifPresentIn(options, builder::version); + ABOUT_URL.ifPresentIn(options, builder::aboutURL); + LICENSE_FILE.ifPresentIn(options, builder::licenseFile); + PREDEFINED_APP_IMAGE.ifPresentIn(options, builder::predefinedAppImage); + PREDEFINED_RUNTIME_IMAGE.ifPresentIn(options, builder::predefinedAppImage); + INSTALL_DIR.ifPresentIn(options, builder::installDir); + + return builder; + } + + + static final class ApplicationBuilderBuilder { + + private ApplicationBuilderBuilder() { + } + + ApplicationBuilder create(Options options, + Function launcherCtor, + BiFunction launcherOverrideCtor, + ApplicationLayout appLayout) { + + final Optional thePredefinedRuntimeLayout; + if (PREDEFINED_RUNTIME_IMAGE.containsIn(options)) { + thePredefinedRuntimeLayout = Optional.ofNullable( + predefinedRuntimeLayout).or(() -> Optional.of(RuntimeLayout.DEFAULT)); + } else { + thePredefinedRuntimeLayout = Optional.empty(); + } + + final var transfomer = new OptionsTransformer(options, appLayout); + final var appBuilder = createApplicationBuilder( + transfomer.appOptions(), + launcherCtor, + launcherOverrideCtor, + appLayout, + Optional.ofNullable(runtimeLayout).orElse(RuntimeLayout.DEFAULT), + thePredefinedRuntimeLayout); + + transfomer.externalApp().ifPresent(appBuilder::externalApplication); + + return appBuilder; + } + + /** + * Sets the layout of the predefined runtime image. + * @param v the layout of the predefined runtime image. Null is permitted. + * @return this + */ + ApplicationBuilderBuilder predefinedRuntimeLayout(RuntimeLayout v) { + predefinedRuntimeLayout = v; + return this; + } + + /** + * Sets the layout of a runtime bundle. + * @param v the layout of a runtime bundle. Null is permitted. + * @return this + */ + ApplicationBuilderBuilder runtimeLayout(RuntimeLayout v) { + runtimeLayout = v; + return this; + } + + private RuntimeLayout runtimeLayout; + private RuntimeLayout predefinedRuntimeLayout; + } + + + private static ApplicationBuilder createApplicationBuilder(Options options, + Function launcherCtor, + BiFunction launcherOverrideCtor, + ApplicationLayout appLayout, RuntimeLayout runtimeLayout, + Optional predefinedRuntimeLayout) { + + final var appBuilder = new ApplicationBuilder(); + + final var isRuntimeInstaller = isRuntimeInstaller(options); + + final var predefinedRuntimeImage = PREDEFINED_RUNTIME_IMAGE.findIn(options); + + final var predefinedRuntimeDirectory = predefinedRuntimeLayout.flatMap(layout -> { + return predefinedRuntimeImage.map(layout::resolveAt); + }).map(RuntimeLayout::runtimeDirectory); + + NAME.findIn(options).or(() -> { + if (isRuntimeInstaller) { + return predefinedRuntimeImage.map(Path::getFileName).map(Path::toString); + } else { + return Optional.empty(); + } + }).ifPresent(appBuilder::name); + DESCRIPTION.ifPresentIn(options, appBuilder::description); + APP_VERSION.ifPresentIn(options, appBuilder::version); + VENDOR.ifPresentIn(options, appBuilder::vendor); + COPYRIGHT.ifPresentIn(options, appBuilder::copyright); + INPUT.ifPresentIn(options, appBuilder::srcDir); + APP_CONTENT.ifPresentIn(options, appBuilder::contentDirs); + + if (isRuntimeInstaller) { + appBuilder.appImageLayout(runtimeLayout); + } else { + appBuilder.appImageLayout(appLayout); + + final var launchers = createLaunchers(options, launcherCtor); + + if (PREDEFINED_APP_IMAGE.containsIn(options)) { + appBuilder.launchers(launchers); + } else { + appBuilder.launchers(normalizeIcons(launchers, RESOURCE_DIR.findIn(options), launcherOverrideCtor)); + + final var runtimeBuilderBuilder = new RuntimeBuilderBuilder(); + + runtimeBuilderBuilder.modulePath(ensureBaseModuleInModulePath(MODULE_PATH.findIn(options).orElseGet(List::of))); + + if (!APP_VERSION.containsIn(options)) { + // Version is not specified explicitly. Try to get it from the app's module. + launchers.mainLauncher().startupInfo().ifPresent(startupInfo -> { + if (startupInfo instanceof LauncherModularStartupInfo modularStartupInfo) { + modularStartupInfo.moduleVersion().ifPresent(moduleVersion -> { + appBuilder.version(moduleVersion); + Log.verbose(I18N.format("message.module-version", + moduleVersion, modularStartupInfo.moduleName())); + }); + } + }); + } + + predefinedRuntimeDirectory.ifPresentOrElse(runtimeBuilderBuilder::forRuntime, () -> { + final var startupInfos = launchers.asList().stream() + .map(Launcher::startupInfo) + .map(Optional::orElseThrow).toList(); + final var jlinkOptionsBuilder = runtimeBuilderBuilder.forNewRuntime(startupInfos); + ADD_MODULES.findIn(options).map(Set::copyOf).ifPresent(jlinkOptionsBuilder::addModules); + JLINK_OPTIONS.ifPresentIn(options, jlinkOptionsBuilder::options); + jlinkOptionsBuilder.apply(); + }); + + appBuilder.runtimeBuilder(runtimeBuilderBuilder.create()); + } + } + + return appBuilder; + } + + private static ApplicationLaunchers createLaunchers(Options options, Function launcherCtor) { + var launchers = ADDITIONAL_LAUNCHERS.getFrom(options); + + var mainLauncher = launcherCtor.apply(options); + + // + // Additional launcher should: + // - Use description from the main launcher by default. + // + var mainLauncherDefaults = Options.of(Map.of(DESCRIPTION, mainLauncher.description())); + + var additionalLaunchers = launchers.stream().map(launcherOptions -> { + return launcherOptions.copyWithParent(mainLauncherDefaults); + }).map(launcherCtor).toList(); + + return new ApplicationLaunchers(mainLauncher, additionalLaunchers); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java deleted file mode 100644 index cee7492dbc7..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (c) 2025, 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.jpackage.internal; - -import static jdk.jpackage.internal.Arguments.CLIOptions.LINUX_SHORTCUT_HINT; -import static jdk.jpackage.internal.Arguments.CLIOptions.WIN_MENU_HINT; -import static jdk.jpackage.internal.Arguments.CLIOptions.WIN_SHORTCUT_HINT; -import static jdk.jpackage.internal.StandardBundlerParam.ABOUT_URL; -import static jdk.jpackage.internal.StandardBundlerParam.ADD_LAUNCHERS; -import static jdk.jpackage.internal.StandardBundlerParam.ADD_MODULES; -import static jdk.jpackage.internal.StandardBundlerParam.APP_CONTENT; -import static jdk.jpackage.internal.StandardBundlerParam.APP_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.COPYRIGHT; -import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.INSTALLER_NAME; -import static jdk.jpackage.internal.StandardBundlerParam.INSTALL_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.JLINK_OPTIONS; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_AS_SERVICE; -import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.LIMIT_MODULES; -import static jdk.jpackage.internal.StandardBundlerParam.MODULE_PATH; -import static jdk.jpackage.internal.StandardBundlerParam.NAME; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; -import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.SOURCE_DIR; -import static jdk.jpackage.internal.StandardBundlerParam.VENDOR; -import static jdk.jpackage.internal.StandardBundlerParam.VERSION; -import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; -import static jdk.jpackage.internal.StandardBundlerParam.isRuntimeInstaller; -import static jdk.jpackage.internal.util.function.ThrowingFunction.toFunction; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Function; -import jdk.jpackage.internal.model.Application; -import jdk.jpackage.internal.model.ApplicationLaunchers; -import jdk.jpackage.internal.model.ApplicationLayout; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.ExternalApplication; -import jdk.jpackage.internal.model.ExternalApplication.LauncherInfo; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.LauncherShortcut; -import jdk.jpackage.internal.model.LauncherShortcutStartupDirectory; -import jdk.jpackage.internal.model.PackageType; -import jdk.jpackage.internal.model.ParseUtils; -import jdk.jpackage.internal.model.RuntimeLayout; -import jdk.jpackage.internal.util.function.ThrowingFunction; - -final class FromParams { - - static ApplicationBuilder createApplicationBuilder(Map params, - Function, Launcher> launcherMapper, - BiFunction launcherOverrideCtor, - ApplicationLayout appLayout) throws ConfigException, IOException { - return createApplicationBuilder(params, launcherMapper, launcherOverrideCtor, appLayout, RuntimeLayout.DEFAULT, Optional.of(RuntimeLayout.DEFAULT)); - } - - static ApplicationBuilder createApplicationBuilder(Map params, - Function, Launcher> launcherMapper, - BiFunction launcherOverrideCtor, - ApplicationLayout appLayout, RuntimeLayout runtimeLayout, - Optional predefinedRuntimeLayout) throws ConfigException, IOException { - - final var appBuilder = new ApplicationBuilder(); - - APP_NAME.copyInto(params, appBuilder::name); - DESCRIPTION.copyInto(params, appBuilder::description); - appBuilder.version(VERSION.fetchFrom(params)); - VENDOR.copyInto(params, appBuilder::vendor); - COPYRIGHT.copyInto(params, appBuilder::copyright); - SOURCE_DIR.copyInto(params, appBuilder::srcDir); - APP_CONTENT.copyInto(params, appBuilder::contentDirs); - - final var isRuntimeInstaller = isRuntimeInstaller(params); - - final var predefinedRuntimeImage = PREDEFINED_RUNTIME_IMAGE.findIn(params); - - final var predefinedRuntimeDirectory = predefinedRuntimeLayout.flatMap( - layout -> predefinedRuntimeImage.map(layout::resolveAt)).map(RuntimeLayout::runtimeDirectory); - - if (isRuntimeInstaller) { - appBuilder.appImageLayout(runtimeLayout); - } else { - appBuilder.appImageLayout(appLayout); - - if (hasPredefinedAppImage(params)) { - final var appImageFile = PREDEFINED_APP_IMAGE_FILE.fetchFrom(params); - appBuilder.initFromExternalApplication(appImageFile, launcherInfo -> { - var launcherParams = mapLauncherInfo(appImageFile, launcherInfo); - return launcherMapper.apply(mergeParams(params, launcherParams)); - }); - } else { - final var launchers = createLaunchers(params, launcherMapper); - - final var runtimeBuilderBuilder = new RuntimeBuilderBuilder(); - - runtimeBuilderBuilder.modulePath(MODULE_PATH.fetchFrom(params)); - - predefinedRuntimeDirectory.ifPresentOrElse(runtimeBuilderBuilder::forRuntime, () -> { - final var startupInfos = launchers.asList().stream() - .map(Launcher::startupInfo) - .map(Optional::orElseThrow).toList(); - final var jlinkOptionsBuilder = runtimeBuilderBuilder.forNewRuntime(startupInfos); - ADD_MODULES.copyInto(params, jlinkOptionsBuilder::addModules); - LIMIT_MODULES.copyInto(params, jlinkOptionsBuilder::limitModules); - JLINK_OPTIONS.copyInto(params, jlinkOptionsBuilder::options); - jlinkOptionsBuilder.apply(); - }); - - final var normalizedLaunchers = ApplicationBuilder.normalizeIcons(launchers, RESOURCE_DIR.findIn(params), launcherOverrideCtor); - - appBuilder.launchers(normalizedLaunchers).runtimeBuilder(runtimeBuilderBuilder.create()); - } - } - - return appBuilder; - } - - static PackageBuilder createPackageBuilder( - Map params, Application app, - PackageType type) throws ConfigException { - - final var builder = new PackageBuilder(app, type); - - builder.name(INSTALLER_NAME.fetchFrom(params)); - DESCRIPTION.copyInto(params, builder::description); - VERSION.copyInto(params, builder::version); - ABOUT_URL.copyInto(params, builder::aboutURL); - LICENSE_FILE.findIn(params).map(Path::of).ifPresent(builder::licenseFile); - PREDEFINED_APP_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); - PREDEFINED_RUNTIME_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); - INSTALL_DIR.findIn(params).map(Path::of).ifPresent(builder::installDir); - - return builder; - } - - static BundlerParamInfo createApplicationBundlerParam( - ThrowingFunction, T> ctor) { - return BundlerParamInfo.createBundlerParam(Application.class, ctor); - } - - static BundlerParamInfo createPackageBundlerParam( - ThrowingFunction, T> ctor) { - return BundlerParamInfo.createBundlerParam(jdk.jpackage.internal.model.Package.class, ctor); - } - - static Optional getCurrentPackage(Map params) { - return Optional.ofNullable((jdk.jpackage.internal.model.Package)params.get( - jdk.jpackage.internal.model.Package.class.getName())); - } - - static Optional findLauncherShortcut( - BundlerParamInfo shortcutParam, - Map mainParams, - Map launcherParams) { - - Optional launcherValue; - if (launcherParams == mainParams) { - // The main launcher - launcherValue = Optional.empty(); - } else { - launcherValue = shortcutParam.findIn(launcherParams); - } - - return launcherValue.map(ParseUtils::parseLauncherShortcutForAddLauncher).or(() -> { - return Optional.ofNullable(mainParams.get(shortcutParam.getID())).map(toFunction(value -> { - if (value instanceof Boolean) { - return new LauncherShortcut(LauncherShortcutStartupDirectory.DEFAULT); - } else { - try { - return ParseUtils.parseLauncherShortcutForMainLauncher((String)value); - } catch (IllegalArgumentException ex) { - throw I18N.buildConfigException("error.invalid-option-value", value, "--" + shortcutParam.getID()).create(); - } - } - })); - }); - } - - private static ApplicationLaunchers createLaunchers( - Map params, - Function, Launcher> launcherMapper) { - var launchers = ADD_LAUNCHERS.findIn(params).orElseGet(List::of); - - var mainLauncher = launcherMapper.apply(params); - var additionalLaunchers = launchers.stream().map(launcherParams -> { - return launcherMapper.apply(mergeParams(params, launcherParams)); - }).toList(); - - return new ApplicationLaunchers(mainLauncher, additionalLaunchers); - } - - private static Map mapLauncherInfo(ExternalApplication appImageFile, LauncherInfo launcherInfo) { - Map launcherParams = new HashMap<>(); - launcherParams.put(NAME.getID(), launcherInfo.name()); - if (!appImageFile.getLauncherName().equals(launcherInfo.name())) { - // This is not the main launcher, accept the value - // of "launcher-as-service" from the app image file (.jpackage.xml). - launcherParams.put(LAUNCHER_AS_SERVICE.getID(), Boolean.toString(launcherInfo.service())); - } - launcherParams.putAll(launcherInfo.extra()); - return launcherParams; - } - - private static Map mergeParams(Map mainParams, - Map launcherParams) { - if (!launcherParams.containsKey(DESCRIPTION.getID())) { - launcherParams = new HashMap<>(launcherParams); -// FIXME: this is a good improvement but it fails existing tests -// launcherParams.put(DESCRIPTION.getID(), String.format("%s (%s)", DESCRIPTION.fetchFrom( -// mainParams), APP_NAME.fetchFrom(launcherParams))); - launcherParams.put(DESCRIPTION.getID(), DESCRIPTION.fetchFrom(mainParams)); - } - return AddLauncherArguments.merge(mainParams, launcherParams, ICON.getID(), - ADD_LAUNCHERS.getID(), FILE_ASSOCIATIONS.getID(), WIN_MENU_HINT.getId(), - WIN_SHORTCUT_HINT.getId(), LINUX_SHORTCUT_HINT.getId()); - } - - static final BundlerParamInfo APPLICATION = createApplicationBundlerParam(null); -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java index 427051719bb..aac113d7777 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java @@ -32,7 +32,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; -import jdk.jpackage.internal.model.PackagerException; +import jdk.jpackage.internal.model.JPackageException; /** * IOUtils @@ -90,19 +90,17 @@ final class IOUtils { } } - static void writableOutputDir(Path outdir) throws PackagerException { + static void writableOutputDir(Path outdir) { if (!Files.isDirectory(outdir)) { try { Files.createDirectories(outdir); } catch (IOException ex) { - throw new PackagerException("error.cannot-create-output-dir", - outdir.toAbsolutePath().toString()); + throw new JPackageException(I18N.format("error.cannot-create-output-dir", outdir.toAbsolutePath())); } } if (!Files.isWritable(outdir)) { - throw new PackagerException("error.cannot-write-to-output-dir", - outdir.toAbsolutePath().toString()); + throw new JPackageException(I18N.format("error.cannot-write-to-output-dir", outdir.toAbsolutePath())); } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java index 6ac9758e179..50d8049bc2d 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkRuntimeBuilder.java @@ -36,7 +36,6 @@ import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.nio.file.Files; import java.nio.file.Path; -import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -52,9 +51,9 @@ import java.util.stream.Stream; import jdk.internal.module.ModulePath; import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.LauncherModularStartupInfo; import jdk.jpackage.internal.model.LauncherStartupInfo; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.model.RuntimeBuilder; final class JLinkRuntimeBuilder implements RuntimeBuilder { @@ -64,7 +63,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { } @Override - public void create(AppImageLayout appImageLayout) throws PackagerException { + public void create(AppImageLayout appImageLayout) { var args = new ArrayList(); args.add("--output"); args.add(appImageLayout.runtimeDirectory().toString()); @@ -79,7 +78,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { args.add(0, "jlink"); Log.verbose(args, List.of(jlinkOut), retVal, -1); if (retVal != 0) { - throw new PackagerException("error.jlink.failed", jlinkOut); + throw new JPackageException(I18N.format("error.jlink.failed", jlinkOut)); } } @@ -182,8 +181,7 @@ final class JLinkRuntimeBuilder implements RuntimeBuilder { for (String option : options) { switch (option) { case "--output", "--add-modules", "--module-path" -> { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.blocked.option"), option), null); + throw I18N.buildConfigException("error.blocked.option", option).create(); } default -> { args.add(option); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java deleted file mode 100644 index 079d03a076c..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2017, 2022, 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.jpackage.internal; - -import java.io.PrintWriter; -import java.util.Optional; -import java.util.spi.ToolProvider; - -/** - * JPackageToolProvider - * - * This is the ToolProvider implementation exported - * to java.util.spi.ToolProvider and ultimately javax.tools.ToolProvider - */ -public class JPackageToolProvider implements ToolProvider { - - public String name() { - return "jpackage"; - } - - public Optional description() { - return Optional.of(jdk.jpackage.main.Main.I18N.getString("jpackage.description")); - } - - public synchronized int run( - PrintWriter out, PrintWriter err, String... args) { - try { - return new jdk.jpackage.main.Main().execute(out, err, args); - } catch (RuntimeException re) { - Log.fatalError(re.getMessage()); - Log.verbose(re); - return 1; - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java deleted file mode 100644 index 488e9106479..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherData.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (c) 2020, 2025, 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.jpackage.internal; - -import jdk.jpackage.internal.model.ConfigException; -import java.io.File; -import java.io.IOException; -import java.lang.module.ModuleReference; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.jar.Attributes; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; - -/** - * Extracts data needed to run application from parameters. - */ -final class LauncherData { - boolean isModular() { - return moduleInfo != null; - } - - String qualifiedClassName() { - return qualifiedClassName; - } - - boolean isClassNameFromMainJar() { - return jarMainClass != null; - } - - String packageName() { - int sepIdx = qualifiedClassName.lastIndexOf('.'); - if (sepIdx < 0) { - return ""; - } - return qualifiedClassName.substring(sepIdx + 1); - } - - String moduleName() { - verifyIsModular(true); - return moduleInfo.name(); - } - - List modulePath() { - verifyIsModular(true); - return modulePath; - } - - Path mainJarName() { - verifyIsModular(false); - return mainJarName; - } - - List classPath() { - return classPath; - } - - String getAppVersion() { - if (isModular()) { - return moduleInfo.version().orElse(null); - } - - return null; - } - - private LauncherData() { - } - - private void verifyIsModular(boolean isModular) { - if ((moduleInfo == null) == isModular) { - throw new IllegalStateException(); - } - } - - static LauncherData create(Map params) throws - ConfigException, IOException { - - final String mainModule = getMainModule(params); - final LauncherData result; - if (mainModule == null) { - result = createNonModular(params); - } else { - result = createModular(mainModule, params); - } - result.initClasspath(params); - return result; - } - - private static LauncherData createModular(String mainModule, - Map params) throws ConfigException, - IOException { - - LauncherData launcherData = new LauncherData(); - - final int sepIdx = mainModule.indexOf("/"); - final String moduleName; - if (sepIdx > 0) { - launcherData.qualifiedClassName = mainModule.substring(sepIdx + 1); - moduleName = mainModule.substring(0, sepIdx); - } else { - moduleName = mainModule; - } - launcherData.modulePath = getModulePath(params); - - // Try to find module in the specified module path list. - ModuleReference moduleRef = JLinkRuntimeBuilder.createModuleFinder( - launcherData.modulePath).find(moduleName).orElse(null); - - if (moduleRef != null) { - launcherData.moduleInfo = ModuleInfo.fromModuleReference(moduleRef); - } else if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) { - // Failed to find module in the specified module path list and - // there is external runtime given to jpackage. - // Lookup module in this runtime. - Path cookedRuntime = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - launcherData.moduleInfo = ModuleInfo.fromCookedRuntime(moduleName, - cookedRuntime).orElse(null); - } - - if (launcherData.moduleInfo == null) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.no-module-in-path"), moduleName), null); - } - - if (launcherData.qualifiedClassName == null) { - launcherData.qualifiedClassName = launcherData.moduleInfo.mainClass().orElse(null); - if (launcherData.qualifiedClassName == null) { - throw new ConfigException(I18N.getString("ERR_NoMainClass"), null); - } - } - - return launcherData; - } - - private static LauncherData createNonModular( - Map params) throws ConfigException, IOException { - LauncherData launcherData = new LauncherData(); - - launcherData.qualifiedClassName = getMainClass(params); - - launcherData.mainJarName = getMainJarName(params); - - Path mainJarDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); - - final Path mainJarPath; - if (launcherData.mainJarName != null && mainJarDir != null) { - mainJarPath = mainJarDir.resolve(launcherData.mainJarName); - if (!Files.exists(mainJarPath)) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.main-jar-does-not-exist"), - launcherData.mainJarName), I18N.getString( - "error.main-jar-does-not-exist.advice")); - } - } else { - mainJarPath = null; - } - - if (launcherData.qualifiedClassName == null) { - if (mainJarPath == null) { - throw new ConfigException(I18N.getString("error.no-main-class"), - I18N.getString("error.no-main-class.advice")); - } - - try (JarFile jf = new JarFile(mainJarPath.toFile())) { - Manifest m = jf.getManifest(); - Attributes attrs = (m != null) ? m.getMainAttributes() : null; - if (attrs != null) { - launcherData.qualifiedClassName = attrs.getValue( - Attributes.Name.MAIN_CLASS); - launcherData.jarMainClass = launcherData.qualifiedClassName; - } - } - } - - if (launcherData.qualifiedClassName == null) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.no-main-class-with-main-jar"), - launcherData.mainJarName), MessageFormat.format( - I18N.getString( - "error.no-main-class-with-main-jar.advice"), - launcherData.mainJarName)); - } - - return launcherData; - } - - private void initClasspath(Map params) - throws IOException { - Path inputDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params); - if (inputDir == null) { - classPath = Collections.emptyList(); - } else { - try (Stream walk = Files.walk(inputDir, Integer.MAX_VALUE)) { - Set jars = walk.filter(Files::isRegularFile) - .filter(file -> file.toString().endsWith(".jar")) - .map(p -> inputDir.toAbsolutePath() - .relativize(p.toAbsolutePath())) - .collect(Collectors.toSet()); - jars.remove(mainJarName); - classPath = jars.stream().sorted().toList(); - } - } - } - - private static String getMainClass(Map params) { - return getStringParam(params, Arguments.CLIOptions.APPCLASS.getId()); - } - - private static Path getMainJarName(Map params) - throws ConfigException { - return getPathParam(params, Arguments.CLIOptions.MAIN_JAR.getId()); - } - - private static String getMainModule(Map params) { - return getStringParam(params, Arguments.CLIOptions.MODULE.getId()); - } - - private static String getStringParam(Map params, - String paramName) { - Optional value = Optional.ofNullable(params.get(paramName)); - return value.map(Object::toString).orElse(null); - } - - private static T getPathParam(String paramName, Supplier func) throws ConfigException { - try { - return func.get(); - } catch (InvalidPathException ex) { - throw new ConfigException(MessageFormat.format(I18N.getString( - "error.not-path-parameter"), paramName, - ex.getLocalizedMessage()), null, ex); - } - } - - private static Path getPathParam(Map params, - String paramName) throws ConfigException { - return getPathParam(paramName, () -> { - String value = getStringParam(params, paramName); - Path result = null; - if (value != null) { - result = Path.of(value); - } - return result; - }); - } - - private static List getModulePath(Map params) - throws ConfigException { - List modulePath = getPathListParameter(Arguments.CLIOptions.MODULE_PATH.getId(), params); - - if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) { - Path runtimePath = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - runtimePath = runtimePath.resolve("lib"); - modulePath = Stream.of(modulePath, List.of(runtimePath)) - .flatMap(List::stream) - .toList(); - } - - return modulePath; - } - - private static List getPathListParameter(String paramName, - Map params) throws ConfigException { - return getPathParam(paramName, () -> - params.get(paramName) instanceof String value ? - Stream.of(value.split(File.pathSeparator)).map(Path::of).toList() : List.of()); - } - - private String qualifiedClassName; - private String jarMainClass; - private Path mainJarName; - private List classPath; - private List modulePath; - private ModuleInfo moduleInfo; -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java new file mode 100644 index 00000000000..0749cc48b9b --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromOptions.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.APPCLASS; +import static jdk.jpackage.internal.cli.StandardOption.ARGUMENTS; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.FILE_ASSOCIATIONS; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.INPUT; +import static jdk.jpackage.internal.cli.StandardOption.JAVA_OPTIONS; +import static jdk.jpackage.internal.cli.StandardOption.LAUNCHER_AS_SERVICE; +import static jdk.jpackage.internal.cli.StandardOption.MAIN_JAR; +import static jdk.jpackage.internal.cli.StandardOption.MODULE; +import static jdk.jpackage.internal.cli.StandardOption.MODULE_PATH; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.stream.IntStream; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationException; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoExtensionsException; +import jdk.jpackage.internal.FileAssociationGroup.FileAssociationNoMimesException; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardFaOption; +import jdk.jpackage.internal.model.CustomLauncherIcon; +import jdk.jpackage.internal.model.DefaultLauncherIcon; +import jdk.jpackage.internal.model.FileAssociation; +import jdk.jpackage.internal.model.Launcher; +import jdk.jpackage.internal.model.LauncherIcon; + +final class LauncherFromOptions { + + LauncherFromOptions() { + } + + LauncherFromOptions faGroupBuilderMutator(BiConsumer v) { + faGroupBuilderMutator = v; + return this; + } + + LauncherFromOptions faMapper(BiFunction v) { + faMapper = v; + return this; + } + + LauncherFromOptions faWithDefaultDescription() { + return faGroupBuilderMutator((faGroupBuilder, launcherBuilder) -> { + if (faGroupBuilder.description().isEmpty()) { + var description = String.format("%s association", launcherBuilder.create().name()); + faGroupBuilder.description(description); + } + }); + } + + Launcher create(Options options) { + final var builder = new LauncherBuilder().defaultIconResourceName(defaultIconResourceName()); + + DESCRIPTION.ifPresentIn(options, builder::description); + builder.icon(toLauncherIcon(ICON.findIn(options).orElse(null))); + LAUNCHER_AS_SERVICE.ifPresentIn(options, builder::isService); + NAME.ifPresentIn(options, builder::name); + + if (PREDEFINED_APP_IMAGE.findIn(options).isEmpty()) { + final var startupInfoBuilder = new LauncherStartupInfoBuilder(); + + INPUT.ifPresentIn(options, startupInfoBuilder::inputDir); + ARGUMENTS.ifPresentIn(options, startupInfoBuilder::defaultParameters); + JAVA_OPTIONS.ifPresentIn(options, startupInfoBuilder::javaOptions); + MAIN_JAR.ifPresentIn(options, startupInfoBuilder::mainJar); + APPCLASS.ifPresentIn(options, startupInfoBuilder::mainClassName); + MODULE.ifPresentIn(options, startupInfoBuilder::moduleName); + MODULE_PATH.ifPresentIn(options, startupInfoBuilder::modulePath); + PREDEFINED_RUNTIME_IMAGE.ifPresentIn(options, startupInfoBuilder::predefinedRuntimeImage); + + builder.startupInfo(startupInfoBuilder.create()); + } + + final var faOptionsList = FILE_ASSOCIATIONS.findIn(options).orElseGet(List::of); + + final var faGroups = IntStream.range(0, faOptionsList.size()).mapToObj(idx -> { + final var faOptions = faOptionsList.get(idx); + + final var faGroupBuilder = FileAssociationGroup.build(); + + StandardFaOption.DESCRIPTION.ifPresentIn(faOptions, faGroupBuilder::description); + StandardFaOption.ICON.ifPresentIn(faOptions, faGroupBuilder::icon); + StandardFaOption.EXTENSIONS.ifPresentIn(faOptions, faGroupBuilder::extensions); + StandardFaOption.CONTENT_TYPE.ifPresentIn(faOptions, faGroupBuilder::mimeTypes); + + faGroupBuilderMutator().ifPresent(mutator -> { + mutator.accept(faGroupBuilder, builder); + }); + + final var faID = idx + 1; + + final FileAssociationGroup faGroup; + try { + faGroup = faGroupBuilder.create(); + } catch (FileAssociationNoMimesException ex) { + throw I18N.buildConfigException() + .message("error.no-content-types-for-file-association", faID) + .advice("error.no-content-types-for-file-association.advice", faID) + .create(); + } catch (FileAssociationNoExtensionsException ex) { + // TODO: Must do something about this condition! + throw new AssertionError(); + } catch (FileAssociationException ex) { + // Should never happen + throw new UnsupportedOperationException(ex); + } + + return faMapper().map(mapper -> { + return new FileAssociationGroup(faGroup.items().stream().map(fa -> { + return mapper.apply(faOptions, fa); + }).toList()); + }).orElse(faGroup); + + }).toList(); + + return builder.faGroups(faGroups).create(); + } + + private Optional> faGroupBuilderMutator() { + return Optional.ofNullable(faGroupBuilderMutator); + } + + private Optional> faMapper() { + return Optional.ofNullable(faMapper); + } + + private static LauncherIcon toLauncherIcon(Path launcherIconPath) { + if (launcherIconPath == null) { + return DefaultLauncherIcon.INSTANCE; + } else if (launcherIconPath.toString().isEmpty()) { + return null; + } else { + return CustomLauncherIcon.create(launcherIconPath); + } + } + + private static String defaultIconResourceName() { + switch (OperatingSystem.current()) { + case WINDOWS -> { + return "JavaApp.ico"; + } + case LINUX -> { + return "JavaApp.png"; + } + case MACOS -> { + return "JavaApp.icns"; + } + default -> { + throw new UnsupportedOperationException(); + } + } + } + + private BiConsumer faGroupBuilderMutator; + private BiFunction faMapper; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java deleted file mode 100644 index b4ce1cdb200..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherFromParams.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2025, 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.jpackage.internal; - -import static jdk.jpackage.internal.I18N.buildConfigException; -import static jdk.jpackage.internal.StandardBundlerParam.ARGUMENTS; -import static jdk.jpackage.internal.StandardBundlerParam.DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FA_CONTENT_TYPE; -import static jdk.jpackage.internal.StandardBundlerParam.FA_DESCRIPTION; -import static jdk.jpackage.internal.StandardBundlerParam.FA_EXTENSIONS; -import static jdk.jpackage.internal.StandardBundlerParam.FA_ICON; -import static jdk.jpackage.internal.StandardBundlerParam.FILE_ASSOCIATIONS; -import static jdk.jpackage.internal.StandardBundlerParam.ICON; -import static jdk.jpackage.internal.StandardBundlerParam.JAVA_OPTIONS; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_AS_SERVICE; -import static jdk.jpackage.internal.StandardBundlerParam.LAUNCHER_DATA; -import static jdk.jpackage.internal.StandardBundlerParam.NAME; -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; -import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier; - -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.stream.IntStream; -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.CustomLauncherIcon; -import jdk.jpackage.internal.model.DefaultLauncherIcon; -import jdk.jpackage.internal.model.FileAssociation; -import jdk.jpackage.internal.model.Launcher; -import jdk.jpackage.internal.model.LauncherIcon; - -record LauncherFromParams(Optional, FileAssociation>> faExtension) { - - LauncherFromParams { - Objects.requireNonNull(faExtension); - } - - LauncherFromParams() { - this(Optional.empty()); - } - - Launcher create(Map params) throws ConfigException { - final var builder = new LauncherBuilder().defaultIconResourceName(defaultIconResourceName()); - - DESCRIPTION.copyInto(params, builder::description); - builder.icon(toLauncherIcon(ICON.findIn(params).orElse(null))); - LAUNCHER_AS_SERVICE.copyInto(params, builder::isService); - NAME.copyInto(params, builder::name); - - if (PREDEFINED_APP_IMAGE.findIn(params).isEmpty()) { - final var startupInfoBuilder = new LauncherStartupInfoBuilder(); - - startupInfoBuilder.launcherData(LAUNCHER_DATA.fetchFrom(params)); - ARGUMENTS.copyInto(params, startupInfoBuilder::defaultParameters); - JAVA_OPTIONS.copyInto(params, startupInfoBuilder::javaOptions); - - builder.startupInfo(startupInfoBuilder.create()); - } - - final var faParamsList = FILE_ASSOCIATIONS.findIn(params).orElseGet(List::of); - - final var faGroups = IntStream.range(0, faParamsList.size()).mapToObj(idx -> { - final var faParams = faParamsList.get(idx); - return toSupplier(() -> { - final var faGroupBuilder = FileAssociationGroup.build(); - - if (OperatingSystem.current() == OperatingSystem.MACOS) { - FA_DESCRIPTION.copyInto(faParams, faGroupBuilder::description); - } else { - faGroupBuilder.description(FA_DESCRIPTION.findIn(faParams).orElseGet(() -> { - return String.format("%s association", toSupplier(builder::create).get().name()); - })); - } - - FA_ICON.copyInto(faParams, faGroupBuilder::icon); - FA_EXTENSIONS.copyInto(faParams, faGroupBuilder::extensions); - FA_CONTENT_TYPE.copyInto(faParams, faGroupBuilder::mimeTypes); - - final var faID = idx + 1; - - final FileAssociationGroup faGroup; - try { - faGroup = faGroupBuilder.create(); - } catch (FileAssociationGroup.FileAssociationNoMimesException ex) { - throw buildConfigException() - .message("error.no-content-types-for-file-association", faID) - .advice("error.no-content-types-for-file-association.advice", faID) - .create(); - } - - if (faExtension.isPresent()) { - return new FileAssociationGroup(faGroup.items().stream().map(fa -> { - return faExtension.get().apply(fa, faParams); - }).toList()); - } else { - return faGroup; - } - }).get(); - }).toList(); - - return builder.faGroups(faGroups).create(); - } - - private static LauncherIcon toLauncherIcon(Path launcherIconPath) { - if (launcherIconPath == null) { - return DefaultLauncherIcon.INSTANCE; - } else if (launcherIconPath.toString().isEmpty()) { - return null; - } else { - return CustomLauncherIcon.create(launcherIconPath); - } - } - - private static String defaultIconResourceName() { - switch (OperatingSystem.current()) { - case WINDOWS -> { - return "JavaApp.ico"; - } - case LINUX -> { - return "JavaApp.png"; - } - case MACOS -> { - return "JavaApp.icns"; - } - default -> { - throw new UnsupportedOperationException(); - } - } - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java index 5273f2d251c..b2fc48af9e4 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/LauncherStartupInfoBuilder.java @@ -24,69 +24,216 @@ */ package jdk.jpackage.internal; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import java.util.function.UnaryOperator; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.stream.Stream; +import jdk.jpackage.internal.model.JPackageException; import jdk.jpackage.internal.model.LauncherJarStartupInfo; import jdk.jpackage.internal.model.LauncherJarStartupInfoMixin; import jdk.jpackage.internal.model.LauncherModularStartupInfo; import jdk.jpackage.internal.model.LauncherModularStartupInfoMixin; -import jdk.jpackage.internal.model.LauncherStartupInfo.Stub; import jdk.jpackage.internal.model.LauncherStartupInfo; final class LauncherStartupInfoBuilder { LauncherStartupInfo create() { - return decorator.apply(new Stub(qualifiedClassName, javaOptions, - defaultParameters, classPath)); + if (moduleName != null) { + return createModular(); + } else if (mainJar != null) { + return createNonModular(); + } else { + throw new JPackageException(I18N.format("ERR_NoEntryPoint")); + } } - LauncherStartupInfoBuilder launcherData(LauncherData launcherData) { - if (launcherData.isModular()) { - decorator = new ModuleStartupInfo(launcherData.moduleName()); - } else { - decorator = new JarStartupInfo(launcherData.mainJarName(), - launcherData.isClassNameFromMainJar()); - } - classPath = launcherData.classPath(); - qualifiedClassName = launcherData.qualifiedClassName(); + LauncherStartupInfoBuilder inputDir(Path v) { + inputDir = v; return this; } LauncherStartupInfoBuilder javaOptions(List v) { + if (v != null) { + v.forEach(Objects::requireNonNull); + } javaOptions = v; return this; } LauncherStartupInfoBuilder defaultParameters(List v) { + if (v != null) { + v.forEach(Objects::requireNonNull); + } defaultParameters = v; return this; } - private static record ModuleStartupInfo(String moduleName) implements UnaryOperator { + LauncherStartupInfoBuilder mainJar(Path v) { + mainJar = v; + return this; + } - @Override - public LauncherStartupInfo apply(LauncherStartupInfo base) { - return LauncherModularStartupInfo.create(base, - new LauncherModularStartupInfoMixin.Stub(moduleName)); + LauncherStartupInfoBuilder mainClassName(String v) { + mainClassName = v; + return this; + } + + LauncherStartupInfoBuilder predefinedRuntimeImage(Path v) { + cookedRuntimePath = v; + return this; + } + + LauncherStartupInfoBuilder moduleName(String v) { + if (v == null) { + moduleName = null; + } else { + var slashIdx = v.indexOf('/'); + if (slashIdx < 0) { + moduleName = v; + } else { + moduleName = v.substring(0, slashIdx); + if (slashIdx < v.length() - 1) { + mainClassName(v.substring(slashIdx + 1)); + } + } + } + return this; + } + + LauncherStartupInfoBuilder modulePath(List v) { + modulePath = v; + return this; + } + + private Optional inputDir() { + return Optional.ofNullable(inputDir); + } + + private Optional mainClassName() { + return Optional.ofNullable(mainClassName); + } + + private Optional cookedRuntimePath() { + return Optional.ofNullable(cookedRuntimePath); + } + + private LauncherStartupInfo createLauncherStartupInfo(String mainClassName, List classpath) { + Objects.requireNonNull(mainClassName); + classpath.forEach(Objects::requireNonNull); + return new LauncherStartupInfo.Stub(mainClassName, + Optional.ofNullable(javaOptions).orElseGet(List::of), + Optional.ofNullable(defaultParameters).orElseGet(List::of), + classpath); + } + + private static List createClasspath(Path inputDir, Set excludes) { + excludes.forEach(Objects::requireNonNull); + try (final var walk = Files.walk(inputDir)) { + return walk.filter(Files::isRegularFile) + .filter(file -> file.getFileName().toString().endsWith(".jar")) + .map(inputDir::relativize) + .filter(Predicate.not(excludes::contains)) + .distinct() + .toList(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); } } - private static record JarStartupInfo(Path jarPath, - boolean isClassNameFromMainJar) implements - UnaryOperator { + private LauncherModularStartupInfo createModular() { + final var fullModulePath = getFullModulePath(); - @Override - public LauncherStartupInfo apply(LauncherStartupInfo base) { - return LauncherJarStartupInfo.create(base, - new LauncherJarStartupInfoMixin.Stub(jarPath, - isClassNameFromMainJar)); - } + // Try to find the module in the specified module path list. + final var moduleInfo = JLinkRuntimeBuilder.createModuleFinder(fullModulePath).find(moduleName) + .map(ModuleInfo::fromModuleReference).or(() -> { + // Failed to find the module in the specified module path list. + return cookedRuntimePath().flatMap(cookedRuntime -> { + // Lookup the module in the external runtime. + return ModuleInfo.fromCookedRuntime(moduleName, cookedRuntime); + }); + }).orElseThrow(() -> { + return I18N.buildConfigException("error.no-module-in-path", moduleName).create(); + }); + + final var effectiveMainClassName = mainClassName().or(moduleInfo::mainClass).orElseThrow(() -> { + return I18N.buildConfigException("ERR_NoMainClass").create(); + }); + + // If module is located in the file system, exclude it from the classpath. + final var classpath = inputDir().map(theInputDir -> { + var classpathExcludes = moduleInfo.fileLocation().filter(moduleFile -> { + return moduleFile.startsWith(theInputDir); + }).map(theInputDir::relativize).map(Set::of).orElseGet(Set::of); + return createClasspath(theInputDir, classpathExcludes); + }).orElseGet(List::of); + + return LauncherModularStartupInfo.create( + createLauncherStartupInfo(effectiveMainClassName, classpath), + new LauncherModularStartupInfoMixin.Stub(moduleInfo.name(), moduleInfo.version())); } - private String qualifiedClassName; + private List getFullModulePath() { + return cookedRuntimePath().map(runtimeImage -> { + return Stream.of(modulePath(), List.of(runtimeImage.resolve("lib"))).flatMap(List::stream).toList(); + }).orElse(modulePath()); + } + + private List modulePath() { + return Optional.ofNullable(modulePath).orElseGet(List::of); + } + + private LauncherJarStartupInfo createNonModular() { + final var theInputDir = inputDir().orElseThrow(); + + final var mainJarPath = theInputDir.resolve(mainJar); + + if (!Files.exists(mainJarPath)) { + throw I18N.buildConfigException() + .message("error.main-jar-does-not-exist", mainJar) + .advice("error.main-jar-does-not-exist.advice") + .create(); + } + + final var effectiveMainClassName = mainClassName().or(() -> { + try (final var jf = new JarFile(mainJarPath.toFile())) { + return Optional.ofNullable(jf.getManifest()).map(Manifest::getMainAttributes).map(attrs -> { + return attrs.getValue(Attributes.Name.MAIN_CLASS); + }); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }).orElseThrow(() -> { + return I18N.buildConfigException() + .message("error.no-main-class-with-main-jar", mainJar) + .advice("error.no-main-class-with-main-jar.advice", mainJar) + .create(); + }); + + return LauncherJarStartupInfo.create( + createLauncherStartupInfo(effectiveMainClassName, createClasspath(theInputDir, Set.of(mainJar))), + new LauncherJarStartupInfoMixin.Stub(mainJar, mainClassName().isEmpty())); + } + + // Modular options + private String moduleName; + private List modulePath; + + // Non-modular options + private Path mainJar; + + // Common options + private Path inputDir; + private String mainClassName; private List javaOptions; private List defaultParameters; - private List classPath; - private UnaryOperator decorator; + private Path cookedRuntimePath; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java new file mode 100644 index 00000000000..97e1274f078 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.BUNDLING_OPERATION_DESCRIPTOR; +import static jdk.jpackage.internal.cli.StandardOption.DEST; +import static jdk.jpackage.internal.cli.StandardOption.MAIN_JAR; +import static jdk.jpackage.internal.cli.StandardOption.MODULE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_RUNTIME_IMAGE; + +import java.nio.file.Path; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardBundlingOperation; + +final class OptionUtils { + + static boolean isRuntimeInstaller(Options options) { + return PREDEFINED_RUNTIME_IMAGE.containsIn(options) + && !PREDEFINED_APP_IMAGE.containsIn(options) + && !MAIN_JAR.containsIn(options) + && !MODULE.containsIn(options); + } + + static Path outputDir(Options options) { + return DEST.getFrom(options); + } + + static StandardBundlingOperation bundlingOperation(Options options) { + return StandardBundlingOperation.valueOf(BUNDLING_OPERATION_DESCRIPTOR.getFrom(options)).orElseThrow(); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java new file mode 100644 index 00000000000..b146e5a7f8d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OptionsTransformer.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal; + +import static jdk.jpackage.internal.cli.StandardOption.ADDITIONAL_LAUNCHERS; +import static jdk.jpackage.internal.cli.StandardOption.APP_VERSION; +import static jdk.jpackage.internal.cli.StandardOption.DESCRIPTION; +import static jdk.jpackage.internal.cli.StandardOption.ICON; +import static jdk.jpackage.internal.cli.StandardOption.NAME; +import static jdk.jpackage.internal.cli.StandardOption.PREDEFINED_APP_IMAGE; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.WithOptionIdentifier; +import jdk.jpackage.internal.model.ApplicationLayout; +import jdk.jpackage.internal.model.ExternalApplication; + +record OptionsTransformer(Options mainOptions, Optional externalApp) { + + OptionsTransformer { + Objects.requireNonNull(mainOptions); + Objects.requireNonNull(externalApp); + } + + OptionsTransformer(Options mainOptions, ApplicationLayout appLayout) { + this(mainOptions, PREDEFINED_APP_IMAGE.findIn(mainOptions).map(appLayout::resolveAt).map(AppImageFile::load)); + } + + Options appOptions() { + return externalApp.map(ea -> { + var overrideOptions = Map.of( + NAME, ea.appName(), + APP_VERSION, ea.appVersion(), + ADDITIONAL_LAUNCHERS, ea.addLaunchers().stream().map(li -> { + return Options.concat(li.extra(), Options.of(Map.of( + NAME, li.name(), + // This should prevent the code building the Launcher instance + // from the Options object from trying to create a startup info object. + PREDEFINED_APP_IMAGE, PREDEFINED_APP_IMAGE.getFrom(mainOptions), + // + // For backward compatibility, descriptions of the additional + // launchers in the predefined app image will be set to + // the application description, if available, or to the name + // of the main launcher in the predefined app image. + // + // All launchers in the predefined app image will have the same description. + // This is wrong and should be revised. + // + DESCRIPTION, DESCRIPTION.findIn(mainOptions).orElseGet(ea::appName) + ))); + }).toList() + ); + return Options.concat( + Options.of(overrideOptions), + ea.extra(), + // Remove icon if any from the application/launcher options. + // If the icon is specified in the main options, it for the installer. + mainOptions.copyWithout(ICON.id()) + ); + }).orElse(mainOptions); + } +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java index 501fd64bdca..8e47b046eb1 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Packager.java @@ -29,7 +29,6 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import jdk.jpackage.internal.model.Package; -import jdk.jpackage.internal.model.PackagerException; final class Packager { @@ -69,7 +68,7 @@ final class Packager { return Objects.requireNonNull(env); } - Path execute(PackagingPipeline.Builder pipelineBuilder) throws PackagerException { + Path execute(PackagingPipeline.Builder pipelineBuilder) { Objects.requireNonNull(pkg); Objects.requireNonNull(env); Objects.requireNonNull(outputDir); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java index 6f4e0d0d2d8..a2750fee260 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java @@ -46,7 +46,6 @@ import jdk.jpackage.internal.model.AppImageLayout; import jdk.jpackage.internal.model.Application; import jdk.jpackage.internal.model.ApplicationLayout; import jdk.jpackage.internal.model.Package; -import jdk.jpackage.internal.model.PackagerException; import jdk.jpackage.internal.pipeline.DirectedEdge; import jdk.jpackage.internal.pipeline.FixedDAG; import jdk.jpackage.internal.pipeline.TaskPipelineBuilder; @@ -62,7 +61,7 @@ final class PackagingPipeline { * @param env the build environment * @param app the application */ - void execute(BuildEnv env, Application app) throws PackagerException { + void execute(BuildEnv env, Application app) { execute(contextMapper.apply(createTaskContext(env, app))); } @@ -81,7 +80,7 @@ final class PackagingPipeline { * @param pkg the package * @param outputDir the output directory for the package file */ - void execute(BuildEnv env, Package pkg, Path outputDir) throws PackagerException { + void execute(BuildEnv env, Package pkg, Path outputDir) { execute((StartupParameters)createPackagingTaskContext(env, pkg, outputDir, taskConfig)); } @@ -91,7 +90,7 @@ final class PackagingPipeline { * * @param startupParameters the pipeline startup parameters */ - void execute(StartupParameters startupParameters) throws PackagerException { + void execute(StartupParameters startupParameters) { execute(contextMapper.apply(createTaskContext((PackagingTaskContext)startupParameters))); } @@ -132,7 +131,7 @@ final class PackagingPipeline { } interface TaskContext extends Predicate { - void execute(TaskAction taskAction) throws IOException, PackagerException; + void execute(TaskAction taskAction) throws IOException; } record AppImageBuildEnv(BuildEnv env, T app) { @@ -161,27 +160,27 @@ final class PackagingPipeline { @FunctionalInterface interface ApplicationImageTaskAction extends TaskAction { - void execute(AppImageBuildEnv env) throws IOException, PackagerException; + void execute(AppImageBuildEnv env) throws IOException; } @FunctionalInterface interface AppImageTaskAction extends TaskAction { - void execute(AppImageBuildEnv env) throws IOException, PackagerException; + void execute(AppImageBuildEnv env) throws IOException; } @FunctionalInterface interface CopyAppImageTaskAction extends TaskAction { - void execute(T pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException, PackagerException; + void execute(T pkg, AppImageLayout srcAppImage, AppImageLayout dstAppImage) throws IOException; } @FunctionalInterface interface PackageTaskAction extends TaskAction { - void execute(PackageBuildEnv env) throws IOException, PackagerException; + void execute(PackageBuildEnv env) throws IOException; } @FunctionalInterface interface NoArgTaskAction extends TaskAction { - void execute() throws IOException, PackagerException; + void execute() throws IOException; } record TaskConfig(Optional action) { @@ -493,7 +492,7 @@ final class PackagingPipeline { return new PackagingTaskContext(BuildEnv.withAppImageLayout(env, dstLayout), pkg, outputDir, srcLayout); } - private void execute(TaskContext context) throws PackagerException { + private void execute(TaskContext context) { final Map> tasks = taskConfig.entrySet().stream().collect(toMap(Map.Entry::getKey, task -> { return createTask(context, task.getKey(), task.getValue()); })); @@ -508,14 +507,8 @@ final class PackagingPipeline { try { builder.create().call(); - } catch (ExceptionBox ex) { - throw new PackagerException(ex.getCause()); - } catch (RuntimeException ex) { - throw ex; - } catch (PackagerException ex) { - throw ex; } catch (Exception ex) { - throw new PackagerException(ex); + throw ExceptionBox.rethrowUnchecked(ex); } } @@ -546,7 +539,7 @@ final class PackagingPipeline { @SuppressWarnings("unchecked") @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { if (taskAction instanceof PackageTaskAction) { ((PackageTaskAction)taskAction).execute(pkgBuildEnv()); } else if (taskAction instanceof CopyAppImageTaskAction) { @@ -600,7 +593,7 @@ final class PackagingPipeline { @SuppressWarnings("unchecked") @Override - public void execute(TaskAction taskAction) throws IOException, PackagerException { + public void execute(TaskAction taskAction) throws IOException { if (taskAction instanceof AppImageTaskAction) { final var taskEnv = pkg.map(PackagingTaskContext::appImageBuildEnv).orElseGet(this::appBuildEnv); ((AppImageTaskAction)taskAction).execute(taskEnv); diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java deleted file mode 100644 index 2b35a6830f8..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java +++ /dev/null @@ -1,513 +0,0 @@ -/* - * Copyright (c) 2014, 2025, 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.jpackage.internal; - - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; -import jdk.jpackage.internal.model.ConfigException; -import jdk.jpackage.internal.model.ExternalApplication; -import static jdk.jpackage.internal.ApplicationLayoutUtils.PLATFORM_APPLICATION_LAYOUT; - -/** - * Standard bundler parameters. - * - * Contains static definitions of all of the common bundler parameters. - * (additional platform specific and mode specific bundler parameters - * are defined in each of the specific bundlers) - * - * Also contains static methods that operate on maps of parameters. - */ -final class StandardBundlerParam { - - private static final String DEFAULT_VERSION = "1.0"; - private static final String DEFAULT_RELEASE = "1"; - private static final String[] DEFAULT_JLINK_OPTIONS = { - "--strip-native-commands", - "--strip-debug", - "--no-man-pages", - "--no-header-files"}; - - static final BundlerParamInfo LAUNCHER_DATA = BundlerParamInfo.createBundlerParam( - LauncherData.class, LauncherData::create); - - static final BundlerParamInfo SOURCE_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.INPUT.getId(), - Path.class, - p -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo OUTPUT_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.OUTPUT.getId(), - Path.class, - p -> Path.of("").toAbsolutePath(), - (s, p) -> Path.of(s) - ); - - // note that each bundler is likely to replace this one with - // their own converter - static final BundlerParamInfo MAIN_JAR = - new BundlerParamInfo<>( - Arguments.CLIOptions.MAIN_JAR.getId(), - Path.class, - params -> LAUNCHER_DATA.fetchFrom(params).mainJarName(), - null - ); - - static final BundlerParamInfo PREDEFINED_APP_IMAGE = - new BundlerParamInfo<>( - Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s)); - - static final BundlerParamInfo PREDEFINED_APP_IMAGE_FILE = BundlerParamInfo.createBundlerParam( - ExternalApplication.class, params -> { - if (hasPredefinedAppImage(params)) { - var appImage = PREDEFINED_APP_IMAGE.fetchFrom(params); - return AppImageFile.load(appImage, PLATFORM_APPLICATION_LAYOUT); - } else { - return null; - } - }); - - static final BundlerParamInfo MAIN_CLASS = - new BundlerParamInfo<>( - Arguments.CLIOptions.APPCLASS.getId(), - String.class, - params -> { - if (isRuntimeInstaller(params)) { - return null; - } else if (hasPredefinedAppImage(params)) { - PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getMainClass(); - } - return LAUNCHER_DATA.fetchFrom(params).qualifiedClassName(); - }, - (s, p) -> s - ); - - static final BundlerParamInfo PREDEFINED_RUNTIME_IMAGE = - new BundlerParamInfo<>( - Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - // this is the raw --app-name arg - used in APP_NAME and INSTALLER_NAME - static final BundlerParamInfo NAME = - new BundlerParamInfo<>( - Arguments.CLIOptions.NAME.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - // this is the application name, either from the app-image (if given), - // the name (if given) derived from the main-class, or the runtime image - static final BundlerParamInfo APP_NAME = - new BundlerParamInfo<>( - "application-name", - String.class, - params -> { - String appName = NAME.fetchFrom(params); - if (hasPredefinedAppImage(params)) { - appName = PREDEFINED_APP_IMAGE_FILE.fetchFrom(params).getLauncherName(); - } else if (appName == null) { - String s = MAIN_CLASS.fetchFrom(params); - if (s != null) { - int idx = s.lastIndexOf("."); - appName = (idx < 0) ? s : s.substring(idx+1); - } else if (isRuntimeInstaller(params)) { - Path f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params); - if (f != null) { - appName = f.getFileName().toString(); - } - } - } - return appName; - }, - (s, p) -> s - ); - - static final BundlerParamInfo INSTALLER_NAME = - new BundlerParamInfo<>( - "installer-name", - String.class, - params -> { - String installerName = NAME.fetchFrom(params); - return (installerName != null) ? installerName : - APP_NAME.fetchFrom(params); - }, - (s, p) -> s - ); - - static final BundlerParamInfo ICON = - new BundlerParamInfo<>( - Arguments.CLIOptions.ICON.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo ABOUT_URL = - new BundlerParamInfo<>( - Arguments.CLIOptions.ABOUT_URL.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo VENDOR = - new BundlerParamInfo<>( - Arguments.CLIOptions.VENDOR.getId(), - String.class, - params -> I18N.getString("param.vendor.default"), - (s, p) -> s - ); - - static final BundlerParamInfo DESCRIPTION = - new BundlerParamInfo<>( - Arguments.CLIOptions.DESCRIPTION.getId(), - String.class, - params -> params.containsKey(APP_NAME.getID()) - ? APP_NAME.fetchFrom(params) - : I18N.getString("param.description.default"), - (s, p) -> s - ); - - static final BundlerParamInfo COPYRIGHT = - new BundlerParamInfo<>( - Arguments.CLIOptions.COPYRIGHT.getId(), - String.class, - params -> MessageFormat.format(I18N.getString( - "param.copyright.default"), new Date()), - (s, p) -> s - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> ARGUMENTS = - new BundlerParamInfo<>( - Arguments.CLIOptions.ARGUMENTS.getId(), - (Class>) (Object) List.class, - params -> Collections.emptyList(), - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> JAVA_OPTIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.JAVA_OPTIONS.getId(), - (Class>) (Object) List.class, - params -> Collections.emptyList(), - (s, p) -> Arrays.asList(s.split("\n\n")) - ); - - static final BundlerParamInfo VERSION = - new BundlerParamInfo<>( - Arguments.CLIOptions.VERSION.getId(), - String.class, - StandardBundlerParam::getDefaultAppVersion, - (s, p) -> s - ); - - static final BundlerParamInfo RELEASE = - new BundlerParamInfo<>( - Arguments.CLIOptions.RELEASE.getId(), - String.class, - params -> DEFAULT_RELEASE, - (s, p) -> s - ); - - public static final BundlerParamInfo LICENSE_FILE = - new BundlerParamInfo<>( - Arguments.CLIOptions.LICENSE_FILE.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo TEMP_ROOT = - new BundlerParamInfo<>( - Arguments.CLIOptions.TEMP_ROOT.getId(), - Path.class, - params -> { - try { - return Files.createTempDirectory("jdk.jpackage"); - } catch (IOException ioe) { - return null; - } - }, - (s, p) -> Path.of(s) - ); - - public static final BundlerParamInfo CONFIG_ROOT = - new BundlerParamInfo<>( - "configRoot", - Path.class, - params -> { - Path root = TEMP_ROOT.fetchFrom(params).resolve("config"); - try { - Files.createDirectories(root); - } catch (IOException ioe) { - return null; - } - return root; - }, - (s, p) -> null - ); - - static final BundlerParamInfo VERBOSE = - new BundlerParamInfo<>( - Arguments.CLIOptions.VERBOSE.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, and we actually do want null - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - - static final BundlerParamInfo RESOURCE_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.RESOURCE_DIR.getId(), - Path.class, - params -> null, - (s, p) -> Path.of(s) - ); - - static final BundlerParamInfo INSTALL_DIR = - new BundlerParamInfo<>( - Arguments.CLIOptions.INSTALL_DIR.getId(), - String.class, - params -> null, - (s, p) -> s - ); - - static final BundlerParamInfo LAUNCHER_AS_SERVICE = - new BundlerParamInfo<>( - Arguments.CLIOptions.LAUNCHER_AS_SERVICE.getId(), - Boolean.class, - params -> false, - // valueOf(null) is false, and we actually do want null - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - true : Boolean.valueOf(s) - ); - - - @SuppressWarnings("unchecked") - static final BundlerParamInfo>> ADD_LAUNCHERS = - new BundlerParamInfo<>( - Arguments.CLIOptions.ADD_LAUNCHER.getId(), - (Class>>) (Object) - List.class, - params -> new ArrayList<>(1), - // valueOf(null) is false, and we actually do want null - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo - >> FILE_ASSOCIATIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(), - (Class>>) (Object) - List.class, - params -> new ArrayList<>(1), - // valueOf(null) is false, and we actually do want null - (s, p) -> null - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> FA_EXTENSIONS = - new BundlerParamInfo<>( - "fileAssociation.extension", - (Class>) (Object) List.class, - params -> null, // null means not matched to an extension - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> FA_CONTENT_TYPE = - new BundlerParamInfo<>( - "fileAssociation.contentType", - (Class>) (Object) List.class, - params -> null, - // null means not matched to a content/mime type - (s, p) -> Arrays.asList(s.split("(,|\\s)+")) - ); - - static final BundlerParamInfo FA_DESCRIPTION = - new BundlerParamInfo<>( - "fileAssociation.description", - String.class, - p -> null, - (s, p) -> s - ); - - static final BundlerParamInfo FA_ICON = - new BundlerParamInfo<>( - "fileAssociation.icon", - Path.class, - ICON::fetchFrom, - (s, p) -> Path.of(s) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> DMG_CONTENT = - new BundlerParamInfo<>( - Arguments.CLIOptions.DMG_CONTENT.getId(), - (Class>) (Object)List.class, - p -> Collections.emptyList(), - (s, p) -> Stream.of(s.split(",")).map(Path::of).toList() - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> APP_CONTENT = - new BundlerParamInfo<>( - Arguments.CLIOptions.APP_CONTENT.getId(), - (Class>) (Object)List.class, - p->Collections.emptyList(), - (s, p) -> Stream.of(s.split(",")).map(Path::of).toList() - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> MODULE_PATH = - new BundlerParamInfo<>( - Arguments.CLIOptions.MODULE_PATH.getId(), - (Class>) (Object)List.class, - p -> JLinkRuntimeBuilder.ensureBaseModuleInModulePath(List.of()), - (s, p) -> { - List modulePath = Stream.of(s.split(File.pathSeparator)) - .map(Path::of) - .toList(); - return JLinkRuntimeBuilder.ensureBaseModuleInModulePath(modulePath); - }); - - static final BundlerParamInfo MODULE = - new BundlerParamInfo<>( - Arguments.CLIOptions.MODULE.getId(), - String.class, - p -> null, - (s, p) -> { - return String.valueOf(s); - }); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> ADD_MODULES = - new BundlerParamInfo<>( - Arguments.CLIOptions.ADD_MODULES.getId(), - (Class>) (Object) Set.class, - p -> new LinkedHashSet(), - (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) - ); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> JLINK_OPTIONS = - new BundlerParamInfo<>( - Arguments.CLIOptions.JLINK_OPTIONS.getId(), - (Class>) (Object) List.class, - p -> Arrays.asList(DEFAULT_JLINK_OPTIONS), - (s, p) -> null); - - @SuppressWarnings("unchecked") - static final BundlerParamInfo> LIMIT_MODULES = - new BundlerParamInfo<>( - "limit-modules", - (Class>) (Object) Set.class, - p -> new LinkedHashSet(), - (s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(","))) - ); - - static final BundlerParamInfo SIGN_BUNDLE = - new BundlerParamInfo<>( - Arguments.CLIOptions.MAC_SIGN.getId(), - Boolean.class, - params -> false, - (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ? - null : Boolean.valueOf(s) - ); - - static boolean isRuntimeInstaller(Map params) { - if (params.containsKey(MODULE.getID()) || - params.containsKey(MAIN_JAR.getID()) || - params.containsKey(PREDEFINED_APP_IMAGE.getID())) { - return false; // we are building or are given an application - } - // runtime installer requires --runtime-image, if this is false - // here then we should have thrown error validating args. - return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID()); - } - - static boolean hasPredefinedAppImage(Map params) { - return params.containsKey(PREDEFINED_APP_IMAGE.getID()); - } - - private static String getDefaultAppVersion(Map params) { - String appVersion = DEFAULT_VERSION; - - if (isRuntimeInstaller(params)) { - return appVersion; - } - - LauncherData launcherData = null; - try { - launcherData = LAUNCHER_DATA.fetchFrom(params); - } catch (RuntimeException ex) { - if (ex.getCause() instanceof ConfigException) { - return appVersion; - } - throw ex; - } - - if (launcherData.isModular()) { - String moduleVersion = launcherData.getAppVersion(); - if (moduleVersion != null) { - Log.verbose(MessageFormat.format(I18N.getString( - "message.module-version"), - moduleVersion, - launcherData.moduleName())); - appVersion = moduleVersion; - } - } - - return appVersion; - } -} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java similarity index 53% rename from src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java index 762b65b530e..50d1701bf0d 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/TempDirectory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -22,36 +22,51 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ - package jdk.jpackage.internal; +import java.io.Closeable; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.Map; +import jdk.jpackage.internal.cli.Options; +import jdk.jpackage.internal.cli.StandardOption; import jdk.jpackage.internal.util.FileUtils; +final class TempDirectory implements Closeable { -/** - * AbstractBundler - * - * This is the base class all bundlers extend from. - * It contains methods and parameters common to all bundlers. - * The concrete implementations are in the platform specific bundlers. - */ -abstract class AbstractBundler implements Bundler { + TempDirectory(Options options) throws IOException { + final var tempDir = StandardOption.TEMP_ROOT.findIn(options); + if (tempDir.isPresent()) { + this.path = tempDir.orElseThrow(); + this.options = options; + } else { + this.path = Files.createTempDirectory("jdk.jpackage"); + this.options = options.copyWithDefaultValue(StandardOption.TEMP_ROOT, path); + } - @Override - public String toString() { - return getName(); + deleteOnClose = tempDir.isEmpty(); + } + + Options options() { + return options; + } + + Path path() { + return path; + } + + boolean deleteOnClose() { + return deleteOnClose; } @Override - public void cleanup(Map params) { - try { - FileUtils.deleteRecursive( - StandardBundlerParam.TEMP_ROOT.fetchFrom(params)); - } catch (IOException e) { - Log.verbose(e.getMessage()); + public void close() throws IOException { + if (deleteOnClose) { + FileUtils.deleteRecursive(path); } } + + private final Path path; + private final Options options; + private final boolean deleteOnClose; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java deleted file mode 100644 index 89a2e4b56fe..00000000000 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2018, 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.jpackage.internal; - -import java.util.EnumSet; -import java.util.HashMap; - -import jdk.internal.util.OperatingSystem; -import jdk.jpackage.internal.Arguments.CLIOptions; - -/** - * ValidOptions - * - * Two basic methods for validating command line options. - * - * initArgs() - * Computes the Map of valid options for each mode on this Platform. - * - * checkIfSupported(CLIOptions arg) - * Determine if the given arg is valid on this platform. - * - * checkIfImageSupported(CLIOptions arg) - * Determine if the given arg is valid for creating app image. - * - * checkIfInstallerSupported(CLIOptions arg) - * Determine if the given arg is valid for creating installer. - * - * checkIfSigningSupported(CLIOptions arg) - * Determine if the given arg is valid for signing app image. - * - */ -class ValidOptions { - - enum USE { - ALL, // valid in all cases - LAUNCHER, // valid when creating a launcher - INSTALL, // valid when creating an installer - SIGN, // valid when signing is requested - } - - private static final HashMap> options = new HashMap<>(); - - // initializing list of mandatory arguments - static { - put(CLIOptions.NAME.getId(), USE.ALL); - put(CLIOptions.VERSION.getId(), USE.ALL); - put(CLIOptions.OUTPUT.getId(), USE.ALL); - put(CLIOptions.TEMP_ROOT.getId(), USE.ALL); - put(CLIOptions.VERBOSE.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), USE.ALL); - put(CLIOptions.RESOURCE_DIR.getId(), USE.ALL); - put(CLIOptions.DESCRIPTION.getId(), USE.ALL); - put(CLIOptions.VENDOR.getId(), USE.ALL); - put(CLIOptions.COPYRIGHT.getId(), USE.ALL); - put(CLIOptions.PACKAGE_TYPE.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.ICON.getId(), USE.ALL); - - put(CLIOptions.INPUT.getId(), USE.LAUNCHER); - put(CLIOptions.MODULE.getId(), USE.LAUNCHER); - put(CLIOptions.MODULE_PATH.getId(), USE.LAUNCHER); - put(CLIOptions.ADD_MODULES.getId(), USE.LAUNCHER); - put(CLIOptions.MAIN_JAR.getId(), USE.LAUNCHER); - put(CLIOptions.APPCLASS.getId(), USE.LAUNCHER); - put(CLIOptions.ARGUMENTS.getId(), USE.LAUNCHER); - put(CLIOptions.JAVA_OPTIONS.getId(), USE.LAUNCHER); - put(CLIOptions.ADD_LAUNCHER.getId(), USE.LAUNCHER); - put(CLIOptions.JLINK_OPTIONS.getId(), USE.LAUNCHER); - put(CLIOptions.APP_CONTENT.getId(), USE.LAUNCHER); - - put(CLIOptions.LICENSE_FILE.getId(), USE.INSTALL); - put(CLIOptions.INSTALL_DIR.getId(), USE.INSTALL); - put(CLIOptions.PREDEFINED_APP_IMAGE.getId(), - (OperatingSystem.isMacOS()) ? - EnumSet.of(USE.INSTALL, USE.SIGN) : - EnumSet.of(USE.INSTALL)); - put(CLIOptions.LAUNCHER_AS_SERVICE.getId(), USE.INSTALL); - - put(CLIOptions.ABOUT_URL.getId(), USE.INSTALL); - - put(CLIOptions.FILE_ASSOCIATIONS.getId(), - (OperatingSystem.isMacOS()) ? USE.ALL : USE.INSTALL); - - if (OperatingSystem.isWindows()) { - put(CLIOptions.WIN_CONSOLE_HINT.getId(), USE.LAUNCHER); - - put(CLIOptions.WIN_HELP_URL.getId(), USE.INSTALL); - put(CLIOptions.WIN_UPDATE_URL.getId(), USE.INSTALL); - - put(CLIOptions.WIN_MENU_HINT.getId(), USE.INSTALL); - put(CLIOptions.WIN_MENU_GROUP.getId(), USE.INSTALL); - put(CLIOptions.WIN_SHORTCUT_HINT.getId(), USE.INSTALL); - put(CLIOptions.WIN_SHORTCUT_PROMPT.getId(), USE.INSTALL); - put(CLIOptions.WIN_DIR_CHOOSER.getId(), USE.INSTALL); - put(CLIOptions.WIN_UPGRADE_UUID.getId(), USE.INSTALL); - put(CLIOptions.WIN_PER_USER_INSTALLATION.getId(), - USE.INSTALL); - } - - if (OperatingSystem.isMacOS()) { - put(CLIOptions.MAC_SIGN.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_BUNDLE_NAME.getId(), USE.ALL); - put(CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), USE.ALL); - put(CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_SIGNING_KEY_NAME.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_APP_IMAGE_SIGN_IDENTITY.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_INSTALLER_SIGN_IDENTITY.getId(), - EnumSet.of(USE.INSTALL, USE.SIGN)); - put(CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.MAC_APP_STORE.getId(), USE.ALL); - put(CLIOptions.MAC_CATEGORY.getId(), USE.ALL); - put(CLIOptions.MAC_ENTITLEMENTS.getId(), - EnumSet.of(USE.ALL, USE.SIGN)); - put(CLIOptions.DMG_CONTENT.getId(), USE.INSTALL); - } - - if (OperatingSystem.isLinux()) { - put(CLIOptions.LINUX_BUNDLE_NAME.getId(), USE.INSTALL); - put(CLIOptions.LINUX_DEB_MAINTAINER.getId(), USE.INSTALL); - put(CLIOptions.LINUX_CATEGORY.getId(), USE.INSTALL); - put(CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), USE.INSTALL); - put(CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(), - USE.INSTALL); - put(CLIOptions.LINUX_MENU_GROUP.getId(), USE.INSTALL); - put(CLIOptions.RELEASE.getId(), USE.INSTALL); - put(CLIOptions.LINUX_SHORTCUT_HINT.getId(), USE.INSTALL); - } - } - - static boolean checkIfSupported(CLIOptions arg) { - return options.containsKey(arg.getId()); - } - - static boolean checkIfImageSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.ALL) || - value.contains(USE.LAUNCHER) || - value.contains(USE.SIGN); - } - - static boolean checkIfInstallerSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.ALL) || value.contains(USE.INSTALL); - } - - static boolean checkIfSigningSupported(CLIOptions arg) { - EnumSet value = options.get(arg.getId()); - return value.contains(USE.SIGN); - } - - private static EnumSet put(String key, USE value) { - return options.put(key, EnumSet.of(value)); - } - - private static EnumSet put(String key, EnumSet value) { - return options.put(key, value); - } -} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java similarity index 76% rename from src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java index 31759c8c529..34019ff9e81 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBuildEnvFromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/AdditionalLauncher.java @@ -22,13 +22,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package jdk.jpackage.internal; -import jdk.jpackage.internal.model.MacPackage; +package jdk.jpackage.internal.cli; -final class MacBuildEnvFromParams { +import java.nio.file.Path; - static final BundlerParamInfo BUILD_ENV = BundlerParamInfo.createBundlerParam(BuildEnv.class, params -> { - return BuildEnvFromParams.create(params, MacPackagingPipeline.APPLICATION_LAYOUT::resolveAt, MacPackage::guessRuntimeLayout); - }); + +record AdditionalLauncher(String name, Path propertyFile) { } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java new file mode 100644 index 00000000000..33525f3e54d --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationModifier.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal.cli; + +/** + * Modifiers for jpackage operations. + */ +enum BundlingOperationModifier implements OptionScope { + /** + * Create runtime native bundle. + */ + BUNDLE_RUNTIME, + + /** + * Create native bundle from the predefined app image. + */ + BUNDLE_PREDEFINED_APP_IMAGE, + + ; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java new file mode 100644 index 00000000000..04af5865baa --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/BundlingOperationOptionScope.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal.cli; + + +import jdk.jpackage.internal.model.BundlingOperationDescriptor; + +/** + * Bundling operation scope. + *

+ * The scope of bundling operations. E.g., app image or native package bundling. + */ +interface BundlingOperationOptionScope extends OptionScope { + BundlingOperationDescriptor descriptor(); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java new file mode 100644 index 00000000000..09ff0997c14 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/CliBundlingEnvironment.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal.cli; + +import java.util.NoSuchElementException; +import jdk.jpackage.internal.model.BundlingEnvironment; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; + +/** + * CLI bundling environment. + */ +public interface CliBundlingEnvironment extends BundlingEnvironment { + + /** + * Requests to run a bundling operation denoted with the given descriptor with + * the given values of command line options. + * + * @param op the descriptor of the requested bundling operation + * @param cmdline the validated values of the command line options + * @throws NoSuchElementException if the specified descriptor is not one of the + * items in the list returned by + * {@link #supportedOperations()} method + */ + void createBundle(BundlingOperationDescriptor op, Options cmdline); +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java new file mode 100644 index 00000000000..91342500277 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/DefaultOptions.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + + +final class DefaultOptions implements Options { + + DefaultOptions(Map values) { + this(values, Optional.empty()); + } + + DefaultOptions( + Map values, + Predicate optionNamesFilter) { + + this(values, Optional.of(optionNamesFilter)); + } + + DefaultOptions( + Map values, + Optional> optionNamesFilter) { + + map = values.entrySet().stream().collect(toUnmodifiableMap(e -> { + return e.getKey().id(); + }, e -> { + return new OptionIdentifierWithValue(e.getKey(), e.getValue()); + })); + + var optionNamesStream = optionNames(values.keySet().stream()); + optionNames = optionNamesFilter.map(optionNamesStream::filter).orElse(optionNamesStream) + .collect(toUnmodifiableSet()); + } + + private DefaultOptions(Snapshot snapshot) { + map = snapshot.map(); + optionNames = snapshot.optionNames(); + } + + static DefaultOptions create(Snapshot snapshot) { + var options = new DefaultOptions(snapshot); + + var mapOptionNames = optionNames( + options.map.values().stream().map(OptionIdentifierWithValue::withId) + ).collect(toUnmodifiableSet()); + + for (var e : options.map.entrySet()) { + if (e.getKey() != e.getValue().withId().id()) { + throw new IllegalArgumentException("Corrupted options map"); + } + } + + if (!mapOptionNames.containsAll(snapshot.optionNames())) { + throw new IllegalArgumentException("Unexpected option names"); + } + return options; + } + + @Override + public Optional find(OptionIdentifier id) { + return Optional.ofNullable(map.get(Objects.requireNonNull(id))).map(OptionIdentifierWithValue::value); + } + + @Override + public boolean contains(OptionName optionName) { + return optionNames.contains(Objects.requireNonNull(optionName)); + } + + @Override + public Set ids() { + return Collections.unmodifiableSet(map.keySet()); + } + + @Override + public DefaultOptions copyWithout(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), false); + } + + @Override + public DefaultOptions copyWith(Iterable ids) { + return copy(StreamSupport.stream(ids.spliterator(), false), true); + } + + DefaultOptions add(DefaultOptions other) { + return new DefaultOptions(new Snapshot(Stream.of(this, other).flatMap(v -> { + return v.map.values().stream(); + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::id, x -> x, (first, _) -> { + return first; + })), Stream.of(this, other) + .map(DefaultOptions::optionNames) + .flatMap(Collection::stream) + .collect(toUnmodifiableSet()))); + } + + Set optionNames() { + return optionNames; + } + + Set withOptionIdentifierSet() { + return map.values().stream() + .map(OptionIdentifierWithValue::withId) + .collect(toUnmodifiableSet()); + } + + record Snapshot(Map map, Set optionNames) { + Snapshot { + Objects.requireNonNull(map); + Objects.requireNonNull(optionNames); + } + } + + record OptionIdentifierWithValue(WithOptionIdentifier withId, Object value) { + OptionIdentifierWithValue { + Objects.requireNonNull(withId); + Objects.requireNonNull(value); + } + + OptionIdentifier id() { + return withId.id(); + } + + OptionIdentifierWithValue copyWithValue(Object value) { + return new OptionIdentifierWithValue(withId, value); + } + } + + private DefaultOptions copy(Stream ids, boolean includes) { + var includeIds = ids.collect(toUnmodifiableSet()); + return new DefaultOptions(map.values().stream().filter(v -> { + return includeIds.contains(v.id()) == includes; + }).collect(toUnmodifiableMap(OptionIdentifierWithValue::withId, OptionIdentifierWithValue::value))); + } + + private static Stream optionNames(Stream options) { + return options.map(v -> { + Optional> spec; + switch (v) { + case Option option -> { + spec = Optional.of(option.spec()); + } + case OptionValue optionValue -> { + spec = optionValue.asOption().map(Option::spec); + } + default -> { + spec = Optional.empty(); + } + } + return spec; + }).filter(Optional::isPresent).map(Optional::get).map(OptionSpec::names).flatMap(Collection::stream); + } + + static final DefaultOptions EMPTY = new DefaultOptions(Map.of()); + + private final Map map; + private final Set optionNames; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java new file mode 100644 index 00000000000..1d8a5eefc79 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/HelpFormatter.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +/** + * Generic help formatter. + */ +final class HelpFormatter { + + private HelpFormatter(List optionGroups, OptionGroupFormatter formatter) { + this.optionGroups = Objects.requireNonNull(optionGroups); + this.formatter = Objects.requireNonNull(formatter); + } + + void format(Consumer sink) { + for (var group : optionGroups) { + formatter.format(group, sink); + } + } + + static Builder build() { + return new Builder(); + } + + + static final class Builder { + + private Builder() { + } + + HelpFormatter create() { + return new HelpFormatter(groups, validatedGroupFormatter()); + } + + Builder groups(Collection v) { + groups.addAll(v); + return this; + } + + Builder groups(OptionGroup... v) { + return groups(List.of(v)); + } + + Builder groupFormatter(OptionGroupFormatter v) { + groupFormatter = v; + return this; + } + + private OptionGroupFormatter validatedGroupFormatter() { + return Optional.ofNullable(groupFormatter).orElseGet(Builder::createConsoleFormatter); + } + + private static OptionGroupFormatter createConsoleFormatter() { + return new ConsoleOptionGroupFormatter(new ConsoleOptionFormatter(2, 10)); + } + + private final List groups = new ArrayList<>(); + private OptionGroupFormatter groupFormatter; + } + + + interface OptionFormatter { + + public default void format(OptionSpec optionSpec, Consumer sink) { + format(optionSpec.names().stream().map(OptionName::formatForCommandLine).collect(Collectors.joining(" ")), + optionSpec.valuePattern(), + optionSpec.description(), sink); + } + + void format(String optionNames, Optional valuePattern, String description, Consumer sink); + } + + interface OptionGroupFormatter { + + default void format(OptionGroup group, Consumer sink) { + formatHeader(group.name(), sink); + formatBody(group.options(), sink); + } + + void formatHeader(String gropName, Consumer sink); + + void formatBody(Iterable> optionSpecs, Consumer sink); + } + + + record ConsoleOptionFormatter(int nameOffset, int descriptionOffset) implements OptionFormatter { + + @Override + public void format(String optionNames, Optional valuePattern, String description, Consumer sink) { + sink.accept(" ".repeat(nameOffset)); + sink.accept(optionNames); + valuePattern.map(v -> " " + v).ifPresent(sink); + eol(sink); + final var descriptionOffsetStr = " ".repeat(descriptionOffset); + Stream.of(description.split("\\R")).map(line -> { + return descriptionOffsetStr + line; + }).forEach(line -> { + sink.accept(line); + eol(sink); + }); + } + } + + + record ConsoleOptionGroupFormatter(OptionFormatter optionFormatter) implements OptionGroupFormatter { + + ConsoleOptionGroupFormatter { + Objects.requireNonNull(optionFormatter); + } + + @Override + public void formatHeader(String groupName, Consumer sink) { + Objects.requireNonNull(groupName); + eol(sink); + sink.accept(groupName + ":"); + eol(sink); + } + + @Override + public void formatBody(Iterable> optionSpecs, Consumer sink) { + optionSpecs.forEach(optionSpec -> { + optionFormatter.format(optionSpec, sink); + }); + } + } + + + record OptionGroup(String name, List> options) { + + OptionGroup { + Objects.requireNonNull(name); + Objects.requireNonNull(options); + } + } + + + static Consumer eol(Consumer sink) { + sink.accept(System.lineSeparator()); + return sink; + } + + + private final List optionGroups; + private final OptionGroupFormatter formatter; +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java similarity index 56% rename from src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java rename to src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java index 5c912728c32..63c2b88039f 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/I18N.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, 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 @@ -22,31 +22,34 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +package jdk.jpackage.internal.cli; -package jdk.jpackage.internal; - -import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; - +import java.util.List; import java.util.Map; -import jdk.jpackage.internal.model.ConfigException; +import jdk.internal.util.OperatingSystem; +import jdk.jpackage.internal.util.MultiResourceBundle; +import jdk.jpackage.internal.util.StringBundle; -public abstract class MacBaseInstallerBundler extends AbstractBundler { +final class I18N { - public MacBaseInstallerBundler() { - appImageBundler = new MacAppBundler(); + private I18N() { } - protected void validateAppImageAndBundeler( - Map params) throws ConfigException { - if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) { - appImageBundler.validate(params); - } + static String format(String key, Object ... args) { + return BUNDLE.format(key, args); } - @Override - public String getBundleType() { - return "INSTALLER"; - } + private static final StringBundle BUNDLE; - private final Bundler appImageBundler; + static { + var prefix = "jdk.jpackage.internal.resources."; + BUNDLE = StringBundle.fromResourceBundle(MultiResourceBundle.create( + prefix + "MainResources", + Map.of( + OperatingSystem.LINUX, List.of(prefix + "LinuxResources"), + OperatingSystem.MACOS, List.of(prefix + "MacResources"), + OperatingSystem.WINDOWS, List.of(prefix + "WinResources") + ) + )); + } } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java new file mode 100644 index 00000000000..57b92471e4a --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/cli/JOptSimpleOptionsBuilder.java @@ -0,0 +1,817 @@ +/* + * Copyright (c) 2025, 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.jpackage.internal.cli; + +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import jdk.internal.joptsimple.ArgumentAcceptingOptionSpec; +import jdk.internal.joptsimple.OptionParser; +import jdk.internal.joptsimple.OptionSet; +import jdk.jpackage.internal.cli.DefaultOptions.OptionIdentifierWithValue; +import jdk.jpackage.internal.cli.DefaultOptions.Snapshot; +import jdk.jpackage.internal.cli.OptionSpec.MergePolicy; +import jdk.jpackage.internal.util.Result; + + +/** + * Builds an instance of {@link Options} interface backed with joptsimple command + * line parser. + * + * Two types of command line argument processing are supported: + *
    + *
  1. Parse command line. Parsed data is stored as a map of strings. + *
  2. Convert strings to objects. Parsed data is stored as a map of objects. + *
+ */ +final class JOptSimpleOptionsBuilder { + + Function> create() { + return createJOptSimpleParser()::parse; + } + + JOptSimpleOptionsBuilder options(Collection v) { + v.stream().map(u -> { + switch (u) { + case Option o -> { + return o; + } + case OptionValue ov -> { + return ov.getOption(); + } + default -> { + throw new IllegalArgumentException(); + } + } + }).forEach(options::add); + return this; + } + + JOptSimpleOptionsBuilder options(WithOptionIdentifier... v) { + return options(List.of(v)); + } + + JOptSimpleOptionsBuilder optionSpecMapper(UnaryOperator> v) { + optionSpecMapper = v; + return this; + } + + JOptSimpleOptionsBuilder jOptSimpleParserErrorHandler(Function v) { + jOptSimpleParserErrorHandler = v; + return this; + } + + private JOptSimpleParser createJOptSimpleParser() { + return JOptSimpleParser.create(options, Optional.ofNullable(optionSpecMapper), + Optional.ofNullable(jOptSimpleParserErrorHandler)); + } + + + static final class ConvertedOptionsBuilder { + + private ConvertedOptionsBuilder(TypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Options create() { + return impl; + } + + ConvertedOptionsBuilder copyWithExcludes(Collection v) { + return new ConvertedOptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final TypedOptions impl; + } + + + static final class OptionsBuilder { + + private OptionsBuilder(UntypedOptions options) { + impl = Objects.requireNonNull(options); + } + + Result convertedOptions() { + return impl.toTypedOptions().map(ConvertedOptionsBuilder::new); + } + + Options create() { + return impl; + } + + OptionsBuilder copyWithExcludes(Collection v) { + return new OptionsBuilder(impl.copyWithout(v)); + } + + List nonOptionArguments() { + return impl.nonOptionArguments(); + } + + List detectedOptions() { + return impl.detectedOptions(); + } + + private final UntypedOptions impl; + } + + + enum JOptSimpleErrorType { + + // jdk.internal.joptsimple.UnrecognizedOptionException + UNRECOGNIZED_OPTION(() -> { + new OptionParser(false).parse("--foo"); + }), + + // jdk.internal.joptsimple.OptionMissingRequiredArgumentException + OPTION_MISSING_REQUIRED_ARGUMENT(() -> { + var parser = new OptionParser(false); + parser.accepts("foo").withRequiredArg(); + parser.parse("--foo"); + }), + ; + + JOptSimpleErrorType(Runnable initializer) { + try { + initializer.run(); + // Should never get to this point as the above line is expected to throw + // an exception of type `jdk.internal.joptsimple.OptionException`. + throw new AssertionError(); + } catch (jdk.internal.joptsimple.OptionException ex) { + type = ex.getClass(); + } + } + + private final Class type; + } + + + record JOptSimpleError(JOptSimpleErrorType type, OptionName optionName) { + + JOptSimpleError { + Objects.requireNonNull(type); + Objects.requireNonNull(optionName); + } + + static JOptSimpleError create(jdk.internal.joptsimple.OptionException ex) { + var optionName = OptionName.of(ex.options().getFirst()); + return Stream.of(JOptSimpleErrorType.values()).filter(v -> { + return v.type.isInstance(ex); + }).findFirst().map(v -> { + return new JOptSimpleError(v, optionName); + }).orElseThrow(); + } + } + + + private record JOptSimpleParser( + OptionParser parser, + Map> optionMap, + Optional> jOptSimpleParserErrorHandler) { + + private JOptSimpleParser { + Objects.requireNonNull(parser); + Objects.requireNonNull(optionMap); + Objects.requireNonNull(jOptSimpleParserErrorHandler); + } + + Result parse(String... args) { + return applyParser(parser, args).map(optionSet -> { + final OptionSet mergerOptionSet; + if (optionMap.values().stream().allMatch(spec -> spec.names().size() == 1)) { + // No specs with multiple names, merger not needed. + mergerOptionSet = optionSet; + } else { + final var parser2 = createOptionParser(); + final var optionSpecApplier = new OptionSpecApplier(); + for (final var spec : optionMap.values()) { + optionSpecApplier.applyToParser(parser2, spec); + } + + mergerOptionSet = parser2.parse(args); + } + return new OptionsBuilder(new UntypedOptions(optionSet, mergerOptionSet, optionMap)); + }); + } + + static JOptSimpleParser create(Iterable