From bbaa2d6e617674b0cfa642cefac3175cebe659b8 Mon Sep 17 00:00:00 2001 From: Nikita Gubarkov Date: Thu, 30 May 2024 20:59:18 +0400 Subject: [PATCH] JBR-6357 JBR API v3 JBR API frontend is moved into a separate repository. Rewritten proxy generation, bridges removed, invokedynamic is used instead. Mapping is now specified using annotations. Support for extension methods. Support for arrays and generics. Added JBR API implementation version. JBR-7232 Refactor deriveFontWithFeatures & JBRFileDialog JBR API (cherry picked from commit a4804efa962ce46e0ca471f0dc5245734eacb77a) --- .gitignore | 1 + jb/jbr-api.version | 1 + .../idea-project-files/jetbrains.api.iml | 13 - jb/project/idea-project-files/modules.xml | 1 - .../tools/common/scripts/build-jbr-api.sh | 18 - .../tools/linux/scripts/mkimages_aarch64.sh | 3 +- .../tools/linux/scripts/mkimages_x64.sh | 3 +- .../tools/linux/scripts/mkimages_x86.sh | 3 +- jb/project/tools/mac/scripts/mkimages.sh | 3 +- .../tools/windows/scripts/mkimages_aarch64.sh | 4 +- .../tools/windows/scripts/mkimages_x64.sh | 4 +- .../tools/windows/scripts/mkimages_x86.sh | 4 +- .../tools/windows/scripts/pack_aarch64.sh | 1 - jb/project/tools/windows/scripts/pack_x64.sh | 1 - jb/project/tools/windows/scripts/pack_x86.sh | 1 - make/CompileJavaModules.gmk | 1 + make/CompileToolsJdk.gmk | 18 +- make/JBRApi.gmk | 100 +-- make/common/JavaCompilation.gmk | 16 +- .../build/tools/jbrapi/JBRApiPlugin.java | 451 +++++++++++ .../com/jetbrains/base/JBRApiModule.java | 43 -- .../jetbrains/bootstrap/JBRApiBootstrap.java | 39 +- .../com/jetbrains/exported/JBRApi.java | 92 +++ .../com/jetbrains/exported/JBRApiSupport.java | 63 ++ .../com/jetbrains/internal/ASMUtils.java | 26 +- .../com/jetbrains/internal/AccessContext.java | 140 ++++ .../com/jetbrains/internal/JBRApi.java | 445 ++++------- .../com/jetbrains/internal/Mapping.java | 441 +++++++++++ .../classes/com/jetbrains/internal/Proxy.java | 350 +++++---- .../internal/ProxyDependencyManager.java | 210 ------ .../jetbrains/internal/ProxyGenerator.java | 701 ++++++++---------- .../com/jetbrains/internal/ProxyInfo.java | 123 --- .../jetbrains/internal/ProxyRepository.java | 341 +++++++++ .../internal/RegisteredProxyInfo.java | 42 -- .../classes/com/jetbrains/internal/Utils.java | 49 ++ .../share/classes/java/lang/Throwable.java | 4 +- .../java/lang/invoke/MethodHandleImpl.java | 5 + .../internal/access/JavaLangInvokeAccess.java | 8 + src/java.base/share/classes/module-info.java | 3 +- .../sun/lwawt/macosx/CPlatformWindow.java | 63 +- .../desktop/ConstrainableGraphics2D.java | 2 + .../com/jetbrains/desktop/FontExtensions.java | 65 -- .../com/jetbrains/desktop/JBRApiModule.java | 71 -- .../com/jetbrains/desktop/JBRFileDialog.java | 7 +- .../desktop/JBRGraphicsDelegate.java | 3 + .../share/classes/java/awt/Desktop.java | 5 +- .../share/classes/java/awt/Font.java | 177 +++-- .../classes/java/awt/GraphicsEnvironment.java | 5 +- .../share/classes/java/awt/Window.java | 141 +--- .../share/classes/sun/font/FontAccess.java | 3 + .../classes/sun/font/FontDesignMetrics.java | 13 +- .../share/classes/sun/font/FontUtilities.java | 4 +- .../share/classes/sun/font/GlyphLayout.java | 8 +- .../classes/sun/font/SunLayoutEngine.java | 8 +- .../sun/swing/AccessibleAnnouncer.java | 4 +- .../classes/sun/awt/WindowMoveService.java | 28 + .../sun/awt/X11/WindowMoveServiceX11.java | 39 + .../unix/classes/sun/awt/X11/XWindowPeer.java | 39 - .../classes/sun/awt/windows/WFramePeer.java | 3 +- .../classes/sun/awt/windows/WWindowPeer.java | 4 +- .../com/jetbrains/AccessibleAnnouncer.java | 44 -- .../com/jetbrains/CustomWindowDecoration.java | 85 --- .../src/com/jetbrains/DesktopActions.java | 42 -- .../src/com/jetbrains/ExtendedGlyphCache.java | 33 - .../src/com/jetbrains/FontExtensions.java | 106 --- .../com/jetbrains/FontMetricsAccessor.java | 128 ---- .../com/jetbrains/FontOpenTypeFeatures.java | 39 - .../src/com/jetbrains/GraphicsUtils.java | 38 - src/jetbrains.api/src/com/jetbrains/JBR.java | 133 ---- .../src/com/jetbrains/JBRFileDialog.java | 86 --- .../src/com/jetbrains/Jstack.java | 46 -- .../src/com/jetbrains/ProjectorUtils.java | 32 - .../com/jetbrains/RoundedCornersManager.java | 46 -- .../src/com/jetbrains/WindowDecorations.java | 177 ----- .../src/com/jetbrains/WindowMove.java | 53 -- src/jetbrains.api/src/module-info.java | 30 - src/jetbrains.api/tools/CheckVersion.java | 127 ---- src/jetbrains.api/tools/Gensrc.java | 311 -------- src/jetbrains.api/version.properties | 14 - .../jb/java/api/backend/BootstrapTest.java | 62 ++ .../api/backend/FindDependenciesTest.java | 104 --- .../java/api/backend/MethodMappingTest.java | 58 +- .../api/backend/MethodMappingTest.registry | 11 + .../api/backend/ProxyInfoResolvingTest.java | 52 +- .../backend/ProxyInfoResolvingTest.registry | 7 + .../api/backend/ProxyRegistrationTest.java | 50 -- test/jdk/jb/java/api/backend/RealTest.java | 76 +- .../jdk/jb/java/api/backend/RealTest.registry | 5 + .../api/backend/ReflectiveBootstrapTest.java | 43 -- .../api/backend/com/jetbrains/Extension.java | 13 + .../api/backend/com/jetbrains/Extensions.java | 6 + .../java/api/backend/com/jetbrains/JBR.java | 10 +- .../api/backend/com/jetbrains/Provided.java | 12 + .../api/backend/com/jetbrains/Provides.java | 12 + .../api/backend/com/jetbrains/Service.java | 12 + .../java/api/backend/com/jetbrains/Util.java | 115 ++- .../com/jetbrains/api/FindDependencies.java | 76 -- .../com/jetbrains/jbr/FindDependencies.java | 31 - .../{ => test}/api/MethodMapping.java | 31 +- .../{ => test}/api/ProxyInfoResolving.java | 23 +- .../com/jetbrains/{ => test}/api/Real.java | 38 +- .../{ => test}/jbr/MethodMapping.java | 35 +- .../{ => test}/jbr/ProxyInfoResolving.java | 10 +- .../com/jetbrains/{ => test}/jbr/Real.java | 45 +- .../frontend/CustomTitleBarDoubleClick.java | 114 --- .../jb/java/api/frontend/FileDialogTest.java | 114 --- .../java/api/frontend/FontExtensionsTest.java | 267 ------- .../api/frontend/FontMetricsAccessorTest.java | 161 ---- test/jdk/jb/java/api/frontend/JBRApiTest.java | 64 -- test/jdk/jb/java/api/frontend/JstackTest.java | 58 -- .../api/frontend/TestSetRoundedCorners.java | 174 ----- .../jb/java/api/frontend/WindowMoveTest.java | 55 -- 112 files changed, 3088 insertions(+), 4890 deletions(-) create mode 100644 jb/jbr-api.version delete mode 100644 jb/project/idea-project-files/jetbrains.api.iml delete mode 100644 jb/project/tools/common/scripts/build-jbr-api.sh create mode 100644 make/jdk/src/classes/build/tools/jbrapi/JBRApiPlugin.java delete mode 100644 src/java.base/share/classes/com/jetbrains/base/JBRApiModule.java create mode 100644 src/java.base/share/classes/com/jetbrains/exported/JBRApi.java create mode 100644 src/java.base/share/classes/com/jetbrains/exported/JBRApiSupport.java create mode 100644 src/java.base/share/classes/com/jetbrains/internal/AccessContext.java create mode 100644 src/java.base/share/classes/com/jetbrains/internal/Mapping.java delete mode 100644 src/java.base/share/classes/com/jetbrains/internal/ProxyDependencyManager.java delete mode 100644 src/java.base/share/classes/com/jetbrains/internal/ProxyInfo.java create mode 100644 src/java.base/share/classes/com/jetbrains/internal/ProxyRepository.java delete mode 100644 src/java.base/share/classes/com/jetbrains/internal/RegisteredProxyInfo.java create mode 100644 src/java.base/share/classes/com/jetbrains/internal/Utils.java delete mode 100644 src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java delete mode 100644 src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java create mode 100644 src/java.desktop/unix/classes/sun/awt/WindowMoveService.java create mode 100644 src/java.desktop/unix/classes/sun/awt/X11/WindowMoveServiceX11.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/AccessibleAnnouncer.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/CustomWindowDecoration.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/DesktopActions.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/ExtendedGlyphCache.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/FontExtensions.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/FontMetricsAccessor.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/FontOpenTypeFeatures.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/JBR.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/JBRFileDialog.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/Jstack.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/ProjectorUtils.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/RoundedCornersManager.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/WindowDecorations.java delete mode 100644 src/jetbrains.api/src/com/jetbrains/WindowMove.java delete mode 100644 src/jetbrains.api/src/module-info.java delete mode 100644 src/jetbrains.api/tools/CheckVersion.java delete mode 100644 src/jetbrains.api/tools/Gensrc.java delete mode 100644 src/jetbrains.api/version.properties create mode 100644 test/jdk/jb/java/api/backend/BootstrapTest.java delete mode 100644 test/jdk/jb/java/api/backend/FindDependenciesTest.java create mode 100644 test/jdk/jb/java/api/backend/MethodMappingTest.registry create mode 100644 test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.registry delete mode 100644 test/jdk/jb/java/api/backend/ProxyRegistrationTest.java create mode 100644 test/jdk/jb/java/api/backend/RealTest.registry delete mode 100644 test/jdk/jb/java/api/backend/ReflectiveBootstrapTest.java create mode 100644 test/jdk/jb/java/api/backend/com/jetbrains/Extension.java create mode 100644 test/jdk/jb/java/api/backend/com/jetbrains/Extensions.java create mode 100644 test/jdk/jb/java/api/backend/com/jetbrains/Provided.java create mode 100644 test/jdk/jb/java/api/backend/com/jetbrains/Provides.java create mode 100644 test/jdk/jb/java/api/backend/com/jetbrains/Service.java delete mode 100644 test/jdk/jb/java/api/backend/com/jetbrains/api/FindDependencies.java delete mode 100644 test/jdk/jb/java/api/backend/com/jetbrains/jbr/FindDependencies.java rename test/jdk/jb/java/api/backend/com/jetbrains/{ => test}/api/MethodMapping.java (71%) rename test/jdk/jb/java/api/backend/com/jetbrains/{ => test}/api/ProxyInfoResolving.java (75%) rename test/jdk/jb/java/api/backend/com/jetbrains/{ => test}/api/Real.java (63%) rename test/jdk/jb/java/api/backend/com/jetbrains/{ => test}/jbr/MethodMapping.java (67%) rename test/jdk/jb/java/api/backend/com/jetbrains/{ => test}/jbr/ProxyInfoResolving.java (86%) rename test/jdk/jb/java/api/backend/com/jetbrains/{ => test}/jbr/Real.java (64%) delete mode 100644 test/jdk/jb/java/api/frontend/CustomTitleBarDoubleClick.java delete mode 100644 test/jdk/jb/java/api/frontend/FileDialogTest.java delete mode 100644 test/jdk/jb/java/api/frontend/FontExtensionsTest.java delete mode 100644 test/jdk/jb/java/api/frontend/FontMetricsAccessorTest.java delete mode 100644 test/jdk/jb/java/api/frontend/JBRApiTest.java delete mode 100644 test/jdk/jb/java/api/frontend/JstackTest.java delete mode 100644 test/jdk/jb/java/api/frontend/TestSetRoundedCorners.java delete mode 100644 test/jdk/jb/java/api/frontend/WindowMoveTest.java diff --git a/.gitignore b/.gitignore index 0743489f8ec1..91f3b0a2420b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ test/benchmarks/**/target /src/hotspot/cmake-build-debug/ /src/hotspot/.cache/ /src/hotspot/.idea/ +/jbr-api/ diff --git a/jb/jbr-api.version b/jb/jbr-api.version new file mode 100644 index 000000000000..bd52db81d0cd --- /dev/null +++ b/jb/jbr-api.version @@ -0,0 +1 @@ +0.0.0 \ No newline at end of file diff --git a/jb/project/idea-project-files/jetbrains.api.iml b/jb/project/idea-project-files/jetbrains.api.iml deleted file mode 100644 index eb800fa994a6..000000000000 --- a/jb/project/idea-project-files/jetbrains.api.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/jb/project/idea-project-files/modules.xml b/jb/project/idea-project-files/modules.xml index b0e4703bf7af..a87145139038 100644 --- a/jb/project/idea-project-files/modules.xml +++ b/jb/project/idea-project-files/modules.xml @@ -4,7 +4,6 @@ ###MODULE_IMLS### - diff --git a/jb/project/tools/common/scripts/build-jbr-api.sh b/jb/project/tools/common/scripts/build-jbr-api.sh deleted file mode 100644 index 48aee8225592..000000000000 --- a/jb/project/tools/common/scripts/build-jbr-api.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin//bash - -set -euo pipefail - -# $1 - Boot JDK -# $2 - JBR part of API version - -cd "`dirname "$0"`/../../../../.." -PWD="`pwd`" -CONF="$PWD/build/jbr-api.conf" -./configure --with-debug-level=release --with-boot-jdk=$1 || exit $? -make jbr-api CONF=release MAKEOVERRIDES= "JBR_API_CONF_FILE=$CONF" JBR_API_JBR_VERSION=$2 || exit $? -. $CONF || exit $? -echo "##teamcity[buildNumber '$VERSION']" -cp "$JAR" ./jbr-api-${VERSION}.jar || exit $? -cp "$SOURCES_JAR" ./jbr-api-${VERSION}-sources.jar || exit $? -echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}.jar']" -echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}-sources.jar']" \ No newline at end of file diff --git a/jb/project/tools/linux/scripts/mkimages_aarch64.sh b/jb/project/tools/linux/scripts/mkimages_aarch64.sh index 125b8d915032..b65de677f34f 100755 --- a/jb/project/tools/linux/scripts/mkimages_aarch64.sh +++ b/jb/project/tools/linux/scripts/mkimages_aarch64.sh @@ -161,8 +161,7 @@ if [ $do_maketest -eq 1 ]; then JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-aarch64-b${build_number} echo Creating "$JBRSDK_TEST" ... [ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java - make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? - cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" + make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $? [ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz" gzip "$JBRSDK_TEST".tar || do_exit $? diff --git a/jb/project/tools/linux/scripts/mkimages_x64.sh b/jb/project/tools/linux/scripts/mkimages_x64.sh index fdf8db3f18f5..05025ce8ca4d 100755 --- a/jb/project/tools/linux/scripts/mkimages_x64.sh +++ b/jb/project/tools/linux/scripts/mkimages_x64.sh @@ -178,8 +178,7 @@ if [ $do_maketest -eq 1 ]; then JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-x64-b${build_number} echo Creating "$JBRSDK_TEST" ... [ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java - make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? - cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" + make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $? [ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz" gzip "$JBRSDK_TEST".tar || do_exit $? diff --git a/jb/project/tools/linux/scripts/mkimages_x86.sh b/jb/project/tools/linux/scripts/mkimages_x86.sh index e8e330f81f9a..049cb0958906 100755 --- a/jb/project/tools/linux/scripts/mkimages_x86.sh +++ b/jb/project/tools/linux/scripts/mkimages_x86.sh @@ -137,8 +137,7 @@ if [ $do_maketest -eq 1 ]; then JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-x86-b${build_number} echo Creating "$JBRSDK_TEST" ... [ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java - make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? - cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" + make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $? [ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz" gzip "$JBRSDK_TEST".tar || do_exit $? diff --git a/jb/project/tools/mac/scripts/mkimages.sh b/jb/project/tools/mac/scripts/mkimages.sh index 5b8f10524bb6..89c539defed0 100755 --- a/jb/project/tools/mac/scripts/mkimages.sh +++ b/jb/project/tools/mac/scripts/mkimages.sh @@ -162,8 +162,7 @@ if [ $do_maketest -eq 1 ]; then JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-osx-test-${architecture}-b${build_number} echo Creating "$JBRSDK_TEST" ... [ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java - make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? - cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" + make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $? [ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz" COPYFILE_DISABLE=1 tar -pczf "$JBRSDK_TEST".tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $? fi diff --git a/jb/project/tools/windows/scripts/mkimages_aarch64.sh b/jb/project/tools/windows/scripts/mkimages_aarch64.sh index 38e5be1864eb..db5b8dc40643 100644 --- a/jb/project/tools/windows/scripts/mkimages_aarch64.sh +++ b/jb/project/tools/windows/scripts/mkimages_aarch64.sh @@ -104,13 +104,13 @@ esac if [ -z "${INC_BUILD:-}" ]; then do_configure || do_exit $? if [ $do_maketest -eq 1 ]; then - make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $? + make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $? else make LOG=info CONF=$RELEASE_NAME clean images || do_exit $? fi else if [ $do_maketest -eq 1 ]; then - make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $? + make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $? else make LOG=info CONF=$RELEASE_NAME images || do_exit $? fi diff --git a/jb/project/tools/windows/scripts/mkimages_x64.sh b/jb/project/tools/windows/scripts/mkimages_x64.sh index 8130086c3516..08f95a153779 100755 --- a/jb/project/tools/windows/scripts/mkimages_x64.sh +++ b/jb/project/tools/windows/scripts/mkimages_x64.sh @@ -97,13 +97,13 @@ esac if [ -z "${INC_BUILD:-}" ]; then do_configure || do_exit $? if [ $do_maketest -eq 1 ]; then - make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $? + make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $? else make LOG=info CONF=$RELEASE_NAME clean images || do_exit $? fi else if [ $do_maketest -eq 1 ]; then - make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $? + make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $? else make LOG=info CONF=$RELEASE_NAME images || do_exit $? fi diff --git a/jb/project/tools/windows/scripts/mkimages_x86.sh b/jb/project/tools/windows/scripts/mkimages_x86.sh index 03a9bc81efdc..19f1d3ce1d26 100755 --- a/jb/project/tools/windows/scripts/mkimages_x86.sh +++ b/jb/project/tools/windows/scripts/mkimages_x86.sh @@ -92,13 +92,13 @@ esac if [ -z "${INC_BUILD:-}" ]; then do_configure || do_exit $? if [ $do_maketest -eq 1 ]; then - make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $? + make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $? else make LOG=info CONF=$RELEASE_NAME clean images || do_exit $? fi else if [ $do_maketest -eq 1 ]; then - make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $? + make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $? else make LOG=info CONF=$RELEASE_NAME images || do_exit $? fi diff --git a/jb/project/tools/windows/scripts/pack_aarch64.sh b/jb/project/tools/windows/scripts/pack_aarch64.sh index 4c7d86bf753e..c31979e0cfe7 100644 --- a/jb/project/tools/windows/scripts/pack_aarch64.sh +++ b/jb/project/tools/windows/scripts/pack_aarch64.sh @@ -51,7 +51,6 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk if [ $do_maketest -eq 1 ]; then JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-aarch64-b$build_number - cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $? echo Creating $JBRSDK_TEST.tar.gz ... /usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $? fi \ No newline at end of file diff --git a/jb/project/tools/windows/scripts/pack_x64.sh b/jb/project/tools/windows/scripts/pack_x64.sh index 1a98a2ead7a8..9e269df05868 100755 --- a/jb/project/tools/windows/scripts/pack_x64.sh +++ b/jb/project/tools/windows/scripts/pack_x64.sh @@ -51,7 +51,6 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk if [ $do_maketest -eq 1 ]; then JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-x64-b$build_number - cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $? echo Creating $JBRSDK_TEST.tar.gz ... /usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $? fi \ No newline at end of file diff --git a/jb/project/tools/windows/scripts/pack_x86.sh b/jb/project/tools/windows/scripts/pack_x86.sh index 20db6e2cbebe..9756f0bc7fe1 100755 --- a/jb/project/tools/windows/scripts/pack_x86.sh +++ b/jb/project/tools/windows/scripts/pack_x86.sh @@ -47,7 +47,6 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk if [ $do_maketest -eq 1 ]; then JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-x86-b$build_number - cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $? echo Creating $JBRSDK_TEST.tar.gz ... /usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $BASE_DIR --exclude='test/jdk/demos' test || do_exit $? fi \ No newline at end of file diff --git a/make/CompileJavaModules.gmk b/make/CompileJavaModules.gmk index 54d063a7a718..0e3ebf57922f 100644 --- a/make/CompileJavaModules.gmk +++ b/make/CompileJavaModules.gmk @@ -107,6 +107,7 @@ $(eval $(call SetupJavaCompilation, $(MODULE), \ BIN := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules), \ HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \ CREATE_API_DIGEST := true, \ + PROCESS_JBR_API := true, \ CLEAN := $(CLEAN), \ CLEAN_FILES := $(CLEAN_FILES), \ COPY := $(COPY), \ diff --git a/make/CompileToolsJdk.gmk b/make/CompileToolsJdk.gmk index c291dbdba0a7..a761293d213c 100644 --- a/make/CompileToolsJdk.gmk +++ b/make/CompileToolsJdk.gmk @@ -80,7 +80,7 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \ TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ SRC := $(TOPDIR)/make/jdk/src/classes, \ INCLUDES := build/tools/depend, \ - BIN := $(BUILDTOOLS_OUTPUTDIR)/depend, \ + BIN := $(BUILDTOOLS_OUTPUTDIR)/plugins, \ DISABLED_WARNINGS := options, \ JAVAC_FLAGS := \ --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ @@ -93,13 +93,21 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \ --add-exports jdk.internal.opt/jdk.internal.opt=jdk.javadoc.interim, \ )) -DEPEND_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services/com.sun.source.util.Plugin +$(eval $(call SetupJavaCompilation, COMPILE_JBR_API_PLUGIN, \ + TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ + SRC := $(TOPDIR)/make/jdk/src/classes, \ + INCLUDES := build/tools/jbrapi, \ + BIN := $(BUILDTOOLS_OUTPUTDIR)/plugins, \ +)) -$(DEPEND_SERVICE_PROVIDER): - $(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services) +PLUGINS_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/plugins/META-INF/services/com.sun.source.util.Plugin + +$(PLUGINS_SERVICE_PROVIDER): + $(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/plugins/META-INF/services) $(ECHO) build.tools.depend.Depend > $@ + $(ECHO) build.tools.jbrapi.JBRApiPlugin >> $@ -TARGETS += $(COMPILE_DEPEND) $(DEPEND_SERVICE_PROVIDER) +TARGETS += $(COMPILE_DEPEND) $(COMPILE_JBR_API_PLUGIN) $(PLUGINS_SERVICE_PROVIDER) ################################################################################ diff --git a/make/JBRApi.gmk b/make/JBRApi.gmk index 056b7641b831..e6be9bb41e38 100644 --- a/make/JBRApi.gmk +++ b/make/JBRApi.gmk @@ -25,69 +25,49 @@ include $(SPEC) include MakeBase.gmk -include JavaCompilation.gmk +include Utils.gmk -JBR_API_ROOT_DIR := $(TOPDIR)/src/jetbrains.api -JBR_API_TOOLS_DIR := $(JBR_API_ROOT_DIR)/tools -JBR_API_SRC_DIR := $(JBR_API_ROOT_DIR)/src -JBR_API_OUTPUT_DIR := $(OUTPUTDIR)/jbr-api -JBR_API_GENSRC_DIR := $(JBR_API_OUTPUT_DIR)/gensrc -JBR_API_BIN_DIR := $(JBR_API_OUTPUT_DIR)/bin -JBR_API_VERSION_PROPERTIES := $(JBR_API_ROOT_DIR)/version.properties -JBR_API_VERSION_GENSRC := $(JBR_API_OUTPUT_DIR)/jbr-api.version -JBR_API_GENSRC_BATCH := $(JBR_API_VERSION_GENSRC) +JBR_API_ORIGIN := https://github.com/JetBrains/JetBrainsRuntimeApi.git +JBR_API_DIR := $(TOPDIR)/jbr-api -JBR_API_SRC_FILES := $(call FindFiles, $(JBR_API_SRC_DIR)) -JBR_API_GENSRC_FILES := $(foreach f, $(call FindFiles, $(JBR_API_SRC_DIR)), \ - $(JBR_API_GENSRC_DIR)/$(call RelativePath, $f, $(JBR_API_SRC_DIR))) - -ifeq ($(JBR_API_JBR_VERSION),) - JBR_API_JBR_VERSION := DEVELOPMENT - JBR_API_FAIL_ON_HASH_MISMATCH := false +ARTIFACT_NAME := jbr-api-SNAPSHOT +ifeq ($(call isBuildOsEnv, windows.cygwin windows.msys2), true) + HOME := $$USERPROFILE + M2_REPO := $(shell $(PATHTOOL) $(HOME))/.m2/repository +else ifeq ($(call isBuildOsEnv, windows.wsl1 windows.wsl2), true) + HOME := `cmd.exe /C "echo %USERPROFILE%" 2> /dev/null` + M2_REPO := $(shell $(PATHTOOL) $(HOME))/.m2/repository else - .PHONY: $(JBR_API_VERSION_PROPERTIES) - JBR_API_FAIL_ON_HASH_MISMATCH := true + M2_REPO := $(HOME)/.m2/repository endif +M2_ARTIFACT := $(M2_REPO)/com/jetbrains/jbr-api/SNAPSHOT +M2_POM_CONTENT := \ + \ + \ + 4.0.0 \ + com.jetbrains \ + jbr-api \ + SNAPSHOT \ + \ -ARCHIVE_BUILD_JBR_API_BIN := $(JBR_API_BIN_DIR) -$(eval $(call SetupJavaCompilation, BUILD_JBR_API, \ - SMALL_JAVA := true, \ - COMPILER := bootjdk, \ - SRC := $(JBR_API_GENSRC_DIR), \ - EXTRA_FILES := $(JBR_API_GENSRC_FILES), \ - BIN := $(JBR_API_BIN_DIR), \ - JAR := $(JBR_API_OUTPUT_DIR)/jbr-api.jar, \ -)) +jbr-api: + if [ -d "$(JBR_API_DIR)" ]; then \ + $(GIT) -C "$(JBR_API_DIR)" fetch; \ + $(GIT) -C "$(JBR_API_DIR)" merge-base --is-ancestor origin/main HEAD || \ + $(ECHO) "!!! Current JBR API revision is outdated, update the branch in $(JBR_API_DIR) !!!"; \ + else \ + $(ECHO) "JBR API directory does not exist. Initializing..."; \ + $(GIT) clone "$(JBR_API_ORIGIN)" "$(JBR_API_DIR)" --config core.autocrlf=false; \ + fi + $(BASH) "$(JBR_API_DIR)/tools/build.sh" dev "$(BOOT_JDK)" + if [ -d "$(M2_REPO)" ]; then \ + $(MKDIR) -p $(M2_ARTIFACT); \ + $(ECHO) "$(M2_POM_CONTENT)" > $(M2_ARTIFACT)/$(ARTIFACT_NAME).pom; \ + $(CP) "$(JBR_API_DIR)/out/$(ARTIFACT_NAME).jar" "$(M2_ARTIFACT)"; \ + $(ECHO) "Installed into local Maven repository as com.jetbrains:jbr-api:SNAPSHOT"; \ + else \ + $(ECHO) "No Maven repository found at $(M2_REPO) - skipping local installation"; \ + fi -$(eval $(call SetupJarArchive, BUILD_JBR_API_SOURCES_JAR, \ - DEPENDENCIES := $(JBR_API_GENSRC_FILES), \ - SRCS := $(JBR_API_GENSRC_DIR), \ - JAR := $(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar, \ - SUFFIXES := .java, \ - BIN := $(JBR_API_BIN_DIR), \ -)) - -# Grouped targets may not be supported, so hack dependencies: sources -> version file -> generated sources -$(JBR_API_VERSION_GENSRC): $(JBR_API_SRC_FILES) $(JBR_API_VERSION_PROPERTIES) $(JBR_API_TOOLS_DIR)/Gensrc.java - $(ECHO) Generating sources for JBR API - $(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/Gensrc.java" \ - "$(TOPDIR)/src" "$(JBR_API_OUTPUT_DIR)" "$(JBR_API_JBR_VERSION)" -$(JBR_API_GENSRC_FILES): $(JBR_API_VERSION_GENSRC) - $(TOUCH) $@ - -jbr-api-check-version: $(JBR_API_GENSRC_FILES) $(JBR_API_VERSION_PROPERTIES) - $(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/CheckVersion.java" \ - "$(JBR_API_ROOT_DIR)" "$(JBR_API_GENSRC_DIR)" "$(JBR_API_FAIL_ON_HASH_MISMATCH)" - -jbr-api: $(BUILD_JBR_API) $(BUILD_JBR_API_SOURCES_JAR) jbr-api-check-version - -.PHONY: jbr-api jbr-api-check-version - -ifneq ($(JBR_API_CONF_FILE),) - $(JBR_API_CONF_FILE): $(JBR_API_GENSRC_FILES) - $(ECHO) "VERSION=`$(CAT) $(JBR_API_VERSION_GENSRC)`" > $(JBR_API_CONF_FILE) - $(ECHO) "JAR=$(JBR_API_OUTPUT_DIR)/jbr-api.jar" >> $(JBR_API_CONF_FILE) - $(ECHO) "SOURCES_JAR=$(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar" >> $(JBR_API_CONF_FILE) - jbr-api: $(JBR_API_CONF_FILE) - .PHONY: $(JBR_API_CONF_FILE) -endif +.PHONY: jbr-api diff --git a/make/common/JavaCompilation.gmk b/make/common/JavaCompilation.gmk index 33f5d10535a0..3b352a8dcfac 100644 --- a/make/common/JavaCompilation.gmk +++ b/make/common/JavaCompilation.gmk @@ -168,6 +168,7 @@ endef # CREATE_API_DIGEST Set to true to use a javac plugin to generate a public API # hash which can be used for down stream dependencies to only rebuild # when the API changes. +# PROCESS_JBR_API Set to true to use an annotation processor to generate JBR API bindings. # KEEP_ALL_TRANSLATIONS Set to true to skip translation filtering SetupJavaCompilation = $(NamedParamsMacroTemplate) define SetupJavaCompilationBody @@ -298,11 +299,20 @@ define SetupJavaCompilationBody "-XDLOG_LEVEL=$(LOG_LEVEL)" \ # - $1_EXTRA_DEPS := $$(BUILDTOOLS_OUTPUTDIR)/depend/_the.COMPILE_DEPEND_batch + $1_EXTRA_DEPS := $$(BUILDTOOLS_OUTPUTDIR)/plugins/_the.COMPILE_DEPEND_batch + endif + + ifeq ($$($1_PROCESS_JBR_API), true) + # Automatic path conversion doesn't work for two arguments, so call fixpath manually + $1_JBR_API_FLAGS := -Xplugin:"jbr-api $$(call FixPath, $$($1_BIN)/java.base/META-INF/jbrapi.registry) $$(call FixPath, $(TOPDIR)/jb/jbr-api.version)" + $1_EXTRA_DEPS := $$($1_EXTRA_DEPS) $$(BUILDTOOLS_OUTPUTDIR)/plugins/_the.COMPILE_JBR_API_PLUGIN_batch + endif + + ifeq ($$(call Or, $$($1_CREATE_API_DIGEST) $$($1_PROCESS_JBR_API)), true) # including the compilation output on the classpath, so that incremental # compilations in unnamed module can refer to other classes from the same # source root, which are not being recompiled in this compilation: - $1_AUGMENTED_CLASSPATH += $$(BUILDTOOLS_OUTPUTDIR)/depend $$($1_BIN) + $1_AUGMENTED_CLASSPATH += $$(BUILDTOOLS_OUTPUTDIR)/plugins $$($1_BIN) endif ifneq ($$($1_AUGMENTED_CLASSPATH), ) @@ -512,7 +522,7 @@ define SetupJavaCompilationBody $$(call MakeDir, $$(@D)) $$(call ExecuteWithLog, $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$$($1_SAFE_NAME)_batch, \ $$($1_JAVAC_CMD) $$($1_FLAGS) \ - $$($1_API_DIGEST_FLAGS) \ + $$($1_API_DIGEST_FLAGS) $$($1_JBR_API_FLAGS) \ -XDmodifiedInputs=$$($1_MODFILELIST_FIXED) \ -d $$($1_BIN) $$($1_HEADERS_ARG) @$$($1_FILELIST)) && \ $(TOUCH) $$@ diff --git a/make/jdk/src/classes/build/tools/jbrapi/JBRApiPlugin.java b/make/jdk/src/classes/build/tools/jbrapi/JBRApiPlugin.java new file mode 100644 index 000000000000..075a7167543b --- /dev/null +++ b/make/jdk/src/classes/build/tools/jbrapi/JBRApiPlugin.java @@ -0,0 +1,451 @@ +package build.tools.jbrapi; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.*; + +import javax.lang.model.element.*; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementScanner14; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.locks.LockSupport; +import java.util.stream.Collectors; + +public class JBRApiPlugin implements Plugin { + + enum Binding { + SERVICE, + PROVIDES, + PROVIDED, + TWO_WAY + } + + record DiagnosticTree(CompilationUnitTree root, Tree tree) {} + record TypeBinding(DiagnosticTree diagnostic, TypeElement element, String currentType, String bindType, Binding binding) {} + record MethodBinding(DiagnosticTree diagnostic, ExecutableElement element, Registry.StaticDescriptor currentMethod, Registry.StaticMethod bindMethod) {} + + final Map typeBindings = new HashMap<>(); + final List methodBindings = new ArrayList<>(); + Elements elements; + Trees trees; + Types types; + + class Registry { + + record Type(String type, Binding binding) {} + record StaticMethod(String type, String name) {} + record StaticDescriptor(StaticMethod method, String descriptor) {} + + final Map types = new HashMap<>(); + final Map methods = new HashMap<>(); + final Set internal = new HashSet<>(); + + void validateInternal(DiagnosticTree diagnostic, String currentType, Binding binding, TypeElement bindType) { + if (bindType.getKind() != ElementKind.CLASS && bindType.getKind() != ElementKind.INTERFACE) { + trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " + + bindType.getQualifiedName().toString() + " (not a class or interface)", + diagnostic.tree, diagnostic.root); + } else if (bindType.getModifiers().contains(Modifier.FINAL) || bindType.getModifiers().contains(Modifier.SEALED)) { + trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " + + bindType.getQualifiedName().toString() + " (not inheritable)", + diagnostic.tree, diagnostic.root); + } + if (binding != Binding.SERVICE) { + trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " + + bindType.getQualifiedName().toString() + " (internal, non-service)", + diagnostic.tree, diagnostic.root); + } + } + + void validateInternalMethod(DiagnosticTree diagnostic, StaticDescriptor currentMethod, TypeElement bindType, String bindMethod) { + boolean methodFound = false; + for (Element m : bindType.getEnclosedElements()) { + if (m.getKind() == ElementKind.METHOD && + !m.getModifiers().contains(Modifier.STATIC) && + !m.getModifiers().contains(Modifier.FINAL) && + m.getSimpleName().contentEquals(bindMethod) && + descriptor(m.asType()).equals(currentMethod.descriptor)) { + methodFound = true; + } + } + if (!methodFound) { + trees.printMessage(Diagnostic.Kind.ERROR, "Invalid static binding: " + + currentMethod.method.type + "#" + currentMethod.method.name + " -> " + + bindType.getQualifiedName().toString() + "#" + bindMethod + + " (no matching method found, type conversions are not allowed for internal bindings)", + diagnostic.tree, diagnostic.root); + } + } + + List addBindings() { + List unresolvedErrors = new ArrayList<>(); + List addedTypes = new ArrayList<>(); + List addedMethods = new ArrayList<>(); + Set validated = new HashSet<>(); + // Remove changed bindings. + for (TypeBinding type : typeBindings.values()) { + if (type.bindType != null) addedTypes.add(type); + types.remove(type.currentType); + } + for (MethodBinding method : methodBindings) { + if (method.bindMethod != null) addedMethods.add(method); + methods.remove(method.currentMethod); + } + methods.entrySet().removeIf(m -> typeBindings.containsKey(m.getKey().method.type)); + // Build inverse binding map. + Map inverseTypes = types.entrySet().stream().collect(Collectors.toMap( + e -> e.getValue().type, Map.Entry::getKey, (a, b) -> { + unresolvedErrors.add("Conflicting JBR API binding: " + a + " and "+ b + " binds to the same type"); + return a + "," + b; + })); + Map inverseMethods = methods.entrySet().stream().collect(Collectors.toMap( + e -> new StaticDescriptor(e.getValue(), e.getKey().descriptor), e -> e.getKey().method, (a, b) -> { + unresolvedErrors.add("Conflicting JBR API binding: " + + a.type + "#" + a.name + " and "+ b.type + "#" + b.name + " binds to the same method"); + return new StaticMethod(a.type + "," + b.type, a.name + "," + b.name); + })); + // Add new bindings. + for (TypeBinding type : addedTypes) types.put(type.currentType, new Type(type.bindType, type.binding)); + for (MethodBinding method : addedMethods) methods.put(method.currentMethod, method.bindMethod); + // Validate type bindings. + for (TypeBinding type : addedTypes) { + String inv = inverseTypes.get(type.bindType); + if (inv != null) { + trees.printMessage(Diagnostic.Kind.ERROR, + "Conflicting JBR API binding: " + type.currentType + " -> " + type.bindType + " <- " + inv, + type.diagnostic.tree, type.diagnostic.root); + inverseTypes.put(type.bindType, inv + "," + type.currentType); + } else inverseTypes.put(type.bindType, type.currentType); + Type next = types.get(type.bindType); + if (next != null) { + trees.printMessage(Diagnostic.Kind.ERROR, + "Conflicting JBR API binding: " + type.currentType + " -> " + type.bindType + " -> " + next, + type.diagnostic.tree, type.diagnostic.root); + } + String prev = inverseTypes.get(type.currentType); + if (prev != null) { + trees.printMessage(Diagnostic.Kind.ERROR, + "Conflicting JBR API binding: " + prev + " -> " + type.currentType + " -> " + type.bindType, + type.diagnostic.tree, type.diagnostic.root); + } + if (validated.add(type.currentType)) { + TypeElement bindElement = elements.getTypeElement(type.bindType); + if (bindElement != null) { + internal.add(type.currentType); + validateInternal(type.diagnostic, type.currentType, type.binding, bindElement); + } + } + } + // Validate method bindings. + for (MethodBinding method : addedMethods) { + StaticDescriptor invDescriptor = new StaticDescriptor(method.bindMethod, method.currentMethod.descriptor); + StaticMethod inv = inverseMethods.get(invDescriptor); + if (inv != null) { + trees.printMessage(Diagnostic.Kind.ERROR, "Conflicting JBR API binding: " + + method.currentMethod.method.type + "#" + method.currentMethod.method.name + " -> " + + method.bindMethod.type + "#" + method.bindMethod.name + " <- " + + inv.type + "#" + inv.name, + method.diagnostic.tree, method.diagnostic.root); + inverseMethods.put(invDescriptor, new StaticMethod( + inv.type + "," + method.currentMethod.method.type, inv.name + "," + method.currentMethod.method.name)); + } else inverseMethods.put(invDescriptor, method.currentMethod.method); + if (validated.add(method.currentMethod)) { + TypeElement bindElement = elements.getTypeElement(method.bindMethod.type); + if (bindElement != null) { + internal.add(method.currentMethod); + validateInternalMethod(method.diagnostic, method.currentMethod, bindElement, method.bindMethod.name); + } + } + } + // [Re]validate remaining. + types.forEach((k, v) -> { + if (validated.add(k)) { + TypeBinding type = typeBindings.get(v.type); + if (type != null) { + internal.add(k); + validateInternal(type.diagnostic, k, v.binding, type.element); + } else if (elements.getTypeElement(v.type) != null) { + internal.add(k); // Couldn't validate, but at least found the type. + if (v.binding != Binding.SERVICE) { + unresolvedErrors.add("Invalid JBR API binding:" + k + " -> " + v.type + " (internal, non-service)"); + } + } + } + }); + methods.forEach((k, v) -> { + if (validated.add(k)) { + TypeBinding type = typeBindings.get(v.type); + if (type != null) { + internal.add(k); + validateInternalMethod(type.diagnostic, k, type.element, v.name); + } else if (elements.getTypeElement(v.type) != null) { + internal.add(k); // Couldn't validate, but at least found the type. + } + } + }); + return unresolvedErrors; + } + + void read(RandomAccessFile file) throws IOException { + String s; + while ((s = file.readLine()) != null) { + String[] tokens = s.split(" "); + switch (tokens[0]) { + case "TYPE" -> { + types.put(tokens[1], new Type(tokens[2], Binding.valueOf(tokens[3]))); + if (tokens.length > 4 && tokens[4].equals("INTERNAL")) internal.add(tokens[1]); + } + case "STATIC" -> { + StaticDescriptor descriptor = new StaticDescriptor(new StaticMethod( + tokens[1], tokens[2]), tokens[3]); + methods.put(descriptor, new StaticMethod(tokens[4], tokens[5])); + if (tokens.length > 6 && tokens[6].equals("INTERNAL")) internal.add(descriptor); + } + } + } + } + + void write(RandomAccessFile file) throws IOException { + for (var t : types.entrySet()) { + file.writeBytes("TYPE " + t.getKey() + " " + t.getValue().type + " " + t.getValue().binding + + (internal.contains(t.getKey()) ? " INTERNAL\n" : "\n")); + } + for (var t : methods.entrySet()) { + file.writeBytes("STATIC " + t.getKey().method.type + " " + t.getKey().method.name + " " + + t.getKey().descriptor + " " + t.getValue().type + " " + t.getValue().name + + (internal.contains(t.getKey()) ? " INTERNAL\n" : "\n")); + } + } + } + + String descriptor(TypeMirror t) { + return switch (t.getKind()) { + case VOID -> "V"; + case BOOLEAN -> "Z"; + case BYTE -> "B"; + case CHAR -> "C"; + case SHORT -> "S"; + case INT -> "I"; + case LONG -> "J"; + case FLOAT -> "F"; + case DOUBLE -> "D"; + case ARRAY -> "[" + descriptor(((ArrayType) t).getComponentType()); + case DECLARED -> "L" + elements.getBinaryName((TypeElement) ((DeclaredType) t).asElement()) + .toString().replace('.', '/') + ";"; + case EXECUTABLE -> "(" + ((ExecutableType) t).getParameterTypes().stream().map(this::descriptor) + .collect(Collectors.joining()) + ")" + descriptor(((ExecutableType) t).getReturnType()); + case TYPEVAR, WILDCARD, UNION, INTERSECTION -> descriptor(types.erasure(t)); + default -> throw new RuntimeException("Cannot generate descriptor for type: " + t); + }; + } + + Registry.StaticDescriptor staticDescriptor(String type, ExecutableElement e) { + return new Registry.StaticDescriptor(new Registry.StaticMethod(type, e.getSimpleName().toString()), descriptor(e.asType())); + } + + AnnotationValue annotationValue(AnnotationMirror m) { + if (m == null) return null; + return m.getElementValues().entrySet().stream() + .filter(t -> t.getKey().getSimpleName().contentEquals("value")) + .map(Map.Entry::getValue).findFirst().orElseThrow(); + } + + static boolean isJavaIdentifier(String name, int from, int to) { + if (!Character.isJavaIdentifierStart(name.charAt(from))) return false; + for (int i = from + 1; i < to; i++) { + if (!Character.isJavaIdentifierPart(name.charAt(i))) return false; + } + return true; + } + static boolean isJavaIdentifier(String name) { + if (name == null || name.isEmpty()) return false; + return isJavaIdentifier(name, 0, name.length()); + } + static boolean isJavaTypeIdentifier(String name) { + if (name == null || name.isEmpty()) return false; + for (int i = 0; i < name.length();) { + int next = name.indexOf('.', i); + if (next == -1) next = name.length(); + if (!isJavaIdentifier(name, i, next)) return false; + i = next + 1; + } + return true; + } + + void scan(CompilationUnitTree root, Element e) { + // Get current type name. + String currentType; + if (e.getKind() == ElementKind.CLASS || e.getKind() == ElementKind.INTERFACE) { + currentType = ((TypeElement) e).getQualifiedName().toString(); + } else if (e.getKind() == ElementKind.METHOD && e.getModifiers().contains(Modifier.STATIC)) { + currentType = ((QualifiedNameable) e.getEnclosingElement()).getQualifiedName().toString(); + } else currentType = null; + + // Find the annotation. + AnnotationMirror providedMirror = null, providesMirror = null, serviceMirror = null; + for (AnnotationMirror m : elements.getAllAnnotationMirrors(e)) { + switch (m.getAnnotationType().toString()) { + case "com.jetbrains.exported.JBRApi.Provided" -> providedMirror = m; + case "com.jetbrains.exported.JBRApi.Provides" -> providesMirror = m; + case "com.jetbrains.exported.JBRApi.Service" -> serviceMirror = m; + } + } + + AnnotationMirror mirror = null; + AnnotationValue value = null; + Binding binding = null; + if (serviceMirror != null) { + if (providesMirror == null) { + trees.printMessage(Diagnostic.Kind.ERROR, + "@Service also requires @Provides", trees.getTree(e, serviceMirror), root); + return; + } + if (providedMirror != null) { + trees.printMessage(Diagnostic.Kind.ERROR, + "@Service cannot be used with @Provided", trees.getTree(e, serviceMirror), root); + return; + } + value = annotationValue(mirror = providesMirror); + binding = Binding.SERVICE; + } else if (providesMirror != null) { + value = annotationValue(mirror = providesMirror); + if (providedMirror != null) { + AnnotationValue v = annotationValue(providedMirror); + if (!value.getValue().toString().equals(v.getValue().toString())) { + trees.printMessage(Diagnostic.Kind.ERROR, + "@Provided and @Provides doesn't match", trees.getTree(e, mirror, value), root); + return; + } + binding = Binding.TWO_WAY; + } else binding = Binding.PROVIDES; + } else if (providedMirror != null) { + value = annotationValue(mirror = providedMirror); + binding = Binding.PROVIDED; + } + + if (value != null && value.getValue().toString().isEmpty()) { + trees.printMessage(Diagnostic.Kind.ERROR, + "Empty JBR API binding", + trees.getTree(e, mirror, value), root); + return; + } + + if (currentType == null) { + if (value != null) { + trees.printMessage(Diagnostic.Kind.ERROR, + "JBR API annotations are only allowed on classes, interfaces and static methods", + trees.getTree(e, mirror), root); + } + return; + } + if (value != null && e.getKind() == ElementKind.METHOD && binding != Binding.PROVIDES) { + trees.printMessage(Diagnostic.Kind.ERROR, + "Only @Provides is allowed for static methods", + trees.getTree(e, mirror), root); + return; + } + + // Determine class/method names. + String bindType = null, bindMethod = null; + if (value != null) { + bindType = value.getValue().toString(); + if (e.getKind() == ElementKind.METHOD) { + int splitIndex = bindType.indexOf('#'); + if (splitIndex != -1) { + bindMethod = bindType.substring(splitIndex + 1); + bindType = bindType.substring(0, splitIndex); + if (!isJavaIdentifier(bindMethod)) { + trees.printMessage(Diagnostic.Kind.ERROR, "Invalid method identifier: " + bindMethod, + trees.getTree(e, mirror, value), root); + return; + } + } else bindMethod = e.getSimpleName().toString(); + } + if (!isJavaTypeIdentifier(bindType)) { + trees.printMessage(Diagnostic.Kind.ERROR, "Invalid type identifier: " + bindType, + trees.getTree(e, mirror, value), root); + return; + } + if (Character.isUpperCase(bindType.charAt(0))) bindType = "com.jetbrains." + bindType; // Short form + } + + // Add entry. + DiagnosticTree diagnostic = new DiagnosticTree(root, trees.getTree(e, mirror, value)); + if (e.getKind() == ElementKind.METHOD) { + ExecutableElement m = (ExecutableElement) e; + methodBindings.add(new MethodBinding(diagnostic, m, staticDescriptor(currentType, m), + bindType == null ? null : new Registry.StaticMethod(bindType, bindMethod))); + } else { + typeBindings.put(currentType, new TypeBinding(diagnostic, (TypeElement) e, currentType, bindType, binding)); + } + } + + @Override + public String getName() { + return "jbr-api"; + } + + @Override + public void init(JavacTask jt, String... args) { + Path output = Path.of(args[0]); + String implVersion; + try { + implVersion = Files.readString(Path.of(args[1])).strip(); + } catch (IOException e) { + throw new RuntimeException(e); + } + elements = jt.getElements(); + trees = Trees.instance(jt); + types = jt.getTypes(); + jt.addTaskListener(new TaskListener() { + @Override + public void finished(TaskEvent te) { + if (te.getKind() == TaskEvent.Kind.ANALYZE && te.getTypeElement() != null) { + new ElementScanner14() { + @Override + public Void visitModule(ModuleElement e, CompilationUnitTree unused) { return null; } + @Override + public Void visitPackage(PackageElement e, CompilationUnitTree unused) { return null; } + @Override + public Void scan(Element e, CompilationUnitTree root) { + JBRApiPlugin.this.scan(root, e); + e.accept(this, root); + return null; + } + }.scan(te.getTypeElement(), te.getCompilationUnit()); + } else if (te.getKind() == TaskEvent.Kind.COMPILATION) { + try (RandomAccessFile file = new RandomAccessFile(output.toFile(), "rw"); + FileChannel channel = file.getChannel()) { + for (;;) { + try { if (channel.lock() != null) break; } catch (OverlappingFileLockException ignore) {} + LockSupport.parkNanos(10_000000); + } + Registry r = new Registry(); + r.read(file); + var unresolvedErrors = r.addBindings(); + file.setLength(0); + file.writeBytes("VERSION " + implVersion + "\n"); + r.write(file); + if (!unresolvedErrors.isEmpty()) { + throw new RuntimeException(String.join("\n", unresolvedErrors)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + }); + } +} \ No newline at end of file diff --git a/src/java.base/share/classes/com/jetbrains/base/JBRApiModule.java b/src/java.base/share/classes/com/jetbrains/base/JBRApiModule.java deleted file mode 100644 index 49fca1b8c81f..000000000000 --- a/src/java.base/share/classes/com/jetbrains/base/JBRApiModule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains.base; - -import com.jetbrains.internal.JBRApi; - -import java.lang.invoke.MethodHandles; - -/** - * This class contains mapping between JBR API interfaces and implementation in {@code java.base} module. - */ -public class JBRApiModule { - static { - JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports) - .service("com.jetbrains.JBR$ServiceApi") - .withStatic("getService", "getService", "com.jetbrains.internal.JBRApi") - .service("com.jetbrains.Jstack") - .withStatic("includeInfoFrom", "$$jb$additionalInfoForJstack", "java.lang.Throwable"); - } -} diff --git a/src/java.base/share/classes/com/jetbrains/bootstrap/JBRApiBootstrap.java b/src/java.base/share/classes/com/jetbrains/bootstrap/JBRApiBootstrap.java index 53f893c2e31b..8d321fc625ba 100644 --- a/src/java.base/share/classes/com/jetbrains/bootstrap/JBRApiBootstrap.java +++ b/src/java.base/share/classes/com/jetbrains/bootstrap/JBRApiBootstrap.java @@ -26,45 +26,36 @@ package com.jetbrains.bootstrap; import com.jetbrains.internal.JBRApi; -import jdk.internal.loader.ClassLoaders; import java.lang.invoke.MethodHandles; +import java.util.Map; + /** * Bootstrap class, used to initialize {@linkplain JBRApi JBR API}. + * @deprecated replaced by {@link com.jetbrains.exported.JBRApiSupport} */ +@Deprecated public class JBRApiBootstrap { private JBRApiBootstrap() {} /** - * Classes that register JBR API implementation for their modules. - */ - private static final String[] MODULES = { - "com.jetbrains.base.JBRApiModule", - "com.jetbrains.desktop.JBRApiModule" - }; - - /** - * Called from static initializer of {@link com.jetbrains.JBR}. + * Old version of bootstrap method without metadata parameter. * @param outerLookup lookup context inside {@code jetbrains.api} module * @return implementation for {@link com.jetbrains.JBR.ServiceApi} interface */ public static synchronized Object bootstrap(MethodHandles.Lookup outerLookup) { - if (!System.getProperty("jetbrains.api.enabled", "true").equalsIgnoreCase("true")) return null; - try { - Class apiInterface = outerLookup.findClass("com.jetbrains.JBR$ServiceApi"); - if (!outerLookup.hasFullPrivilegeAccess() || - outerLookup.lookupClass().getPackage() != apiInterface.getPackage()) { - throw new IllegalArgumentException("Lookup must be full-privileged from the com.jetbrains package: " + - outerLookup.lookupClass().getName()); - } - JBRApi.init(outerLookup); - ClassLoader classLoader = ClassLoaders.platformClassLoader(); - for (String m : MODULES) Class.forName(m, true, classLoader); - return JBRApi.getService(apiInterface); - } catch(ClassNotFoundException | IllegalAccessException e) { - throw new RuntimeException(e); + if (!JBRApi.ENABLED) return null; + if (JBRApi.VERBOSE) { + System.out.println("JBR API bootstrap in compatibility mode: Object bootstrap(MethodHandles.Lookup)"); } + Class apiInterface; + try { + apiInterface = outerLookup.findClass("com.jetbrains.JBR$ServiceApi"); + } catch (ClassNotFoundException | IllegalAccessException e) { + throw new RuntimeException("Failed to retrieve JBR API metadata", e); + } + return com.jetbrains.exported.JBRApiSupport.bootstrap(apiInterface, null, null, null, Map.of(), m -> null); } } diff --git a/src/java.base/share/classes/com/jetbrains/exported/JBRApi.java b/src/java.base/share/classes/com/jetbrains/exported/JBRApi.java new file mode 100644 index 000000000000..19a85cfe3524 --- /dev/null +++ b/src/java.base/share/classes/com/jetbrains/exported/JBRApi.java @@ -0,0 +1,92 @@ +package com.jetbrains.exported; + +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; + +import java.io.Serial; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JBR API utility class. + */ +public class JBRApi { + private JBRApi() {} + + /** + * Marks classes and interfaces whose implementation is provided by JBR API client. + * These types must not be inherited by JBR unless explicitly marked with {@link Provides}. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public @interface Provided { + /** + * Binding target. + * This is a fully qualified name of corresponding {@code @Provides} class/interface, + * e.g. {@code com.jetbrains.My.Nested.Class}. + * Package {@code com.jetbrains} may be omitted, e.g. {@code My.Nested.Class}, {@code MyClass#myMethod}. + */ + String value(); + } + + /** + * Marks classes, interfaces and static methods which provide their functionality to JBR API. + */ + @Target({ElementType.TYPE, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Provides { + /** + * Binding target. + * For type binding this is a fully qualified name of corresponding {@code @Provided} class/interface, + * e.g. {@code com.jetbrains.My.Nested.Class}. + * For static method binding this is a fully qualified name of corresponding {@code @Provided} class/interface with optional + * method name appended after '#', e.g. {@code com.jetbrains.MyClass#myMethod}. + * If method name is omitted, name of annotated method itself is used. + * In all cases, package {@code com.jetbrains} may be omitted, e.g. {@code My.Nested.Class}, {@code MyClass#myMethod}. + */ + String value(); + } + + /** + * Marks JBR API service. + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + public @interface Service {} + + /** + * Creates new internal service instance of caller class type. + * Intended usage: {@code public static final MyService INSTANCE = JBRApi.internalService();} + * @return internal service instance + */ + @CallerSensitive + @SuppressWarnings("unchecked") + public static T internalService() { + var caller = (Class) Reflection.getCallerClass(); + if (caller == null) throw new IllegalCallerException("No caller frame"); + return com.jetbrains.internal.JBRApi.getInternalService(caller); + } + + /** + * If a JBR API service has a defined implementation class, it is instantiated via static factory method + * {@code create()} or a no-arg constructor. {@link ServiceNotAvailableException} + * may be thrown from that constructor or factory method to indicate that service is unavailable for some reason. + * Exception is not propagated to user, but rather {@code null} is returned for that service. + * Optional message and cause can be specified, but only used for logging (when explicitly enabled by VM flag). + */ + public static class ServiceNotAvailableException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + public ServiceNotAvailableException() { super(); } + + public ServiceNotAvailableException(String message) { super(message); } + + public ServiceNotAvailableException(String message, Throwable cause) { super(message, cause); } + + public ServiceNotAvailableException(Throwable cause) { super(cause); } + } +} diff --git a/src/java.base/share/classes/com/jetbrains/exported/JBRApiSupport.java b/src/java.base/share/classes/com/jetbrains/exported/JBRApiSupport.java new file mode 100644 index 000000000000..f4bdc0af35b9 --- /dev/null +++ b/src/java.base/share/classes/com/jetbrains/exported/JBRApiSupport.java @@ -0,0 +1,63 @@ +package com.jetbrains.exported; + +import java.lang.annotation.Annotation; +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.function.Function; + +/** + * Supporting exported API for JBR API backend. Not intended to be used by JBR or client code directly. + */ +public class JBRApiSupport { + private JBRApiSupport() {} + + /** + * Initializes JBR API. + * @param apiInterface internal {@code JBR.ServiceApi} interface + * @param serviceAnnotation {@code @Service} annotation class + * @param providedAnnotation {@code @Provided} annotation class + * @param providesAnnotation {@code @Provides} annotation class + * @param knownExtensions map of known extension enums and classes defining them + * @param extensionExtractor receives method, returns its extension enum, or null + * @return implementation for {@code JBR.ServiceApi} interface + */ + @SuppressWarnings("rawtypes") + public static synchronized Object bootstrap(Class apiInterface, + Class serviceAnnotation, + Class providedAnnotation, + Class providesAnnotation, + Map, Class[]> knownExtensions, + Function> extensionExtractor) { + if (!com.jetbrains.internal.JBRApi.ENABLED) return null; + com.jetbrains.internal.JBRApi.init( + null, + serviceAnnotation, + providedAnnotation, + providesAnnotation, + knownExtensions, + extensionExtractor); + return com.jetbrains.internal.JBRApi.getService(apiInterface); + } + + /** + * Bootstrap method for JBR API dynamic invocations. + */ + public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) { + return new ConstantCallSite(com.jetbrains.internal.JBRApi.bindDynamic(caller, name, type)); + } + + /** + * JBR API proxy class. Every generated proxy class implements this interface. + */ + public interface Proxy { + /** + * We really don't want to clash with other possible method names. + * @return target object + */ + Object $getProxyTarget(); + } +} diff --git a/src/java.base/share/classes/com/jetbrains/internal/ASMUtils.java b/src/java.base/share/classes/com/jetbrains/internal/ASMUtils.java index af28b5c0ac86..c70c98d88066 100644 --- a/src/java.base/share/classes/com/jetbrains/internal/ASMUtils.java +++ b/src/java.base/share/classes/com/jetbrains/internal/ASMUtils.java @@ -58,23 +58,21 @@ class ASMUtils { InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod); MethodVisitor p = writer.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(), methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames()); - p.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException"); + p.visitCode(); + throwException(p, "java/lang/UnsupportedOperationException", "No implementation found for this method"); + p.visitMaxs(0, 0); + p.visitEnd(); + } + + public static void throwException(MethodVisitor p, String type, String message) { + p.visitTypeInsn(NEW, type); p.visitInsn(DUP); - p.visitLdcInsn("No implementation found for this method"); - p.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "", "(Ljava/lang/String;)V", false); + p.visitLdcInsn(message); + p.visitMethodInsn(INVOKESPECIAL, type, "", "(Ljava/lang/String;)V", false); p.visitInsn(ATHROW); - p.visitMaxs(-1, -1); } - public static void logDeprecated(MethodVisitor writer, String message) { - writer.visitTypeInsn(NEW, "java/lang/Exception"); - writer.visitInsn(DUP); - writer.visitLdcInsn(message); - writer.visitMethodInsn(INVOKESPECIAL, "java/lang/Exception", "", "(Ljava/lang/String;)V", false); - writer.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false); - } - - protected record InternalMethodInfo(String name, String descriptor, String genericSignature, + public record InternalMethodInfo(String name, String descriptor, String genericSignature, String[] exceptionNames) {} public static InternalMethodInfo getInternalMethodInfo(Method method) { @@ -121,7 +119,7 @@ class ASMUtils { return IRETURN + getOpcodeOffset(c); } - public static int getOpcodeOffset(Class c) { + private static int getOpcodeOffset(Class c) { if (c.isPrimitive()) { if (c == Long.TYPE) { return 1; diff --git a/src/java.base/share/classes/com/jetbrains/internal/AccessContext.java b/src/java.base/share/classes/com/jetbrains/internal/AccessContext.java new file mode 100644 index 000000000000..c6a72a488e8f --- /dev/null +++ b/src/java.base/share/classes/com/jetbrains/internal/AccessContext.java @@ -0,0 +1,140 @@ +package com.jetbrains.internal; + +import jdk.internal.org.objectweb.asm.Handle; +import jdk.internal.org.objectweb.asm.MethodVisitor; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandleInfo; +import java.lang.invoke.MethodType; +import java.util.*; +import java.util.function.Supplier; + +import static java.lang.invoke.MethodHandles.Lookup; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC; +import static jdk.internal.org.objectweb.asm.Type.getInternalName; + +/** + * Context used by {@link ProxyGenerator} to determine whether class or method is + * accessible from a caller class and generate appropriate direct or dynamic calls. + */ +class AccessContext { + + private final Map, Boolean> accessibleClasses = new HashMap<>(); + final Map dependencies = new HashMap<>(); // true for required, false for optional + final List dynamicCallTargets = new ArrayList<>(); + final Lookup caller; + + AccessContext(Lookup caller) { + this.caller = caller; + } + + record DynamicCallTarget(String name, String descriptor, Supplier futureHandle) {} + + class Method { + final MethodVisitor writer; + private final boolean methodRequired; + + Method(MethodVisitor writer, boolean methodRequired) { + this.writer = writer; + this.methodRequired = methodRequired; + } + + AccessContext access() { + return AccessContext.this; + } + + void addDependency(Proxy p) { + if (methodRequired) dependencies.put(p, true); + else dependencies.putIfAbsent(p, false); + } + + void invokeDynamic(MethodHandle handle) { + invokeDynamic(handle.type(), () -> handle); + } + + void invokeDynamic(MethodType type, Supplier futureHandle) { + String descriptor = erase(type).descriptorString(); + DynamicCallTarget t = new DynamicCallTarget("dynamic" + dynamicCallTargets.size(), descriptor, futureHandle); + dynamicCallTargets.add(t); + writer.visitInvokeDynamicInsn(t.name, descriptor, + new Handle(H_INVOKESTATIC, "com/jetbrains/exported/JBRApiSupport", "bootstrapDynamic", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false)); + } + + void invokeDirect(MethodHandleInfo handleInfo) { + int opcode = switch (handleInfo.getReferenceKind()) { + default -> throw new RuntimeException("Unknown reference type"); + case MethodHandleInfo.REF_getField -> GETFIELD; + case MethodHandleInfo.REF_getStatic -> GETSTATIC; + case MethodHandleInfo.REF_putField -> PUTFIELD; + case MethodHandleInfo.REF_putStatic -> PUTSTATIC; + case MethodHandleInfo.REF_invokeVirtual -> INVOKEVIRTUAL; + case MethodHandleInfo.REF_invokeStatic -> INVOKESTATIC; + case MethodHandleInfo.REF_invokeSpecial -> INVOKESPECIAL; + case MethodHandleInfo.REF_newInvokeSpecial -> NEW; + case MethodHandleInfo.REF_invokeInterface -> INVOKEINTERFACE; + }; + String targetName = getInternalName(handleInfo.getDeclaringClass()); + if (opcode == NEW) { + writer.visitTypeInsn(NEW, targetName); + writer.visitInsn(DUP); + opcode = INVOKESPECIAL; + } + switch (opcode) { + case GETFIELD, GETSTATIC -> writer.visitFieldInsn(opcode, targetName, handleInfo.getName(), + handleInfo.getMethodType().returnType().descriptorString()); + case PUTFIELD, PUTSTATIC -> writer.visitFieldInsn(opcode, targetName, handleInfo.getName(), + handleInfo.getMethodType().parameterType(0).descriptorString()); + default -> writer.visitMethodInsn(opcode, targetName, handleInfo.getName(), + handleInfo.getMethodType().descriptorString(), handleInfo.getDeclaringClass().isInterface()); + } + } + } + + MethodHandleInfo resolveDirect(MethodHandle handle) { + try { + MethodHandleInfo handleInfo = caller.revealDirect(handle); + if (isClassLoaderAccessible(caller.lookupClass().getClassLoader(), + handleInfo.getDeclaringClass().getClassLoader())) { + // Accessible directly. + return handleInfo; + } + } catch (Exception ignore) {} + // Not accessible directly. + return null; + } + + /** + * Replaces all inaccessible types with {@link Object}. + * @param type original method type + * @return erased method type + */ + private MethodType erase(MethodType type) { + if (!canAccess(type.returnType())) type = type.changeReturnType(Object.class); + for (int i = 0; i < type.parameterCount(); i++) { + if (!canAccess(type.parameterType(i))) type = type.changeParameterType(i, Object.class); + } + return type; + } + + boolean canAccess(Class clazz) { + return accessibleClasses.computeIfAbsent(clazz, c -> canAccess(caller, c)); + } + + static boolean canAccess(Lookup caller, Class target) { + try { + if (isClassLoaderAccessible(caller.lookupClass().getClassLoader(), + caller.accessClass(target).getClassLoader())) return true; + } catch (IllegalAccessException ignore) {} + return false; + } + + private static boolean isClassLoaderAccessible(ClassLoader caller, ClassLoader target) { + if (target == null) return true; + for (ClassLoader cl = caller; cl != null; cl = cl.getParent()) { + if (cl == target) return true; + } + return false; + } +} diff --git a/src/java.base/share/classes/com/jetbrains/internal/JBRApi.java b/src/java.base/share/classes/com/jetbrains/internal/JBRApi.java index dd45238c5f1d..97ee533ce714 100644 --- a/src/java.base/share/classes/com/jetbrains/internal/JBRApi.java +++ b/src/java.base/share/classes/com/jetbrains/internal/JBRApi.java @@ -25,328 +25,203 @@ package com.jetbrains.internal; -import sun.security.action.GetBooleanAction; +import com.jetbrains.exported.JBRApi.Provides; -import java.io.Serial; +import java.io.InputStream; +import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.*; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; import static java.lang.invoke.MethodHandles.Lookup; /** * JBR API is a collection of JBR-specific features that are accessed by client though - * {@link com.jetbrains.JBR jetbrains.api} module. Actual implementation is linked by + * {@link com.jetbrains.JBR jetbrains.runtime.api} module. Actual implementation is linked by * JBR at runtime by generating {@linkplain Proxy proxy objects}. - * Mapping between interfaces and implementation code is defined in - * {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}. + * Mapping between interfaces and implementation code is defined using + * {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations. *

- * This class has most basic methods for working with JBR API and cache of generated proxies. - *

- *

How to add a new service

- *
    - *
  1. Create your service interface in module {@link com.jetbrains.JBR jetbrains.api}: - *
    {@code
    - *         package com.jetbrains;
    - *
    - *         interface StringOptimizer {
    - *             void optimize(String string);
    - *         }
    - *         }
    - *
  2. - *
  3. Create an implementation inside JBR: - *
    {@link java.lang.String java.lang.String}:{@code
    - *         private static void optimizeInternal(String string) {
    - *             string.hash = 0;
    - *             string.hashIsZero = true;
    - *         }
    - *         }
    - *
  4. - *
  5. Register your service in corresponding - * {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES module registry class}: - *
    {@link com.jetbrains.base.JBRApiModule}:{@code
    - *         .service("com.jetbrains.StringOptimizer", null)
    - *             .withStatic("optimize", "java.lang.String", "optimizeInternal")
    - *         }
    - *
  6. - *
  7. You can also bind the interface to implementation class - * (without actually implementing the interface): - *
    {@link java.lang.String java.lang.String}:{@code
    - *         private static class StringOptimizerImpl {
    - *
    - *             private void optimize(String string) {
    - *                 string.hash = 0;
    - *                 string.hashIsZero = true;
    - *             }
    - *         }
    - *         }
    - *
    {@link com.jetbrains.base.JBRApiModule}:{@code
    - *         .service("com.jetbrains.StringOptimizer", "java.lang.String$StringOptimizerImpl")
    - *         }
    - *
  8. - *
- *

How to add a new proxy

- * Registering a proxy is similar to registering a service: - *
{@code
- * .proxy("com.jetbrains.SomeProxy", "a.b.c.SomeProxyImpl")
- * }
- * Note that unlike service, proxy must have a target type. - * Also, proxy expects target object as a single constructor argument - * and can only be instantiated by JBR, there's no API that would allow - * user to directly create proxy object. + * This class is an entry point into JBR API backend. + * @see Proxy */ public class JBRApi { - @SuppressWarnings("removal") - static final boolean VERBOSE = AccessController.doPrivileged(new GetBooleanAction("jetbrains.api.verbose")); - - private static final Map registeredProxyInfoByInterfaceName = new HashMap<>(); - private static final Map registeredProxyInfoByTargetName = new HashMap<>(); - private static final ConcurrentMap, Proxy> proxyByInterface = new ConcurrentHashMap<>(); - /** - * lookup context inside {@code jetbrains.api} module + * Enable JBR API, it wouldn't init when disabled. Enabled by default. */ - static Lookup outerLookup; + public static final boolean ENABLED = Utils.property("jetbrains.runtime.api.enabled", true); /** - * Known service and proxy interfaces extracted from {@link com.jetbrains.JBR.Metadata} + * Enable API extensions. When disabled, extension methods are treated like any other method, + * {@link JBRApi#isExtensionSupported} always returns false, {@link JBRApi#getService(Class, Enum[])} + * behaves the same as {@link JBRApi#getService(Class)}. Enabled by default. */ - static Set knownServices, knownProxies; + static final boolean EXTENSIONS_ENABLED = Utils.property("jetbrains.runtime.api.extensions.enabled", true); + /** + * Enable extensive debugging logging. Disabled by default. + */ + public static final boolean VERBOSE = Utils.property("jetbrains.runtime.api.verbose", false); + /** + * Print warnings about usage of deprecated interfaces and methods to {@link System#err}. Enabled by default. + */ + static final boolean LOG_DEPRECATED = Utils.property("jetbrains.runtime.api.logDeprecated", true); + /** + * Enable additional verification of generated bytecode. Disabled by default. + */ + static final boolean VERIFY_BYTECODE = Utils.property("jetbrains.runtime.api.verifyBytecode", false); + /** + * Allow extending registry. Disabled by default, used for tests. + */ + private static final boolean EXTEND_REGISTRY = Utils.property("jetbrains.runtime.api.extendRegistry", false); - public static void init(Lookup outerLookup) { - JBRApi.outerLookup = outerLookup; - try { - Class metadataClass = outerLookup.findClass("com.jetbrains.JBR$Metadata"); - Lookup lookup = MethodHandles.privateLookupIn(metadataClass, outerLookup); - knownServices = Set.of((String[]) lookup.findStaticVarHandle(metadataClass, - "KNOWN_SERVICES", String[].class).get()); - knownProxies = Set.of((String[]) lookup.findStaticVarHandle(metadataClass, - "KNOWN_PROXIES", String[].class).get()); - } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - knownServices = Set.of(); - knownProxies = Set.of(); + record DynamicCallTargetKey(Class proxy, String name, String descriptor) {} + static final ConcurrentMap> dynamicCallTargets = new ConcurrentHashMap<>(); + private static final ProxyRepository proxyRepository = new ProxyRepository(); + + private static Boolean[] supportedExtensions; + private static long[] emptyExtensionsBitfield; + @SuppressWarnings("rawtypes") + private static Map, Class[]> knownExtensions; + static Function> extensionExtractor; + + @SuppressWarnings("rawtypes") + public static void init(InputStream extendedRegistryStream, + Class serviceAnnotation, + Class providedAnnotation, + Class providesAnnotation, + Map, Class[]> knownExtensions, + Function> extensionExtractor) { + if (extendedRegistryStream != null && !EXTEND_REGISTRY) { + throw new Error("Extending JBR API registry is not supported"); } + proxyRepository.init(extendedRegistryStream, serviceAnnotation, providedAnnotation, providesAnnotation); + + if (EXTENSIONS_ENABLED) { + JBRApi.knownExtensions = knownExtensions; + JBRApi.extensionExtractor = extensionExtractor; + supportedExtensions = new Boolean[ + knownExtensions.keySet().stream().mapToInt(Enum::ordinal).max().orElse(-1) + 1]; + emptyExtensionsBitfield = new long[(supportedExtensions.length + 63) / 64]; + } + if (VERBOSE) { - System.out.println("JBR API init\nKNOWN_SERVICES = " + knownServices + "\nKNOWN_PROXIES = " + knownProxies); + System.out.println("JBR API init\n knownExtensions = " + (EXTENSIONS_ENABLED ? knownExtensions.keySet() : "DISABLED")); } } + public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) { + if (VERBOSE) { + System.out.println("Binding call site " + caller.lookupClass().getName() + "#" + name + ": " + type); + } + if (!caller.hasFullPrivilegeAccess()) throw new Error("Caller lookup must have full privilege access"); // Authenticity check. + return dynamicCallTargets.get(new DynamicCallTargetKey(caller.lookupClass(), name, type.descriptorString())).get().asType(type); + } + + /** + * @return JBR API version supported by current implementation. + */ + @Provides("JBR.ServiceApi") + public static String getImplVersion() { + return proxyRepository.getVersion(); + } + + /** + * @param extension extension name + * @return true if all methods belonging to given extension are supported + * @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi} + * service, but is not directly exposed to user. + */ + @Provides("JBR.ServiceApi") + public static boolean isExtensionSupported(Enum extension) { + if (!EXTENSIONS_ENABLED) return false; + int i = extension.ordinal(); + if (supportedExtensions[i] == null) { + synchronized (JBRApi.class) { + if (supportedExtensions[i] == null) { + boolean result = true; + for (Class c : knownExtensions.get(extension)) { + result &= proxyRepository.getProxy(c, null).isExtensionSupported(extension); + } + supportedExtensions[i] = result; + } + } + } + return supportedExtensions[i]; + } + + /** + * @return fully supported service implementation for the given interface with specified extensions, or null + * @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi} + * service, but is not directly exposed to user. + */ + @Provides("JBR.ServiceApi") + public static T getService(Class interFace, Enum... extensions) { + if (!EXTENSIONS_ENABLED) return getService(interFace); + + long[] bitfield = new long[emptyExtensionsBitfield.length]; + for (Enum e : extensions) { + if (isExtensionSupported(e)) { + int i = e.ordinal() / 64; + int j = e.ordinal() % 64; + bitfield[i] |= 1L << j; + } else { + if (VERBOSE) { + Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Extension not supported: " + e.name()); + } + return null; + } + } + + return getService(interFace, bitfield, true); + } + /** * @return fully supported service implementation for the given interface, or null * @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi} * service, but is not directly exposed to user. */ + @Provides("JBR.ServiceApi") public static T getService(Class interFace) { - Proxy p = getProxy(interFace); - return p != null && p.isSupported() ? p.getInstance() : null; + return getService(interFace, emptyExtensionsBitfield, true); + } + + public static T getInternalService(Class interFace) { + return getService(interFace, emptyExtensionsBitfield, false); } - /** - * @return proxy for the given interface, or {@code null} - */ @SuppressWarnings("unchecked") - static Proxy getProxy(Class interFace) { - return (Proxy) proxyByInterface.computeIfAbsent(interFace, i -> { - RegisteredProxyInfo info = registeredProxyInfoByInterfaceName.get(i.getName()); - if (info == null) return null; - ProxyInfo resolved = ProxyInfo.resolve(info); - if (resolved == null) { - if (VERBOSE) { - System.err.println("Couldn't resolve proxy info: " + i.getName()); - } - return null; - } else return new Proxy<>(resolved); - }); - } - - /** - * @return true if given class represents a proxy interface. Even if {@code jetbrains.api} - * introduces new interfaces JBR is not aware of, these interfaces would still be detected - * by this method. - */ - static boolean isKnownProxyInterface(Class clazz) { - String name = clazz.getName(); - return registeredProxyInfoByInterfaceName.containsKey(name) || - knownServices.contains(name) || knownProxies.contains(name); - } - - /** - * Reverse lookup by proxy target type name. - * @return user-side interface for given implementation target type name. - */ - static Class getProxyInterfaceByTargetName(String targetName) { - RegisteredProxyInfo info = registeredProxyInfoByTargetName.get(targetName); - if (info == null) return null; - try { - return Class.forName(info.interfaceName(), true, - (info.type().isPublicApi() ? outerLookup : info.apiModule()).lookupClass().getClassLoader()); - } catch (ClassNotFoundException e) { - if (VERBOSE) e.printStackTrace(); + private static T getService(Class interFace, long[] extensions, boolean publicService) { + Proxy p = proxyRepository.getProxy(interFace, null); + if ((p.getFlags() & Proxy.SERVICE) == 0 || (publicService && (p.getFlags() & Proxy.INTERNAL) != 0)) { + if (VERBOSE) { + Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Not allowed as a service: " + interFace.getCanonicalName()); + } return null; } - } - - public static InternalServiceBuilder internalServiceBuilder(Lookup interFace, String... targets) { - return new InternalServiceBuilder(new RegisteredProxyInfo( - interFace, interFace.lookupClass().getName(), targets, ProxyInfo.Type.INTERNAL_SERVICE, new ArrayList<>())); - } - - public static class InternalServiceBuilder { - - private final RegisteredProxyInfo info; - - private InternalServiceBuilder(RegisteredProxyInfo info) { - this.info = info; + if (!p.init()) { + if (VERBOSE) { + Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not supported: " + interFace.getCanonicalName()); + } + return null; } - - public InternalServiceBuilder withStatic(String interfaceMethodName, String methodName, String... classes) { - info.staticMethods().add( - new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, methodName, classes)); - return this; - } - - @SuppressWarnings("removal") - public Object build() { - return AccessController.doPrivileged((PrivilegedAction) () -> { - ProxyInfo info = ProxyInfo.resolve(this.info); - if (info == null) return null; - ProxyGenerator generator = new ProxyGenerator(info); - if (!generator.areAllMethodsImplemented()) return null; - generator.defineClasses(); - MethodHandle constructor = generator.findConstructor(); - generator.init(); - try { - return constructor.invoke(); - } catch (Throwable e) { - throw new RuntimeException(e); + try { + MethodHandle constructor = p.getConstructor(); + return (T) (EXTENSIONS_ENABLED ? constructor.invoke(extensions) : constructor.invoke()); + } catch (com.jetbrains.exported.JBRApi.ServiceNotAvailableException | NullPointerException e) { + if (VERBOSE) { + synchronized (System.err) { + Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not available: " + interFace.getCanonicalName()); + System.err.print("Caused by: "); + e.printStackTrace(System.err); } - }); - } - } - - /** - * Called by {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes} - * to register a new mapping for corresponding modules. - */ - public static ModuleRegistry registerModule(Lookup lookup, BiFunction addExports) { - addExports.apply(lookup.lookupClass().getPackageName(), outerLookup.lookupClass().getModule()); - return new ModuleRegistry(lookup); - } - - public static class ModuleRegistry { - - private final Lookup lookup; - private RegisteredProxyInfo lastProxy; - - private ModuleRegistry(Lookup lookup) { - this.lookup = lookup; - } - - private ModuleRegistry addProxy(ProxyInfo.Type type, String interfaceName, String... targets) { - lastProxy = new RegisteredProxyInfo(lookup, interfaceName, targets, type, new ArrayList<>()); - registeredProxyInfoByInterfaceName.put(interfaceName, lastProxy); - for (String target : targets) { - registeredProxyInfoByTargetName.put(target, lastProxy); } - if (targets.length == 1) { - validate2WayMapping(lastProxy, registeredProxyInfoByInterfaceName.get(targets[0])); - validate2WayMapping(lastProxy, registeredProxyInfoByTargetName.get(interfaceName)); - } - return this; + } catch (Throwable e) { + throw new RuntimeException(e); } - private static void validate2WayMapping(RegisteredProxyInfo p, RegisteredProxyInfo reverse) { - if (reverse != null && - (!p.interfaceName().equals(reverse.targets()[0]) || !p.targets()[0].equals(reverse.interfaceName()))) { - throw new IllegalArgumentException("Invalid 2-way proxy mapping: " + - p.interfaceName() + " -> " + p.targets()[0] + " & " + - reverse.interfaceName() + " -> " + reverse.targets()[0]); - } - } - - /** - * Register new {@linkplain ProxyInfo.Type#PROXY proxy} mapping. - *

- * When {@code target} object is passed from JBR to client through service or any other proxy, - * it's converted to corresponding {@code interFace} type by creating a proxy object - * that implements {@code interFace} and delegates method calls to {@code target}. - * @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module. - * @param targets corresponding class/interface names in current JBR module, first found will be used. This must not be empty. - * @apiNote class name example: {@code pac.ka.ge.Outer$Inner} - */ - public ModuleRegistry proxy(String interFace, String... targets) { - if (targets.length == 0) throw new IllegalArgumentException("Proxy must have at least one target"); - return addProxy(ProxyInfo.Type.PROXY, interFace, targets); - } - - /** - * Register new {@linkplain ProxyInfo.Type#SERVICE service} mapping. - *

- * Service is a singleton, which may be accessed by client using {@link com.jetbrains.JBR} class. - * @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module. - * @param targets corresponding implementation class names in current JBR module, first found will be used. - * @apiNote class name example: {@code pac.ka.ge.Outer$Inner} - */ - public ModuleRegistry service(String interFace, String... targets) { - return addProxy(ProxyInfo.Type.SERVICE, interFace, targets); - } - - /** - * Register new {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} mapping. - * This mapping type allows implementation of callbacks. - *

- * When {@code target} object is passed from client to JBR through service or any other proxy, - * it's converted to corresponding {@code interFace} type by creating a proxy object - * that implements {@code interFace} and delegates method calls to {@code target}. - * @param interFace interface name in current JBR module. - * @param target corresponding class/interface name in {@link com.jetbrains.JBR jetbrains.api} module. - * @apiNote class name example: {@code pac.ka.ge.Outer$Inner} - */ - public ModuleRegistry clientProxy(String interFace, String target) { - Objects.requireNonNull(target); - return addProxy(ProxyInfo.Type.CLIENT_PROXY, interFace, target); - } - - /** - * Register new 2-way mapping. - * It creates both {@linkplain ProxyInfo.Type#PROXY proxy} and - * {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} between given interfaces. - *

- * It links together two given interfaces and allows passing such objects back and forth - * between JBR and {@link com.jetbrains.JBR jetbrains.api} module through services and other proxy methods. - * @param apiInterface interface name in {@link com.jetbrains.JBR jetbrains.api} module. - * @param jbrInterface interface name in current JBR module. - * @apiNote class name example: {@code pac.ka.ge.Outer$Inner} - */ - public ModuleRegistry twoWayProxy(String apiInterface, String jbrInterface) { - clientProxy(jbrInterface, apiInterface); - proxy(apiInterface, jbrInterface); - return this; - } - - /** - * Delegate "{@code interfaceMethodName}" method calls to first found static "{@code methodName}" in "{@code classes}". - */ - public ModuleRegistry withStatic(String interfaceMethodName, String methodName, String... classes) { - lastProxy.staticMethods().add( - new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, methodName, classes)); - return this; - } - } - - /** - * Thrown by service implementations indicating that the service is not available for some reason - */ - public static class ServiceNotAvailableException extends RuntimeException { - @Serial - private static final long serialVersionUID = 1L; - public ServiceNotAvailableException() { super(); } - public ServiceNotAvailableException(String message) { super(message); } - public ServiceNotAvailableException(String message, Throwable cause) { super(message, cause); } - public ServiceNotAvailableException(Throwable cause) { super(cause); } + return null; } } diff --git a/src/java.base/share/classes/com/jetbrains/internal/Mapping.java b/src/java.base/share/classes/com/jetbrains/internal/Mapping.java new file mode 100644 index 000000000000..8a0792ab42db --- /dev/null +++ b/src/java.base/share/classes/com/jetbrains/internal/Mapping.java @@ -0,0 +1,441 @@ +/* + * Copyright 2023 JetBrains s.r.o. + * 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.jetbrains.internal; + +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.*; +import java.util.*; +import java.util.stream.Stream; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static jdk.internal.org.objectweb.asm.Type.getInternalName; + +/** + * Mapping defines conversion of parameters and return types between source and destination method. + */ +abstract class Mapping { + + static class Query { + boolean valid = true; + boolean needsExtensions = false; + } + + final Class from, to; + + private Mapping(Class from, Class to) { + this.from = from; + this.to = to; + } + + void convert(AccessContext.Method context) { + MethodVisitor m = context.writer; + Label skipConvert = new Label(); + m.visitInsn(DUP); + m.visitJumpInsn(IFNULL, skipConvert); + convertNonNull(context); + m.visitLabel(skipConvert); + } + + abstract void convertNonNull(AccessContext.Method context); + + void cast(AccessContext.Method context) { + if (context.access().canAccess(to)) context.writer.visitTypeInsn(CHECKCAST, getInternalName(to)); + } + + abstract Mapping inverse(); + + void query(Query q) {} + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); + + @Override + public abstract String toString(); + + static class Identity extends Mapping { + private Identity(Class c) { + super(c, c); + } + @Override + void convert(AccessContext.Method context) {} + @Override + void convertNonNull(AccessContext.Method context) {} + @Override + Mapping inverse() { return this; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Identity i = (Identity) o; + return from.equals(i.from); + } + @Override + public int hashCode() { return from.hashCode(); } + @Override + public String toString() { return from.getName(); } + } + + static class Invalid extends Identity { + private Invalid(Class c) { + super(c); + } + @Override + void query(Query q) { q.valid = false; } + @Override + public String toString() { return "INVALID(" + from.getName() + ")"; } + } + + static abstract class Nesting extends Mapping { + final Mapping component; + Nesting(Class from, Class to, Mapping component) { + super(from, to); + this.component = component; + } + @Override + void query(Query q) { component.query(q); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Nesting nesting = (Nesting) o; + if (!Objects.equals(from, nesting.from)) return false; + if (!Objects.equals(to, nesting.to)) return false; + return component.equals(nesting.component); + } + @Override + public int hashCode() { + int result = from != null ? from.hashCode() : 0; + result = 31 * result + (to != null ? to.hashCode() : 0); + result = 31 * result + component.hashCode(); + return result; + } + } + + static class Array extends Nesting { + private Array(Mapping component) { + super(component.from.arrayType(), component.to.arrayType(), component); + } + static Mapping wrap(Mapping m) { + if (m instanceof Identity) return new Identity(m.from.arrayType()); + else return new Array(m); + } + @Override + void convert(AccessContext.Method context) { + super.convert(context); + cast(context); // Explicitly cast to result type after non-null branch + } + @Override + void convertNonNull(AccessContext.Method context) { + final int TEMP_COUNTER_SLOT = 1; // Warning! We overwrite 1st local slot. + MethodVisitor m = context.writer; + Label loopStart = new Label(), loopEnd = new Label(); + // Stack: fromArray -> toArray, fromArray, i=length + if (!context.access().canAccess(from)) m.visitTypeInsn(CHECKCAST, "[Ljava/lang/Object;"); + m.visitInsn(DUP); + m.visitInsn(ARRAYLENGTH); + if (context.access().canAccess(to)) { + m.visitTypeInsn(ANEWARRAY, getInternalName(Objects.requireNonNull(to.componentType()))); + } else context.invokeDynamic(MethodHandles.arrayConstructor(to)); + m.visitInsn(SWAP); + m.visitInsn(DUP); + m.visitInsn(ARRAYLENGTH); + // Check loop conditions + m.visitLabel(loopStart); + m.visitInsn(DUP); + m.visitJumpInsn(IFLE, loopEnd); + // Stack: toArray, fromArray, i -> toArray, fromArray, i, toArray, i, from + m.visitVarInsn(ISTORE, TEMP_COUNTER_SLOT); + m.visitInsn(DUP2); + m.visitIincInsn(TEMP_COUNTER_SLOT, -1); + m.visitVarInsn(ILOAD, TEMP_COUNTER_SLOT); + m.visitInsn(DUP_X2); + m.visitInsn(DUP_X1); + m.visitInsn(AALOAD); + // Stack from -> to + component.convert(context); + // Stack: toArray, fromArray, i, toArray, i, to -> toArray, fromArray, i + m.visitInsn(AASTORE); + m.visitJumpInsn(GOTO, loopStart); + m.visitLabel(loopEnd); + // Stack: toArray, fromArray, i -> toArray + m.visitInsn(POP2); + } + @Override + Mapping inverse() { return new Array(component.inverse()); } + @Override + public String toString() { return "[" + component + "]"; } + } + + private static abstract class ProxyConversion extends Mapping { + final Proxy fromProxy, toProxy; + private ProxyConversion(Class from, Class to, Proxy fromProxy, Proxy toProxy) { + super(from, to); + this.fromProxy = fromProxy; + this.toProxy = toProxy; + } + void wrapNonNull(AccessContext.Method context) { + context.addDependency(toProxy); + MethodType mt; + if (JBRApi.EXTENSIONS_ENABLED) { + context.writer.visitVarInsn(ALOAD, 0); + mt = MethodType.methodType(to, from, long[].class); + } else { + mt = MethodType.methodType(to, from); + } + context.invokeDynamic(mt, toProxy::getConstructor); + } + void extractNonNull(AccessContext.Method context) { + context.addDependency(fromProxy); + context.writer.visitMethodInsn(INVOKEINTERFACE, + "com/jetbrains/exported/JBRApiSupport$Proxy", "$getProxyTarget", "()Ljava/lang/Object;", true); + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProxyConversion i = (ProxyConversion) o; + return from.equals(i.from) && to.equals(i.to) && + Objects.equals(fromProxy, i.fromProxy) && Objects.equals(toProxy, i.toProxy); + } + @Override + public int hashCode() { + int result = from.hashCode(); + result = 31 * result + to.hashCode(); + result = 31 * result + Objects.hashCode(fromProxy); + result = 31 * result + Objects.hashCode(toProxy); + return result; + } + } + + private static class Wrap extends ProxyConversion { + private Wrap(Class from, Class to, Proxy proxy) { + super(from, to, null, proxy); + } + @Override + void convertNonNull(AccessContext.Method context) { wrapNonNull(context); } + @Override + Mapping inverse() { return new Extract(to, from, toProxy); } + @Override + void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; } + @Override + public String toString() { return from.getName() + " --wrap-> " + to.getName(); } + } + + private static class Extract extends ProxyConversion { + private Extract(Class from, Class to, Proxy proxy) { + super(from, to, proxy, null); + } + @Override + void convert(AccessContext.Method context) { + super.convert(context); + cast(context); // Explicitly cast to result type after non-null branch + } + @Override + void convertNonNull(AccessContext.Method context) { extractNonNull(context); } + @Override + Mapping inverse() { return new Wrap(to, from, fromProxy); } + @Override + public String toString() { return from.getName() + " --extract-> " + to.getName(); } + } + + private static class Dynamic2Way extends ProxyConversion { + private Dynamic2Way(Class from, Class to, Proxy fromProxy, Proxy toProxy) { + super(from, to, fromProxy, toProxy); + } + @Override + void convert(AccessContext.Method context) { + Label elseBranch = new Label(), afterBranch = new Label(); + MethodVisitor m = context.writer; + m.visitInsn(DUP); + m.visitJumpInsn(IFNULL, afterBranch); + m.visitInsn(DUP); + m.visitTypeInsn(INSTANCEOF, "com/jetbrains/exported/JBRApiSupport$Proxy"); + m.visitJumpInsn(IFEQ, elseBranch); + extractNonNull(context); + m.visitJumpInsn(GOTO, afterBranch); + m.visitLabel(elseBranch); + wrapNonNull(context); + m.visitLabel(afterBranch); + cast(context); // Explicitly cast to result type after non-null branch + } + @Override + void convertNonNull(AccessContext.Method context) {} + @Override + Mapping inverse() { return new Dynamic2Way(to, from, toProxy, fromProxy); } + @Override + void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; } + @Override + public String toString() { return from.getName() + " --2way-> " + to.getName(); } + } + + private static class CustomOptional extends Nesting { + private CustomOptional(Mapping component) { + super(Optional.class, Optional.class, component); + } + static Mapping wrap(Mapping m) { + if (m instanceof Identity) return new Identity(Optional.class); + else return new CustomOptional(m); + } + @Override + void convertNonNull(AccessContext.Method context) { + MethodVisitor m = context.writer; + m.visitInsn(ACONST_NULL); + m.visitMethodInsn(INVOKEVIRTUAL, "java/util/Optional", "orElse", "(Ljava/lang/Object;)Ljava/lang/Object;", false); + component.convert(context); + m.visitMethodInsn(INVOKESTATIC, "java/util/Optional", "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;", false); + } + @Override + Mapping inverse() { return new CustomOptional(component.inverse()); } + @Override + public String toString() { return "Optional<" + component + ">"; } + } + + record Method(MethodType type, Mapping returnMapping, Mapping[] parameterMapping, Query query) { + @Override + public String toString() { + return returnMapping + "(" + Arrays.toString(parameterMapping) + ")"; + } + } + + static class Context { + + private final ProxyRepository proxyRepository; + private final Map, Mapping> tvMappings = new HashMap<>(); + + Context(ProxyRepository proxyRepository) { + this.proxyRepository = proxyRepository; + } + + void initTypeParameters(Class type, Stream typeParameters) { + if (type == null) return; + if (typeParameters != null) { + TypeVariable[] tvs = type.getTypeParameters(); + Iterator tpIterator = typeParameters.iterator(); + for (int i = 0;; i++) { + if ((i < tvs.length) ^ tpIterator.hasNext()) throw new RuntimeException("Number of type parameters doesn't match"); + if (i >= tvs.length) break; + tvMappings.put(tvs[i], tpIterator.next()); + } + } + initTypeParameters(type.getGenericSuperclass()); + for (Type t : type.getGenericInterfaces()) initTypeParameters(t); + } + + void initTypeParameters(Type supertype) { + if (supertype instanceof ParameterizedType type) { + initTypeParameters((Class) type.getRawType(), + Stream.of(type.getActualTypeArguments()).map(this::getMapping)); + } else if (supertype instanceof Class c) { + initTypeParameters(c, null); + } else if (supertype != null) { + throw new RuntimeException("Unknown supertype kind: " + supertype.getClass()); + } + } + + Method getMapping(java.lang.reflect.Method interfaceMethod) { + Type[] params = interfaceMethod.getGenericParameterTypes(); + List> ptypes = new ArrayList<>(params.length); + Mapping[] paramMappings = new Mapping[params.length]; + for (int i = 0; i < params.length; i++) { + paramMappings[i] = getMapping(params[i]); + ptypes.add(paramMappings[i].to); + } + Mapping returnMapping = getMapping(interfaceMethod.getGenericReturnType()).inverse(); + return new Method(MethodType.methodType(returnMapping.from, ptypes), returnMapping, paramMappings, + query(returnMapping, paramMappings)); + } + + private Mapping getMapping(Type userType) { + if (userType instanceof Class t) { + return getMapping(t, null); + } else if (userType instanceof GenericArrayType t) { + return Array.wrap(getMapping(t.getGenericComponentType())); + } else if (userType instanceof ParameterizedType t) { + return getMapping(t); + } else if (userType instanceof TypeVariable t) { + Mapping tvMapping = tvMappings.get(t); + if (tvMapping != null) return tvMapping; + Type[] bounds = t.getBounds(); + return getMapping(bounds.length > 0 ? bounds[0] : Object.class); + } else if (userType instanceof WildcardType t) { + Type[] bounds = t.getUpperBounds(); + return getMapping(bounds.length > 0 ? bounds[0] : Object.class); + } else { + throw new RuntimeException("Unknown type kind: " + userType.getClass()); + } + } + + private Mapping getMapping(ParameterizedType userType) { + Type[] actual = userType.getActualTypeArguments(); + Mapping[] specialization = null; + for (int i = 0; i < actual.length; i++) { + Mapping m = getMapping(actual[i]); + if (m instanceof Identity) continue; + if (specialization == null) specialization = new Mapping[actual.length]; + specialization[i] = m; + } + return getMapping((Class) userType.getRawType(), specialization); + } + + private Mapping getMapping(Class userType, Mapping[] specialization) { + if (userType.isArray()) { + return Array.wrap(getMapping(userType.componentType())); + } + if (specialization != null && specialization.length == 1 && specialization[0] != null && + userType.equals(Optional.class)) { + return CustomOptional.wrap(specialization[0]); + } + Proxy p = proxyRepository.getProxy(userType, specialization); + if (p.supported() != Boolean.FALSE && p.inverse().supported() != Boolean.FALSE && + (p.getFlags() & Proxy.SERVICE) == 0 && (p.inverse().getFlags() & Proxy.SERVICE) == 0) { + if (p.getInterface() != null && p.inverse().getInterface() != null) { + return new Dynamic2Way(userType, p.inverse().getInterface(), p, p.inverse()); + } else if (p.getInterface() != null) { + if (p.getTarget() != null) return new Extract(userType, p.getTarget(), p); + } else if (p.inverse().getInterface() != null) { + return new Wrap(userType, p.inverse().getInterface(), p.inverse()); + } else { + return new Identity(userType); + } + } + return new Invalid(userType); + } + + private static Query query(Mapping returnMapping, Mapping[] parameterMapping) { + Query q = new Query(); + returnMapping.query(q); + for (Mapping p : parameterMapping) p.query(q); + return q; + } + } +} diff --git a/src/java.base/share/classes/com/jetbrains/internal/Proxy.java b/src/java.base/share/classes/com/jetbrains/internal/Proxy.java index 29195c3f5621..0a70b7792a4d 100644 --- a/src/java.base/share/classes/com/jetbrains/internal/Proxy.java +++ b/src/java.base/share/classes/com/jetbrains/internal/Proxy.java @@ -26,203 +26,255 @@ package com.jetbrains.internal; import java.lang.invoke.MethodHandle; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; +import java.lang.invoke.MethodType; +import java.util.*; + +import static java.lang.invoke.MethodHandles.Lookup; /** * Proxy is needed to dynamically link JBR API interfaces and implementation at runtime. - * It implements user-side interfaces and delegates method calls to actual implementation - * code through {@linkplain java.lang.invoke.MethodHandle method handles}. + * Proxy implements or extends a base interface or class and delegates method calls to + * target object or static methods. Calls may be delegated even to methods inaccessible by base type. *

- * There are 3 type of proxy objects: - *

    - *
  1. {@linkplain ProxyInfo.Type#PROXY Proxy} - implements client-side interface from - * {@code jetbrains.api} and delegates calls to JBR-side target object and optionally static methods.
  2. - *
  3. {@linkplain ProxyInfo.Type#SERVICE Service} - singleton {@linkplain ProxyInfo.Type#PROXY proxy}, - * may delegate calls only to static methods, without target object.
  4. - *
  5. {@linkplain ProxyInfo.Type#CLIENT_PROXY Client proxy} - reverse proxy, implements JBR-side interface - * and delegates calls to client-side target object by interface defined in {@code jetbrains.api}. - * May be used to implement callbacks which are created by client and called by JBR.
  6. - *
+ * Mapping between interfaces and implementation code is defined using + * {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations. *

- * Method signatures of proxy interfaces and implementation are validated to ensure that proxy can + * When JBR API interface or imeplementation type is used as a method parameter or return type, + * JBR API backend performs conversions to ensure each side receives object or correct type, + * for example, given following types: + *

{@code
+ * interface A {
+ *     B foo(B b);
+ * }
+ * interface B {
+ *     void bar();
+ * }
+ * class AImpl {
+ *     BImpl foo(BImpl b) {}
+ * }
+ * class BImpl {
+ *     void bar() {}
+ * }
+ * }
+ * {@code A#foo(B)} will be mapped to {@code AImpl#foo(BImpl)}. + * Conversion between {@code B} and {@code BImpl} will be done by JBR API backend. + * Here's what generated proxies would look like: + *
{@code
+ * final class AProxy implements A {
+ *     final AImpl target;
+ *     AProxy(AImpl t) {
+ *         target = t;
+ *     }
+ *     @Override
+ *     B foo(B b) {
+ *         BImpl ret = t.foo(b.target);
+ *         return new BProxy(ret);
+ *     }
+ * }
+ * final class BProxy implements B {
+ *     final BImpl target;
+ *     BProxy(BImpl t) {
+ *         target = t;
+ *     }
+ *     @Override
+ *     void bar() {
+ *         t.bar();
+ *     }
+ * }
+ * }
+ *

+ * Method signatures of base type and implementation are validated to ensure that proxy can * properly delegate call to the target implementation code. If there's no implementation found for some * interface methods, corresponding proxy is considered unsupported. Proxy is also considered unsupported - * if any proxy used by it is unsupported, more about it at {@link ProxyDependencyManager}. + * if any proxy used by it is unsupported. *

- * Mapping between interfaces and implementation code is defined in - * {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}. - * @param interface type for this proxy. + * Extensions are an exception to this rule. Methods of base type may be marked as extension methods, + * which makes them optional for support checks. Moreover, extension must be explicitly enabled for a + * proxy instance to enable usage of corresponding methods. */ -class Proxy { - private final ProxyInfo info; +class Proxy { + /** + * @see Proxy.Info#flags + */ + static final int + INTERNAL = 1, + SERVICE = 2; + private final Proxy inverse; + private final Class interFace, target; + private final int flags; private volatile ProxyGenerator generator; - private volatile Boolean allMethodsImplemented; - private volatile Boolean supported; - private volatile Class proxyClass; + private volatile Set directDependencies = Set.of(); + private volatile Set> supportedExtensions = Set.of(); + private volatile MethodHandle constructor; - private volatile MethodHandle targetExtractor; - private volatile boolean instanceInitialized; - private volatile INTERFACE instance; - - Proxy(ProxyInfo info) { - this.info = info; + /** + * Creates empty proxy. + */ + static Proxy empty(Boolean supported) { + return new Proxy(supported); } /** - * @return {@link ProxyInfo} structure of this proxy + * Creates a new proxy (and possibly an inverse one) from {@link Info}. */ - ProxyInfo getInfo() { - return info; + static Proxy create(ProxyRepository repository, + Info info, Mapping[] specialization, + Info inverseInfo, Mapping[] inverseSpecialization) { + return new Proxy(repository, info, specialization, null, inverseInfo, inverseSpecialization); } - private synchronized void initGenerator() { - if (generator != null) return; - generator = new ProxyGenerator(info); - allMethodsImplemented = generator.areAllMethodsImplemented(); + private Proxy(Boolean supported) { + interFace = target = null; + flags = 0; + inverse = this; + this.supported = supported; } - /** - * Checks if implementation is found for all abstract interface methods of this proxy. - */ - boolean areAllMethodsImplemented() { - if (allMethodsImplemented != null) return allMethodsImplemented; - synchronized (this) { - if (allMethodsImplemented == null) initGenerator(); - return allMethodsImplemented; + private Proxy(ProxyRepository repository, Info info, Mapping[] specialization, + Proxy inverseProxy, Info inverseInfo, Mapping[] inverseSpecialization) { + if (info != null) { + interFace = info.interfaceLookup.lookupClass(); + target = info.targetLookup == null ? null : info.targetLookup.lookupClass(); + flags = info.flags; + generator = new ProxyGenerator(repository, info, specialization); + } else { + interFace = target = null; + flags = 0; } + inverse = inverseProxy == null ? new Proxy(repository, inverseInfo, inverseSpecialization, this, null, null) : inverseProxy; + if (inverse.getInterface() != null) directDependencies = Set.of(inverse); } /** - * Checks if all methods are {@linkplain #areAllMethodsImplemented() implemented} - * for this proxy and all proxies it {@linkplain ProxyDependencyManager uses}. + * @return inverse proxy */ - boolean isSupported() { + Proxy inverse() { + return inverse; + } + + /** + * @return interface class + */ + Class getInterface() { + return interFace; + } + + /** + * @return target class + */ + Class getTarget() { + return target; + } + + /** + * @return flags + */ + int getFlags() { + return flags; + } + + /** + * Checks if all methods are implemented for this proxy and all proxies it uses. + * {@code null} means not known yet. + */ + Boolean supported() { + return supported; + } + + private synchronized boolean generate() { if (supported != null) return supported; - synchronized (this) { - if (supported == null) { - Set> dependencies = ProxyDependencyManager.getProxyDependencies(info.interFace); - for (Class d : dependencies) { - Proxy p = JBRApi.getProxy(d); - if (p == null || !p.areAllMethodsImplemented()) { - supported = false; - return false; - } - } - supported = true; + if (generator == null) return false; + Set deps = new HashSet<>(directDependencies); + supported = generator.generate(); + for (Map.Entry e : generator.getDependencies().entrySet()) { + if (e.getKey().generate()) deps.add(e.getKey()); + else if (e.getValue()) { + supported = false; + break; } - return supported; } - } - - private synchronized void defineClasses() { - if (constructor != null) return; - initGenerator(); - generator.defineClasses(); - proxyClass = generator.getProxyClass(); - constructor = generator.findConstructor(); - targetExtractor = generator.findTargetExtractor(); + if (supported) { + directDependencies = deps; + supportedExtensions = generator.getSupportedExtensions(); + } else generator = null; // Release for gc + return supported; } /** - * @return generated proxy class + * Checks if specified extension is implemented by this proxy. + * Implicitly runs bytecode generation. */ - Class getProxyClass() { - if (proxyClass != null) return proxyClass; - synchronized (this) { - if (proxyClass == null) defineClasses(); - return proxyClass; + boolean isExtensionSupported(Enum extension) { + if (supported == null) generate(); + return supportedExtensions.contains(extension); + } + + private synchronized boolean define() { + if (constructor != null) return true; + if (!generate()) return false; + constructor = generator.define((flags & SERVICE) != 0); + if (constructor == null) { + supported = false; + return false; } + for (Proxy p : directDependencies) { + if (!p.define()) return false; + } + return true; + } + + synchronized boolean init() { + if (!define()) return false; + if (generator != null) { + ProxyGenerator gen = generator; + generator = null; + gen.init(); + for (Proxy p : directDependencies) p.init(); + } + return supported; } /** * @return method handle for the constructor of this proxy. - *

    - *
  • For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.
  • - *
  • For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg, - * expecting target object to which it would delegate method calls.
  • - *
+ * First parameter is target object to which it would delegate method calls. + * Second parameter is extensions bitfield (if extensions are enabled). */ - MethodHandle getConstructor() { - if (constructor != null) return constructor; - synchronized (this) { - if (constructor == null) defineClasses(); - return constructor; + MethodHandle getConstructor() throws NullPointerException { + if (JBRApi.LOG_DEPRECATED && interFace.isAnnotationPresent(Deprecated.class)) { + Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err, + "Warning: using deprecated JBR API interface " + interFace.getCanonicalName()); } + return constructor; } /** - * @return method handle for that extracts target object of the proxy, or null. + * Proxy descriptor with all classes and lookup contexts resolved. + * Contains all necessary information to create a {@linkplain Proxy proxy}. */ - MethodHandle getTargetExtractor() { - // targetExtractor may be null, so check constructor instead - if (constructor != null) return targetExtractor; - synchronized (this) { - if (constructor == null) defineClasses(); - return targetExtractor; - } - } + static class Info { + private record StaticMethod(String name, MethodType targetType) {} - private synchronized void initClass(Set> actualUsages) { - defineClasses(); - if (generator != null) { - actualUsages.addAll(generator.getDirectProxyDependencies()); - generator.init(); - generator = null; - } - } - private synchronized void initDependencyGraph() { - defineClasses(); - if (generator == null) return; - Set> dependencyClasses = ProxyDependencyManager.getProxyDependencies(info.interFace); - Set> dependencies = new HashSet<>(); - Set> actualUsages = new HashSet<>(); - for (Class d : dependencyClasses) { - Proxy p = JBRApi.getProxy(d); - if (p != null) { - dependencies.add(p); - p.initClass(actualUsages); - } - } - actualUsages.removeAll(dependencies); - if (!actualUsages.isEmpty()) { - // Should never happen, this is a sign of broken dependency search - throw new RuntimeException("Some proxies are not in dependencies of " + info.interFace.getName() + - ", but are actually used by it: " + - actualUsages.stream().map(p -> p.info.interFace.getName()).collect(Collectors.joining(", "))); - } - } + private final Map staticMethods = new HashMap<>(); + final Lookup interfaceLookup; + final Lookup targetLookup; + private final int flags; - /** - * @return instance for this {@linkplain ProxyInfo.Type#SERVICE service}, - * returns {@code null} for other proxy types. - */ - @SuppressWarnings("unchecked") - INTERFACE getInstance() { - if (instanceInitialized) return instance; - if (info.type != ProxyInfo.Type.SERVICE) return null; - synchronized (this) { - if (instance == null) { - initDependencyGraph(); - try { - instance = (INTERFACE) getConstructor().invoke(); - } catch (JBRApi.ServiceNotAvailableException e) { - if (JBRApi.VERBOSE) { - System.err.println("Service not available: " + info.interFace.getName()); - e.printStackTrace(); - } - } catch (Throwable e) { - throw new RuntimeException(e); - } finally { - instanceInitialized = true; - } - } - return instance; + Info(Lookup interfaceLookup, Lookup targetLookup, int flags) { + this.interfaceLookup = interfaceLookup; + this.targetLookup = targetLookup; + this.flags = flags; + } + + void addStaticMethod(String name, MethodHandle target) { + staticMethods.put(new StaticMethod(name, target.type()), target); + } + + MethodHandle getStaticMethod(String name, MethodType targetType) { + return staticMethods.get(new StaticMethod(name, targetType)); } } } \ No newline at end of file diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyDependencyManager.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyDependencyManager.java deleted file mode 100644 index b4ac4035b28f..000000000000 --- a/src/java.base/share/classes/com/jetbrains/internal/ProxyDependencyManager.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains.internal; - -import java.lang.reflect.*; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Consumer; - -/** - * This class collects {@linkplain Proxy proxy} dependencies. - *

- * Dependencies of a class {@code C} are other classes that are - * used by {@code C} (i.e. supertypes, classes that appear in method - * parameters, return types) and all their dependencies. Any class - * is also considered a dependency of itself. - *

- * Dependencies allow JBR to validate whole set of interfaces for - * a particular feature instead of treating them as separate entities. - *

Example

- * Suppose we implemented some feature and added some API for it: - *
{@code
- * interface SomeFeature {
- *     SomeOtherObject createSomeObject(int magicNumber);
- * }
- * interface SomeOtherObject {
- *     int getMagicNumber();
- * }
- * }
- * And then used it: - *
{@code
- * if (JBR.isSomeFeatureSupported()) {
- *     SomeOtherObject object = JBR.getSomeFeature().createSomeObject(123);
- *     int magic = object.getMagicNumber();
- * }
- * }
- * Suppose JBR was able to find implementation for {@code SomeFeature.createSomeObject()}, - * but not for {@code SomeOtherObject.getMagicNumber()}. So {@code JBR.getSomeFeature()} - * would succeed and return service instance, but {@code createSomeObject()} would fail, - * because JBR wasn't able to find implementation for {@code SomeOtherObject.getMagicNumber()} - * and therefore couldn't create proxy for {@code SomeOtherObject} class. - *

- * To avoid such issues, not only proxy interface itself, but all proxies that are accessible - * from current proxy interface must have proper implementation. - */ -class ProxyDependencyManager { - - private static final ConcurrentMap, Set>> cache = new ConcurrentHashMap<>(); - - /** - * @return all proxy interfaces that are used (directly or indirectly) by given interface, including itself. - */ - static Set> getProxyDependencies(Class interFace) { - Set> dependencies = cache.get(interFace); - if (dependencies != null) return dependencies; - step(null, interFace); - return cache.get(interFace); - } - - /** - * Collect dependencies for given class and store them into cache. - */ - private static void step(Node parent, Class clazz) { - if (!clazz.getPackageName().startsWith("com.jetbrains") && !JBRApi.isKnownProxyInterface(clazz)) return; - if (parent != null && parent.findAndMergeCycle(clazz) != null) { - return; - } - Set> cachedDependencies = cache.get(clazz); - if (cachedDependencies != null) { - if (parent != null) parent.cycle.dependencies.addAll(cachedDependencies); - return; - } - Node node = new Node(parent, clazz); - ClassUsagesFinder.visitUsages(clazz, c -> step(node, c)); - Class correspondingProxyInterface = JBRApi.getProxyInterfaceByTargetName(clazz.getName()); - if (correspondingProxyInterface != null) { - step(node, correspondingProxyInterface); - } - if (parent != null && parent.cycle != node.cycle) { - parent.cycle.dependencies.addAll(node.cycle.dependencies); - } - if (node.cycle.origin.equals(clazz)) { - // Put collected dependencies into cache only when we exit from the cycle - // Otherwise cache will contain incomplete data - for (Class c : node.cycle.members) { - cache.put(c, node.cycle.dependencies); - if (JBRApi.VERBOSE) { - System.out.println("Found dependencies for " + c.getName() + ": " + node.cycle.dependencies); - } - } - } - } - - /** - * Graph node, one per visited class - */ - private static class Node { - private final Node parent; - private final Class clazz; - private Cycle cycle; - - private Node(Node parent, Class clazz) { - this.parent = parent; - this.clazz = clazz; - cycle = new Cycle(clazz); - } - - /** - * When classes form dependency cycle, they share all their dependencies. - * If cycle was found, merge all found dependencies for nodes that form the cycle. - */ - private Cycle findAndMergeCycle(Class clazz) { - if (this.clazz.equals(clazz)) return cycle; - if (parent == null) return null; - Cycle c = parent.findAndMergeCycle(clazz); - if (c != null && c != cycle) { - c.members.addAll(cycle.members); - c.dependencies.addAll(cycle.dependencies); - cycle = c; - } - return c; - } - } - - /** - * Cycle info. For the sake of elegant code, single node - * also forms a cycle with itself as a single member and dependency. - */ - private static class Cycle { - /** - * Origin is the first visited class from that cycle. - */ - private final Class origin; - private final Set> members = new HashSet<>(); - private final Set> dependencies = new HashSet<>(); - - private Cycle(Class origin) { - this.origin = origin; - members.add(origin); - if (JBRApi.isKnownProxyInterface(origin)) { - dependencies.add(origin); - } - } - } - - /** - * Utility class that collects direct class usages using reflection - */ - private static class ClassUsagesFinder { - - private static void visitUsages(Class c, Consumer> action) { - collect(c.getGenericSuperclass(), action); - for (java.lang.reflect.Type t : c.getGenericInterfaces()) collect(t, action); - for (Field f : c.getDeclaredFields()) collect(f.getGenericType(), action); - for (Method m : c.getDeclaredMethods()) { - collect(m.getGenericParameterTypes(), action); - collect(m.getGenericReturnType(), action); - collect(m.getGenericExceptionTypes(), action); - } - } - - private static void collect(java.lang.reflect.Type type, Consumer> action) { - if (type instanceof Class c) { - while (c.isArray()) c = Objects.requireNonNull(c.getComponentType()); - if (!c.isPrimitive()) action.accept(c); - } else if (type instanceof TypeVariable v) { - collect(v.getBounds(), action); - } else if (type instanceof WildcardType w) { - collect(w.getUpperBounds(), action); - collect(w.getLowerBounds(), action); - } else if (type instanceof ParameterizedType p) { - collect(p.getActualTypeArguments(), action); - collect(p.getRawType(), action); - collect(p.getOwnerType(), action); - } else if (type instanceof GenericArrayType a) { - collect(a.getGenericComponentType(), action); - } - } - - private static void collect(java.lang.reflect.Type[] types, Consumer> action) { - for (java.lang.reflect.Type t : types) collect(t, action); - } - } -} diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java index ca0b37e233f0..d5bcfb740a98 100644 --- a/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java +++ b/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java @@ -29,457 +29,392 @@ import jdk.internal.org.objectweb.asm.*; import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandleInfo; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; +import java.lang.reflect.*; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import static com.jetbrains.exported.JBRApi.ServiceNotAvailableException; import static com.jetbrains.internal.ASMUtils.*; +import static com.jetbrains.internal.JBRApi.EXTENSIONS_ENABLED; import static java.lang.invoke.MethodHandles.Lookup; import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static jdk.internal.org.objectweb.asm.Type.getInternalName; /** - * This class generates {@linkplain Proxy proxy} classes. - * Each proxy is just a generated class implementing some interface and - * delegating method calls to method handles. + * Generates {@linkplain Proxy proxy} classes. *

- * There are 2 proxy dispatch modes: - *

    - *
  • interface -> proxy -> {@linkplain #generateBridge bridge} -> method handle -> implementation code
  • - *
  • interface -> proxy -> method handle -> implementation code
  • - *
- * Generated proxy is always located in the same package with its interface and optional bridge is located in the - * same module with target implementation code. Bridge allows proxy to safely call hidden non-static implementation - * methods and is only needed for {@code jetbrains.api} -> JBR calls. For JBR -> {@code jetbrains.api} calls, proxy can - * invoke method handle directly. + * Proxy class always implements/extends a base interface/class. It's defined as a + * {@linkplain MethodHandles.Lookup#defineHiddenClass(byte[], boolean, Lookup.ClassOption...) hidden class} + * sharing the nest with either interface or target implementation class. + * Defining proxy as a nestmate of a target class is preferred, + * as in this case proxy can call implementation methods directly. + * However, this may not be possible if interface is not accessible by target + * (thus making it impossible for proxy to implement it), or if there is no target class at all + * (e.g. a service may delegate all calls exclusively to static methods). + *

+ * Proxy invokes target methods in two ways: either directly, when accessible + * ({@code invokestatic}, {@code invokevirtual}, {@code invokeinterface}), + * or via {@code invokedynamic} in cases when target method is inaccessible + * (see {@link com.jetbrains.exported.JBRApiSupport#bootstrapDynamic(Lookup, String, MethodType)}). + * @see Proxy */ class ProxyGenerator { - private static final String OBJECT_DESCRIPTOR = "Ljava/lang/Object;"; - private static final String MH_NAME = "java/lang/invoke/MethodHandle"; - private static final String MH_DESCRIPTOR = "Ljava/lang/invoke/MethodHandle;"; - private static final String CONVERSION_DESCRIPTOR = "(Ljava/lang/Object;)Ljava/lang/Object;"; - /** - * Print warnings about usage of deprecated interfaces and methods to {@link System#err}. - */ - private static final boolean LOG_DEPRECATED = System.getProperty("jetbrains.api.logDeprecated", String.valueOf(JBRApi.VERBOSE)).equalsIgnoreCase("true"); - private static final boolean VERIFY_BYTECODE = Boolean.getBoolean("jetbrains.api.verifyBytecode"); + private static final String PROXY_INTERFACE_NAME = getInternalName(com.jetbrains.exported.JBRApiSupport.Proxy.class); - private static final AtomicInteger nameCounter = new AtomicInteger(); + private final Proxy.Info info; + private final Class interFace; + private final Lookup proxyGenLookup; + private final Mapping[] specialization; + private final Mapping.Context mappingContext; + private final AccessContext accessContext; - private final ProxyInfo info; - private final boolean generateBridge; - private final String proxyName, bridgeName; - private final ClassWriter originalProxyWriter, originalBridgeWriter; - private final ClassVisitor proxyWriter, bridgeWriter; - private final List> handles = new ArrayList<>(); - private final List>> classReferences = new ArrayList<>(); - private final Set> directProxyDependencies = new HashSet<>(); - private final List exceptions = new ArrayList<>(); - private int bridgeMethodCounter; - private boolean allMethodsImplemented = true; - private Lookup generatedHandlesHolder, generatedProxy; + private final Class constructorTargetParameterType; + private final String targetDescriptor, proxyName, superclassName; + private final String[] superinterfaceNames; + private final ClassWriter originalProxyWriter; + private final ClassVisitor proxyWriter; + + private final Map, Boolean> supportedExtensions = new HashMap<>(); + + private boolean supported = true; + private Lookup generatedProxy; /** - * Creates new proxy generator from given {@link ProxyInfo}, - * looks for abstract interface methods, corresponding implementation methods - * and generates proxy bytecode. However, it doesn't actually load generated - * classes until {@link #defineClasses()} is called. + * Creates new proxy generator from given {@link Proxy.Info}, */ - ProxyGenerator(ProxyInfo info) { - if (JBRApi.VERBOSE) { - System.out.println("Generating proxy " + info.interFace.getName()); - } + ProxyGenerator(ProxyRepository proxyRepository, Proxy.Info info, Mapping[] specialization) { this.info = info; - generateBridge = info.type.isPublicApi(); - int nameId = nameCounter.getAndIncrement(); - proxyName = Type.getInternalName(info.interFace) + "$$JBRApiProxy$" + nameId; - bridgeName = generateBridge ? info.apiModule.lookupClass().getPackageName().replace('.', '/') + "/" + - info.interFace.getSimpleName() + "$$JBRApiBridge$" + nameId : null; + this.interFace = info.interfaceLookup.lookupClass(); + this.specialization = specialization; - originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - proxyWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter; - originalBridgeWriter = generateBridge ? new ClassWriter(ClassWriter.COMPUTE_FRAMES) : null; - if (generateBridge) { - bridgeWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalBridgeWriter, true) : originalBridgeWriter; - } else bridgeWriter = new ClassVisitor(Opcodes.ASM9) { // Empty visitor + // Placing our proxy implementation into the target's nest is preferred, as it gives us direct method calls. + this.proxyGenLookup = info.targetLookup != null && AccessContext.canAccess(info.targetLookup, interFace) ? + info.targetLookup : info.interfaceLookup; + + this.mappingContext = new Mapping.Context(proxyRepository); + this.accessContext = new AccessContext(proxyGenLookup); + + constructorTargetParameterType = info.targetLookup == null ? null : + accessContext.canAccess(info.targetLookup.lookupClass()) ? info.targetLookup.lookupClass() : Object.class; + targetDescriptor = constructorTargetParameterType == null ? "" : constructorTargetParameterType.descriptorString(); + + // Even though generated proxy is hidden and therefore has no qualified name, + // it can reference itself via internal name, which can lead to name collisions. + // Let's consider specialized proxy for java/util/List - if we name proxy similarly, + // methods calls to java/util/List will be treated by VM as calls to proxy class, + // not standard library interface. Therefore we append $$$ to proxy name to avoid name collision. + proxyName = proxyGenLookup.lookupClass().getPackageName().replace('.', '/') + "/" + interFace.getSimpleName() + "$$$"; + + originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES) { @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - return new MethodVisitor(api) {}; + protected ClassLoader getClassLoader() { + return ProxyGenerator.this.proxyGenLookup.lookupClass().getClassLoader(); } }; - proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null, - "java/lang/Object", new String[] {Type.getInternalName(info.interFace)}); - bridgeWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC | ACC_PUBLIC, bridgeName, null, - "java/lang/Object", null); - generateConstructor(); - generateMethods(); - proxyWriter.visitEnd(); - bridgeWriter.visitEnd(); - } - - boolean areAllMethodsImplemented() { - return allMethodsImplemented; - } - - Set> getDirectProxyDependencies() { - return directProxyDependencies; + proxyWriter = JBRApi.VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter; + if (interFace.isInterface()) { + superclassName = "java/lang/Object"; + superinterfaceNames = new String[] {getInternalName(interFace), PROXY_INTERFACE_NAME}; + } else { + superclassName = getInternalName(interFace); + superinterfaceNames = new String[] {PROXY_INTERFACE_NAME}; + } } /** - * Insert all method handles and class references into static fields, so that proxy can call implementation methods. + * Direct (non-transitive) only. These are the proxies accessed by current one. + * True for required and false for optional. + */ + Map getDependencies() { + return accessContext.dependencies; + } + + Set> getSupportedExtensions() { + return supportedExtensions.entrySet().stream() + .filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()); + } + + /** + * @return service target instance, if any + */ + private Object createServiceTarget() throws Throwable { + Exception exception = null; + MethodHandle constructor = null; + try { + constructor = info.targetLookup.findStatic( + info.targetLookup.lookupClass(), "create", MethodType.methodType(info.targetLookup.lookupClass())); + } catch (NoSuchMethodException | IllegalAccessException e) { + exception = e; + } + try { + if (constructor == null) constructor = info.targetLookup.findConstructor( + info.targetLookup.lookupClass(), MethodType.methodType(void.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + if (exception == null) exception = e; + else exception.addSuppressed(e); + } + if (constructor == null) throw new ServiceNotAvailableException("No service factory method or constructor found", exception); + return constructor.invoke(); + } + + /** + * First parameter is target object to which it would delegate method calls (if target exists). + * Second parameter is extensions bitfield (if extensions are enabled). + * @return method handle to constructor of generated proxy class, or null. + */ + private MethodHandle findConstructor() throws NoSuchMethodException, IllegalAccessException { + MethodType constructorType = MethodType.methodType(void.class); + if (info.targetLookup != null) constructorType = constructorType.appendParameterTypes(constructorTargetParameterType); + if (EXTENSIONS_ENABLED) constructorType = constructorType.appendParameterTypes(long[].class); + return generatedProxy.findConstructor(generatedProxy.lookupClass(), constructorType); + } + + /** + * Initialize method handles used by generated class via {@code invokedynamic}. */ void init() { - try { - for (int i = 0; i < handles.size(); i++) { - generatedHandlesHolder - .findStaticVarHandle(generatedHandlesHolder.lookupClass(), "h" + i, MethodHandle.class) - .set(handles.get(i).get()); - } - for (int i = 0; i < classReferences.size(); i++) { - generatedHandlesHolder - .findStaticVarHandle(generatedHandlesHolder.lookupClass(), "c" + i, Class.class) - .set(classReferences.get(i).get()); - } - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); + if (JBRApi.VERBOSE) { + System.out.println("Initializing proxy " + interFace.getName()); + } + for (var t : accessContext.dynamicCallTargets) { + JBRApi.dynamicCallTargets.put(new JBRApi.DynamicCallTargetKey( + generatedProxy.lookupClass(), t.name(), t.descriptor() + ), t.futureHandle()); } } - Class getProxyClass() { - return generatedProxy.lookupClass(); - } - /** - * @return method handle to constructor of generated proxy class. - *

    - *
  • For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.
  • - *
  • For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg, - * expecting target object to which it would delegate method calls.
  • - *
+ * Define generated class. + * @return method handle to constructor of generated proxy class, or null. */ - MethodHandle findConstructor() { + MethodHandle define(boolean service) { try { - if (info.target == null) { - return generatedProxy.findConstructor(generatedProxy.lookupClass(), MethodType.methodType(void.class)); - } else { - MethodHandle c = generatedProxy.findConstructor(generatedProxy.lookupClass(), - MethodType.methodType(void.class, Object.class)); - if (info.type.isService()) { - try { - return MethodHandles.foldArguments(c, info.target.findConstructor(info.target.lookupClass(), - MethodType.methodType(void.class)).asType(MethodType.methodType(Object.class))); - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new RuntimeException("Service implementation must have no-args constructor: " + - info.target.lookupClass(), e); - } - } - return c; - } - } catch (IllegalAccessException | NoSuchMethodException e) { + Object sericeTarget = service && info.targetLookup != null ? createServiceTarget() : null; + generatedProxy = proxyGenLookup.defineHiddenClass( + originalProxyWriter.toByteArray(), false, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE); + MethodHandle constructor = findConstructor(); + if (sericeTarget != null) constructor = MethodHandles.insertArguments(constructor, 0, sericeTarget); + return constructor; + } catch (ServiceNotAvailableException e) { + if (JBRApi.VERBOSE) e.printStackTrace(System.err); + return null; + } catch (Throwable e) { throw new RuntimeException(e); } } /** - * @return method handle that receives proxy and returns its target, or null + * Generate bytecode for class. + * @return true if generated proxy is considered supported */ - MethodHandle findTargetExtractor() { - if (info.target == null) return null; - try { - return generatedProxy.findGetter(generatedProxy.lookupClass(), "target", Object.class); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); + boolean generate() { + if (!supported) return false; + proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null, + superclassName, superinterfaceNames); + if (JBRApi.VERBOSE) { + synchronized (System.out) { + System.out.print("Generating proxy " + interFace.getName()); + if (specialization != null) System.out.print(" <" + + Stream.of(specialization).map(t -> t == null ? "?" : t.toString()).collect(Collectors.joining(", ")) + ">"); + System.out.println(); + } } + if (specialization != null) { + mappingContext.initTypeParameters(interFace, Stream.of(specialization)); + } + mappingContext.initTypeParameters(interFace); + generateFields(); + generateConstructor(); + generateTargetGetter(); + generateMethods(interFace); + if (interFace.isInterface()) generateMethods(Object.class); + proxyWriter.visitEnd(); + return supported; } - /** - * Loads generated classes. - */ - void defineClasses() { - try { - Lookup bridge = !generateBridge ? null : MethodHandles.privateLookupIn( - info.apiModule.defineClass(originalBridgeWriter.toByteArray()), info.apiModule); - generatedProxy = info.interFaceLookup.defineHiddenClass( - originalProxyWriter.toByteArray(), true, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE); - generatedHandlesHolder = generateBridge ? bridge : generatedProxy; - } catch (IllegalAccessException e) { - throw new RuntimeException(e); + private void generateFields() { + if (info.targetLookup != null) { + proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", targetDescriptor, null, null); + } + if (EXTENSIONS_ENABLED) { + proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "extensions", "[J", null, null); } } private void generateConstructor() { - if (info.target != null) { - proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", OBJECT_DESCRIPTOR, null, null); + MethodVisitor m = proxyWriter.visitMethod(ACC_PRIVATE, "", "(" + targetDescriptor + + (EXTENSIONS_ENABLED ? "[J" : "") + ")V", null, null); + m.visitCode(); + m.visitVarInsn(ALOAD, 0); + if (info.targetLookup != null) { + m.visitInsn(DUP); + m.visitVarInsn(ALOAD, 1); + m.visitFieldInsn(PUTFIELD, proxyName, "target", targetDescriptor); } - MethodVisitor p = proxyWriter.visitMethod(ACC_PRIVATE, "", "(" + - (info.target == null ? "" : OBJECT_DESCRIPTOR) + ")V", null, null); - if (LOG_DEPRECATED && info.interFace.isAnnotationPresent(Deprecated.class)) { - logDeprecated(p, "Warning: using deprecated JBR API interface " + info.interFace.getName()); + if (EXTENSIONS_ENABLED) { + m.visitInsn(DUP); + m.visitVarInsn(ALOAD, info.targetLookup != null ? 2 : 1); + m.visitFieldInsn(PUTFIELD, proxyName, "extensions", "[J"); } - p.visitCode(); - p.visitVarInsn(ALOAD, 0); - if (info.target != null) { - p.visitInsn(DUP); - p.visitVarInsn(ALOAD, 1); - p.visitFieldInsn(PUTFIELD, proxyName, "target", OBJECT_DESCRIPTOR); - } - p.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); - p.visitInsn(RETURN); - p.visitMaxs(0, 0); - p.visitEnd(); + m.visitMethodInsn(INVOKESPECIAL, superclassName, "", "()V", false); + m.visitInsn(RETURN); + m.visitMaxs(0, 0); + m.visitEnd(); } - private void generateMethods() { - for (Method method : info.interFace.getMethods()) { + private void generateTargetGetter() { + MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, "$getProxyTarget", "()" + + Object.class.descriptorString(), null, null); + m.visitCode(); + if (info.targetLookup != null) { + m.visitVarInsn(ALOAD, 0); + m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor); + } else m.visitInsn(ACONST_NULL); + m.visitInsn(ARETURN); + m.visitMaxs(0, 0); + m.visitEnd(); + } + + private void generateMethods(Class interFace) { + for (Method method : interFace.getMethods()) { int mod = method.getModifiers(); + if ((mod & (Modifier.STATIC | Modifier.FINAL)) != 0) continue; + + Exception exception = null; + Enum extension = EXTENSIONS_ENABLED && JBRApi.extensionExtractor != null ? + JBRApi.extensionExtractor.apply(method) : null; + Mapping.Method methodMapping = mappingContext.getMapping(method); + MethodHandle handle; + boolean passInstance; + + if (methodMapping.query().valid) { + // Try static method. + handle = info.getStaticMethod(method.getName(), methodMapping.type()); + passInstance = false; + + // Try target class. + if (handle == null && info.targetLookup != null) { + try { + handle = interFace.equals(info.targetLookup.lookupClass()) ? + info.targetLookup.unreflect(method) : info.targetLookup.findVirtual( + info.targetLookup.lookupClass(), method.getName(), methodMapping.type()); + passInstance = true; + } catch (NoSuchMethodException | IllegalAccessException e) { + exception = e; + } + } + + if (handle != null) { + // Method found. + generateMethod(method, handle, methodMapping, extension, passInstance); + if (extension != null) supportedExtensions.putIfAbsent(extension, true); + continue; + } + } else { + exception = new Exception("Method mapping is invalid: " + methodMapping); + } + + // Skip if possible. if (!Modifier.isAbstract(mod)) continue; - MethodMapping methodMapping = getTargetMethodMapping(method); - Exception e1 = null; - if (info.target != null) { - try { - MethodHandle handle = info.target.findVirtual( - info.target.lookupClass(), method.getName(), methodMapping.type()); - generateMethod(method, handle, methodMapping, true); - continue; - } catch (NoSuchMethodException | IllegalAccessException e) { - e1 = e; - } - } - - Exception e2 = null; - ProxyInfo.StaticMethodMapping mapping = info.staticMethods.get(method.getName()); - if (mapping != null) { - try { - MethodHandle staticHandle = - mapping.lookup().findStatic(mapping.lookup().lookupClass(), mapping.methodName(), methodMapping.type()); - generateMethod(method, staticHandle, methodMapping, false); - continue; - } catch (NoSuchMethodException | IllegalAccessException e) { - e2 = e; - } - } - - if (e1 != null) exceptions.add(e1); - if (e2 != null) exceptions.add(e2); + // Generate unsupported stub. generateUnsupportedMethod(proxyWriter, method); if (JBRApi.VERBOSE) { - System.err.println("Couldn't generate method " + method.getName()); - if (e1 != null) e1.printStackTrace(); - if (e2 != null) e2.printStackTrace(); + synchronized (System.err) { + System.err.println("Couldn't generate method " + method.getName()); + if (exception != null) exception.printStackTrace(System.err); + } } - allMethodsImplemented = false; + if (extension == null) supported = false; + else supportedExtensions.put(extension, false); } } - private void generateMethod(Method interfaceMethod, MethodHandle handle, MethodMapping mapping, boolean passInstance) { + private void generateMethod(Method interfaceMethod, MethodHandle handle, Mapping.Method mapping, Enum extension, boolean passInstance) { + boolean passExtensions = mapping.query().needsExtensions; InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod); - String bridgeMethodDescriptor = mapping.getBridgeDescriptor(passInstance); + MethodHandleInfo directCall = accessContext.resolveDirect(handle); + Supplier futureHandle = () -> handle; - ClassVisitor handleWriter = generateBridge ? bridgeWriter : proxyWriter; - String bridgeOrProxyName = generateBridge ? bridgeName : proxyName; - String handleName = addHandle(handleWriter, () -> handle); - for (TypeMapping m : mapping) { - if (m.conversion() == TypeConversion.EXTRACT_TARGET || - m.conversion() == TypeConversion.DYNAMIC_2_WAY) { - Proxy from = m.fromProxy(); - m.metadata.extractTargetHandle = addHandle(handleWriter, from::getTargetExtractor); - directProxyDependencies.add(from); - } - if (m.conversion() == TypeConversion.WRAP_INTO_PROXY || - m.conversion() == TypeConversion.DYNAMIC_2_WAY) { - Proxy to = m.toProxy(); - m.metadata.proxyConstructorHandle = addHandle(handleWriter, to::getConstructor); - directProxyDependencies.add(to); - } - if (m.conversion() == TypeConversion.DYNAMIC_2_WAY) { - String classField = "c" + classReferences.size(); - m.metadata.extractableClassField = classField; - classReferences.add(m.fromProxy()::getProxyClass); - handleWriter.visitField(ACC_PRIVATE | ACC_STATIC, classField, "Ljava/lang/Class;", null, null); - } - } - String bridgeMethodName = methodInfo.name() + "$bridge$" + bridgeMethodCounter; - bridgeMethodCounter++; - - MethodVisitor p = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(), - methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames()); - MethodVisitor b = bridgeWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, bridgeMethodName, - bridgeMethodDescriptor, null, null); - if (LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) { - logDeprecated(p, "Warning: using deprecated JBR API method " + - interfaceMethod.getDeclaringClass().getName() + "#" + interfaceMethod.getName()); - } - p.visitCode(); - b.visitCode(); - MethodVisitor bp = generateBridge ? b : p; - bp.visitFieldInsn(GETSTATIC, bridgeOrProxyName, handleName, MH_DESCRIPTOR); - if (passInstance) { - p.visitVarInsn(ALOAD, 0); - p.visitFieldInsn(GETFIELD, proxyName, "target", OBJECT_DESCRIPTOR); - b.visitVarInsn(ALOAD, 0); - } - int lvIndex = 1; - for (TypeMapping param : mapping.parameterMapping) { - int opcode = getLoadOpcode(param.from()); - p.visitVarInsn(opcode, lvIndex); - b.visitVarInsn(opcode, lvIndex - (passInstance ? 0 : 1)); - lvIndex += getParameterSize(param.from()); - convertValue(bp, bridgeOrProxyName, param); - } - if (generateBridge) { - p.visitMethodInsn(INVOKESTATIC, bridgeName, bridgeMethodName, bridgeMethodDescriptor, false); - } - bp.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", bridgeMethodDescriptor, false); - convertValue(bp, bridgeOrProxyName, mapping.returnMapping()); - int returnOpcode = getReturnOpcode(mapping.returnMapping().to()); - p.visitInsn(returnOpcode); - b.visitInsn(returnOpcode); - p.visitMaxs(0, 0); - b.visitMaxs(0, 0); - p.visitEnd(); - b.visitEnd(); - } - - private String addHandle(ClassVisitor classWriter, Supplier handleSupplier) { - String handleName = "h" + handles.size(); - handles.add(handleSupplier); - classWriter.visitField(ACC_PRIVATE | ACC_STATIC, handleName, MH_DESCRIPTOR, null, null); - return handleName; - } - - private static void convertValue(MethodVisitor m, String handlesHolderName, TypeMapping mapping) { - if (mapping.conversion() == TypeConversion.IDENTITY) return; - Label skipConvert = new Label(); - m.visitInsn(DUP); - m.visitJumpInsn(IFNULL, skipConvert); - switch (mapping.conversion()) { - case EXTRACT_TARGET -> - m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR); - case WRAP_INTO_PROXY -> - m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR); - case DYNAMIC_2_WAY -> { - m.visitInsn(DUP); - m.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false); - m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractableClassField, "Ljava/lang/Class;"); - Label elseBranch = new Label(), afterBranch = new Label(); - m.visitJumpInsn(IF_ACMPNE, elseBranch); - m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR); - m.visitJumpInsn(GOTO, afterBranch); - m.visitLabel(elseBranch); - m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR); - m.visitLabel(afterBranch); - } - } - m.visitInsn(SWAP); - m.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", CONVERSION_DESCRIPTOR, false); - m.visitLabel(skipConvert); - } - - private static MethodMapping getTargetMethodMapping(Method interfaceMethod) { - Class[] params = interfaceMethod.getParameterTypes(); - TypeMapping[] paramMappings = new TypeMapping[params.length]; - for (int i = 0; i < params.length; i++) { - paramMappings[i] = getTargetTypeMapping(params[i]); - params[i] = paramMappings[i].to(); - } - TypeMapping returnMapping = getTargetTypeMapping(interfaceMethod.getReturnType()).inverse(); - return new MethodMapping(MethodType.methodType(returnMapping.from(), params), returnMapping, paramMappings); - } - - private static TypeMapping getTargetTypeMapping(Class userType) { - TypeMappingMetadata m = new TypeMappingMetadata(); - Proxy p = JBRApi.getProxy(userType); - if (p != null && p.getInfo().target != null) { - Proxy r = JBRApi.getProxy(p.getInfo().target.lookupClass()); - if (r != null && r.getInfo().target != null) { - return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.DYNAMIC_2_WAY, p, r, m); - } - return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.EXTRACT_TARGET, p, null, m); - } - Class interFace = JBRApi.getProxyInterfaceByTargetName(userType.getName()); - if (interFace != null) { - Proxy r = JBRApi.getProxy(interFace); - if (r != null) { - return new TypeMapping(userType, interFace, TypeConversion.WRAP_INTO_PROXY, null, r, m); - } - } - return new TypeMapping(userType, userType, TypeConversion.IDENTITY, null, null, m); - } - - private record MethodMapping(MethodType type, TypeMapping returnMapping, TypeMapping[] parameterMapping) - implements Iterable { - @Override - public Iterator iterator() { - return new Iterator<>() { - private int index = -1; - @Override - public boolean hasNext() { - return index < parameterMapping.length; - } - @Override - public TypeMapping next() { - TypeMapping m = index == -1 ? returnMapping : parameterMapping[index]; - index++; - return m; - } + // Check usage of deprecated API. + if (JBRApi.LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) { + directCall = null; // Force invokedynamic. + futureHandle = () -> { // Log warning when binding the call site. + Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err, "Warning: using deprecated JBR API method " + + interfaceMethod.getDeclaringClass().getCanonicalName() + "." + interfaceMethod.getName()); + return handle; }; } - /** - * Every convertable parameter type is replaced with {@link Object} for bridge descriptor. - * Optional {@link Object} is added as first parameter for instance methods. - */ - String getBridgeDescriptor(boolean passInstance) { - StringBuilder bd = new StringBuilder("("); - if (passInstance) bd.append(OBJECT_DESCRIPTOR); - for (TypeMapping m : parameterMapping) { - bd.append(m.getBridgeDescriptor()); + if (JBRApi.VERBOSE) { + System.out.println(" " + + mapping.returnMapping() + " " + + interfaceMethod.getName() + "(" + + Stream.of(mapping.parameterMapping()).map(Mapping::toString).collect(Collectors.joining(", ")) + + ")" + (directCall != null ? " (direct)" : "") + ); + } + + MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(), + methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames()); + AccessContext.Method methodContext = accessContext.new Method(m, extension == null); + m.visitCode(); + // Load `this`. + if (passInstance || passExtensions || extension != null) { + m.visitVarInsn(ALOAD, 0); + if (passInstance && (passExtensions || extension != null)) m.visitInsn(DUP); + } + // Check enabled extension. + if (passExtensions || extension != null) { + m.visitFieldInsn(GETFIELD, proxyName, "extensions", "[J"); + if (passExtensions && extension != null) m.visitInsn(DUP); + if (passExtensions) { + // If this method converts any parameters, we need to store extensions for later use. + // We overwrite `this` slot, but we have it on stack, so it's ok. + m.visitVarInsn(ASTORE, 0); + } + if (extension != null) { + // Check the specific bit inside long[]. + Label afterExtensionCheck = new Label(); + m.visitIntInsn(extension.ordinal() < 8192 ? BIPUSH : SIPUSH, extension.ordinal() / 64); + m.visitInsn(LALOAD); + m.visitLdcInsn(1L << (extension.ordinal() % 64)); + m.visitInsn(LAND); + m.visitInsn(LCONST_0); + m.visitInsn(LCMP); + m.visitJumpInsn(IFNE, afterExtensionCheck); + throwException(m, "java/lang/UnsupportedOperationException", + interFace.getCanonicalName() + '.' + interfaceMethod.getName() + + " - extension " + extension.name() + " is disabled"); + m.visitLabel(afterExtensionCheck); } - bd.append(')'); - bd.append(returnMapping.getBridgeDescriptor()); - return bd.toString(); } - } - - private record TypeMapping(Class from, Class to, TypeConversion conversion, - Proxy fromProxy, Proxy toProxy, TypeMappingMetadata metadata) { - TypeMapping inverse() { - return new TypeMapping(to, from, switch (conversion) { - case EXTRACT_TARGET -> TypeConversion.WRAP_INTO_PROXY; - case WRAP_INTO_PROXY -> TypeConversion.EXTRACT_TARGET; - default -> conversion; - }, toProxy, fromProxy, metadata); + // Extract target from `this`. + if (passInstance) { + // We already have `this` on stack. + m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor); } - String getBridgeDescriptor() { - if (conversion == TypeConversion.IDENTITY) return Type.getDescriptor(from); - else return "Ljava/lang/Object;"; + // Load and convert parameters. + for (int lvIndex = 1, i = 0; i < mapping.parameterMapping().length; i++) { + Mapping param = mapping.parameterMapping()[i]; + int opcode = getLoadOpcode(param.from); + m.visitVarInsn(opcode, lvIndex); + lvIndex += getParameterSize(param.from); + param.convert(methodContext); } - } - - private static class TypeMappingMetadata { - private String extractTargetHandle, proxyConstructorHandle, extractableClassField; - } - - private enum TypeConversion { - /** - * No conversion. - */ - IDENTITY, - /** - * Take a proxy object and extract its target implementation object. - */ - EXTRACT_TARGET, - /** - * Create new proxy targeting given implementation object. - */ - WRAP_INTO_PROXY, - /** - * Decide between {@link #EXTRACT_TARGET} and {@link #WRAP_INTO_PROXY} at runtime, depending on actual object. - */ - DYNAMIC_2_WAY + // Invoke target method. + if (directCall != null) methodContext.invokeDirect(directCall); + else methodContext.invokeDynamic(handle.type(), futureHandle); + // Convert return value. + mapping.returnMapping().convert(methodContext); + int opcode = getReturnOpcode(mapping.returnMapping().to); + m.visitInsn(opcode); + m.visitMaxs(0, 0); + m.visitEnd(); } } diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyInfo.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyInfo.java deleted file mode 100644 index 649747496da7..000000000000 --- a/src/java.base/share/classes/com/jetbrains/internal/ProxyInfo.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains.internal; - -import java.lang.invoke.MethodHandles; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Stream; - -import static java.lang.invoke.MethodHandles.Lookup; - -/** - * Proxy info, like {@link RegisteredProxyInfo}, but with all classes and lookup contexts resolved. - * Contains all necessary information to create a {@linkplain Proxy proxy}. - */ -class ProxyInfo { - - final Lookup apiModule; - final Type type; - final Lookup interFaceLookup; - final Class interFace; - final Lookup target; - final Map staticMethods = new HashMap<>(); - - private ProxyInfo(RegisteredProxyInfo i) { - this.apiModule = i.apiModule(); - type = i.type(); - interFaceLookup = lookup(getInterfaceLookup(), i.interfaceName()); - interFace = interFaceLookup == null ? null : interFaceLookup.lookupClass(); - target = Stream.of(i.targets()) - .map(t -> lookup(getTargetLookup(), t)) - .filter(Objects::nonNull).findFirst().orElse(null); - for (RegisteredProxyInfo.StaticMethodMapping m : i.staticMethods()) { - Stream.of(m.classes()) - .map(t -> lookup(getTargetLookup(), t)) - .filter(Objects::nonNull).findFirst() - .ifPresent(l -> staticMethods.put(m.interfaceMethodName(), new StaticMethodMapping(l, m.methodName()))); - } - } - - /** - * Resolve all classes and lookups for given {@link RegisteredProxyInfo}. - */ - static ProxyInfo resolve(RegisteredProxyInfo i) { - ProxyInfo info = new ProxyInfo(i); - if (info.interFace == null || (info.target == null && info.staticMethods.isEmpty())) return null; - if (!info.interFace.isInterface()) { - if (info.type == Type.CLIENT_PROXY) { - throw new RuntimeException("Tried to create client proxy for non-interface: " + info.interFace); - } else { - return null; - } - } - return info; - } - - Lookup getInterfaceLookup() { - return type == Type.CLIENT_PROXY || type == Type.INTERNAL_SERVICE ? apiModule : JBRApi.outerLookup; - } - - Lookup getTargetLookup() { - return type == Type.CLIENT_PROXY ? JBRApi.outerLookup : apiModule; - } - - private Lookup lookup(Lookup lookup, String clazz) { - String[] nestedClasses = clazz.split("\\$"); - clazz = ""; - for (int i = 0; i < nestedClasses.length; i++) { - try { - if (i != 0) clazz += "$"; - clazz += nestedClasses[i]; - lookup = MethodHandles.privateLookupIn(Class.forName(clazz, false, lookup.lookupClass().getClassLoader()), lookup); - } catch (ClassNotFoundException | IllegalAccessException ignore) { - return null; - } - } - return lookup; - } - - record StaticMethodMapping(Lookup lookup, String methodName) {} - - /** - * Proxy type, see {@link Proxy} - */ - enum Type { - PROXY, - SERVICE, - CLIENT_PROXY, - INTERNAL_SERVICE; - - public boolean isPublicApi() { - return this == PROXY || this == SERVICE; - } - - public boolean isService() { - return this == SERVICE || this == INTERNAL_SERVICE; - } - } -} diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyRepository.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyRepository.java new file mode 100644 index 000000000000..c59744da2744 --- /dev/null +++ b/src/java.base/share/classes/com/jetbrains/internal/ProxyRepository.java @@ -0,0 +1,341 @@ +/* + * Copyright 2023 JetBrains s.r.o. + * 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.jetbrains.internal; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.loader.BootLoader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Stream; + +import static java.lang.invoke.MethodHandles.Lookup; + +/** + * Proxy repository keeps track of all generated proxies. + * @see ProxyRepository#getProxy(Class, Mapping[]) + */ +class ProxyRepository { + private static final Proxy NONE = Proxy.empty(null), INVALID = Proxy.empty(false); + + private final Registry registry = new Registry(); + private final Map proxies = new HashMap<>(); + + void init(InputStream extendedRegistryStream, + Class serviceAnnotation, + Class providedAnnotation, + Class providesAnnotation) { + registry.initAnnotations(serviceAnnotation, providedAnnotation, providesAnnotation); + if (extendedRegistryStream != null) registry.readEntries(extendedRegistryStream); + } + + String getVersion() { + return registry.version; + } + + synchronized Proxy getProxy(Class clazz, Mapping[] specialization) { + Key key = new Key(clazz, specialization); + Proxy p = proxies.get(key); + if (p == null) { + registry.updateClassLoader(clazz.getClassLoader()); + Mapping[] inverseSpecialization = specialization == null ? null : + Stream.of(specialization).map(m -> m == null ? null : m.inverse()).toArray(Mapping[]::new); + Key inverseKey = null; + + Registry.Entry entry = registry.entries.get(key.clazz().getCanonicalName()); + if (entry != null) { // This is a registered proxy + Proxy.Info infoByInterface = entry.resolve(), + infoByTarget = entry.inverse != null ? entry.inverse.resolve() : null; + inverseKey = infoByTarget != null && infoByTarget.interfaceLookup != null ? + new Key(infoByTarget.interfaceLookup.lookupClass(), inverseSpecialization) : null; + if ((infoByInterface == null && infoByTarget == null) || + infoByInterface == Registry.Entry.INVALID || + infoByTarget == Registry.Entry.INVALID) { + p = INVALID; + } else { + p = Proxy.create(this, infoByInterface, specialization, infoByTarget, inverseSpecialization); + } + } else if (!Arrays.equals(specialization, inverseSpecialization)) { // Try implicit proxy + inverseKey = new Key(clazz, inverseSpecialization); + Lookup lookup = SharedSecrets.getJavaLangInvokeAccess().lookupIn(clazz); + Proxy.Info info = new Proxy.Info(lookup, lookup, 0); + p = Proxy.create(this, info, specialization, info, inverseSpecialization); + } else p = NONE; + proxies.put(key, p); + if (inverseKey != null) proxies.put(inverseKey, p.inverse()); + } + return p; + } + + private record Key(Class clazz, Mapping[] specialization) { + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Key key = (Key) o; + if (!clazz.equals(key.clazz)) return false; + return Arrays.equals(specialization, key.specialization); + } + + @Override + public int hashCode() { + int result = clazz.hashCode(); + result = 31 * result + Arrays.hashCode(specialization); + return result; + } + + @Override + public String toString() { + return clazz.getName() + "<" + Arrays.toString(specialization) + ">"; + } + } + + /** + * Registry contains all information about mapping between JBR API interfaces and implementation. + * This mapping information can be {@linkplain Entry#resolve() resolved} into {@link Proxy.Info}. + */ + private static class Registry { + + private record StaticKey(String methodName, String targetMethodDescriptor) {} + private record StaticValue(String targetType, String targetMethodName) {} + + private class Entry { + private static final Proxy.Info INVALID = new Proxy.Info(null, null, 0); + + private final Map staticMethods = new HashMap<>(); + private String type, target; + private Entry inverse; + private int flags; + + private Entry(String type) { this.type = type; } + + private Proxy.Info resolve() { + if (type == null) return null; + Lookup l, t; + try { + l = resolveType(type, classLoader); + t = target != null ? resolveType(target, classLoader) : null; + } catch (ClassNotFoundException e) { + if (JBRApi.VERBOSE) { + System.err.println(type + " not eligible"); + e.printStackTrace(System.err); + } + return INVALID; + } + if (l.lookupClass().isAnnotation() || l.lookupClass().isEnum() || l.lookupClass().isRecord()) { + if (JBRApi.VERBOSE) { + System.err.println(type + " not eligible: not a class or interface"); + } + return INVALID; + } + if (Modifier.isFinal(l.lookupClass().getModifiers()) || l.lookupClass().isSealed()) { + if (JBRApi.VERBOSE) { + System.err.println(type + " not eligible: invalid type (final/sealed)"); + } + return INVALID; + } + if (target == null) flags |= Proxy.SERVICE; + if (needsAnnotation(l.lookupClass())) { + if (!hasAnnotation(l.lookupClass(), providedAnnotation)) { + if (JBRApi.VERBOSE) { + System.err.println(type + " not eligible: no @Provided annotation"); + } + return INVALID; + } + if (!hasAnnotation(l.lookupClass(), serviceAnnotation)) flags &= ~Proxy.SERVICE; + } + Proxy.Info info; + if (t != null) { + if (needsAnnotation(t.lookupClass()) && !hasAnnotation(t.lookupClass(), providesAnnotation)) { + if (JBRApi.VERBOSE) { + System.err.println(target + " not eligible: no @Provides annotation"); + } + return INVALID; + } + info = new Proxy.Info(l, t, flags); + } else info = new Proxy.Info(l, null, flags); + for (var method : staticMethods.entrySet()) { + String methodName = method.getKey().methodName; + String targetMethodDescriptor = method.getKey().targetMethodDescriptor; + String targetType = method.getValue().targetType; + String targetMethodName = method.getValue().targetMethodName; + try { + Lookup lookup = resolveType(targetType, classLoader); + MethodType mt = MethodType.fromMethodDescriptorString(targetMethodDescriptor, classLoader); + MethodHandle handle = lookup.findStatic(lookup.lookupClass(), targetMethodName, mt); + info.addStaticMethod(methodName, handle); + } catch (ClassNotFoundException | IllegalArgumentException | TypeNotPresentException | + NoSuchMethodException | IllegalAccessException | ClassCastException e) { + if (JBRApi.VERBOSE) { + System.err.println(targetType + "#" + targetMethodName + " cannot be resolved"); + e.printStackTrace(System.err); + } + } + } + return info; + } + + private static Lookup resolveType(String type, ClassLoader classLoader) throws ClassNotFoundException { + Class c = null; + try { + c = Class.forName(type, false, classLoader); + } catch (ClassNotFoundException e) { + String dollars = type.replace('.', '$'); + for (int i = 0;; i++) { + i = type.indexOf('.', i); + if (i == -1) break; + String name = type.substring(0, i) + dollars.substring(i); + try { + c = Class.forName(name, false, classLoader); + break; + } catch (ClassNotFoundException ignore) {} + } + if (c == null) throw e; + } + return SharedSecrets.getJavaLangInvokeAccess().lookupIn(c); + } + + @Override + public String toString() { return type; } + } + + private Class serviceAnnotation, providedAnnotation, providesAnnotation; + private Module annotationsModule; + private ClassLoader classLoader; + private final Map entries = new HashMap<>(); + private final String version; + + private Registry() { + try (InputStream registryStream = BootLoader.findResourceAsStream("java.base", "META-INF/jbrapi.registry")) { + version = readEntries(registryStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void initAnnotations(Class serviceAnnotation, + Class providedAnnotation, + Class providesAnnotation) { + this.serviceAnnotation = serviceAnnotation; + this.providedAnnotation = providedAnnotation; + this.providesAnnotation = providesAnnotation; + annotationsModule = serviceAnnotation == null ? null : serviceAnnotation.getModule(); + if (annotationsModule != null) classLoader = annotationsModule.getClassLoader(); + } + + private String readEntries(InputStream inputStream) { + String version = null; + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String s; + while ((s = reader.readLine()) != null) { + String[] tokens = s.split(" "); + switch (tokens[0]) { + case "TYPE" -> { + Entry a = entries.computeIfAbsent(tokens[1], Entry::new); + Entry b = entries.computeIfAbsent(tokens[2], Entry::new); + if ((a.inverse != null || b.inverse != null) && (a.inverse != b || b.inverse != a)) { + throw new RuntimeException("Conflicting mapping: " + + tokens[1] + " <-> " + tokens[2] + + ", " + a + " -> " + a.inverse + + ", " + b + " -> " + b.inverse); + } + a.inverse = b; + b.inverse = a; + a.target = tokens[2]; + b.target = tokens[1]; + switch (tokens[3]) { + case "SERVICE" -> { + a.type = null; + b.flags |= Proxy.SERVICE; + } + case "PROVIDES" -> a.type = null; + case "PROVIDED" -> b.type = null; + } + if (tokens.length > 4 && tokens[4].equals("INTERNAL")) { + a.flags |= Proxy.INTERNAL; + b.flags |= Proxy.INTERNAL; + } + } + case "STATIC" -> { + Entry entry = entries.computeIfAbsent(tokens[4], Entry::new); + StaticValue target = new StaticValue(tokens[1], tokens[2]); + StaticValue prev = entry.staticMethods.put(new StaticKey(tokens[5], tokens[3]), target); + if (prev != null && !prev.equals(target)) { + throw new RuntimeException("Conflicting mapping: " + + target.targetType + "#" + target.targetMethodName + " <- " + + tokens[4] + "#" + tokens[5] + " -> " + + prev.targetType + "#" + prev.targetMethodName); + } + if (tokens.length > 6 && tokens[6].equals("INTERNAL")) entry.flags |= Proxy.INTERNAL; + } + case "VERSION" -> version = tokens[1]; + } + } + } catch (IOException e) { + entries.clear(); + throw new RuntimeException(e); + } catch (RuntimeException | Error e) { + entries.clear(); + throw e; + } + return version; + } + + private synchronized void updateClassLoader(ClassLoader newLoader) { + // New loader is descendant of current one -> update + for (ClassLoader cl = newLoader;; cl = cl.getParent()) { + if (cl == classLoader) { + classLoader = newLoader; + return; + } + if (cl == null) break; + } + // Current loader is descendant of the new one -> leave + for (ClassLoader cl = classLoader;; cl = cl.getParent()) { + if (cl == newLoader) return; + if (cl == null) break; + } + // Independent classloaders -> error? Or maybe reset cache and start fresh? + throw new RuntimeException("Incompatible classloader"); + } + + private boolean needsAnnotation(Class c) { + return annotationsModule != null && annotationsModule.equals(c.getModule()); + } + + private static boolean hasAnnotation(Class c, Class a) { + return c.getAnnotation(a) != null; + } + } +} diff --git a/src/java.base/share/classes/com/jetbrains/internal/RegisteredProxyInfo.java b/src/java.base/share/classes/com/jetbrains/internal/RegisteredProxyInfo.java deleted file mode 100644 index ec636e82b39f..000000000000 --- a/src/java.base/share/classes/com/jetbrains/internal/RegisteredProxyInfo.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains.internal; - -import java.lang.invoke.MethodHandles; -import java.util.List; - -/** - * Raw proxy info, as it was registered through {@link JBRApi.ModuleRegistry}. - * Contains all necessary information to create a {@linkplain Proxy proxy}. - */ -record RegisteredProxyInfo(MethodHandles.Lookup apiModule, - String interfaceName, - String[] targets, - ProxyInfo.Type type, - List staticMethods) { - - record StaticMethodMapping(String interfaceMethodName, String methodName, String[] classes) {} -} diff --git a/src/java.base/share/classes/com/jetbrains/internal/Utils.java b/src/java.base/share/classes/com/jetbrains/internal/Utils.java new file mode 100644 index 000000000000..8660ae8e3c89 --- /dev/null +++ b/src/java.base/share/classes/com/jetbrains/internal/Utils.java @@ -0,0 +1,49 @@ +package com.jetbrains.internal; + +import jdk.internal.misc.VM; + +import java.io.PrintStream; +import java.util.function.Function; +import java.util.stream.Stream; + +class Utils { + + static final Function, Stream> + BEFORE_BOOTSTRAP_DYNAMIC = st -> st + .dropWhile(e -> !e.getClassName().equals("com.jetbrains.exported.JBRApiSupport") || + !e.getMethodName().equals("bootstrapDynamic")).skip(1) + .dropWhile(e -> e.getClassName().startsWith("java.lang.invoke.")); + static final Function, Stream> + BEFORE_JBR = st -> st + .dropWhile(e -> !e.getClassName().equals("com.jetbrains.JBR")) + .dropWhile(e -> e.getClassName().equals("com.jetbrains.JBR")); + + static void log(Function, Stream> preprocessor, + PrintStream out, String message) { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + synchronized (out) { + out.println(message); + preprocessor.apply(Stream.of(st)).forEach(e -> out.println("\tat " + e)); + } + } + + /** + * System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}. + */ + static boolean property(String name, boolean defaultValue) { + String value = VM.getSavedProperty(name); + if (value != null) { + if (value.equalsIgnoreCase("true")) return true; + if (value.equalsIgnoreCase("false")) return false; + } + return defaultValue; + } + + /** + * System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}. + */ + static String property(String name, String defaultValue) { + String value = VM.getSavedProperty(name); + return value != null ? value : defaultValue; + } +} diff --git a/src/java.base/share/classes/java/lang/Throwable.java b/src/java.base/share/classes/java/lang/Throwable.java index db240023476c..a57058051bf6 100644 --- a/src/java.base/share/classes/java/lang/Throwable.java +++ b/src/java.base/share/classes/java/lang/Throwable.java @@ -27,6 +27,8 @@ package java.lang; import java.io.*; import java.util.*; + +import com.jetbrains.exported.JBRApi; import jdk.internal.event.ThrowableTracer; /** @@ -1138,7 +1140,7 @@ public class Throwable implements Serializable { private static volatile java.util.function.Supplier $$jb$additionalInfoSupplier = null; - // JBR API internals + @JBRApi.Provides("Jstack#includeInfoFrom") private static void $$jb$additionalInfoForJstack(java.util.function.Supplier supplier) { $$jb$additionalInfoSupplier = supplier; } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index 5b8a4478be57..c9f15b7b3212 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -1645,6 +1645,11 @@ abstract class MethodHandleImpl { return IMPL_LOOKUP.serializableConstructor(decl, ctorToCall); } + @Override + public Lookup lookupIn(Class lookupClass) { + return IMPL_LOOKUP.in(lookupClass); + } + }); } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java index 722447eece67..05588e692f8f 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java @@ -29,6 +29,7 @@ import jdk.internal.foreign.abi.NativeEntryPoint; import java.lang.foreign.MemoryLayout; import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.lang.reflect.Constructor; @@ -170,4 +171,11 @@ public interface JavaLangInvokeAccess { * This method should only be used by ReflectionFactory::newConstructorForSerialization. */ MethodHandle serializableConstructor(Class decl, Constructor ctorToCall) throws IllegalAccessException; + + /** + * Returns a lookup object corresponding to given class with full access privileges. + * @param lookupClass lookup class + * @return full-privileged lookup object + */ + Lookup lookupIn(Class lookupClass); } diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 932bfa0e43bb..74600eb65dee 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -138,10 +138,9 @@ module java.base { // additional qualified exports may be inserted at build time // see make/gensrc/GenModuleInfo.gmk + exports com.jetbrains.exported; opens com.jetbrains.bootstrap; - exports com.jetbrains.internal to - java.desktop; exports com.sun.crypto.provider to jdk.crypto.cryptoki; exports sun.invoke.util to diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java index 65af936820d9..71b210382c4e 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java @@ -44,7 +44,6 @@ import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.FocusEvent; -import java.awt.event.PaintEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowStateListener; import java.awt.peer.ComponentPeer; @@ -64,11 +63,11 @@ import javax.swing.SwingUtilities; import com.apple.laf.ClientPropertyApplicator; import com.apple.laf.ClientPropertyApplicator.Property; +import com.jetbrains.exported.JBRApi; import sun.awt.AWTAccessor; import sun.awt.AWTAccessor.ComponentAccessor; import sun.awt.AWTAccessor.WindowAccessor; import sun.awt.AWTThreading; -import sun.awt.PaintEventDispatcher; import sun.java2d.SunGraphicsEnvironment; import sun.java2d.SurfaceData; import sun.lwawt.LWKeyboardFocusManagerPeer; @@ -81,7 +80,7 @@ import sun.lwawt.PlatformWindow; import sun.util.logging.PlatformLogger; public class CPlatformWindow extends CFRetainedResource implements PlatformWindow { - private native long nativeCreateNSWindow(long nsViewPtr,long ownerPtr, long styleBits, double x, double y, double w, double h, double transparentTitleBarHeight); + private native long nativeCreateNSWindow(long nsViewPtr,long ownerPtr, long styleBits, double x, double y, double w, double h); private static native void nativeSetNSWindowStyleBits(long nsWindowPtr, int mask, int data); private static native void nativeSetNSWindowAppearance(long nsWindowPtr, String appearanceName); private static native void nativeSetNSWindowMenuBar(long nsWindowPtr, long menuBarPtr); @@ -108,7 +107,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo static native CPlatformWindow nativeGetTopmostPlatformWindowUnderMouse(); private static native void nativeRaiseLevel(long nsWindowPtr, boolean popup, boolean onlyIfParentIsActive); private static native boolean nativeDelayShowing(long nsWindowPtr); - private static native void nativeSetTransparentTitleBarHeight(long nsWindowPtr, float height); + private static native void nativeUpdateCustomTitleBar(long nsWindowPtr); private static native void nativeCallDeliverMoveResizeEvent(long nsWindowPtr); private static native void nativeSetRoundedCorners(long nsWindowPrt, float radius, int borderWidth, int borderColor); @@ -143,7 +142,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo public static final String WINDOW_TRANSPARENT_TITLE_BAR = "apple.awt.transparentTitleBar"; public static final String WINDOW_TITLE_VISIBLE = "apple.awt.windowTitleVisible"; public static final String WINDOW_APPEARANCE = "apple.awt.windowAppearance"; - public static final String WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT = "apple.awt.windowTransparentTitleBarHeight"; public static final String WINDOW_CORNER_RADIUS = "apple.awt.windowCornerRadius"; // This system property is named as jdk.* because it is not specific to AWT @@ -293,17 +291,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo } } }, - new Property(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT) { - public void applyProperty(final CPlatformWindow c, final Object value) { - if (value != null && (value instanceof Float)) { - boolean enabled = (float) value != 0f; - c.setStyleBits(FULL_WINDOW_CONTENT, enabled); - c.setStyleBits(TRANSPARENT_TITLE_BAR, enabled); - c.setStyleBits(TITLE_VISIBLE, !enabled); - c.execute(ptr -> AWTThreading.executeWaitToolkit(wait -> nativeSetTransparentTitleBarHeight(ptr, (float) value))); - } - } - }, new Property(WINDOW_CORNER_RADIUS) { public void applyProperty(final CPlatformWindow c, final Object value) { c.setRoundedCorners(value); @@ -374,8 +361,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo responder = createPlatformResponder(); contentView.initialize(peer, responder); - float transparentTitleBarHeight = getTransparentTitleBarHeight(_target); - Rectangle bounds; if (!IS(DECORATED, styleBits)) { // For undecorated frames the move/resize event does not come if the frame is centered on the screen @@ -396,7 +381,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo + ", bounds=" + bounds); } long windowPtr = createNSWindow(viewPtr, ownerPtr, styleBits, - bounds.x, bounds.y, bounds.width, bounds.height, transparentTitleBarHeight); + bounds.x, bounds.y, bounds.width, bounds.height); if (logger.isLoggable(PlatformLogger.Level.FINE)) { logger.fine("window created: " + Long.toHexString(windowPtr)); } @@ -411,7 +396,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo + ", bounds=" + bounds); } long windowPtr = createNSWindow(viewPtr, 0, styleBits, - bounds.x, bounds.y, bounds.width, bounds.height, transparentTitleBarHeight); + bounds.x, bounds.y, bounds.width, bounds.height); if (logger.isLoggable(PlatformLogger.Level.FINE)) { logger.fine("window created: " + Long.toHexString(windowPtr)); } @@ -575,14 +560,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo if (prop != null) { styleBits = SET(styleBits, TITLE_VISIBLE, Boolean.parseBoolean(prop.toString())); } - - prop = rootpane.getClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT); - if (prop != null) { - boolean enabled = Float.parseFloat(prop.toString()) != 0f; - styleBits = SET(styleBits, FULL_WINDOW_CONTENT, enabled); - styleBits = SET(styleBits, TRANSPARENT_TITLE_BAR, enabled); - styleBits = SET(styleBits, TITLE_VISIBLE, !enabled); - } } if (isDialog) { @@ -1506,10 +1483,9 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo double x, double y, double w, - double h, - double transparentTitleBarHeight) { + double h) { return AWTThreading.executeWaitToolkit(() -> - nativeCreateNSWindow(nsViewPtr, ownerPtr, styleBits, x, y, w, h, transparentTitleBarHeight)); + nativeCreateNSWindow(nsViewPtr, ownerPtr, styleBits, x, y, w, h)); } // ---------------------------------------------------------------------- @@ -1543,28 +1519,15 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo isFullScreenAnimationOn = false; } - private float getTransparentTitleBarHeight(Window target) { - if (target instanceof javax.swing.RootPaneContainer) { - final javax.swing.JRootPane rootpane = ((javax.swing.RootPaneContainer)target).getRootPane(); - if (rootpane != null) { - Object transparentTitleBarHeightProperty = rootpane.getClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT); - if (transparentTitleBarHeightProperty != null) { - return Float.parseFloat(transparentTitleBarHeightProperty.toString()); - } - } - } - return 0f; - } - - // JBR API internals - private static void setCustomDecorationTitleBarHeight(Window target, ComponentPeer peer, float height) { - if (target instanceof javax.swing.RootPaneContainer) { - final javax.swing.JRootPane rootpane = ((javax.swing.RootPaneContainer)target).getRootPane(); - if (rootpane != null) rootpane.putClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT, height); + @JBRApi.Provides("java.awt.Window.CustomTitleBarPeer#update") + private static void updateCustomTitleBar(ComponentPeer peer) { + if (peer instanceof LWWindowPeer lwwp && + lwwp.getPlatformWindow() instanceof CPlatformWindow cpw) { + cpw.execute(CPlatformWindow::nativeUpdateCustomTitleBar); } } - // JBR API internals + @JBRApi.Provides("RoundedCornersManager") private static void setRoundedCorners(Window window, Object params) { Object peer = AWTAccessor.getComponentAccessor().getPeer(window); if (peer instanceof CPlatformWindow) { diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java b/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java index 6751da518bea..93732116070e 100644 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java @@ -23,10 +23,12 @@ package com.jetbrains.desktop; +import com.jetbrains.exported.JBRApi; import sun.awt.ConstrainableGraphics; import java.awt.geom.Rectangle2D; +@JBRApi.Provided("GraphicsUtils.ConstrainableGraphics2D") public interface ConstrainableGraphics2D extends ConstrainableGraphics { public void constrain(Rectangle2D region); public Object getDestination(); diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java b/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java deleted file mode 100644 index 4d300d9fe366..000000000000 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains.desktop; - -import com.jetbrains.internal.JBRApi; - -import java.awt.*; -import java.lang.invoke.MethodHandles; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; - -public class FontExtensions { - private interface FontExtension { - FontExtension INSTANCE = (FontExtension) JBRApi.internalServiceBuilder(MethodHandles.lookup()) - .withStatic("getFeatures", "getFeatures", "java.awt.Font") - .withStatic("isComplexRendering", "isComplexRendering", "java.awt.Font") - .withStatic("isKerning", "isKerning", "java.awt.Font") - .build(); - - TreeMap getFeatures(Font font); - boolean isComplexRendering(Font font); - boolean isKerning(Font font); - } - - public static String featuresToString(Map features) { - return features.entrySet().stream().map(feature -> (feature.getKey() + "=" + feature.getValue())). - collect(Collectors.joining(";")); - } - - public static TreeMap getFeatures(Font font) { - return FontExtension.INSTANCE.getFeatures(font); - } - - public static boolean isComplexRendering(Font font) { - return FontExtension.INSTANCE.isComplexRendering(font); - } - - public static boolean isKerning(Font font) { - return FontExtension.INSTANCE.isKerning(font); - } -} diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java deleted file mode 100644 index 32e5cf6ffa4f..000000000000 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains.desktop; - -import com.jetbrains.internal.JBRApi; - -import java.lang.invoke.MethodHandles; - -/** - * This class contains mapping between JBR API interfaces and implementation in {@code java.desktop} module. - */ -public class JBRApiModule { - static { - JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports) - .service("com.jetbrains.ExtendedGlyphCache") - .withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities") - .service("com.jetbrains.JBRFileDialogService") - .withStatic("getFileDialog", "get", "com.jetbrains.desktop.JBRFileDialog") - .proxy("com.jetbrains.JBRFileDialog", "com.jetbrains.desktop.JBRFileDialog") - .service("com.jetbrains.CustomWindowDecoration", "java.awt.Window$CustomWindowDecoration") - .service("com.jetbrains.RoundedCornersManager") - .withStatic("setRoundedCorners", "setRoundedCorners", "sun.lwawt.macosx.CPlatformWindow", - "sun.awt.windows.WWindowPeer") - .service("com.jetbrains.DesktopActions") - .withStatic("setHandler", "setDesktopActionsHandler", "java.awt.Desktop") - .clientProxy("java.awt.Desktop$DesktopActionsHandler", "com.jetbrains.DesktopActions$Handler") - .service("com.jetbrains.ProjectorUtils") - .withStatic("overrideGraphicsEnvironment", "overrideLocalGraphicsEnvironment", "java.awt.GraphicsEnvironment") - .withStatic("setLocalGraphicsEnvironmentProvider", "setLocalGraphicsEnvironmentProvider", "java.awt.GraphicsEnvironment") - .service("com.jetbrains.AccessibleAnnouncer") - .withStatic("announce", "announce", "sun.swing.AccessibleAnnouncer") - .service("com.jetbrains.GraphicsUtils") - .withStatic("createConstrainableGraphics", "create", "com.jetbrains.desktop.JBRGraphicsDelegate") - .clientProxy("com.jetbrains.desktop.ConstrainableGraphics2D", "com.jetbrains.GraphicsUtils$ConstrainableGraphics2D") - .service("com.jetbrains.WindowDecorations", "java.awt.Window$WindowDecorations") - .proxy("com.jetbrains.WindowDecorations$CustomTitleBar", "java.awt.Window$CustomTitleBar") - .service("com.jetbrains.WindowMove", "java.awt.Window$WindowMoveService") - .service("com.jetbrains.FontExtensions") - .withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities") - .withStatic("deriveFontWithFeatures", "deriveFont", "java.awt.Font") - .withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font") - .service("com.jetbrains.FontOpenTypeFeatures") - .withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font") - .clientProxy("java.awt.Font$Features", "com.jetbrains.FontExtensions$Features") - .service("com.jetbrains.FontMetricsAccessor", "sun.font.FontDesignMetrics$Accessor") - .clientProxy("sun.font.FontDesignMetrics$Overrider", "com.jetbrains.FontMetricsAccessor$Overrider"); - } -} diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java index 7f6a960e38ad..a41cb1175610 100644 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java @@ -1,5 +1,7 @@ package com.jetbrains.desktop; +import com.jetbrains.exported.JBRApi; + import java.io.Serial; import java.io.Serializable; import java.lang.annotation.Native; @@ -8,7 +10,8 @@ import java.lang.invoke.VarHandle; import java.awt.*; import java.util.Objects; -public class JBRFileDialog implements Serializable { +@JBRApi.Provides("JBRFileDialog") +public final class JBRFileDialog implements Serializable { @Serial private static final long serialVersionUID = -9154712118353824660L; @@ -22,6 +25,8 @@ public class JBRFileDialog implements Serializable { throw new Error(e); } } + + @JBRApi.Provides("JBRFileDialogService#getFileDialog") public static JBRFileDialog get(FileDialog dialog) { return (JBRFileDialog) getter.get(dialog); } diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java index 6f35e1eb2f8e..62c686b16aef 100644 --- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java +++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java @@ -23,6 +23,8 @@ package com.jetbrains.desktop; +import com.jetbrains.exported.JBRApi; + import java.awt.*; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; @@ -38,6 +40,7 @@ import java.util.Map; public class JBRGraphicsDelegate extends Graphics2D implements ConstrainableGraphics2D { + @JBRApi.Provides("GraphicsUtils#createConstrainableGraphics") public static Graphics2D create(Graphics2D graphics2D, ConstrainableGraphics2D constrainable) { return new JBRGraphicsDelegate(graphics2D, constrainable); diff --git a/src/java.desktop/share/classes/java/awt/Desktop.java b/src/java.desktop/share/classes/java/awt/Desktop.java index 0a48c739f19d..1e1a714654a4 100644 --- a/src/java.desktop/share/classes/java/awt/Desktop.java +++ b/src/java.desktop/share/classes/java/awt/Desktop.java @@ -46,6 +46,7 @@ import java.util.Objects; import javax.swing.JMenuBar; +import com.jetbrains.exported.JBRApi; import sun.awt.SunToolkit; /** @@ -858,6 +859,7 @@ public class Desktop { return peer.moveToTrash(file); } + @JBRApi.Provided("DesktopActions.Handler") private interface DesktopActionsHandler { void open(File file) throws IOException; void edit(File file) throws IOException; @@ -890,7 +892,8 @@ public class Desktop { } private static volatile DesktopActions actions; - static void setDesktopActionsHandler(DesktopActionsHandler h) { + @JBRApi.Provides("DesktopActions#setHandler") + private static void setDesktopActionsHandler(DesktopActionsHandler h) { try { actions = new DesktopActions(h); } catch (Exception e) { diff --git a/src/java.desktop/share/classes/java/awt/Font.java b/src/java.desktop/share/classes/java/awt/Font.java index 8e8fed68d8c2..fa9b371c59ac 100644 --- a/src/java.desktop/share/classes/java/awt/Font.java +++ b/src/java.desktop/share/classes/java/awt/Font.java @@ -45,17 +45,12 @@ import java.lang.ref.SoftReference; import java.nio.file.Files; import java.text.AttributedCharacterIterator.Attribute; import java.text.CharacterIterator; -import java.util.EventListener; -import java.util.Hashtable; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; +import java.util.*; +import com.jetbrains.exported.JBRApi; import sun.awt.ComponentFactory; import sun.font.AttributeMap; import sun.font.AttributeValues; -import sun.font.CompositeFont; import sun.font.CoreMetrics; import sun.font.Font2D; import sun.font.Font2DHandle; @@ -280,6 +275,28 @@ public class Font implements java.io.Serializable public FontPeer getFontPeer(final Font font) { return font.getFontPeer(); } + + @Override + public String[] getFeatures(Font font) { + int size = font.featureArray == null ? 0 : font.featureArray.length; + String[] fs = new String[size + 3]; + if (size > 0) System.arraycopy(font.featureArray, 0, fs, 0, size); + fs[size++] = KERN_FEATURE + "=" + font.getAttributeValues().getKerning(); + fs[size++] = LIGA_FEATURE + "=" + font.getAttributeValues().getLigatures(); + fs[size] = CALT_FEATURE + "=" + font.getAttributeValues().getLigatures(); + return fs; + } + + @Override + public boolean isComplexRendering(Font font) { + return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 || + font.values.getBaselineTransform() != null)) || font.featureArray != null; + } + + @Override + public boolean isKerning(Font font) { + return font.values != null && (font.values.getKerning() != 0); + } } static { @@ -451,10 +468,11 @@ public class Font implements java.io.Serializable * Ordered map choose intentionally as field's type. It allows to correctly comparing two Font objects * * @serial - * @see #getFeatures * @see #deriveFont(Font, Features) */ - private TreeMap features = new TreeMap(); + private String[] featureArray; + @Deprecated + private TreeMap features; // Kept for compatibility /** * The platform specific font information. @@ -557,10 +575,6 @@ public class Font implements java.io.Serializable return font2DHandle.font2D; } - private boolean anyEnabledFeatures() { - return features.values().stream().anyMatch(x -> x != 0); - } - /** * Creates a new {@code Font} from the specified name, style and * point size. @@ -624,18 +638,19 @@ public class Font implements java.io.Serializable this.pointSize = size; } - private Font(String name, int style, float sizePts, TreeMap features) { + private Font(String name, int style, float sizePts, String[] features) { this.name = (name != null) ? name : "Default"; this.style = (style & ~0x03) == 0 ? style : 0; this.size = (int)(sizePts + 0.5); this.pointSize = sizePts; - this.features = features; + this.featureArray = features; + this.hasLayoutAttributes |= this.featureArray != null; } /* This constructor is used by deriveFont when attributes is null */ private Font(String name, int style, float sizePts, boolean created, boolean withFallback, - Font2DHandle handle, boolean useOldHandle, TreeMap features) { + Font2DHandle handle, boolean useOldHandle, String[] features) { this(name, style, sizePts, features); this.createdFont = created; this.withFallback = withFallback; @@ -699,9 +714,9 @@ public class Font implements java.io.Serializable */ private Font(AttributeValues values, String oldName, int oldStyle, boolean created, boolean withFallback, - Font2DHandle handle, boolean useOldHandle, TreeMap features) { + Font2DHandle handle, boolean useOldHandle, String[] features) { - this.features = features; + this.featureArray = features; this.createdFont = created; this.withFallback = withFallback; if (created || withFallback) { @@ -735,6 +750,7 @@ public class Font implements java.io.Serializable this.font2DHandle = handle; } initFromValues(values); + this.hasLayoutAttributes |= this.featureArray != null; } /** @@ -763,6 +779,11 @@ public class Font implements java.io.Serializable * @since 1.6 */ protected Font(Font font) { + this(font, font.featureArray); + } + + private Font(Font font, String[] features) { + this.featureArray = features; if (font.values != null) { initFromValues(font.getAttributeValues().clone()); } else { @@ -771,15 +792,10 @@ public class Font implements java.io.Serializable this.size = font.size; this.pointSize = font.pointSize; } + this.hasLayoutAttributes |= this.featureArray != null; this.font2DHandle = font.font2DHandle; this.createdFont = font.createdFont; this.withFallback = font.withFallback; - this.features = font.features; - } - - private Font(Font font, TreeMap features) { - this(font); - this.features = features; } /** @@ -829,7 +845,7 @@ public class Font implements java.io.Serializable if (values.getPosture() >= .2f) this.style |= ITALIC; // not == .2f this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK); - this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK); + this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK); } /** @@ -910,7 +926,7 @@ public class Font implements java.io.Serializable values.merge(attributes, SECONDARY_MASK); return new Font(values, font.name, font.style, font.createdFont, font.withFallback, - font.font2DHandle, false, new TreeMap()); + font.font2DHandle, false, null); } return new Font(attributes); } @@ -922,7 +938,7 @@ public class Font implements java.io.Serializable values.merge(attributes, SECONDARY_MASK); return new Font(values, font.name, font.style, font.createdFont, font.withFallback, - font.font2DHandle, false, new TreeMap()); + font.font2DHandle, false, null); } return font; @@ -1511,17 +1527,7 @@ public class Font implements java.io.Serializable * @since 1.6 */ public boolean hasLayoutAttributes() { - return anyEnabledFeatures() || hasLayoutAttributes; - } - - private static TreeMap getFeatures(Font font) { - TreeMap res = new TreeMap<>(); - res.putAll(font.features); - res.put(KERN_FEATURE, font.getAttributeValues().getKerning()); - res.put(LIGA_FEATURE, font.getAttributeValues().getLigatures()); - res.put(CALT_FEATURE, font.getAttributeValues().getLigatures()); - - return res; + return hasLayoutAttributes; } /** @@ -1723,7 +1729,7 @@ public class Font implements java.io.Serializable */ public int hashCode() { if (hash == 0) { - hash = name.hashCode() ^ style ^ size ^ features.hashCode(); + hash = name.hashCode() ^ style ^ size ^ Arrays.hashCode(featureArray); /* It is possible many fonts differ only in transform. * So include the transform in the hash calculation. * nonIdentityTx is set whenever there is a transform in @@ -1762,7 +1768,7 @@ public class Font implements java.io.Serializable pointSize == font.pointSize && withFallback == font.withFallback && name.equals(font.name) && - features.equals(font.features)) { + Arrays.equals(featureArray, font.featureArray)) { /* 'values' is usually initialized lazily, except when * the font is constructed from a Map, or derived using @@ -1868,6 +1874,11 @@ public class Font implements java.io.Serializable pointSize = (float)size; } + if (features != null) { + featureArray = features.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).toArray(String[]::new); + features = null; + } + // Handle fRequestedAttributes. // in 1.5, we always streamed out the font values plus // TRANSFORM, SUPERSCRIPT, and WIDTH, regardless of whether the @@ -1886,17 +1897,14 @@ public class Font implements java.io.Serializable } values = getAttributeValues().merge(extras); this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK); - this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK); + this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK); } catch (Throwable t) { throw new IOException(t); } finally { fRequestedAttributes = null; // don't need it any more } } - - if (features == null) { - features = new TreeMap<>(); - } + this.hasLayoutAttributes |= this.featureArray != null; } /** @@ -2000,14 +2008,14 @@ public class Font implements java.io.Serializable public Font deriveFont(int style, float size){ if (values == null) { return new Font(name, style, size, createdFont, withFallback, - font2DHandle, false, features); + font2DHandle, false, featureArray); } AttributeValues newValues = getAttributeValues().clone(); int oldStyle = (this.style != style) ? this.style : -1; applyStyle(style, newValues); newValues.setSize(size); return new Font(newValues, null, oldStyle, createdFont, withFallback, - font2DHandle, false, features); + font2DHandle, false, featureArray); } /** @@ -2027,7 +2035,7 @@ public class Font implements java.io.Serializable applyStyle(style, newValues); applyTransform(trans, newValues); return new Font(newValues, null, oldStyle, createdFont, withFallback, - font2DHandle, false, features); + font2DHandle, false, featureArray); } /** @@ -2040,12 +2048,12 @@ public class Font implements java.io.Serializable public Font deriveFont(float size){ if (values == null) { return new Font(name, style, size, createdFont, withFallback, - font2DHandle, true, features); + font2DHandle, true, featureArray); } AttributeValues newValues = getAttributeValues().clone(); newValues.setSize(size); return new Font(newValues, null, -1, createdFont, withFallback, - font2DHandle, true, features); + font2DHandle, true, featureArray); } /** @@ -2062,7 +2070,7 @@ public class Font implements java.io.Serializable AttributeValues newValues = getAttributeValues().clone(); applyTransform(trans, newValues); return new Font(newValues, null, -1, createdFont, withFallback, - font2DHandle, true, features); + font2DHandle, true, featureArray); } /** @@ -2075,13 +2083,13 @@ public class Font implements java.io.Serializable public Font deriveFont(int style){ if (values == null) { return new Font(name, style, size, createdFont, withFallback, - font2DHandle, false, features); + font2DHandle, false, featureArray); } AttributeValues newValues = getAttributeValues().clone(); int oldStyle = (this.style != style) ? this.style : -1; applyStyle(style, newValues); return new Font(newValues, null, oldStyle, createdFont, withFallback, - font2DHandle, false, features); + font2DHandle, false, featureArray); } /* @@ -2119,15 +2127,62 @@ public class Font implements java.io.Serializable } } return new Font(newValues, name, style, createdFont, withFallback, - font2DHandle, keepFont2DHandle, features); + font2DHandle, keepFont2DHandle, featureArray); } + @JBRApi.Provided("FontExtensions.Features") + @Deprecated(forRemoval = true) private interface Features { TreeMap getAsTreeMap(); } + @JBRApi.Provides("FontExtensions#deriveFontWithFeatures") + @Deprecated(forRemoval = true) private static Font deriveFont(Font font, Features features) { - return new Font(font, features.getAsTreeMap()); + TreeMap map = features.getAsTreeMap(); + String[] array = new String[map.size()]; + int i = 0; + for (Map.Entry e : map.entrySet()) { + validateFeature(array[i++] = e.getKey() + "=" + e.getValue()); + } + return new Font(font, array); + } + + private static void validateFeature(String f) { + int len = f.length(); + invalid:if ((len == 4 || len > 5) && + Character.isLetterOrDigit(f.charAt(0)) && + Character.isLetterOrDigit(f.charAt(1)) && + Character.isLetterOrDigit(f.charAt(2)) && + Character.isLetterOrDigit(f.charAt(3))) { + if (len > 5 && f.charAt(4) == '=') { + for (int i = 5; i < len; i++) { + if (!Character.isDigit(f.charAt(i))) break invalid; + } + if (f.startsWith("kern")) throw new IllegalArgumentException( + "\"kern\" feature is not allowed here, use java.awt.font.TextAttribute.KERNING instead"); + if (f.startsWith("liga") || f.startsWith("calt")) throw new IllegalArgumentException( + "\"liga\" and \"calt\" features are not allowed here, use java.awt.font.TextAttribute.LIGATURES instead"); + } + return; + } + throw new IllegalArgumentException("Invalid feature: \"" + f + "\", allowed syntax: \"kern\", or \"aalt=2\""); + } + + @JBRApi.Provides("FontExtensions#deriveFontWithFeatures") + private static Font deriveFont(Font font, String... features) { + if (features == null || features.length == 0) return new Font(font, null); + for (String f : features) validateFeature(f); + return new Font(font, Arrays.copyOf(features, features.length)); + } + + /** + * Returns a list of OpenType's features enabled on the current Font. + * @return array of enabled OpenType's features + */ + @JBRApi.Provides("FontExtensions") + private static String[] getEnabledFeatures(Font font) { + return font.featureArray == null ? new String[0] : Arrays.copyOf(font.featureArray, font.featureArray.length); } /** @@ -2581,20 +2636,12 @@ public class Font implements java.io.Serializable return metrics.charsBounds(chars, beginIndex, limit - beginIndex); } - private static boolean isComplexRendering(Font font) { - return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 || - font.values.getBaselineTransform() != null)) || font.anyEnabledFeatures(); - } - - private static boolean isKerning(Font font) { - return font.values != null && (font.values.getKerning() != 0); - } - /** * Returns a list of OpenType's features supported by current Font. * Implementation of such logic goes to HarfBuzz library. - * @return list of OpenType's features concatenated to String + * @return set of available OpenType's features */ + @JBRApi.Provides("FontExtensions") private static Set getAvailableFeatures(Font font) { return SunLayoutEngine.getAvailableFeatures(FontUtilities.getFont2D(font)); } diff --git a/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java b/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java index 1f8cdaf6d38d..15886a6e2869 100644 --- a/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java +++ b/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java @@ -29,6 +29,7 @@ import java.awt.image.BufferedImage; import java.util.Locale; import java.util.function.Supplier; +import com.jetbrains.exported.JBRApi; import sun.awt.PlatformGraphicsInfo; import sun.font.FontManager; import sun.font.FontManagerFactory; @@ -112,12 +113,12 @@ public abstract class GraphicsEnvironment { return LocalGE.INSTANCE; } - // JBR API internals + @JBRApi.Provides("ProjectorUtils") private static void setLocalGraphicsEnvironmentProvider(Supplier geProvider) { graphicsEnvironmentProvider = geProvider; } - // JBR API internals + @JBRApi.Provides("ProjectorUtils#overrideGraphicsEnvironment") private static void overrideLocalGraphicsEnvironment(GraphicsEnvironment overriddenGE) { setLocalGraphicsEnvironmentProvider(() -> overriddenGE); } diff --git a/src/java.desktop/share/classes/java/awt/Window.java b/src/java.desktop/share/classes/java/awt/Window.java index fbc61a87e356..36bba6387667 100644 --- a/src/java.desktop/share/classes/java/awt/Window.java +++ b/src/java.desktop/share/classes/java/awt/Window.java @@ -47,14 +47,12 @@ import java.io.OptionalDataException; import java.io.Serial; import java.io.Serializable; import java.lang.annotation.Native; -import java.lang.invoke.MethodHandles; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EventListener; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.HashMap; @@ -70,7 +68,7 @@ import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleState; import javax.accessibility.AccessibleStateSet; -import com.jetbrains.internal.JBRApi; +import com.jetbrains.exported.JBRApi; import sun.awt.AWTAccessor; import sun.awt.AppContext; import sun.awt.DebugSettings; @@ -3904,7 +3902,9 @@ public class Window extends Container implements Accessible { // ************************** Custom title bar support ******************************* - private static class WindowDecorations { + @JBRApi.Service + @JBRApi.Provides("WindowDecorations") + private static final class WindowDecorations { WindowDecorations() { CustomTitleBar.assertSupported(); } void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar) { ((Window) frame).setCustomTitleBar(customTitleBar); } void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar) { ((Window) dialog).setCustomTitleBar(customTitleBar); } @@ -3912,7 +3912,8 @@ public class Window extends Container implements Accessible { } - private static class CustomTitleBar implements Serializable { + @JBRApi.Provides("WindowDecorations.CustomTitleBar") + private static final class CustomTitleBar implements Serializable { @Serial private static final long serialVersionUID = -2330620200902241173L; @@ -4007,8 +4008,7 @@ public class Window extends Container implements Accessible { } private interface CustomTitleBarPeer { - CustomTitleBarPeer INSTANCE = (CustomTitleBarPeer) JBRApi.internalServiceBuilder(MethodHandles.lookup()) - .withStatic("update", "updateCustomTitleBar", "sun.awt.windows.WFramePeer", "sun.lwawt.macosx.CPlatformWindow").build(); + CustomTitleBarPeer INSTANCE = JBRApi.internalService(); void update(ComponentPeer peer); } @@ -4044,28 +4044,6 @@ public class Window extends Container implements Accessible { Window updateCustomTitleBarHitTest(boolean allowNativeActions) { if (customTitleBar == null) return null; pendingCustomTitleBarHitTest = allowNativeActions ? CustomTitleBar.HIT_TITLEBAR : CustomTitleBar.HIT_CLIENT; - if (customDecorHitTestSpots != null) { // Compatibility bridge, to be removed with old API - Point p = getMousePosition(true); - if (p == null) return this; - // Perform old-style hit test - int result = CustomWindowDecoration.NO_HIT_SPOT; - for (var spot : customDecorHitTestSpots) { - if (spot.getKey().contains(p.x, p.y)) { - result = spot.getValue(); - break; - } - } - // Convert old hit test value to new one - pendingCustomTitleBarHitTest = switch (result) { - case CustomWindowDecoration.MINIMIZE_BUTTON -> CustomTitleBar.HIT_MINIMIZE_BUTTON; - case CustomWindowDecoration.MAXIMIZE_BUTTON -> CustomTitleBar.HIT_MAXIMIZE_BUTTON; - case CustomWindowDecoration.CLOSE_BUTTON -> CustomTitleBar.HIT_CLOSE_BUTTON; - case CustomWindowDecoration.NO_HIT_SPOT, CustomWindowDecoration.DRAGGABLE_AREA -> CustomTitleBar.HIT_TITLEBAR; - default -> CustomTitleBar.HIT_CLIENT; - }; - // Overwrite hit test value - applyCustomTitleBarHitTest(); - } return this; } @@ -4077,111 +4055,6 @@ public class Window extends Container implements Accessible { customTitleBarHitTest = pendingCustomTitleBarHitTest; } - // *** Following custom decorations code is kept for backward compatibility and will be removed soon. *** - - @Deprecated - private transient volatile boolean hasCustomDecoration; - @Deprecated - private transient volatile List> customDecorHitTestSpots; - @Deprecated - private transient volatile int customDecorTitleBarHeight = -1; // 0 can be a legal value when no title bar is expected - @Deprecated - private static class CustomWindowDecoration { - - CustomWindowDecoration() { CustomTitleBar.assertSupported(); } - - @Native public static final int - NO_HIT_SPOT = 0, - OTHER_HIT_SPOT = 1, - MINIMIZE_BUTTON = 2, - MAXIMIZE_BUTTON = 3, - CLOSE_BUTTON = 4, - MENU_BAR = 5, - DRAGGABLE_AREA = 6; - - void setCustomDecorationEnabled(Window window, boolean enabled) { - window.hasCustomDecoration = enabled; - setTitleBar(window, enabled ? Math.max(window.customDecorTitleBarHeight, 0.01f) : 0); - } - boolean isCustomDecorationEnabled(Window window) { - return window.hasCustomDecoration; - } - - void setCustomDecorationHitTestSpots(Window window, List> spots) { - window.customDecorHitTestSpots = List.copyOf(spots); - } - List> getCustomDecorationHitTestSpots(Window window) { - return window.customDecorHitTestSpots; - } - - void setCustomDecorationTitleBarHeight(Window window, int height) { - window.customDecorTitleBarHeight = height; - setTitleBar(window, window.hasCustomDecoration ? Math.max(height, 0.01f) : 0); - } - int getCustomDecorationTitleBarHeight(Window window) { - return window.customDecorTitleBarHeight; - } - - // Bridge from old to new API - private static void setTitleBar(Window window, float height) { - if (height <= 0.0f) window.setCustomTitleBar(null); - else { - CustomTitleBar t = new CustomTitleBar(); - // Old API accepts title bar height with insets, subtract it for new API. - // We use bottom insets here because top insets may change when toggling custom title bar, they are usually equal. - if (window instanceof Frame f && (f.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0) { - height -= window.getInsets().bottom; - } - t.setHeight(Math.max(height, 0.01f)); - // In old API versions there were no control buttons on Windows. - if (System.getProperty("os.name").toLowerCase().contains("win")) t.putProperty("controls.visible", false); - window.setCustomTitleBar(t); - } - } - } - - private interface WindowMovePeer { - void startMovingWindowTogetherWithMouse(Window window, int mouseButton); - } - - private interface WindowMovePeerX11 extends WindowMovePeer { - WindowMovePeerX11 INSTANCE = (WindowMovePeerX11) JBRApi.internalServiceBuilder(MethodHandles.lookup()) - .withStatic("startMovingWindowTogetherWithMouse", - "startMovingWindowTogetherWithMouse", - "sun.awt.X11.XWindowPeer") - .build(); - } - - private static class WindowMoveService { - WindowMovePeer windowMovePeer; - - WindowMoveService() { - var toolkit = Toolkit.getDefaultToolkit(); - var ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - if (toolkit == null || ge == null) { - throw new JBRApi.ServiceNotAvailableException("Supported only with a Toolkit present"); - } - - if (!objectIsInstanceOf(toolkit, "sun.awt.X11.XToolkit") - || !objectIsInstanceOf(ge, "sun.awt.X11GraphicsEnvironment")) { - throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment"); - } - - // This will throw if the service is not supported by the underlying WM - windowMovePeer = WindowMovePeerX11.INSTANCE; - } - - boolean objectIsInstanceOf(Object o, String className) { - Objects.requireNonNull(o); - return o.getClass().getName().equals(className); - } - - void startMovingTogetherWithMouse(Window window, int mouseButton) { - Objects.requireNonNull(window); - windowMovePeer.startMovingWindowTogetherWithMouse(window, mouseButton); - } - } - // ************************** JBR stuff ******************************* private volatile boolean ignoreMouseEvents; diff --git a/src/java.desktop/share/classes/sun/font/FontAccess.java b/src/java.desktop/share/classes/sun/font/FontAccess.java index 70359e237809..7343917e39ad 100644 --- a/src/java.desktop/share/classes/sun/font/FontAccess.java +++ b/src/java.desktop/share/classes/sun/font/FontAccess.java @@ -47,4 +47,7 @@ public abstract class FontAccess { public abstract void setWithFallback(Font f); public abstract boolean isCreatedFont(Font f); public abstract FontPeer getFontPeer(Font f); + public abstract String[] getFeatures(Font font); + public abstract boolean isComplexRendering(Font font); + public abstract boolean isKerning(Font font); } diff --git a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java index 844c16b1f6f2..8e05fdb9cb51 100644 --- a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java +++ b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java @@ -43,7 +43,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import com.jetbrains.desktop.FontExtensions; +import com.jetbrains.exported.JBRApi; import sun.java2d.Disposer; import sun.java2d.DisposerRecord; @@ -481,7 +481,7 @@ public final class FontDesignMetrics extends FontMetrics { assert (data instanceof String || data instanceof char[]); float width = 0; - if (overrider == null && FontExtensions.isComplexRendering(font) && len > 0) { + if (overrider == null && FontAccess.getFontAccess().isComplexRendering(font) && len > 0) { return textLayoutBounds(data, off, len); } @@ -492,7 +492,7 @@ public final class FontDesignMetrics extends FontMetrics { return new Rectangle2D.Float(0f, -ascent, width, height); } - boolean isKerning = FontExtensions.isKerning(font); + boolean isKerning = FontAccess.getFontAccess().isKerning(font); float consecutiveDoubleCharacterWidth = 0f; char prev = 0; for (int i = off; i < off + len; i++) { @@ -608,7 +608,9 @@ public final class FontDesignMetrics extends FontMetrics { return height; } - private static class Accessor { // used by JBR API + @JBRApi.Service + @JBRApi.Provides("FontMetricsAccessor") + private static final class Accessor { // Keeping metrics instances here prevents them from being garbage collected // and being re-created by FontDesignMetrics.getMetrics method private final Set PINNED_METRICS = new HashSet<>(); @@ -647,7 +649,8 @@ public final class FontDesignMetrics extends FontMetrics { } } - private interface Overrider { // used by JBR API + @JBRApi.Provided("FontMetricsAccessor.Overrider") + private interface Overrider { float charWidth(int codePoint); } } diff --git a/src/java.desktop/share/classes/sun/font/FontUtilities.java b/src/java.desktop/share/classes/sun/font/FontUtilities.java index 1f71d1c26fcd..7e2f3b73ed70 100644 --- a/src/java.desktop/share/classes/sun/font/FontUtilities.java +++ b/src/java.desktop/share/classes/sun/font/FontUtilities.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap; import javax.swing.plaf.FontUIResource; +import com.jetbrains.exported.JBRApi; import sun.awt.OSInfo; import sun.util.logging.PlatformLogger; @@ -126,7 +127,8 @@ public final class FontUtilities { } } - static Dimension getSubpixelResolution() { + @JBRApi.Provides("FontExtensions") + private static Dimension getSubpixelResolution() { return subpixelResolution; } diff --git a/src/java.desktop/share/classes/sun/font/GlyphLayout.java b/src/java.desktop/share/classes/sun/font/GlyphLayout.java index 87094b617074..9e0e8565847e 100644 --- a/src/java.desktop/share/classes/sun/font/GlyphLayout.java +++ b/src/java.desktop/share/classes/sun/font/GlyphLayout.java @@ -68,8 +68,6 @@ package sun.font; -import com.jetbrains.desktop.FontExtensions; - import java.lang.ref.SoftReference; import java.awt.Font; import java.awt.font.FontRenderContext; @@ -176,7 +174,7 @@ public final class GlyphLayout { * leave pt and the gvdata unchanged. */ public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int slot, int slotShift, int baseIndex, TextRecord text, - boolean ltrDirection, Map features, Point2D.Float pt, GVData data); + boolean ltrDirection, String[] features, Point2D.Float pt, GVData data); } /** @@ -435,7 +433,7 @@ public final class GlyphLayout { EngineRecord er = _erecords.get(ix); for (;;) { try { - er.layout(ltrDirection, FontExtensions.getFeatures(font)); + er.layout(ltrDirection, FontAccess.getFontAccess().getFeatures(font)); break; } catch (IndexOutOfBoundsException e) { @@ -643,7 +641,7 @@ public final class GlyphLayout { this.engine = _lef.getEngine(key); // flags? } - void layout(boolean ltrDirection, Map features) { + void layout(boolean ltrDirection, String[] features) { _textRecord.start = start; _textRecord.limit = limit; engine.layout(_sd, _mat, ptSize, slot, slotShift, start - _offset, _textRecord, diff --git a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java index 65bcab0d17ef..2c5d1a491d90 100644 --- a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java +++ b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java @@ -30,7 +30,6 @@ package sun.font; -import com.jetbrains.desktop.FontExtensions; import sun.font.GlyphLayout.*; import sun.java2d.Disposer; import sun.java2d.DisposerRecord; @@ -39,7 +38,6 @@ import java.awt.geom.Point2D; import java.lang.foreign.MemorySegment; import java.lang.ref.SoftReference; import java.util.Arrays; -import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.WeakHashMap; @@ -182,7 +180,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory } public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int slot, int slotShift, - int baseIndex, TextRecord tr, boolean ltrDirection, Map features, + int baseIndex, TextRecord tr, boolean ltrDirection, String[] features, Point2D.Float pt, GVData data) { Font2D font = key.font(); @@ -193,7 +191,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory HBShaper.shape(font, strike, ptSize, mat, face, tr.text, data, key.script(), tr.start, tr.limit, baseIndex, pt, - ltrDirection, FontExtensions.featuresToString(features), + ltrDirection, String.join(";", features), slot, slotShift); } } else { @@ -202,7 +200,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory SunLayoutEngine.shape(font, strike, ptSize, mat, pFace, tr.text, data, key.script(), tr.start, tr.limit, baseIndex, pt, - ltrDirection, FontExtensions.featuresToString(features), + ltrDirection, String.join(";", features), slot, slotShift); } } diff --git a/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java b/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java index e40082d97b3e..c5dc69873758 100644 --- a/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java +++ b/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java @@ -26,6 +26,7 @@ package sun.swing; +import com.jetbrains.exported.JBRApi; import jdk.internal.misc.InnocuousThread; import sun.awt.AWTThreading; @@ -72,7 +73,8 @@ public class AccessibleAnnouncer { * @param str string for announcing * @param priority priority for announcing */ - public static void announce(Accessible a, final String str, final int priority) throws Exception { + @JBRApi.Provides("AccessibleAnnouncer") + public static void announce(Accessible a, final String str, final int priority) { if (str == null || priority != ANNOUNCE_WITHOUT_INTERRUPTING_CURRENT_OUTPUT && priority != ANNOUNCE_WITH_INTERRUPTING_CURRENT_OUTPUT) { diff --git a/src/java.desktop/unix/classes/sun/awt/WindowMoveService.java b/src/java.desktop/unix/classes/sun/awt/WindowMoveService.java new file mode 100644 index 000000000000..dd4574a2d820 --- /dev/null +++ b/src/java.desktop/unix/classes/sun/awt/WindowMoveService.java @@ -0,0 +1,28 @@ +package sun.awt; + +import com.jetbrains.exported.JBRApi; +import sun.awt.X11.WindowMoveServiceX11; + +import java.awt.*; + +@JBRApi.Service +@JBRApi.Provides("WindowMove") +public interface WindowMoveService { + + private static WindowMoveService create() { + JBRApi.ServiceNotAvailableException exception; + try { + return new WindowMoveServiceX11(); + } catch (JBRApi.ServiceNotAvailableException e) { + exception = e; + } +// try { +// return new WindowMoveServiceWL(); +// } catch (JBRApi.ServiceNotAvailableException e) { +// exception.addSuppressed(e); +// } + throw exception; + } + + void startMovingTogetherWithMouse(Window window, int mouseButton); +} diff --git a/src/java.desktop/unix/classes/sun/awt/X11/WindowMoveServiceX11.java b/src/java.desktop/unix/classes/sun/awt/X11/WindowMoveServiceX11.java new file mode 100644 index 000000000000..8c6b306509d8 --- /dev/null +++ b/src/java.desktop/unix/classes/sun/awt/X11/WindowMoveServiceX11.java @@ -0,0 +1,39 @@ +package sun.awt.X11; + +import com.jetbrains.exported.JBRApi; +import sun.awt.AWTAccessor; +import sun.awt.WindowMoveService; + +import java.awt.*; +import java.awt.peer.ComponentPeer; +import java.util.Objects; + +public class WindowMoveServiceX11 implements WindowMoveService { + + public WindowMoveServiceX11() { + final var toolkit = Toolkit.getDefaultToolkit(); + final var ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + if (toolkit == null || ge == null + || !toolkit.getClass().getName().equals("sun.awt.X11.XToolkit") + || !ge.getClass().getName().equals("sun.awt.X11GraphicsEnvironment")) { + throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment"); + } + + if (!((XToolkit)Toolkit.getDefaultToolkit()).isWindowMoveSupported()) { + throw new JBRApi.ServiceNotAvailableException("Window manager does not support _NET_WM_MOVE_RESIZE"); + } + } + + @Override + public void startMovingTogetherWithMouse(Window window, int mouseButton) { + Objects.requireNonNull(window); + + final AWTAccessor.ComponentAccessor acc = AWTAccessor.getComponentAccessor(); + ComponentPeer peer = acc.getPeer(window); + if (peer instanceof XWindowPeer xWindowPeer) { + xWindowPeer.startMovingTogetherWithMouse(mouseButton); + } else { + throw new IllegalArgumentException("AWT window must have XWindowPeer as its peer"); + } + } +} diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java b/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java index 2a27c10fd37e..b17317642288 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java @@ -37,7 +37,6 @@ import java.util.Set; import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; -import com.jetbrains.internal.JBRApi; import sun.awt.AWTAccessor; import sun.awt.AWTAccessor.ComponentAccessor; import sun.awt.DisplayChangedListener; @@ -2573,42 +2572,4 @@ class XWindowPeer extends XPanelPeer implements WindowPeer, setGrab(false); XWM.getWM().startMovingWindowTogetherWithMouse(getParentTopLevel().getWindow(), getLastButtonPressAbsLocation(), mouseButton); } - - private static void startMovingWindowTogetherWithMouse(Window window, int mouseButton) { - final AWTAccessor.ComponentAccessor acc = AWTAccessor.getComponentAccessor(); - ComponentPeer peer = acc.getPeer(window); - if (peer instanceof XWindowPeer xWindowPeer) { - xWindowPeer.startMovingTogetherWithMouse(mouseButton); - } else { - throw new IllegalArgumentException("AWT window must have XWindowPeer as its peer"); - } - } - - private static class WindowMoveService { - WindowMoveService() { - final var toolkit = Toolkit.getDefaultToolkit(); - final var ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - if (toolkit == null || ge == null - || !toolkit.getClass().getName().equals("sun.awt.X11.XToolkit") - || !ge.getClass().getName().equals("sun.awt.X11GraphicsEnvironment")) { - throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment"); - } - - if (!((XToolkit)Toolkit.getDefaultToolkit()).isWindowMoveSupported()) { - throw new JBRApi.ServiceNotAvailableException("Window manager does not support _NET_WM_MOVE_RESIZE"); - } - } - - void startMovingTogetherWithMouse(Window window, int mouseButton) { - Objects.requireNonNull(window); - - final AWTAccessor.ComponentAccessor acc = AWTAccessor.getComponentAccessor(); - ComponentPeer peer = acc.getPeer(window); - if (peer instanceof XWindowPeer xWindowPeer) { - xWindowPeer.startMovingTogetherWithMouse(mouseButton); - } else { - throw new IllegalArgumentException("AWT window must have XWindowPeer as its peer"); - } - } - } } diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java index 57dc477d5811..d310b1829e77 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java @@ -34,6 +34,7 @@ import java.awt.Rectangle; import java.awt.peer.ComponentPeer; import java.awt.peer.FramePeer; +import com.jetbrains.exported.JBRApi; import sun.awt.AWTAccessor; import sun.awt.im.InputMethodManager; @@ -260,7 +261,7 @@ class WFramePeer extends WWindowPeer implements FramePeer { private native void synthesizeWmActivate(boolean activate); - // JBR API internals + @JBRApi.Provides("java.awt.Window.CustomTitleBarPeer#update") private static void updateCustomTitleBar(ComponentPeer peer) { // In native code AwtDialog is actually a descendant of AwtFrame, // so we don't distinguish between WFramePeer and WDialogPeer here, diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java index 0bfa0473fe32..06a81de3790e 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java @@ -48,7 +48,6 @@ import java.awt.event.FocusEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.awt.geom.AffineTransform; -import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.DataBufferInt; @@ -61,6 +60,7 @@ import java.util.List; import javax.swing.JRootPane; import javax.swing.RootPaneContainer; +import com.jetbrains.exported.JBRApi; import sun.awt.AWTAccessor; import sun.awt.AppContext; import sun.awt.DisplayChangedListener; @@ -832,7 +832,7 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer, } } - // JBR API internals + @JBRApi.Provides("RoundedCornersManager") private static void setRoundedCorners(Window window, Object params) { Object peer = AWTAccessor.getComponentAccessor().getPeer(window); if (peer instanceof WWindowPeer) { diff --git a/src/jetbrains.api/src/com/jetbrains/AccessibleAnnouncer.java b/src/jetbrains.api/src/com/jetbrains/AccessibleAnnouncer.java deleted file mode 100644 index 19afe948c270..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/AccessibleAnnouncer.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2000-2022 JetBrains s.r.o. - * 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 com.jetbrains; - -import javax.accessibility.Accessible; - -/** - * This interface provides the ability to speak a given string using screen readers. - * - */ -public interface AccessibleAnnouncer { - - /*CONST sun.swing.AccessibleAnnouncer.ANNOUNCE_**/ - - /** - * This method makes an announcement with the specified priority from an accessible to which the announcing relates - * - * @param a an accessible to which the announcing relates - * @param str string for announcing - * @param priority priority for announcing - */ - void announce(Accessible a, final String str, final int priority); -} diff --git a/src/jetbrains.api/src/com/jetbrains/CustomWindowDecoration.java b/src/jetbrains.api/src/com/jetbrains/CustomWindowDecoration.java deleted file mode 100644 index 6ec882571c09..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/CustomWindowDecoration.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2000-2022 JetBrains s.r.o. - * 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 com.jetbrains; - -import java.awt.*; -import java.util.List; -import java.util.Map; - -/** - * Custom window decoration allows merging of window content with native title bar, - * which is usually done by treating title bar as part of client area, but with some - * special behavior like dragging or maximizing on double click. - * @implNote Behavior is platform-dependent, only macOS and Windows are supported. - */ -@Deprecated(forRemoval=true) -public interface CustomWindowDecoration { - - /*CONST java.awt.Window.*_HIT_SPOT*/ - /*CONST java.awt.Window.*_BUTTON*/ - /*CONST java.awt.Window.MENU_BAR*/ - /*CONST java.awt.Window.DRAGGABLE_AREA*/ - - /** - * Turn custom decoration on or off, {@link #setCustomDecorationTitleBarHeight(Window, int)} - * must be called to configure title bar height. - */ - void setCustomDecorationEnabled(Window window, boolean enabled); - /** - * @see #setCustomDecorationEnabled(Window, boolean) - */ - boolean isCustomDecorationEnabled(Window window); - - /** - * Set list of hit test spots for a custom decoration. - * Hit test spot is a special area inside custom-decorated title bar. - * Each hit spot type has its own (probably platform-specific) behavior: - *
    - *
  • {@link #NO_HIT_SPOT} - default title bar area, can be dragged to move a window, maximizes on double-click
  • - *
  • {@link #OTHER_HIT_SPOT} - generic hit spot, window cannot be moved with drag or maximized on double-click
  • - *
  • {@link #MINIMIZE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip on Windows when hovered
  • - *
  • {@link #MAXIMIZE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip / snap layout menu on Windows when hovered
  • - *
  • {@link #CLOSE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip on Windows when hovered
  • - *
  • {@link #MENU_BAR} - currently no different from {@link #OTHER_HIT_SPOT}
  • - *
  • {@link #DRAGGABLE_AREA} - special type, moves window when dragged, but doesn't maximize on double-click
  • - *
- * @param spots pairs of hit spot shapes with corresponding types. - * @implNote Hit spots are tested in sequence, so in case of overlapping areas, first found wins. - */ - void setCustomDecorationHitTestSpots(Window window, List> spots); - /** - * @see #setCustomDecorationHitTestSpots(Window, List) - */ - List> getCustomDecorationHitTestSpots(Window window); - - /** - * Set height of custom window decoration, won't take effect until custom decoration - * is enabled via {@link #setCustomDecorationEnabled(Window, boolean)}. - */ - void setCustomDecorationTitleBarHeight(Window window, int height); - /** - * @see #setCustomDecorationTitleBarHeight(Window, int) - */ - int getCustomDecorationTitleBarHeight(Window window); -} diff --git a/src/jetbrains.api/src/com/jetbrains/DesktopActions.java b/src/jetbrains.api/src/com/jetbrains/DesktopActions.java deleted file mode 100644 index f2efcb477a26..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/DesktopActions.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2000-2022 JetBrains s.r.o. - * 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 com.jetbrains; - -import java.io.File; -import java.io.IOException; -import java.net.URI; - -public interface DesktopActions { - - void setHandler(Handler handler); - - interface Handler { - default void open(File file) throws IOException { throw new UnsupportedOperationException(); } - default void edit(File file) throws IOException { throw new UnsupportedOperationException(); } - default void print(File file) throws IOException { throw new UnsupportedOperationException(); } - default void mail(URI mailtoURL) throws IOException { throw new UnsupportedOperationException(); } - default void browse(URI uri) throws IOException { throw new UnsupportedOperationException(); } - } - -} diff --git a/src/jetbrains.api/src/com/jetbrains/ExtendedGlyphCache.java b/src/jetbrains.api/src/com/jetbrains/ExtendedGlyphCache.java deleted file mode 100644 index 32f000d43d71..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/ExtendedGlyphCache.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.*; - -@Deprecated(forRemoval=true) -public interface ExtendedGlyphCache { - Dimension getSubpixelResolution(); -} diff --git a/src/jetbrains.api/src/com/jetbrains/FontExtensions.java b/src/jetbrains.api/src/com/jetbrains/FontExtensions.java deleted file mode 100644 index 9c4a6cc679c6..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/FontExtensions.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.*; -import java.io.Serial; -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; - -public interface FontExtensions { - public static final int FEATURE_ON = 1; - public static final int FEATURE_OFF = 0; - - /** - * The list of all supported features. For feature's description look at - * documentation
- * The following features: KERN, LIGA, CALT are missing intentionally. These features will be added automatically - * by adding {@link java.awt.font.TextAttribute} to {@link java.awt.Font}: - *
    - *
  • Attribute {@link java.awt.font.TextAttribute#KERNING} manages KERN feature
  • - *
  • Attribute {@link java.awt.font.TextAttribute#LIGATURES} manages LIGA and CALT features
  • - *
- */ - enum FeatureTag { - AALT, ABVF, ABVM, ABVS, AFRC, AKHN, BLWF, BLWM, BLWS, CASE, CCMP, CFAR, CHWS, CJCT, CLIG, CPCT, CPSP, CSWH, CURS, - CV01, CV02, CV03, CV04, CV05, CV06, CV07, CV08, CV09, CV10, CV11, CV12, CV13, CV14, CV15, CV16, CV17, CV18, CV19, - CV20, CV21, CV22, CV23, CV24, CV25, CV26, CV27, CV28, CV29, CV30, CV31, CV32, CV33, CV34, CV35, CV36, CV37, CV38, - CV39, CV40, CV41, CV42, CV43, CV44, CV45, CV46, CV47, CV48, CV49, CV50, CV51, CV52, CV53, CV54, CV55, CV56, CV57, - CV58, CV59, CV60, CV61, CV62, CV63, CV64, CV65, CV66, CV67, CV68, CV69, CV70, CV71, CV72, CV73, CV74, CV75, CV76, - CV77, CV78, CV79, CV80, CV81, CV82, CV83, CV84, CV85, CV86, CV87, CV88, CV89, CV90, CV91, CV92, CV93, CV94, CV95, - CV96, CV97, CV98, CV99, C2PC, C2SC, DIST, DLIG, DNOM, DTLS, EXPT, FALT, FIN2, FIN3, FINA, FLAC, FRAC, FWID, HALF, - HALN, HALT, HIST, HKNA, HLIG, HNGL, HOJO, HWID, INIT, ISOL, ITAL, JALT, JP78, JP83, JP90, JP04, LFBD, LJMO, LNUM, - LOCL, LTRA, LTRM, MARK, MED2, MEDI, MGRK, MKMK, MSET, NALT, NLCK, NUKT, NUMR, ONUM, OPBD, ORDN, ORNM, PALT, PCAP, - PKNA, PNUM, PREF, PRES, PSTF, PSTS, PWID, QWID, RAND, RCLT, RKRF, RLIG, RPHF, RTBD, RTLA, RTLM, RUBY, RVRN, SALT, - SINF, SIZE, SMCP, SMPL, SS01, SS02, SS03, SS04, SS05, SS06, SS07, SS08, SS09, SS10, SS11, SS12, SS13, SS14, SS15, - SS16, SS17, SS18, SS19, SS20, SSTY, STCH, SUBS, SUPS, SWSH, TITL, TJMO, TNAM, TNUM, TRAD, TWID, UNIC, VALT, VATU, - VCHW, VERT, VHAL, VJMO, VKNA, VKRN, VPAL, VRT2, VRTR, ZERO; - - public String getName() { - return toString().toLowerCase(); - } - - public static Optional getFeatureTag(String str) { - try { - return Optional.of(FeatureTag.valueOf(str.toUpperCase())); - } catch (IllegalArgumentException ignored) { - return Optional.empty(); - } - } - } - - final class Features extends TreeMap { - @Serial - private static final long serialVersionUID = 1L; - - public Features(Map map) { - super(map); - } - - public Features(FontExtensions.FeatureTag... features) { - Arrays.stream(features).forEach(tag -> put(tag, FontExtensions.FEATURE_ON)); - } - - private TreeMap getAsTreeMap() { - TreeMap res = new TreeMap<>(); - forEach((tag, value) -> res.put(tag.getName(), value)); - return res; - } - } - - /** - * This method derives a new {@link java.awt.Font} object with certain set of {@link FeatureTag} - * and correspoinding values - * - * @param font basic font - * @param features set of OpenType's features wrapped inside {@link Features} - */ - Font deriveFontWithFeatures(Font font, Features features); - - Dimension getSubpixelResolution(); -} \ No newline at end of file diff --git a/src/jetbrains.api/src/com/jetbrains/FontMetricsAccessor.java b/src/jetbrains.api/src/com/jetbrains/FontMetricsAccessor.java deleted file mode 100644 index 90d2a3c7a486..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/FontMetricsAccessor.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.*; -import java.awt.font.FontRenderContext; -import java.awt.image.BufferedImage; - -/** - * Provides convenience methods to access {@link FontMetrics} instances, and obtain character advances from them without - * rounding. Also provides an (unsafe) way to override character advances in those instances with arbitrary specified - * values. - */ -public interface FontMetricsAccessor { - /** - * Returns a {@link FontMetrics} instance for the given {@link Font} and {@link FontRenderContext}. This is supposed - * to be the same instance as returned by the public API methods ({@link Graphics#getFontMetrics()}, - * {@link Graphics#getFontMetrics(Font)} and {@link Component#getFontMetrics(Font)}) in the same context. - */ - FontMetrics getMetrics(Font font, FontRenderContext context); - - /** - * Returns not rounded value for the character's advance. It's not accessible directly via public - * {@link FontMetrics} API, one can only extract it from one of the {@code getStringBounds} methods' output. - */ - float codePointWidth(FontMetrics metrics, int codePoint); - - /** - * Allows to override advance values returned by the specified {@link FontMetrics} instance. It's not generally - * guaranteed the invocation of this method actually has the desired effect. One can verify whether it's the case - * using {@link #hasOverride(FontMetrics)} method. - *

- * A subsequent invocation of this method will override any previous invocations. Passing {@code null} as the - * {@code overrider} value will remove any override, if it was set up previously. - *

- * While this method operates on a specific {@link FontMetrics} instance, it's expected that overriding will have - * effect regardless of the method font metrics are accessed, for all future character advance requests. This is - * feasible, as JDK implementation generally uses the same cached {@link FontMetrics} instance in identical - * contexts. - *

- * The method doesn't provides any concurrency guarantees, i.e. the override isn't guaranteed to be immediately - * visible for font metrics readers in other threads. - *

- * WARNING. This method can break the consistency of a UI application, as previously calculated/returned advance - * values can already be used/cached by some UI components. It's the calling code's responsibility to remediate such - * consequences (e.g. re-validating all components which use the relevant font might be required). - */ - void setOverride(FontMetrics metrics, Overrider overrider); - - /** - * Tells whether character advances returned by the specified {@link FontMetrics} instance are overridden using the - * previous {@link #setOverride(FontMetrics, Overrider)} call. - */ - boolean hasOverride(FontMetrics metrics); - - /** - * Removes all overrides set previously by {@link #setOverride(FontMetrics, Overrider)} invocations. - */ - void removeAllOverrides(); - - /** - * @see #setOverride(FontMetrics, Overrider) - */ - interface Overrider { - /** - * Returning {@code NaN} means the default (not overridden) value should be used. - */ - float charWidth(int codePoint); - } - - class __Fallback implements FontMetricsAccessor { - private final Graphics2D g; - - __Fallback() { - g = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB).createGraphics(); - } - - @Override - public FontMetrics getMetrics(Font font, FontRenderContext context) { - synchronized (g) { - g.setTransform(context.getTransform()); - g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, context.getAntiAliasingHint()); - g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, context.getFractionalMetricsHint()); - return g.getFontMetrics(font); - } - } - - @Override - public float codePointWidth(FontMetrics metrics, int codePoint) { - String s = new String(new int[]{codePoint}, 0, 1); - return (float) metrics.getFont().getStringBounds(s, metrics.getFontRenderContext()).getWidth(); - } - - @Override - public void setOverride(FontMetrics metrics, Overrider overrider) {} - - @Override - public boolean hasOverride(FontMetrics metrics) { - return false; - } - - @Override - public void removeAllOverrides() {} - } -} diff --git a/src/jetbrains.api/src/com/jetbrains/FontOpenTypeFeatures.java b/src/jetbrains.api/src/com/jetbrains/FontOpenTypeFeatures.java deleted file mode 100644 index 01559a4dec81..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/FontOpenTypeFeatures.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2024 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.*; -import java.util.Set; - -@Deprecated(forRemoval=true) -public interface FontOpenTypeFeatures { - /** - * This method returns set of OpenType's features converted to String supported by current font - * - * @param font basic font - */ - Set getAvailableFeatures(Font font); -} diff --git a/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java b/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java deleted file mode 100644 index 902b0421f0f2..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * 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 com.jetbrains; - -import java.awt.*; -import java.awt.geom.Rectangle2D; - -public interface GraphicsUtils { - Graphics2D createConstrainableGraphics(Graphics2D graphics2D, - ConstrainableGraphics2D constrainable); - - public interface ConstrainableGraphics2D { - Object getDestination(); - void constrain(Rectangle2D region); - void constrain(int x, int y, int w, int h); - } -} diff --git a/src/jetbrains.api/src/com/jetbrains/JBR.java b/src/jetbrains.api/src/com/jetbrains/JBR.java deleted file mode 100644 index bbfc953a5b44..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/JBR.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains; - -import java.lang.invoke.MethodHandles; -import java.lang.reflect.InvocationTargetException; - -/** - * This class is an entry point into JBR API. - * JBR API is a collection of services, classes, interfaces, etc., - * which require tight interaction with JRE and therefore are implemented inside JBR. - *

JBR API consists of two parts:
- *
    - *
  • Client side - {@code jetbrains.api} module, mostly containing interfaces
  • - *
  • JBR side - actual implementation code inside JBR
  • - *
- * Client and JBR side are linked dynamically at runtime and do not have to be of the same version. - * In some cases (e.g. running on different JRE or old JBR) system will not be able to find - * implementation for some services, so you'll need a fallback behavior for that case. - *

Simple usage example:

- *
{@code
- * if (JBR.isSomeServiceSupported()) {
- *     JBR.getSomeService().doSomething();
- * } else {
- *     planB();
- * }
- * }
- * @implNote JBR API is initialized on first access to this class (in static initializer). - * Actual implementation is linked on demand, when corresponding service is requested by client. - */ -public class JBR { - - private static final ServiceApi api; - private static final Exception bootstrapException; - static { - ServiceApi a = null; - Exception exception = null; - try { - a = (ServiceApi) Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap") - .getMethod("bootstrap", MethodHandles.Lookup.class) - .invoke(null, MethodHandles.lookup()); - } catch (InvocationTargetException e) { - Throwable t = e.getCause(); - if (t instanceof Error error) throw error; - else throw new Error(t); - } catch (IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { - exception = e; - } - api = a; - bootstrapException = exception; - } - - private JBR() {} - - private static T getService(Class interFace, FallbackSupplier fallback) { - T service = getService(interFace); - try { - return service != null ? service : fallback != null ? fallback.get() : null; - } catch (Throwable ignore) { - return null; - } - } - - static T getService(Class interFace) { - return api == null ? null : api.getService(interFace); - } - - /** - * @return true when running on JBR which implements JBR API - */ - public static boolean isAvailable() { - return api != null; - } - - /** - * @return JBR API version in form {@code JBR.MAJOR.MINOR.PATCH} - * @implNote This is an API version, which comes with client application, - * it has nothing to do with JRE it runs on. - */ - public static String getApiVersion() { - return "/*API_VERSION*/"; - } - - /** - * Internal API interface, contains most basic methods for communication between client and JBR. - */ - private interface ServiceApi { - - T getService(Class interFace); - } - - @FunctionalInterface - private interface FallbackSupplier { - T get() throws Throwable; - } - - // ========================== Generated metadata ========================== - - /** - * Generated client-side metadata, needed by JBR when linking the implementation. - */ - private static final class Metadata { - private static final String[] KNOWN_SERVICES = {/*KNOWN_SERVICES*/}; - private static final String[] KNOWN_PROXIES = {/*KNOWN_PROXIES*/}; - } - - // ======================= Generated static methods ======================= - - /*GENERATED_METHODS*/ -} diff --git a/src/jetbrains.api/src/com/jetbrains/JBRFileDialog.java b/src/jetbrains.api/src/com/jetbrains/JBRFileDialog.java deleted file mode 100644 index e64cac60ed29..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/JBRFileDialog.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2000-2024 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.*; - -/** - * Extensions to the AWT {@code FileDialog} that allow clients fully use a native file chooser - * on supported platforms (currently macOS and Windows; the latter requires setting - * {@code sun.awt.windows.useCommonItemDialog} property to {@code true}). - */ -public interface JBRFileDialog { - - /*CONST com.jetbrains.desktop.JBRFileDialog.*_HINT*/ - - static JBRFileDialog get(FileDialog dialog) { - if (JBRFileDialogService.INSTANCE == null) return null; - else return JBRFileDialogService.INSTANCE.getFileDialog(dialog); - } - - /** - * Set file dialog hints: - *
    - *
  • SELECT_FILES_HINT, SELECT_DIRECTORIES_HINT - whether to select files, directories, or both; - * if neither of the two is set, the behavior is platform-specific
  • - *
  • CREATE_DIRECTORIES_HINT - whether to allow creating directories or not (macOS)
  • - *
- */ - void setHints(int hints); - - /** - * @see #setHints(int) - */ - int getHints(); - - /*CONST com.jetbrains.desktop.JBRFileDialog.*_KEY*/ - - /** - * Change text of UI elements (Windows). - * Supported keys: - *
    - *
  • OPEN_FILE_BUTTON_KEY - "open" button when a file is selected in the list
  • - *
  • OPEN_DIRECTORY_BUTTON_KEY - "open" button when a directory is selected in the list
  • - *
  • ALL_FILES_COMBO_KEY - "all files" item in the file filter combo box
  • - *
- */ - void setLocalizationString(String key, String text); - - /** @deprecated use {@link #setLocalizationString} */ - @Deprecated(forRemoval = true) - void setLocalizationStrings(String openButtonText, String selectFolderButtonText); - - /** - * Set file filter - a set of file extensions for files to be visible (Windows) - * or not greyed out (macOS), and a name for the file filter combo box (Windows). - */ - void setFileFilterExtensions(String fileFilterDescription, String[] fileFilterExtensions); -} - -interface JBRFileDialogService { - JBRFileDialogService INSTANCE = JBR.getService(JBRFileDialogService.class); - JBRFileDialog getFileDialog(FileDialog dialog); -} diff --git a/src/jetbrains.api/src/com/jetbrains/Jstack.java b/src/jetbrains.api/src/com/jetbrains/Jstack.java deleted file mode 100644 index 702e8a808e9f..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/Jstack.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * 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 com.jetbrains; - -import java.util.function.Supplier; - -public interface Jstack { - /** - * Specifies a supplier of additional information to be included into - * the output of {@code jstack}. The String supplied will be included - * as-is with no header surrounded only with line breaks. - * - * {@code infoSupplier} will be invoked on an unspecified thread that - * must not be left blocked for a long time. - * - * Only one supplier is allowed, so subsequent calls to - * {@code includeInfoFrom} will overwrite the previously specified supplier. - * - * @param infoSupplier a supplier of {@code String} values to be - * included into jstack's output. If {@code null}, - * then the previously registered supplier is removed - * (if any) and no extra info will be included. - */ - void includeInfoFrom(Supplier infoSupplier); -} diff --git a/src/jetbrains.api/src/com/jetbrains/ProjectorUtils.java b/src/jetbrains.api/src/com/jetbrains/ProjectorUtils.java deleted file mode 100644 index 2cddfac79b0b..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/ProjectorUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2022 JetBrains s.r.o. - * 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 com.jetbrains; - -import java.awt.*; -import java.util.function.Supplier; - -public interface ProjectorUtils { - void setLocalGraphicsEnvironmentProvider(Supplier geProvider); - void overrideGraphicsEnvironment(GraphicsEnvironment overriddenGE); -} diff --git a/src/jetbrains.api/src/com/jetbrains/RoundedCornersManager.java b/src/jetbrains.api/src/com/jetbrains/RoundedCornersManager.java deleted file mode 100644 index 4810631e9a8b..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/RoundedCornersManager.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.Window; - -/** - * This manager allows decorate awt Window with rounded corners. - * Appearance depends from operating system. - */ -public interface RoundedCornersManager { - /** - * @param params for macOS is Float object with radius or - * Array with {Float for radius, Integer for border width, java.awt.Color for border color}. - * - * @param params for Windows 11 is String with values: - * "default" - let the system decide whether or not to round window corners, - * "none" - never round window corners, - * "full" - round the corners if appropriate, - * "small" - round the corners if appropriate, with a small radius. - */ - void setRoundedCorners(Window window, Object params); -} diff --git a/src/jetbrains.api/src/com/jetbrains/WindowDecorations.java b/src/jetbrains.api/src/com/jetbrains/WindowDecorations.java deleted file mode 100644 index 06c523f3082c..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/WindowDecorations.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.*; -import java.util.Map; - -/** - * Window decorations consist of title bar, window controls and border. - * @see WindowDecorations.CustomTitleBar - */ -public interface WindowDecorations { - - /** - * If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the - * top of the frame with window controls painted over the client area. - * {@code customTitleBar=null} resets to the default appearance with system-provided title bar. - * @see CustomTitleBar - * @see #createCustomTitleBar() - */ - void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar); - - /** - * If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the - * top of the dialog with window controls painted over the client area. - * {@code customTitleBar=null} resets to the default appearance with system-provided title bar. - * @see CustomTitleBar - * @see #createCustomTitleBar() - */ - void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar); - - /** - * You must {@linkplain CustomTitleBar#setHeight(float) set title bar height} before adding it to a window. - * @see CustomTitleBar - * @see #setCustomTitleBar(Frame, CustomTitleBar) - * @see #setCustomTitleBar(Dialog, CustomTitleBar) - */ - CustomTitleBar createCustomTitleBar(); - - /** - * Custom title bar allows merging of window content with native title bar, - * which is done by treating title bar as part of client area, but with some - * special behavior like dragging or maximizing on double click. - * Custom title bar has {@linkplain CustomTitleBar#getHeight() height} and controls. - * @implNote Behavior is platform-dependent, only macOS and Windows are supported. - * @see #setCustomTitleBar(Frame, CustomTitleBar) - */ - interface CustomTitleBar { - - /** - * @return title bar height, measured in pixels from the top of client area, i.e. excluding top frame border. - */ - float getHeight(); - - /** - * @param height title bar height, measured in pixels from the top of client area, - * i.e. excluding top frame border. Must be > 0. - */ - void setHeight(float height); - - /** - * @see #putProperty(String, Object) - */ - Map getProperties(); - - /** - * @see #putProperty(String, Object) - */ - void putProperties(Map m); - - /** - * Windows & macOS properties: - *
    - *
  • {@code controls.visible} : {@link Boolean} - whether title bar controls - * (minimize/maximize/close buttons) are visible, default = true.
  • - *
- * Windows properties: - *
    - *
  • {@code controls.width} : {@link Number} - width of block of buttons (not individual buttons). - * Note that dialogs have only one button, while frames usually have 3 of them.
  • - *
  • {@code controls.dark} : {@link Boolean} - whether to use dark or light color theme - * (light or dark icons respectively).
  • - *
  • {@code controls..} : {@link Color} - precise control over button colors, - * where {@code } is one of: - *
    • {@code foreground}
    • {@code background}
    - * and {@code } is one of: - *
      - *
    • {@code normal}
    • - *
    • {@code hovered}
    • - *
    • {@code pressed}
    • - *
    • {@code disabled}
    • - *
    • {@code inactive}
    • - *
    - *
- */ - void putProperty(String key, Object value); - - /** - * @return space occupied by title bar controls on the left (px) - */ - float getLeftInset(); - /** - * @return space occupied by title bar controls on the right (px) - */ - float getRightInset(); - - /** - * By default, any component which has no cursor or mouse event listeners set is considered transparent for - * native title bar actions. That is, dragging simple JPanel in title bar area will drag the - * window, but dragging a JButton will not. Adding mouse listener to a component will prevent any native actions - * inside bounds of that component. - *

- * This method gives you precise control of whether to allow native title bar actions or not. - *

    - *
  • {@code client=true} means that mouse is currently over a client area. Native title bar behavior is disabled.
  • - *
  • {@code client=false} means that mouse is currently over a non-client area. Native title bar behavior is enabled.
  • - *
- * Intended usage: - *
    - *
  • This method must be called in response to all {@linkplain java.awt.event.MouseEvent mouse events} - * except {@link java.awt.event.MouseEvent#MOUSE_EXITED} and {@link java.awt.event.MouseEvent#MOUSE_WHEEL}.
  • - *
  • This method is called per-event, i.e. when component has multiple listeners, you only need to call it once.
  • - *
  • If this method hadn't been called, title bar behavior is reverted back to default upon processing the event.
  • - *
- * Note that hit test value is relevant only for title bar area, e.g. calling - * {@code forceHitTest(false)} will not make window draggable via non-title bar area. - * - *

Example:

- * Suppose you have a {@code JPanel} in the title bar area. You want it to respond to right-click for - * some popup menu, but also retain native drag and double-click behavior. - *
-         *     CustomTitleBar titlebar = ...;
-         *     JPanel panel = ...;
-         *     MouseAdapter adapter = new MouseAdapter() {
-         *         private void hit() { titlebar.forceHitTest(false); }
-         *         public void mouseClicked(MouseEvent e) {
-         *             hit();
-         *             if (e.getButton() == MouseEvent.BUTTON3) ...;
-         *         }
-         *         public void mousePressed(MouseEvent e) { hit(); }
-         *         public void mouseReleased(MouseEvent e) { hit(); }
-         *         public void mouseEntered(MouseEvent e) { hit(); }
-         *         public void mouseDragged(MouseEvent e) { hit(); }
-         *         public void mouseMoved(MouseEvent e) { hit(); }
-         *     };
-         *     panel.addMouseListener(adapter);
-         *     panel.addMouseMotionListener(adapter);
-         * 
- */ - void forceHitTest(boolean client); - - Window getContainingWindow(); - } -} diff --git a/src/jetbrains.api/src/com/jetbrains/WindowMove.java b/src/jetbrains.api/src/com/jetbrains/WindowMove.java deleted file mode 100644 index 60d80c80c4db..000000000000 --- a/src/jetbrains.api/src/com/jetbrains/WindowMove.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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.jetbrains; - -import java.awt.Window; - -public interface WindowMove { - /** - * Starts moving the top-level parent window of the given window together with the mouse pointer. - * The intended use is to facilitate the implementation of window management similar to the way - * it is done natively on the platform. - * - * Preconditions for calling this method: - *
    - *
  • WM supports _NET_WM_MOVE_RESIZE (this is checked automatically when an implementation - * of this interface is obtained).
  • - *
  • Mouse pointer is within this window's bounds.
  • - *
  • The mouse button specified by {@code mouseButton} is pressed.
  • - *
- * - * Calling this method will make the window start moving together with the mouse pointer until - * the specified mouse button is released or Esc is pressed. The conditions for cancelling - * the move may differ between WMs. - * - * @param mouseButton indicates the mouse button that was pressed to start moving the window; - * must be one of {@code MouseEvent.BUTTON1}, {@code MouseEvent.BUTTON2}, - * or {@code MouseEvent.BUTTON3}. - */ - void startMovingTogetherWithMouse(Window window, int mouseButton); -} diff --git a/src/jetbrains.api/src/module-info.java b/src/jetbrains.api/src/module-info.java deleted file mode 100644 index e103e1b859b6..000000000000 --- a/src/jetbrains.api/src/module-info.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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. - */ - -module jetbrains.api { - exports com.jetbrains; - - requires static transitive java.desktop; -} \ No newline at end of file diff --git a/src/jetbrains.api/tools/CheckVersion.java b/src/jetbrains.api/tools/CheckVersion.java deleted file mode 100644 index 6cd7e01b90df..000000000000 --- a/src/jetbrains.api/tools/CheckVersion.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.function.Function; - -public class CheckVersion { - private static final Map> FILE_TRANSFORMERS = Map.of( - "com/jetbrains/JBR.java", c -> { - // Exclude API version from hash calculation - int versionMethodIndex = c.indexOf("getApiVersion()"); - int versionStartIndex = c.indexOf("\"", versionMethodIndex) + 1; - int versionEndIndex = c.indexOf("\"", versionStartIndex); - return c.substring(0, versionStartIndex) + c.substring(versionEndIndex); - } - ); - - private static Path gensrc; - - /** - *
    - *
  • $0 - absolute path to {@code JetBrainsRuntime/src/jetbrains.api} dir
  • - *
  • $1 - absolute path to gensrc dir ({@code JetBrainsRuntime/build//jbr-api/gensrc})
  • - *
  • $2 - true if hash mismatch is an error
  • - *
- */ - public static void main(String[] args) throws IOException, NoSuchAlgorithmException { - Path versionFile = Path.of(args[0]).resolve("version.properties"); - gensrc = Path.of(args[1]); - boolean error = args[2].equals("true"); - - Properties props = new Properties(); - props.load(Files.newInputStream(versionFile)); - String hash = SourceHash.calculate(); - - if (hash.equals(props.getProperty("HASH"))) return; - System.err.println("================================================================================"); - if (error) { - System.err.println("Error: jetbrains.api code was changed, hash and API version must be updated in " + versionFile); - } else { - System.err.println("Warning: jetbrains.api code was changed, " + - "update hash and increment API version in " + versionFile + " before committing these changes"); - } - System.err.println("HASH = " + hash); - if (!error) System.err.println("DO NOT COMMIT YOUR CHANGES WITH THIS WARNING"); - System.err.println("================================================================================"); - if (error) System.exit(-1); - } - - private static class SourceHash { - - private static String calculate() throws NoSuchAlgorithmException, IOException { - MessageDigest hash = MessageDigest.getInstance("MD5"); - calculate(gensrc, hash); - byte[] digest = hash.digest(); - StringBuilder result = new StringBuilder(); - for (byte b : digest) { - result.append(String.format("%X", b)); - } - return result.toString(); - } - - private static void calculate(Path dir, MessageDigest hash) throws IOException { - for (Entry f : findFiles(dir)) { - hash.update(f.name.getBytes(StandardCharsets.UTF_8)); - hash.update(f.content.getBytes(StandardCharsets.UTF_8)); - } - } - - private static List findFiles(Path dir) throws IOException { - List files = new ArrayList<>(); - FileVisitor fileFinder = new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - try { - Path rel = dir.relativize(file); - StringBuilder name = new StringBuilder(); - for (int i = 0; i < rel.getNameCount(); i++) { - if (!name.isEmpty()) name.append('/'); - name.append(rel.getName(i)); - } - String content = Files.readString(file); - String fileName = name.toString(); - files.add(new Entry(FILE_TRANSFORMERS.getOrDefault(fileName, c -> c).apply(content), fileName)); - return FileVisitResult.CONTINUE; - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - }; - Files.walkFileTree(dir, fileFinder); - files.sort(Comparator.comparing(Entry::name)); - return files; - } - - private record Entry(String content, String name) {} - } -} diff --git a/src/jetbrains.api/tools/Gensrc.java b/src/jetbrains.api/tools/Gensrc.java deleted file mode 100644 index 698be1640549..000000000000 --- a/src/jetbrains.api/tools/Gensrc.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 java.io.FileNotFoundException; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.nio.file.StandardOpenOption.*; -import static java.util.regex.Pattern.compile; - -/** - * Codegen script for {@code jetbrains.api} module. - * It produces "main" {@link com.jetbrains.JBR} class from template by - * inspecting interfaces and implementation code and inserting - * static utility methods for public services as well as some metadata - * needed by JBR at runtime. - */ -public class Gensrc { - - private static Path srcroot, src, gensrc; - private static String apiVersion; - private static JBRModules modules; - - /** - *
    - *
  • $0 - absolute path to {@code JetBrainsRuntime/src} dir
  • - *
  • $1 - absolute path to jbr-api output dir ({@code JetBrainsRuntime/build//jbr-api})
  • - *
  • $2 - {@code JBR} part of API version
  • - *
- */ - public static void main(String[] args) throws IOException { - srcroot = Path.of(args[0]); - Path module = srcroot.resolve("jetbrains.api"); - src = module.resolve("src"); - Path output = Path.of(args[1]); - gensrc = output.resolve("gensrc"); - Files.createDirectories(gensrc); - - Properties props = new Properties(); - props.load(Files.newInputStream(module.resolve("version.properties"))); - apiVersion = args[2] + "." + props.getProperty("VERSION"); - Files.writeString(output.resolve("jbr-api.version"), apiVersion, - CREATE, WRITE, TRUNCATE_EXISTING); - - modules = new JBRModules(); - generateFiles(); - } - - private static void generateFiles() throws IOException { - Files.walkFileTree(src, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - try { - Path rel = src.relativize(file); - Path output = gensrc.resolve(rel); - Files.createDirectories(output.getParent()); - String content = generateContent(file.getFileName().toString(), Files.readString(file)); - Files.writeString(output, content, CREATE, WRITE, TRUNCATE_EXISTING); - return FileVisitResult.CONTINUE; - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - }); - } - - private static String generateContent(String fileName, String content) throws IOException { - return switch (fileName) { - case "JBR.java" -> JBR.generate(content); - default -> generate(content); - }; - } - - private static String generate(String content) throws IOException { - Pattern pattern = compile("/\\*CONST ((?:[a-zA-Z0-9]+\\.)+)([a-zA-Z0-9_*]+)\\s*\\*/"); - for (;;) { - Matcher matcher = pattern.matcher(content); - if (!matcher.find()) return content; - String placeholder = matcher.group(0), file = matcher.group(1), name = matcher.group(2); - file = file.substring(0, file.length() - 1).replace('.', '/') + ".java"; - List statements = new ArrayList<>(); - for (Path module : modules.paths) { - Path f = module.resolve("share/classes").resolve(file); - if (Files.exists(f)) { - Pattern namePattern = compile(name.replaceAll("\\*", "\\\\w+")); - Pattern statementPattern = compile( - "((?:(?:MODS) ){2,3})([a-zA-Z0-9]+) (FIELD(?:, FIELD)*);" - .replaceAll("MODS", "public|protected|private|static|final") - .replaceAll("FIELD", "\\\\w+ = [\\\\w\"']+ ") - .replaceAll(" ", "\\\\s+") - .replaceAll(" ", "\\\\s*") - ); - Matcher statementMatcher = statementPattern.matcher(Files.readString(f)); - while (statementMatcher.find()) { - String mods = statementMatcher.group(1); - if (!mods.contains("static") || !mods.contains("final")) continue; - for (String s : statementMatcher.group(3).split(",")) { - s = s.strip(); - String nm = s.substring(0, s.indexOf('=')).strip(); - if (!namePattern.matcher(nm).matches()) continue; - statements.add("public static final " + statementMatcher.group(2) + " " + s + ";"); - } - } - break; - } - } - if (statements.isEmpty()) throw new RuntimeException("Constant not found: " + placeholder); - content = replaceTemplate(content, placeholder, statements, true); - } - } - - private static String findRegex(String src, Pattern regex) { - Matcher matcher = regex.matcher(src); - if (!matcher.find()) throw new IllegalArgumentException("Regex not found: " + regex.pattern()); - return matcher.group(1); - } - - private static String replaceTemplate(String src, String placeholder, Iterable statements, boolean compact) { - int placeholderIndex = src.indexOf(placeholder); - int indent = 0; - while (placeholderIndex - indent >= 1 && src.charAt(placeholderIndex - indent - 1) == ' ') indent++; - int nextLineIndex = src.indexOf('\n', placeholderIndex + placeholder.length()) + 1; - if (nextLineIndex == 0) nextLineIndex = placeholderIndex + placeholder.length(); - String before = src.substring(0, placeholderIndex - indent), after = src.substring(nextLineIndex); - StringBuilder sb = new StringBuilder(before); - boolean firstStatement = true; - for (String s : statements) { - if (!firstStatement && !compact) sb.append('\n'); - sb.append(s.indent(indent)); - firstStatement = false; - } - sb.append(after); - return sb.toString(); - } - - /** - * Code for generating {@link com.jetbrains.JBR} class. - */ - private static class JBR { - - private static String generate(String content) { - Service[] interfaces = findPublicServiceInterfaces(); - List statements = new ArrayList<>(); - for (Service i : interfaces) statements.add(generateMethodsForService(i)); - content = replaceTemplate(content, "/*GENERATED_METHODS*/", statements, false); - content = content.replace("/*KNOWN_SERVICES*/", - modules.services.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", "))); - content = content.replace("/*KNOWN_PROXIES*/", - modules.proxies.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", "))); - content = content.replace("/*API_VERSION*/", apiVersion); - return content; - } - - private static Service[] findPublicServiceInterfaces() { - Pattern javadocPattern = Pattern.compile("/\\*\\*((?:.|\n)*?)\\s*\\*/"); - Pattern deprecatedPattern = Pattern.compile("@Deprecated( *\\(.*?forRemoval *= *true.*?\\))?"); - return modules.services.stream() - .map(fullName -> { - if (fullName.indexOf('$') != -1) return null; // Only top level services can be public - Path path = src.resolve(fullName.replace('.', '/') + ".java"); - if (!Files.exists(path)) return null; - String name = fullName.substring(fullName.lastIndexOf('.') + 1); - try { - String content = Files.readString(path); - int indexOfDeclaration = content.indexOf("public interface " + name); - if (indexOfDeclaration == -1) return null; - Matcher javadocMatcher = javadocPattern.matcher(content.substring(0, indexOfDeclaration)); - String javadoc; - int javadocEnd; - if (javadocMatcher.find()) { - javadoc = javadocMatcher.group(1); - javadocEnd = javadocMatcher.end(); - } else { - javadoc = ""; - javadocEnd = 0; - } - Matcher deprecatedMatcher = deprecatedPattern.matcher(content.substring(javadocEnd, indexOfDeclaration)); - Status status; - if (!deprecatedMatcher.find()) status = Status.NORMAL; - else if (deprecatedMatcher.group(1) == null) status = Status.DEPRECATED; - else status = Status.FOR_REMOVAL; - return new Service(name, javadoc, status, content.contains("__Fallback")); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }) - .filter(Objects::nonNull).toArray(Service[]::new); - } - - private static String generateMethodsForService(Service service) { - return """ - private static class $__Holder { - private static final $ INSTANCE = getService($.class, ); - } - /** - * @return true if current runtime has implementation for all methods in {@link $} - * and its dependencies (can fully implement given service). - * @see #get$() - */ - public static boolean is$Supported() { - return $__Holder.INSTANCE != null; - } - /** - * @return full implementation of {@link $} service if any, or {@code null} otherwise - */ - public static $ get$() { - return $__Holder.INSTANCE; - } - """ - .replace("", service.hasFallback ? "$.__Fallback::new" : "null") - .replaceAll("\\$", service.name) - .replace("", service.javadoc) - .replaceAll("", service.status.text); - } - - private enum Status { - NORMAL(""), - DEPRECATED("\n@Deprecated"), - FOR_REMOVAL("\n@Deprecated(forRemoval=true)\n@SuppressWarnings(\"removal\")"); - - private final String text; - Status(String text) { this.text = text; } - } - - private record Service(String name, String javadoc, Status status, boolean hasFallback) {} - } - - /** - * Finds and analyzes JBR API implementation modules and collects proxy definitions. - */ - private static class JBRModules { - - private final Path[] paths; - private final Set proxies = new HashSet<>(), services = new HashSet<>(); - - private JBRModules() throws IOException { - String[] moduleNames = findJBRApiModules(); - paths = findPotentialJBRApiContributorModules(); - for (String moduleName : moduleNames) { - Path module = findJBRApiModuleFile(moduleName, paths); - findInModule(Files.readString(module)); - } - } - - private void findInModule(String content) { - Pattern servicePattern = compile("(service|proxy|twoWayProxy)\\s*\\(([^,)]+)"); - Matcher matcher = servicePattern.matcher(content); - while (matcher.find()) { - String type = matcher.group(1); - String interfaceName = extractFromStringLiteral(matcher.group(2)); - if (type.equals("service")) services.add(interfaceName); - else proxies.add(interfaceName); - } - } - - private static String extractFromStringLiteral(String value) { - value = value.strip(); - return value.substring(1, value.length() - 1); - } - - private static Path findJBRApiModuleFile(String module, Path[] potentialPaths) throws FileNotFoundException { - for (Path p : potentialPaths) { - Path m = p.resolve("share/classes").resolve(module + ".java"); - if (Files.exists(m)) return m; - } - throw new FileNotFoundException("JBR API module file not found: " + module); - } - - private static String[] findJBRApiModules() throws IOException { - String bootstrap = Files.readString( - srcroot.resolve("java.base/share/classes/com/jetbrains/bootstrap/JBRApiBootstrap.java")); - Pattern modulePattern = compile("\"([^\"]+)"); - return Stream.of(findRegex(bootstrap, compile("MODULES *=([^;]+)")).split(",")) - .map(m -> findRegex(m, modulePattern).replace('.', '/')).toArray(String[]::new); - } - - private static Path[] findPotentialJBRApiContributorModules() throws IOException { - return Files.list(srcroot) - .filter(p -> Files.exists(p.resolve("share/classes/com/jetbrains"))).toArray(Path[]::new); - } - } -} diff --git a/src/jetbrains.api/version.properties b/src/jetbrains.api/version.properties deleted file mode 100644 index d00dcef0cebf..000000000000 --- a/src/jetbrains.api/version.properties +++ /dev/null @@ -1,14 +0,0 @@ -# When any changes are made to jetbrains.api module, hash and version value MUST be updated in this file. -# Version has the following format: MAJOR.MINOR.PATCH -# -# How to increment JBR API version? -# 1. For small changes if no public API was changed (e.g., only javadoc changes) - increment PATCH. -# 2. When only new API is added, or some existing API was @Deprecated - increment MINOR, reset PATCH to 0. -# 3. For major backwards incompatible API changes - increment MAJOR, reset MINOR and PATCH to 0. - -VERSION = 0.0.18 - -# Hash is used to track changes to jetbrains.api, so you would not forget to update the version when needed. -# When you make any changes, "make jbr-api" will fail and ask you to update hash and version number here. - -HASH = C3233ED7B0A05816D1B0DB6139AE5F2F diff --git a/test/jdk/jb/java/api/backend/BootstrapTest.java b/test/jdk/jb/java/api/backend/BootstrapTest.java new file mode 100644 index 000000000000..c6fb51ce1e9a --- /dev/null +++ b/test/jdk/jb/java/api/backend/BootstrapTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2023 JetBrains s.r.o. + * 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 + * @build com.jetbrains.JBR + * @run main BootstrapTest new + * @run main BootstrapTest old + */ + +import com.jetbrains.JBR; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +public class BootstrapTest { + + public static void main(String[] args) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(JBR.class, MethodHandles.lookup()); + JBR.ServiceApi api; + if (!args[0].equals("old")) { + Class bootstrap = Class.forName("com.jetbrains.exported.JBRApiSupport"); + Objects.requireNonNull(bootstrap, "JBRApi class not accessible"); + api = (JBR.ServiceApi) (Object) lookup + .findStatic(bootstrap, "bootstrap", MethodType.methodType(Object.class, Class.class, + Class.class, Class.class, Class.class, Map.class, Function.class)) + .invokeExact(JBR.ServiceApi.class, (Class) null, (Class) null, (Class) null, + Map.of(), (Function>) m -> null); + } else { + Class bootstrap = Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap"); + Objects.requireNonNull(bootstrap, "JBRApiBootstrap class not accessible"); + api = (JBR.ServiceApi) (Object) lookup + .findStatic(bootstrap, "bootstrap", MethodType.methodType(Object.class, MethodHandles.Lookup.class)) + .invokeExact(lookup); + } + Objects.requireNonNull(api, "JBR API bootstrap failed"); + } +} diff --git a/test/jdk/jb/java/api/backend/FindDependenciesTest.java b/test/jdk/jb/java/api/backend/FindDependenciesTest.java deleted file mode 100644 index 7ae2e8211fe8..000000000000 --- a/test/jdk/jb/java/api/backend/FindDependenciesTest.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 - * @modules java.base/com.jetbrains.internal:+open - * @build com.jetbrains.* com.jetbrains.api.FindDependencies com.jetbrains.jbr.FindDependencies - * @run main FindDependenciesTest - */ - -import com.jetbrains.internal.JBRApi; - -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static com.jetbrains.Util.*; -import static com.jetbrains.api.FindDependencies.*; -import static com.jetbrains.jbr.FindDependencies.*; - -public class FindDependenciesTest { - - public static void main(String[] args) throws Throwable { - JBRApi.ModuleRegistry r = init(new String[] {}, names(""" - A AL AR ARL /*ARR is skipped*/ ARRL ARRR - BV B1 B2 B3 B4 B5 B6 B7 B8 B9 - C1 !C3 C5 C6 - """).toArray(String[]::new)); - // Simple tree with non-proxy type ARR - validateDependencies(AR.class, cs("AR ARL /*ARR is skipped*/ ARRL ARRR")); - validateDependencies(A.class, cs("A AL AR ARL /*ARR is skipped*/ ARRL ARRR")); - validateDependencies(ARRR.class, ARRR.class); - // Complex graph with many cycles - for (Class c : cs("B4 B6 B2 B3 B5")) { - validateDependencies(c, cs("BV B2 B3 B4 B5 B6 B7 B8 B9")); - } - validateDependencies(B1.class, cs("BV B1 B2 B3 B4 B5 B6 B7 B8 B9")); - validateDependencies(B7.class, B7.class, BV.class); - validateDependencies(B8.class, B8.class, BV.class); - validateDependencies(B9.class, B9.class); - validateDependencies(BV.class, BV.class); - // Client proxy dependencies - r.clientProxy(C3.class.getName(), C2.class.getName()); - r.proxy(C5.class.getName(), C4.class.getName()); - validateDependencies(C1.class, C1.class, C3.class, C5.class, C6.class); - validateDependencies(C5.class, C5.class, C6.class); - validateDependencies(C6.class, C6.class); - validateDependencies(C3.class, C3.class, C5.class, C6.class); - } - - private static Class[] cs(String interfaces) { - return names(interfaces).map(c -> { - try { - return Class.forName(c); - } catch (ClassNotFoundException e) { - throw new Error(e); - } - }).toArray(Class[]::new); - } - private static Stream names(String interfaces) { - return Stream.of(interfaces.replaceAll("/\\*[^*]*\\*/", "").split("(\s|\n)+")).map(String::strip) - .map(s -> { - if (s.startsWith("!")) return "com.jetbrains.jbr.FindDependencies$" + s.substring(1); - else return "com.jetbrains.api.FindDependencies$" + s; - }); - } - - private static void validateDependencies(Class src, Class... expected) throws Throwable { - Set> actual = getProxyDependencies(src); - if (actual.size() != expected.length || !actual.containsAll(List.of(expected))) { - throw new Error("Invalid proxy dependencies for class " + src + - ". Expected: [" + Stream.of(expected).map(Class::getSimpleName).collect(Collectors.joining(" ")) + - "]. Actual: [" + actual.stream().map(Class::getSimpleName).collect(Collectors.joining(" ")) + "]."); - } - } - - private static final ReflectedMethod getProxyDependencies = - getMethod("com.jetbrains.internal.ProxyDependencyManager", "getProxyDependencies", Class.class); - @SuppressWarnings("unchecked") - static Set> getProxyDependencies(Class interFace) throws Throwable { - return (Set>) getProxyDependencies.invoke(null, interFace); - } -} diff --git a/test/jdk/jb/java/api/backend/MethodMappingTest.java b/test/jdk/jb/java/api/backend/MethodMappingTest.java index 7fa80b7868e4..2f90d047b0cd 100644 --- a/test/jdk/jb/java/api/backend/MethodMappingTest.java +++ b/test/jdk/jb/java/api/backend/MethodMappingTest.java @@ -24,55 +24,39 @@ /* * @test * @modules java.base/com.jetbrains.internal:+open - * @build com.jetbrains.* com.jetbrains.api.MethodMapping com.jetbrains.jbr.MethodMapping - * @run main MethodMappingTest + * @build com.jetbrains.* com.jetbrains.test.api.MethodMapping com.jetbrains.test.jbr.MethodMapping + * @run main/othervm -Djetbrains.runtime.api.extendRegistry=true MethodMappingTest */ -import com.jetbrains.internal.JBRApi; +import java.util.Map; import static com.jetbrains.Util.*; -import static com.jetbrains.api.MethodMapping.*; -import static com.jetbrains.jbr.MethodMapping.*; +import static com.jetbrains.test.api.MethodMapping.*; +import static com.jetbrains.test.jbr.MethodMapping.*; public class MethodMappingTest { public static void main(String[] args) throws Throwable { - JBRApi.ModuleRegistry r = init(); + init("MethodMappingTest", Map.of()); // Simple empty interface - r.proxy(SimpleEmpty.class.getName(), SimpleEmptyImpl.class.getName()); - requireImplemented(SimpleEmpty.class); + requireSupported(getProxy(SimpleEmpty.class)); + requireUnsupported(getProxy(SimpleEmptyImpl.class)); + requireUnsupported(inverse(getProxy(SimpleEmpty.class))); + requireSupported(inverse(getProxy(SimpleEmptyImpl.class))); // Plain method mapping - r.proxy(PlainFail.class.getName(), PlainImpl.class.getName()); - r.service(Plain.class.getName(), PlainImpl.class.getName()) - .withStatic("c", "main", MethodMappingTest.class.getName()); - requireNotImplemented(PlainFail.class); - requireImplemented(Plain.class); + requireSupported(getProxy(Plain.class)); + requireUnsupported(getProxy(PlainFail.class)); // Callback (client proxy) - r.clientProxy(Callback.class.getName(), ApiCallback.class.getName()); - requireImplemented(Callback.class); + requireSupported(getProxy(Callback.class)); // 2-way - r.twoWayProxy(ApiTwoWay.class.getName(), JBRTwoWay.class.getName()); - requireImplemented(ApiTwoWay.class); - requireImplemented(JBRTwoWay.class); + requireSupported(getProxy(ApiTwoWay.class)); + requireSupported(getProxy(JBRTwoWay.class)); // Conversion - r.twoWayProxy(Conversion.class.getName(), ConversionImpl.class.getName()); - r.proxy(ConversionSelf.class.getName(), ConversionSelfImpl.class.getName()); - r.proxy(ConversionFail.class.getName(), ConversionFailImpl.class.getName()); - requireImplemented(Conversion.class); - requireImplemented(ConversionImpl.class); - requireImplemented(ConversionSelf.class); - requireNotImplemented(ConversionFail.class); - } - - private static final ReflectedMethod methodsImplemented = getMethod("com.jetbrains.internal.Proxy", "areAllMethodsImplemented"); - private static void requireImplemented(Class interFace) throws Throwable { - if (!(boolean) methodsImplemented.invoke(getProxy(interFace))) { - throw new Error("All methods must be implemented"); - } - } - private static void requireNotImplemented(Class interFace) throws Throwable { - if ((boolean) methodsImplemented.invoke(getProxy(interFace))) { - throw new Error("Not all methods must be implemented"); - } + requireSupported(getProxy(Conversion.class)); + requireSupported(getProxy(ConversionImpl.class)); + requireSupported(getProxy(ConversionSelf.class)); + requireUnsupported(getProxy(ConversionFail.class)); + requireSupported(getProxy(ArrayConversion.class)); + requireSupported(getProxy(GenericConversion.class)); } } diff --git a/test/jdk/jb/java/api/backend/MethodMappingTest.registry b/test/jdk/jb/java/api/backend/MethodMappingTest.registry new file mode 100644 index 000000000000..26eebbb48fea --- /dev/null +++ b/test/jdk/jb/java/api/backend/MethodMappingTest.registry @@ -0,0 +1,11 @@ +TYPE com.jetbrains.test.jbr.MethodMapping.SimpleEmptyImpl com.jetbrains.test.api.MethodMapping.SimpleEmpty PROVIDES +TYPE com.jetbrains.test.jbr.MethodMapping.PlainImpl com.jetbrains.test.api.MethodMapping.Plain SERVICE +STATIC MethodMappingTest main ([Ljava/lang/String;)V com.jetbrains.test.api.MethodMapping.Plain c +TYPE com.jetbrains.test.jbr.MethodMapping.PlainFailImpl com.jetbrains.test.api.MethodMapping.PlainFail SERVICE +TYPE com.jetbrains.test.jbr.MethodMapping.Callback com.jetbrains.test.api.MethodMapping.ApiCallback PROVIDED +TYPE com.jetbrains.test.jbr.MethodMapping.JBRTwoWay com.jetbrains.test.api.MethodMapping.ApiTwoWay TWO_WAY +TYPE com.jetbrains.test.jbr.MethodMapping.ConversionImpl com.jetbrains.test.api.MethodMapping.Conversion TWO_WAY +TYPE com.jetbrains.test.jbr.MethodMapping.ConversionSelfImpl com.jetbrains.test.api.MethodMapping.ConversionSelf PROVIDES +TYPE com.jetbrains.test.jbr.MethodMapping.ConversionFailImpl com.jetbrains.test.api.MethodMapping.ConversionFail PROVIDES +TYPE com.jetbrains.test.jbr.MethodMapping.ArrayConversionImpl com.jetbrains.test.api.MethodMapping.ArrayConversion PROVIDES +TYPE com.jetbrains.test.jbr.MethodMapping.GenericConversionImpl com.jetbrains.test.api.MethodMapping.GenericConversion PROVIDES diff --git a/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java index 5ef678d7e954..13962f7081d4 100644 --- a/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java +++ b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java @@ -24,45 +24,35 @@ /* * @test * @modules java.base/com.jetbrains.internal:+open - * @build com.jetbrains.* com.jetbrains.api.ProxyInfoResolving com.jetbrains.jbr.ProxyInfoResolving - * @run main ProxyInfoResolvingTest + * @build com.jetbrains.* com.jetbrains.test.api.ProxyInfoResolving com.jetbrains.test.jbr.ProxyInfoResolving + * @run main/othervm -Djetbrains.runtime.api.extendRegistry=true ProxyInfoResolvingTest */ -import com.jetbrains.internal.JBRApi; - -import java.util.Objects; +import java.util.Map; import static com.jetbrains.Util.*; -import static com.jetbrains.api.ProxyInfoResolving.*; -import static com.jetbrains.jbr.ProxyInfoResolving.*; +import static com.jetbrains.test.api.ProxyInfoResolving.*; +import static com.jetbrains.test.jbr.ProxyInfoResolving.*; public class ProxyInfoResolvingTest { public static void main(String[] args) throws Throwable { - JBRApi.ModuleRegistry r = init(); - // No mapping defined -> null - requireNull(getProxy(ProxyInfoResolvingTest.class)); - // Invalid JBR-side target class -> null - r.proxy(InterfaceWithoutImplementation.class.getName(), "absentImpl"); - requireNull(getProxy(InterfaceWithoutImplementation.class)); - // Invalid JBR-side target static method mapping -> null - r.service(ServiceWithoutImplementation.class.getName()) - .withStatic("someMethod", "someMethod", "NoClass"); - requireNull(getProxy(ServiceWithoutImplementation.class)); - // Service without target class or static method mapping -> null - r.service(EmptyService.class.getName()); - requireNull(getProxy(EmptyService.class)); - // Class passed instead of interface for client proxy -> error - r.clientProxy(ClientProxyClass.class.getName(), ClientProxyClassImpl.class.getName()); - mustFail(() -> getProxy(ClientProxyClass.class), RuntimeException.class); - // Class passed instead of interface for proxy -> null - r.proxy(ProxyClass.class.getName(), ProxyClassImpl.class.getName()); - requireNull(getProxy(ProxyClass.class)); + init("ProxyInfoResolvingTest", Map.of()); + // No mapping defined -> unsupported + requireUnsupported(getProxy(ProxyInfoResolvingTest.class)); + // Invalid JBR-side target class -> unsupported + requireUnsupported(getProxy(InterfaceWithoutImplementation.class)); + // Invalid JBR-side target static method mapping -> unsupported + requireUnsupported(getProxy(ServiceWithoutImplementation.class)); // Valid proxy - r.proxy(ValidApi.class.getName(), ValidApiImpl.class.getName()); - Objects.requireNonNull(getProxy(ValidApi.class)); - // Multiple implementations - r.proxy(ValidApi2.class.getName(), "MissingClass", ValidApi2Impl.class.getName()); - Objects.requireNonNull(getProxy(ValidApi2.class)); + requireSupported(getProxy(ValidApi.class)); + // Class instead of interface for proxy + requireSupported(getProxy(ProxyClass.class)); + // Class instead of interface for client proxy + requireSupported(getProxy(ClientProxyClass.class)); + // Service without annotation -> unsupported + requireUnsupported(getProxy(ServiceWithoutAnnotation.class)); + // Service with extension method + requireSupported(getProxy(ServiceWithExtension.class)); } } diff --git a/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.registry b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.registry new file mode 100644 index 000000000000..2d42d92cfbef --- /dev/null +++ b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.registry @@ -0,0 +1,7 @@ +TYPE absentImpl com.jetbrains.test.api.ProxyInfoResolving.InterfaceWithoutImplementation PROVIDES +STATIC NoClass foo ()V com.jetbrains.test.api.ProxyInfoResolving.ServiceWithoutImplementation foo +TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ValidApiImpl com.jetbrains.test.api.ProxyInfoResolving.ValidApi PROVIDES +TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ProxyClassImpl com.jetbrains.test.api.ProxyInfoResolving.ProxyClass PROVIDES +TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ClientProxyClass com.jetbrains.test.api.ProxyInfoResolving.ClientProxyClassImpl PROVIDED +TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ServiceWithoutAnnotationImpl com.jetbrains.test.api.ProxyInfoResolving.ServiceWithoutAnnotation SERVICE +STATIC NoClass foo ()V com.jetbrains.test.api.ProxyInfoResolving.ServiceWithExtension foo diff --git a/test/jdk/jb/java/api/backend/ProxyRegistrationTest.java b/test/jdk/jb/java/api/backend/ProxyRegistrationTest.java deleted file mode 100644 index 60556339abde..000000000000 --- a/test/jdk/jb/java/api/backend/ProxyRegistrationTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 - * @modules java.base/com.jetbrains.internal:+open - * @build com.jetbrains.* - * @run main ProxyRegistrationTest - */ - -import com.jetbrains.internal.JBRApi; - -import static com.jetbrains.Util.*; - -public class ProxyRegistrationTest { - - public static void main(String[] args) { - JBRApi.ModuleRegistry r = init(); - // Only service may not have target type - r.service("s"); - mustFail(() -> r.proxy("i"), IllegalArgumentException.class); - mustFail(() -> r.proxy("i", null), NullPointerException.class); - mustFail(() -> r.clientProxy("i", null), NullPointerException.class); - mustFail(() -> r.twoWayProxy("i", null), NullPointerException.class); - // Invalid 2-way mapping - r.proxy("a", "b"); - mustFail(() -> r.clientProxy("b", "c"), IllegalArgumentException.class); - mustFail(() -> r.clientProxy("c", "a"), IllegalArgumentException.class); - } -} diff --git a/test/jdk/jb/java/api/backend/RealTest.java b/test/jdk/jb/java/api/backend/RealTest.java index 80643bb46b03..1654d41f50c7 100644 --- a/test/jdk/jb/java/api/backend/RealTest.java +++ b/test/jdk/jb/java/api/backend/RealTest.java @@ -24,29 +24,43 @@ /* * @test * @modules java.base/com.jetbrains.internal:+open - * @build com.jetbrains.* com.jetbrains.api.Real com.jetbrains.jbr.Real - * @run main RealTest + * @build com.jetbrains.* com.jetbrains.test.api.Real com.jetbrains.test.jbr.Real + * @run main/othervm -Djetbrains.runtime.api.extendRegistry=true RealTest */ +import com.jetbrains.Extensions; import com.jetbrains.internal.JBRApi; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.Objects; import static com.jetbrains.Util.*; -import static com.jetbrains.api.Real.*; -import static com.jetbrains.jbr.Real.*; +import static com.jetbrains.test.api.Real.*; public class RealTest { public static void main(String[] args) { - init() - .service(Service.class.getName(), ServiceImpl.class.getName()) - .twoWayProxy(Api2Way.class.getName(), JBR2Way.class.getName()) - .twoWayProxy(ApiLazyNumber.class.getName(), JBRLazyNumber.class.getName()); + init("RealTest", Map.of(Extensions.FOO, new Class[] {Proxy.class}, Extensions.BAR, new Class[] {Proxy.class})); // Get service Service service = Objects.requireNonNull(JBRApi.getService(Service.class)); + // Proxy passthrough + Proxy p = Objects.requireNonNull(service.getProxy()); + Proxy np = Objects.requireNonNull(service.passthrough(p)); + if (p.getClass() != np.getClass()) { + throw new Error("Different classes when passing through the same object"); + } + + // Client proxy passthrough + ClientImpl c = new ClientImpl(); + ClientImpl nc = Objects.requireNonNull(service.passthrough(c)); + if (c.getClass() != nc.getClass()) { + throw new Error("Different classes when passing through the same object"); + } + // Test JBR-side proxy wrapping & unwrapping Api2Way stw = Objects.requireNonNull(service.get2Way()); Api2Way nstw = Objects.requireNonNull(service.passthrough(stw)); @@ -65,15 +79,51 @@ public class RealTest { Objects.requireNonNull(tw.value); // Passing through null object -> null - requireNull(service.passthrough(null)); - - if (!service.isSelf(service)) { - throw new Error("service.isSelf(service) == false"); - } + requireNull(service.passthrough((Api2Way) null)); if (service.sum(() -> 200, () -> 65).get() != 265) { throw new Error("Lazy numbers conversion error"); } + + // Check extensions + if (!JBRApi.isExtensionSupported(Extensions.FOO)) { + throw new Error("FOO extension must be supported"); + } + if (JBRApi.isExtensionSupported(Extensions.BAR)) { + throw new Error("BAR extension must not be supported"); + } + try { + service.getProxy().foo(); + throw new Error("FOO extension was disabled but call succeeded"); + } catch (UnsupportedOperationException ignore) {} + try { + service.getProxy().bar(); + throw new Error("BAR extension was disabled but call succeeded"); + } catch (UnsupportedOperationException ignore) {} + // foo() must succeed when enabled + JBRApi.getService(Service.class, Extensions.FOO).getProxy().foo(); + // Asking for BAR must return null, as it is not supported + requireNull(JBRApi.getService(Service.class, Extensions.FOO, Extensions.BAR)); + requireNull(JBRApi.getService(Service.class, Extensions.BAR)); + + // Test specialized (implicit) List proxy + List list = Objects.requireNonNull(service.testList(null)); + Api2Way listItem = new TwoWayImpl(); + list.add(listItem); + if (list.size() != 1) { + throw new Error("Unexpected List size"); + } + if (list.get(0) != listItem) { + throw new Error("Unexpected List item"); + } + service.testList(list); + if (!list.isEmpty()) { + throw new Error("Unexpected List size"); + } + list = new ArrayList<>(); + if (list != service.testList(list)) { + throw new Error("List passthrough mismatch"); + } } private static class TwoWayImpl implements Api2Way { diff --git a/test/jdk/jb/java/api/backend/RealTest.registry b/test/jdk/jb/java/api/backend/RealTest.registry new file mode 100644 index 000000000000..57e136534cd1 --- /dev/null +++ b/test/jdk/jb/java/api/backend/RealTest.registry @@ -0,0 +1,5 @@ +TYPE com.jetbrains.test.jbr.Real.ServiceImpl com.jetbrains.test.api.Real.Service SERVICE +TYPE com.jetbrains.test.jbr.Real.ProxyImpl com.jetbrains.test.api.Real.Proxy PROVIDES +TYPE com.jetbrains.test.jbr.Real.Client com.jetbrains.test.api.Real.ClientImpl PROVIDED +TYPE com.jetbrains.test.jbr.Real.JBR2Way com.jetbrains.test.api.Real.Api2Way TWO_WAY +TYPE com.jetbrains.test.jbr.Real.JBRLazyNumber com.jetbrains.test.api.Real.ApiLazyNumber TWO_WAY diff --git a/test/jdk/jb/java/api/backend/ReflectiveBootstrapTest.java b/test/jdk/jb/java/api/backend/ReflectiveBootstrapTest.java deleted file mode 100644 index 4c6f7c72e38a..000000000000 --- a/test/jdk/jb/java/api/backend/ReflectiveBootstrapTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 - * @build com.jetbrains.JBR - * @run main ReflectiveBootstrapTest - */ - -import com.jetbrains.JBR; - -import java.lang.invoke.MethodHandles; -import java.util.Objects; - -public class ReflectiveBootstrapTest { - - public static void main(String[] args) throws Exception { - JBR.ServiceApi api = (JBR.ServiceApi) Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap") - .getMethod("bootstrap", MethodHandles.Lookup.class) - .invoke(null, MethodHandles.privateLookupIn(JBR.class, MethodHandles.lookup())); - Objects.requireNonNull(api, "JBR API bootstrap failed"); - } -} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Extension.java b/test/jdk/jb/java/api/backend/com/jetbrains/Extension.java new file mode 100644 index 000000000000..26fe4cb7e73a --- /dev/null +++ b/test/jdk/jb/java/api/backend/com/jetbrains/Extension.java @@ -0,0 +1,13 @@ +package com.jetbrains; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(METHOD) +@Retention(RUNTIME) +public @interface Extension { + Extensions value(); +} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Extensions.java b/test/jdk/jb/java/api/backend/com/jetbrains/Extensions.java new file mode 100644 index 000000000000..a78421aaec95 --- /dev/null +++ b/test/jdk/jb/java/api/backend/com/jetbrains/Extensions.java @@ -0,0 +1,6 @@ +package com.jetbrains; + +public enum Extensions { + FOO, + BAR +} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java b/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java index e3eb6e93ea53..47fa97f35ea8 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java @@ -28,13 +28,5 @@ package com.jetbrains; */ public class JBR { - public interface ServiceApi { - - T getService(Class interFace); - } - - static final class Metadata { - static String[] KNOWN_SERVICES = {}; - static String[] KNOWN_PROXIES = {}; - } + public interface ServiceApi {} } diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Provided.java b/test/jdk/jb/java/api/backend/com/jetbrains/Provided.java new file mode 100644 index 000000000000..61afa82b919d --- /dev/null +++ b/test/jdk/jb/java/api/backend/com/jetbrains/Provided.java @@ -0,0 +1,12 @@ +package com.jetbrains; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(TYPE) +@Retention(RUNTIME) +public @interface Provided { +} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Provides.java b/test/jdk/jb/java/api/backend/com/jetbrains/Provides.java new file mode 100644 index 000000000000..19646cb4317b --- /dev/null +++ b/test/jdk/jb/java/api/backend/com/jetbrains/Provides.java @@ -0,0 +1,12 @@ +package com.jetbrains; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(TYPE) +@Retention(RUNTIME) +public @interface Provides { +} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Service.java b/test/jdk/jb/java/api/backend/com/jetbrains/Service.java new file mode 100644 index 000000000000..49871e201b5b --- /dev/null +++ b/test/jdk/jb/java/api/backend/com/jetbrains/Service.java @@ -0,0 +1,12 @@ +package com.jetbrains; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(TYPE) +@Retention(RUNTIME) +public @interface Service { +} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Util.java b/test/jdk/jb/java/api/backend/com/jetbrains/Util.java index 99d9a68e6781..9bda4115ff66 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/Util.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/Util.java @@ -25,81 +25,76 @@ package com.jetbrains; import com.jetbrains.internal.JBRApi; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.InvocationTargetException; +import java.io.*; +import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Map; public class Util { - public static ReflectedMethod getMethod(String className, String method, Class... parameterTypes) { - try { - Method m = Class.forName(className, false, JBRApi.class.getClassLoader()) - .getDeclaredMethod(method, parameterTypes); - m.setAccessible(true); - return (o, a) -> { - try { - return m.invoke(o, a); - } catch (IllegalAccessException e) { - throw new Error(e); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - }; - } catch (NoSuchMethodException | ClassNotFoundException e) { + /** + * Invoke internal {@link JBRApi#init} bypassing {@link com.jetbrains.exported.JBRApiSupport#bootstrap}. + */ + public static void init(String registryName, Map, Class[]> extensionClasses) { + try (InputStream in = new FileInputStream(new File(System.getProperty("test.src", "."), registryName + ".registry"))) { + JBRApi.init(in, Service.class, Provided.class, Provides.class, extensionClasses, m -> { + Extension e = m.getAnnotation(Extension.class); + return e == null ? null : e.value(); + }); + } catch (IOException e) { throw new Error(e); } } - public static JBRApi.ModuleRegistry init() { - return init(new String[0], new String[0]); + private static Object proxyRepository; + public static Object getProxyRepository() throws Throwable { + if (proxyRepository == null) { + Field f = JBRApi.class.getDeclaredField("proxyRepository"); + f.setAccessible(true); + proxyRepository = f.get(null); + } + return proxyRepository; } - /** - * Set known services & proxies at runtime and invoke internal - * {@link JBRApi#init} bypassing {@link com.jetbrains.bootstrap.JBRApiBootstrap#bootstrap} - * in order not to init normal JBR API modules. - */ - public static JBRApi.ModuleRegistry init(String[] knownServices, String[] knownProxies) { - JBR.Metadata.KNOWN_SERVICES = knownServices; - JBR.Metadata.KNOWN_PROXIES = knownProxies; - JBRApi.init(MethodHandles.lookup()); - return JBRApi.registerModule(MethodHandles.lookup(), JBR.class.getModule()::addExports); - } - - private static final ReflectedMethod getProxy = getMethod(JBRApi.class.getName(), "getProxy", Class.class); + private static Method getProxy; public static Object getProxy(Class interFace) throws Throwable { - return getProxy.invoke(null, interFace); + var repo = getProxyRepository(); + if (getProxy == null) { + getProxy = repo.getClass() + .getDeclaredMethod("getProxy", Class.class, Class.forName("com.jetbrains.internal.Mapping").arrayType()); + getProxy.setAccessible(true); + } + return getProxy.invoke(repo, interFace, null); + } + + private static Method inverse; + public static Object inverse(Object proxy) throws Throwable { + if (inverse == null) { + inverse = proxy.getClass().getDeclaredMethod("inverse"); + inverse.setAccessible(true); + } + return inverse.invoke(proxy); + } + + private static Method generate; + public static boolean isSupported(Object proxy) throws Throwable { + if (generate == null) { + generate = proxy.getClass().getDeclaredMethod("generate"); + generate.setAccessible(true); + } + return (boolean) generate.invoke(proxy); + } + + public static void requireSupported(Object proxy) throws Throwable { + if (!isSupported(proxy)) throw new RuntimeException("Proxy must be supported"); + } + + public static void requireUnsupported(Object proxy) throws Throwable { + if (isSupported(proxy)) throw new RuntimeException("Proxy must be unsupported"); } public static void requireNull(Object o) { if (o != null) throw new RuntimeException("Value must be null"); } - @SafeVarargs - public static void mustFail(ThrowingRunnable action, Class... exceptionTypeChain) { - try { - action.run(); - } catch(Throwable exception) { - Throwable e = exception; - error: { - for (Class c : exceptionTypeChain) { - if (e == null || !c.isInstance(e)) break error; - e = e.getCause(); - } - if (e == null) return; - } - throw new Error("Unexpected exception", exception); - } - throw new Error("Operation must fail, but succeeded"); - } - - @FunctionalInterface - public interface ThrowingRunnable { - void run() throws Throwable; - } - - @FunctionalInterface - public interface ReflectedMethod { - Object invoke(Object obj, Object... args) throws Throwable; - } } diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/FindDependencies.java b/test/jdk/jb/java/api/backend/com/jetbrains/api/FindDependencies.java deleted file mode 100644 index 9ddc1e1dfc06..000000000000 --- a/test/jdk/jb/java/api/backend/com/jetbrains/api/FindDependencies.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 com.jetbrains.api; - -public class FindDependencies { - public interface A { - void f(AL l, AR r); - } - public interface AL {} - public interface AR { - void f(ARL l, ARR r); - } - public interface ARL {} - public interface ARR { - void f(ARRL l, ARRR r); - } - public interface ARRL {} - public interface ARRR {} - - public interface BV {} - public interface B1 { - void f(BV bvs, B2 t, BV bve); - } - public interface B2 { - void f(BV bvs, B3 t, BV bve); - } - public interface B3 { - void f(B8 l, BV bvs, B4 t, B2 b, BV bve, B9 r); - } - public interface B4 { - void f(BV bvs, B2 b, B5 t, BV bve); - } - public interface B5 { - void f(BV bvs, B6 s, B3 b, B7 t, BV bve); - } - public interface B6 { - void f(BV bvs, B5 b, BV bve); - } - public interface B7 { - void f(BV v); - } - public interface B8 { - void f(BV v); - } - public interface B9 {} - - public interface C1 { - void f(C2 c); - } - public interface C2 {} - public interface C5 { - void f(C6 c); - } - public interface C6 {} -} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/FindDependencies.java b/test/jdk/jb/java/api/backend/com/jetbrains/jbr/FindDependencies.java deleted file mode 100644 index 45e7c4012157..000000000000 --- a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/FindDependencies.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 com.jetbrains.jbr; - -public class FindDependencies { - public interface C3 { - void f(C4 c); - } - public interface C4 {} -} diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/MethodMapping.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/MethodMapping.java similarity index 71% rename from test/jdk/jb/java/api/backend/com/jetbrains/api/MethodMapping.java rename to test/jdk/jb/java/api/backend/com/jetbrains/test/api/MethodMapping.java index d71ff0394fdb..bc4f0eea7596 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/api/MethodMapping.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/MethodMapping.java @@ -21,29 +21,56 @@ * questions. */ -package com.jetbrains.api; +package com.jetbrains.test.api; + +import com.jetbrains.Provides; +import com.jetbrains.Provided; +import com.jetbrains.Service; public class MethodMapping { + @Provided public interface SimpleEmpty {} + @Service + @Provided public interface Plain { void a(); boolean b(); void c(String... a); } + @Service + @Provided public interface PlainFail extends Plain {} + @Provides public interface ApiCallback { void hello(); } + @Provided + @Provides public interface ApiTwoWay { void something(); } + @Provided + @Provides public interface Conversion { - SimpleEmpty convert(Plain a, ApiCallback b, ApiTwoWay c); + SimpleEmpty convert(SimpleEmpty a, ApiCallback b, ApiTwoWay c); } + @Provided public interface ConversionSelf extends Conversion { ConversionSelf convert(Object a, Object b, Object c); } + @Provided public interface ConversionFail extends Conversion { void missingMethod(); } + @Provided + public interface ArrayConversion { + Conversion[] convert(); + } + @Provided + public interface GenericConversion { + ImplicitGeneric convert(); + } + public interface ImplicitGeneric { + R apply(P p); + } } diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/ProxyInfoResolving.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/ProxyInfoResolving.java similarity index 75% rename from test/jdk/jb/java/api/backend/com/jetbrains/api/ProxyInfoResolving.java rename to test/jdk/jb/java/api/backend/com/jetbrains/test/api/ProxyInfoResolving.java index e885f1430b55..1e9423e7d1c1 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/api/ProxyInfoResolving.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/ProxyInfoResolving.java @@ -21,14 +21,29 @@ * questions. */ -package com.jetbrains.api; +package com.jetbrains.test.api; + +import com.jetbrains.*; public class ProxyInfoResolving { + @Provided public interface InterfaceWithoutImplementation {} - public interface ServiceWithoutImplementation {} - public interface EmptyService {} + @Service + @Provided + public interface ServiceWithoutImplementation { + void foo(); + } + @Provides public static class ClientProxyClassImpl {} + @Provided public static class ProxyClass {} + @Provided public interface ValidApi {} - public interface ValidApi2 {} + public interface ServiceWithoutAnnotation {} + @Service + @Provided + public interface ServiceWithExtension { + @Extension(Extensions.FOO) + void foo(); + } } diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/Real.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/Real.java similarity index 63% rename from test/jdk/jb/java/api/backend/com/jetbrains/api/Real.java rename to test/jdk/jb/java/api/backend/com/jetbrains/test/api/Real.java index b65757d763a2..d175173b1a99 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/api/Real.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/Real.java @@ -21,28 +21,58 @@ * questions. */ -package com.jetbrains.api; +package com.jetbrains.test.api; +import com.jetbrains.Extension; +import com.jetbrains.Extensions; +import com.jetbrains.Provides; +import com.jetbrains.Provided; + +import java.util.List; import java.util.function.Consumer; +import java.util.function.IntSupplier; public class Real { + @com.jetbrains.Service + @Provided public interface Service { + Proxy getProxy(); + Proxy passthrough(Proxy a); + ClientImpl passthrough(ClientImpl a); Api2Way get2Way(); Api2Way passthrough(Api2Way a); - boolean isSelf(Service a); ApiLazyNumber sum(ApiLazyNumber a, ApiLazyNumber b); /** - * When generating bridge class, convertible method parameters are changed to Objects, - * which may cause name collisions + * Convertible method parameters are changed to Objects, which may cause name collisions */ void testMethodNameConflict(Api2Way a); void testMethodNameConflict(ApiLazyNumber a); + List testList(List list); } + @Provided + public interface Proxy extends IntSupplier { + @Extension(Extensions.FOO) + void foo(); + @Extension(Extensions.BAR) + void bar(); + } + + @Provides + public static class ClientImpl { + int getAsInt() { + return 562; + } + } + + @Provided + @Provides @FunctionalInterface public interface Api2Way extends Consumer {} + @Provided + @Provides @FunctionalInterface public interface ApiLazyNumber { int get(); diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/MethodMapping.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/MethodMapping.java similarity index 67% rename from test/jdk/jb/java/api/backend/com/jetbrains/jbr/MethodMapping.java rename to test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/MethodMapping.java index 1ce467872054..843f71bf9f22 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/MethodMapping.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/MethodMapping.java @@ -21,31 +21,62 @@ * questions. */ -package com.jetbrains.jbr; +package com.jetbrains.test.jbr; + +import com.jetbrains.Provided; +import com.jetbrains.Provides; public class MethodMapping { + @Provides public static class SimpleEmptyImpl {} + @Provides public static class PlainImpl { public void a() {} public boolean b() { return false; } } + @Provides + public static class PlainFailImpl { + public void a() {} + public boolean b() { + return false; + } + } + @Provided public interface Callback { void hello(); } + @Provided + @Provides public interface JBRTwoWay { void something(); } + @Provided + @Provides public interface ConversionImpl { - default SimpleEmptyImpl convert(PlainImpl a, Callback b, JBRTwoWay c) { + default SimpleEmptyImpl convert(SimpleEmptyImpl a, Callback b, JBRTwoWay c) { return null; } } + @Provides public static class ConversionSelfImpl implements ConversionImpl { public ConversionSelfImpl convert(Object a, Object b, Object c) { return null; } } + @Provides public static class ConversionFailImpl implements ConversionImpl {} + @Provides + public static class ArrayConversionImpl { + ConversionImpl[] convert() { + return null; + } + } + @Provides + public static class GenericConversionImpl { + com.jetbrains.test.api.MethodMapping.ImplicitGeneric convert() { + return null; + } + } } diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/ProxyInfoResolving.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/ProxyInfoResolving.java similarity index 86% rename from test/jdk/jb/java/api/backend/com/jetbrains/jbr/ProxyInfoResolving.java rename to test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/ProxyInfoResolving.java index b5f103703a0b..e73d7ee9efd6 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/ProxyInfoResolving.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/ProxyInfoResolving.java @@ -21,11 +21,17 @@ * questions. */ -package com.jetbrains.jbr; +package com.jetbrains.test.jbr; + +import com.jetbrains.Provided; +import com.jetbrains.Provides; public class ProxyInfoResolving { + @Provided public static class ClientProxyClass {} + @Provides public static class ProxyClassImpl {} + @Provides public static class ValidApiImpl {} - public static class ValidApi2Impl {} + public interface ServiceWithoutAnnotationImpl {} } diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/Real.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/Real.java similarity index 64% rename from test/jdk/jb/java/api/backend/com/jetbrains/jbr/Real.java rename to test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/Real.java index 67487fcbf011..5f4e4642116a 100644 --- a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/Real.java +++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/jbr/Real.java @@ -21,13 +21,29 @@ * questions. */ -package com.jetbrains.jbr; +package com.jetbrains.test.jbr; +import com.jetbrains.Provided; +import com.jetbrains.Provides; + +import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; +import java.util.function.IntSupplier; public class Real { + @Provides public static class ServiceImpl { + ProxyImpl getProxy() { + return new ProxyImpl(); + } + ProxyImpl passthrough(ProxyImpl a) { + return a; + } + Client passthrough(Client a) { + return a; + } JBR2Way get2Way() { return a -> {}; } @@ -35,20 +51,41 @@ public class Real { if (a != null) a.accept(a); return a; } - boolean isSelf(ServiceImpl a) { - return a == this; - } JBRLazyNumber sum(JBRLazyNumber a, JBRLazyNumber b) { return () -> a.get() + b.get(); } void testMethodNameConflict(JBR2Way a) {} void testMethodNameConflict(JBRLazyNumber a) {} + List testList(List list) { + if (list == null) { + return new ArrayList<>(); + } else { + list.clear(); + return list; + } + } + } + + @Provides + public static class ProxyImpl { + int getAsInt() { + return 265; + } + void foo() {} } @FunctionalInterface + @Provided + public interface Client extends IntSupplier {} + + @FunctionalInterface + @Provided + @Provides public interface JBR2Way extends Consumer {} @FunctionalInterface + @Provided + @Provides public interface JBRLazyNumber { int get(); } diff --git a/test/jdk/jb/java/api/frontend/CustomTitleBarDoubleClick.java b/test/jdk/jb/java/api/frontend/CustomTitleBarDoubleClick.java deleted file mode 100644 index c005c922f167..000000000000 --- a/test/jdk/jb/java/api/frontend/CustomTitleBarDoubleClick.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 - @requires os.family == "mac" | os.family == "windows" - @key headful - @summary Test that window state changes on titlebar double-click - @library ../../../../java/awt/regtesthelpers - @build Util - @run main CustomTitleBarDoubleClick -*/ - -import com.jetbrains.JBR; -import com.jetbrains.WindowDecorations; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import test.java.awt.regtesthelpers.Util; - -public class CustomTitleBarDoubleClick implements WindowListener, WindowStateListener { - //Declare things used in the test, like buttons and labels here - private final static Rectangle BOUNDS = new Rectangle(300, 300, 300, 300); - private final static int TITLE_BAR_OFFSET = 10; - - JFrame frame; - Robot robot; - volatile boolean stateChanged; - - public static void main(final String[] args) { - CustomTitleBarDoubleClick app = new CustomTitleBarDoubleClick(); - app.start(); - } - - public void start () { - robot = Util.createRobot(); - robot.setAutoDelay(100); - robot.mouseMove(BOUNDS.x + (BOUNDS.width / 2), - BOUNDS.y + (BOUNDS.height/ 2)); - - frame = new JFrame("CustomTitleBarDoubleClick"); // Custom decorations doesn't work for AWT Frames on macOS - frame.setBounds(BOUNDS); - frame.addWindowListener(this); - frame.addWindowStateListener(this); - - WindowDecorations.CustomTitleBar titleBar = JBR.getWindowDecorations().createCustomTitleBar(); - titleBar.setHeight(50); - JBR.getWindowDecorations().setCustomTitleBar(frame, titleBar); - frame.setVisible(true); - robot.delay(2000); - if (!stateChanged) throw new AWTError("Test failed"); - } - - // Move the mouse into the title bar and double click to maximize the Frame - static boolean hasRun = false; - - private void doTest() { - if (hasRun) return; - hasRun = true; - - System.out.println("doing test"); - robot.mouseMove(BOUNDS.x + (BOUNDS.width / 2), - BOUNDS.y + TITLE_BAR_OFFSET); - robot.delay(50); - // Util.waitForIdle(robot) seem always hangs here. - // Need to use it instead robot.delay() when the bug become fixed. - System.out.println("1st press: currentTimeMillis: " + System.currentTimeMillis()); - robot.mousePress(InputEvent.BUTTON1_MASK); - robot.delay(50); - System.out.println("1st release: currentTimeMillis: " + System.currentTimeMillis()); - robot.mouseRelease(InputEvent.BUTTON1_MASK); - robot.delay(50); - System.out.println("2nd press: currentTimeMillis: " + System.currentTimeMillis()); - robot.mousePress(InputEvent.BUTTON1_MASK); - robot.delay(50); - System.out.println("2nd release: currentTimeMillis: " + System.currentTimeMillis()); - robot.mouseRelease(InputEvent.BUTTON1_MASK); - System.out.println("done: currentTimeMillis: " + System.currentTimeMillis()); - } - - public void windowActivated(WindowEvent e) {doTest();} - public void windowClosed(WindowEvent e) {} - public void windowClosing(WindowEvent e) {} - public void windowDeactivated(WindowEvent e) {} - public void windowDeiconified(WindowEvent e) {} - public void windowIconified(WindowEvent e) {} - public void windowOpened(WindowEvent e) {} - - public void windowStateChanged(WindowEvent e) { - stateChanged = true; // On macOS double-click may cause window to be maximized or iconified depending on AppleActionOnDoubleClick setting - } - -} diff --git a/test/jdk/jb/java/api/frontend/FileDialogTest.java b/test/jdk/jb/java/api/frontend/FileDialogTest.java deleted file mode 100644 index 2b48db572a2d..000000000000 --- a/test/jdk/jb/java/api/frontend/FileDialogTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 - * @requires (os.family == "mac") - * @run main/manual JBRApiTest - */ - -import com.jetbrains.JBRFileDialog; - -import java.awt.*; -import java.io.File; -import java.util.Arrays; -import java.util.List; - -public class FileDialogTest { - - private static final File directory = new File("FileDialogTest").getAbsoluteFile(); - private static void refresh() { - for (File f : directory.listFiles()) f.delete(); - } - - public static void main(String[] args) throws Exception { - directory.mkdirs(); - refresh(); - var dialog = new FileDialog((Frame) null); - dialog.setTitle("Press Save"); - dialog.setDirectory(directory.getAbsolutePath()); - dialog.setFile("PASS"); - dialog.setMode(FileDialog.SAVE); - dialog.setVisible(true); - var file = new File(directory, "PASS"); - check(dialog, file); - - refresh(); - file = new File(directory, "OPEN ME"); - file.createNewFile(); - dialog = open(JBRFileDialog.SELECT_FILES_HINT); - dialog.setVisible(true); - check(dialog, file); - - refresh(); - file.mkdir(); - dialog = open(JBRFileDialog.SELECT_DIRECTORIES_HINT); - dialog.setVisible(true); - check(dialog, file); - - refresh(); - file = new File(directory, "OPEN US"); - file.createNewFile(); - var file2 = new File(directory, "OPEN US"); - file2.mkdir(); - dialog = open(JBRFileDialog.SELECT_FILES_HINT | JBRFileDialog.SELECT_DIRECTORIES_HINT); - dialog.setMultipleMode(true); - dialog.setVisible(true); - check(dialog, file, file2); - - refresh(); - new File(directory, "CREATE DIRECTORY").createNewFile(); - new File(directory, "AND OPEN IT").createNewFile(); - dialog = open(JBRFileDialog.SELECT_DIRECTORIES_HINT | JBRFileDialog.CREATE_DIRECTORIES_HINT); - dialog.setVisible(true); - file = dialog.getFiles()[0]; - dialog.dispose(); - if (!file.isDirectory() || !file.exists()) { - throw new RuntimeException("Selected file doesn't exist"); - } - } - - private static FileDialog open(int hints) { - var dialog = new FileDialog((Frame) null); - dialog.setMode(FileDialog.LOAD); - JBRFileDialog.get(dialog).setHints(hints); - return dialog; - } - - private static void check(FileDialog dialog, File... expected) { - List actual = List.of(dialog.getFiles()); - dialog.dispose(); - boolean match = expected.length == actual.size(); - if (match) { - for (File f : expected) { - if (!actual.contains(f)) { - match = false; - break; - } - } - } - if (!match) { - throw new RuntimeException("Invalid files, expected: " + Arrays.toString(expected) + ", actual: " + actual); - } - } -} diff --git a/test/jdk/jb/java/api/frontend/FontExtensionsTest.java b/test/jdk/jb/java/api/frontend/FontExtensionsTest.java deleted file mode 100644 index ae3a541688e9..000000000000 --- a/test/jdk/jb/java/api/frontend/FontExtensionsTest.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 checking visualisation of drawing text with custom OpenType's features - @modules java.desktop/com.jetbrains.desktop:+open - @run main FontExtensionsTest -*/ - -import com.jetbrains.FontExtensions; -import com.jetbrains.JBR; - -import java.awt.*; -import java.awt.font.TextAttribute; -import java.awt.image.BufferedImage; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static com.jetbrains.desktop.FontExtensions.featuresToString; -import static com.jetbrains.desktop.FontExtensions.getFeatures; - -public class FontExtensionsTest { - @Retention(RetentionPolicy.RUNTIME) - private @interface JBRTest {} - - private static final int IMG_WIDTH = 500; - private static final int IMG_HEIGHT = 50; - private static final String LIGATURES_STRING = "== != -> <>"; - private static final String FRACTION_STRING = "1/2"; - private static final String ZERO_STRING = "0"; - private static final String TEST_STRING = "hello abc 012345 " + FRACTION_STRING + LIGATURES_STRING; - private static final Font BASE_FONT = new Font("JetBrains Mono", Font.PLAIN, 20); - - private static BufferedImage getImageWithString(Font font, String str) { - BufferedImage img = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB); - Graphics g = img.getGraphics(); - g.setColor(Color.white); - g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT); - - g.setColor(Color.black); - g.setFont(font); - g.drawString(str, 20, 20); - g.dispose(); - - return img; - } - - private static Boolean isImageEquals(BufferedImage first, BufferedImage second) { - for (int y = 0; y < first.getHeight(); y++) { - for (int x = 0; x < first.getWidth(); x++) { - if (first.getRGB(x, y) != second.getRGB(x, y)) { - return false; - } - } - } - return true; - } - - private static Font fontWithFeatures(Font font, FontExtensions.FeatureTag... features) { - return JBR.getFontExtensions().deriveFontWithFeatures(font, new FontExtensions.Features(features)); - } - - private static Font fontWithFeatures(FontExtensions.FeatureTag... features) { - return fontWithFeatures(BASE_FONT, features); - } - - private static Boolean textDrawingEquals(Font font1, Font font2, String text) { - BufferedImage image1 = getImageWithString(font1, text); - BufferedImage image2 = getImageWithString(font2, text); - return isImageEquals(image1, image2); - } - - private static String fontFeaturesAsString(Font font) { - return featuresToString(getFeatures(font)); - } - - @JBRTest - private static Boolean testFeatureFromString() { - return FontExtensions.FeatureTag.getFeatureTag("frac").isPresent() && - FontExtensions.FeatureTag.getFeatureTag("FrAc").isPresent() && - FontExtensions.FeatureTag.getFeatureTag("ss10").isPresent() && - FontExtensions.FeatureTag.getFeatureTag("tttt").isEmpty(); - } - - @JBRTest - private static Boolean testFeaturesToString1() { - Font font = JBR.getFontExtensions().deriveFontWithFeatures(BASE_FONT, new FontExtensions.Features(Map.of( - FontExtensions.FeatureTag.ZERO, FontExtensions.FEATURE_ON, - FontExtensions.FeatureTag.SALT, 123, - FontExtensions.FeatureTag.FRAC, FontExtensions.FEATURE_OFF))); - String features = "calt=0;frac=0;kern=0;liga=0;salt=123;zero=1"; - return fontFeaturesAsString(font).equals(features); - } - - @JBRTest - private static Boolean testFeaturesToString2() { - Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON, - TextAttribute.KERNING, TextAttribute.KERNING_ON)); - String features = "calt=1;kern=1;liga=1"; - return fontFeaturesAsString(font).equals(features); - } - - @JBRTest - private static Boolean testDisablingLigatureByDefault() { - return textDrawingEquals(BASE_FONT, fontWithFeatures(FontExtensions.FeatureTag.ZERO), LIGATURES_STRING); - } - - @JBRTest - private static Boolean testSettingValuesToFeatures() { - Font font = JBR.getFontExtensions().deriveFontWithFeatures(BASE_FONT, new FontExtensions.Features(Map.of( - FontExtensions.FeatureTag.ZERO, FontExtensions.FEATURE_ON, - FontExtensions.FeatureTag.SALT, 123, - FontExtensions.FeatureTag.FRAC, 9999))); - return textDrawingEquals(fontWithFeatures(FontExtensions.FeatureTag.FRAC, FontExtensions.FeatureTag.ZERO, - FontExtensions.FeatureTag.SALT), font, TEST_STRING); - } - - @JBRTest - private static Boolean testLigature() { - Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON)); - return !textDrawingEquals(BASE_FONT, font, LIGATURES_STRING); - } - - // TODO include this test when will be found suitable example - // @JBRTest - private static Boolean testKerning() { - Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.KERNING, TextAttribute.KERNING_ON)); - return !textDrawingEquals(BASE_FONT, font, TEST_STRING); - } - - @JBRTest - private static Boolean testTracking() { - Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.TRACKING, 0.3f)); - return !textDrawingEquals(BASE_FONT, font, TEST_STRING); - } - - @JBRTest - private static Boolean testWeight() { - Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD)); - return !textDrawingEquals(BASE_FONT, font, TEST_STRING); - } - - @JBRTest - private static Boolean testDisablingFeatures() { - Font font = JBR.getFontExtensions().deriveFontWithFeatures(BASE_FONT, new FontExtensions.Features(Map.of( - FontExtensions.FeatureTag.ZERO, FontExtensions.FEATURE_OFF, - FontExtensions.FeatureTag.FRAC, FontExtensions.FEATURE_OFF))); - return textDrawingEquals(BASE_FONT, font, TEST_STRING); - } - - @JBRTest - private static Boolean testFeaturesZero() { - return !textDrawingEquals(BASE_FONT, fontWithFeatures(FontExtensions.FeatureTag.ZERO), ZERO_STRING); - } - - @JBRTest - private static Boolean testFeaturesFrac() { - return !textDrawingEquals(BASE_FONT, fontWithFeatures(FontExtensions.FeatureTag.FRAC), FRACTION_STRING); - } - - @JBRTest - private static Boolean testFeaturesDerive1() { - Font fontFZ1 = fontWithFeatures(FontExtensions.FeatureTag.FRAC, FontExtensions.FeatureTag.ZERO). - deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON)); - Font fontFZ2 = fontWithFeatures(BASE_FONT.deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON)), - FontExtensions.FeatureTag.FRAC, FontExtensions.FeatureTag.ZERO); - return textDrawingEquals(fontFZ1, fontFZ2, TEST_STRING); - } - - @JBRTest - private static Boolean testFeaturesDerive2() { - Font fontFZ1 = fontWithFeatures(FontExtensions.FeatureTag.FRAC, FontExtensions.FeatureTag.ZERO); - Font fontFZ2 = fontWithFeatures(BASE_FONT.deriveFont(Map.of(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON)), - FontExtensions.FeatureTag.FRAC, FontExtensions.FeatureTag.ZERO); - fontFZ2 = fontFZ2.deriveFont(Map.of(TextAttribute.LIGATURES, false)); - return textDrawingEquals(fontFZ1, fontFZ2, TEST_STRING); - } - - @JBRTest - private static Boolean testTrackingWithFeatures() { - Font font = BASE_FONT.deriveFont(Map.of(TextAttribute.TRACKING, 0.3f)); - return !textDrawingEquals(font, fontWithFeatures(font, FontExtensions.FeatureTag.ZERO), TEST_STRING); - } - - @JBRTest - private static Boolean testFeaturesEmpty() { - return textDrawingEquals(BASE_FONT, fontWithFeatures(), TEST_STRING); - } - - @JBRTest - private static Boolean getAvailableFeatures1() { - Set features = JBR.getFontOpenTypeFeatures().getAvailableFeatures(BASE_FONT); - Set expected = - Set.of(FontExtensions.FeatureTag.SS01, FontExtensions.FeatureTag.CV03, FontExtensions.FeatureTag.ZERO); - return features.containsAll(expected.stream().map(FontExtensions.FeatureTag::getName).toList()); - } - - @JBRTest - private static Boolean getAvailableFeatures2() { - Set features = JBR.getFontOpenTypeFeatures().getAvailableFeatures(new Font("Inconsolata", Font.PLAIN, 20)); - return features.isEmpty(); - } - - @JBRTest - private static Boolean getAvailableFeatures3() { - Set features = JBR.getFontOpenTypeFeatures().getAvailableFeatures(new Font("Inter", Font.PLAIN, 20)); - Set expected = - Set.of(FontExtensions.FeatureTag.SS01, FontExtensions.FeatureTag.SS02, FontExtensions.FeatureTag.SS03); - return features.containsAll(expected.stream().map(FontExtensions.FeatureTag::getName).toList()); - } - - public static void main(final String[] args) { - if (!JBR.isFontExtensionsSupported()) { - throw new RuntimeException("JBR FontExtension API is not available"); - } - - if (!JBR.isFontOpenTypeFeaturesSupported()) { - throw new RuntimeException("JBR FontOpenTypeFeatures API is not available"); - } - - String error = ""; - try { - for (final Method method : FontExtensionsTest.class.getDeclaredMethods()) { - if (method.isAnnotationPresent(JBRTest.class)) { - System.out.print("Testing " + method.getName() + "..."); - boolean statusOk = (boolean) method.invoke(null); - if (!statusOk) { - error += System.lineSeparator() + "Test failed: " + method.getName(); - } - System.out.println(statusOk ? "passed" : "failed"); - } - } - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("JBR: internal error during testing"); - } - - if (!error.isEmpty()) { - throw new RuntimeException(error); - } - } -} diff --git a/test/jdk/jb/java/api/frontend/FontMetricsAccessorTest.java b/test/jdk/jb/java/api/frontend/FontMetricsAccessorTest.java deleted file mode 100644 index 6e4d4331cb08..000000000000 --- a/test/jdk/jb/java/api/frontend/FontMetricsAccessorTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * 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 com.jetbrains.FontMetricsAccessor; -import com.jetbrains.JBR; - -import java.awt.*; -import java.awt.font.FontRenderContext; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; - -/** - * @test - * @summary verify the implementation of FontMetricsAccessor in JBR API - */ - -public class FontMetricsAccessorTest { - private static final FontMetricsAccessor ACCESSOR = JBR.getFontMetricsAccessor(); - private static final Font FONT = new Font(Font.SERIF, Font.ITALIC, 12); - private static final FontRenderContext CONTEXT = new FontRenderContext(AffineTransform.getScaleInstance(2, 2), - true, true); - - public static void main(final String[] args) { - if (!JBR.isFontMetricsAccessorSupported()) { - throw new RuntimeException("JBR FontMetricsAccessor API is not available"); - } - testGetMetricsInstance(); - testNotRoundedMetrics(); - testOverriding(); - testRemoveAllOverrides(); - } - - private static void testGetMetricsInstance() { - BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); - Graphics2D g = img.createGraphics(); - g.setTransform(CONTEXT.getTransform()); - g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, CONTEXT.getAntiAliasingHint()); - g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, CONTEXT.getFractionalMetricsHint()); - g.setFont(FONT); - - FontMetrics expectedFromGraphics = g.getFontMetrics(); - FontMetrics actualFromAccessor = ACCESSOR.getMetrics(g.getFont(), g.getFontRenderContext()); - if (actualFromAccessor != expectedFromGraphics) { - throw new RuntimeException("Font metrics instance doesn't match one obtained from Graphics2D"); - } - } - - private static void testNotRoundedMetrics() { - FontMetrics baseMetrics = ACCESSOR.getMetrics(FONT, CONTEXT); - for (char character = 'A'; character <= 'Z'; character++) { - int roundedAdvance = baseMetrics.charWidth(character); - float notRoundedAdvance = ACCESSOR.codePointWidth(baseMetrics, character); - if (Math.round(notRoundedAdvance) != roundedAdvance) { - throw new RuntimeException("Unexpected advance returned: notRoundedAdvance=" + notRoundedAdvance + - ", roundedAdvance=" + roundedAdvance + ", character=" + character); - } - } - - float baseAdvance = ACCESSOR.codePointWidth(baseMetrics, 'A'); - for (int scale = 2; scale <= 10; scale++) { - Font scaledFont = FONT.deriveFont(FONT.getSize2D() / scale); - float scaledAdvance = ACCESSOR.codePointWidth(ACCESSOR.getMetrics(scaledFont, CONTEXT), 'A'); - if (Math.abs(scaledAdvance * scale - baseAdvance) > 0.1) { - throw new RuntimeException("Unexpected advance returned: baseAdvance=" + baseAdvance + - ", scaledAdvance=" + scaledAdvance + ", scale=" + scale); - } - } - } - - private static void testOverriding() { - FontMetrics metrics = ACCESSOR.getMetrics(FONT, CONTEXT); - - if (ACCESSOR.hasOverride(metrics)) { - throw new RuntimeException("Override is reported incorrectly"); - } - - float aWidth = ACCESSOR.codePointWidth(metrics, 'A'); - float bWidth = ACCESSOR.codePointWidth(metrics, 'B'); - float bWidthOverride = bWidth * 2; - - ACCESSOR.setOverride(metrics, cp -> cp == 'B' ? bWidthOverride : Float.NaN); - - if (!ACCESSOR.hasOverride(metrics)) { - throw new RuntimeException("Override is not reported"); - } - - float aWidthAfterOverride = ACCESSOR.codePointWidth(metrics, 'A'); - float bWidthAfterOverride = ACCESSOR.codePointWidth(metrics, 'B'); - - if (aWidthAfterOverride != aWidth) { - throw new RuntimeException("Override works where it shouldn't: aWidthAfterOverride=" + aWidthAfterOverride + - ", aWidth=" + aWidth); - } - if (bWidthAfterOverride != bWidthOverride) { - throw new RuntimeException("Override doesn't work: bWidthAfterOverride=" + bWidthAfterOverride + - ", bWidthOverride=" + bWidthOverride); - } - - ACCESSOR.setOverride(metrics, null); - - if (ACCESSOR.hasOverride(metrics)) { - throw new RuntimeException("Override is reported after clearing"); - } - - float aWidthAfterReset = ACCESSOR.codePointWidth(metrics, 'A'); - float bWidthAfterReset = ACCESSOR.codePointWidth(metrics, 'B'); - - if (aWidthAfterReset != aWidth) { - throw new RuntimeException("Override has an effect after reset: aWidthAfterReset=" + aWidthAfterReset + - ", aWidth=" + aWidth); - } - if (bWidthAfterReset != bWidth) { - throw new RuntimeException("Override has an effect after reset: bWidthAfterReset=" + bWidthAfterReset + - ", bWidth=" + bWidth); - } - } - - private static void testRemoveAllOverrides() { - FontMetrics m1 = ACCESSOR.getMetrics(FONT, CONTEXT); - FontMetrics m2 = ACCESSOR.getMetrics(FONT.deriveFont(24f), CONTEXT); - - float m1Width = ACCESSOR.codePointWidth(m1, 'A'); - float m2Width = ACCESSOR.codePointWidth(m2, 'B'); - - ACCESSOR.setOverride(m1, cp -> 12345f); - ACCESSOR.setOverride(m2, cp -> 67890f); - ACCESSOR.removeAllOverrides(); - - if (ACCESSOR.hasOverride(m1) || ACCESSOR.hasOverride(m2)) { - throw new RuntimeException("Override is reported after clearing"); - } - - float m1WidthAfterReset = ACCESSOR.codePointWidth(m1, 'A'); - float m2WidthAfterReset = ACCESSOR.codePointWidth(m2, 'B'); - - if (m1WidthAfterReset != m1Width || m2WidthAfterReset != m2Width) { - throw new RuntimeException("Override has an effect after reset: m1Width=" + m1Width + ", m2Width=" + m2Width - + ", m1WidthAfterReset=" + m1WidthAfterReset + ", m2WidthAfterReset=" + m2WidthAfterReset); - } - } -} diff --git a/test/jdk/jb/java/api/frontend/JBRApiTest.java b/test/jdk/jb/java/api/frontend/JBRApiTest.java deleted file mode 100644 index 2bf4e17e93e5..000000000000 --- a/test/jdk/jb/java/api/frontend/JBRApiTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 - * @run main JBRApiTest - */ - -import com.jetbrains.JBR; -import com.jetbrains.JBRFileDialog; - -import java.awt.*; -import java.lang.reflect.Field; -import java.util.List; -import java.util.Objects; - -public class JBRApiTest { - - public static void main(String[] args) throws Exception { - if (!JBR.getApiVersion().matches("\\w+\\.\\d+\\.\\d+\\.\\d+")) throw new Error("Invalid JBR API version: " + JBR.getApiVersion()); - if (!JBR.isAvailable()) throw new Error("JBR API is not available"); - checkMetadata(); - testServices(); - } - - private static void checkMetadata() throws Exception { - Class metadata = Class.forName(JBR.class.getName() + "$Metadata"); - Field field = metadata.getDeclaredField("KNOWN_SERVICES"); - field.setAccessible(true); - List knownServices = List.of((String[]) field.get(null)); - if (!knownServices.contains("com.jetbrains.JBR$ServiceApi")) { - throw new Error("com.jetbrains.JBR$ServiceApi was not found in known services of com.jetbrains.JBR$Metadata"); - } - } - - private static void testServices() { - Objects.requireNonNull(JBR.getFontExtensions().getSubpixelResolution()); - Objects.requireNonNull(JBRFileDialog.get(new FileDialog((Frame) null))); - Objects.requireNonNull(JBR.getAccessibleAnnouncer()); - if (!System.getProperty("os.name").toLowerCase().contains("linux")) { - Objects.requireNonNull(JBR.getRoundedCornersManager()); - } - } -} diff --git a/test/jdk/jb/java/api/frontend/JstackTest.java b/test/jdk/jb/java/api/frontend/JstackTest.java deleted file mode 100644 index c6b7796d0c13..000000000000 --- a/test/jdk/jb/java/api/frontend/JstackTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * @test - * @summary Verifies that jstack includes whatever the supplier - * provided to Jstack.includeInfoFrom() returns. - * @library /test/lib - * @run main JstackTest - */ - -import com.jetbrains.JBR; - -import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; -import jdk.test.lib.JDKToolFinder; -import jdk.test.lib.Platform; - -import java.io.IOException; - -public class JstackTest { - final static String MAGIC_STRING = "Additional info:\nthis appears in jstack's output"; - - public static void main(String[] args) { - JBR.getJstack().includeInfoFrom( () -> MAGIC_STRING ); - long pid = ProcessHandle.current().pid(); - final OutputAnalyzer jstackOutput = runJstack(pid); - jstackOutput - .shouldHaveExitValue(0) - .shouldContain(MAGIC_STRING); - } - - static OutputAnalyzer runJstack(long pid) { - try { - final String JSTACK = JDKToolFinder.getTestJDKTool("jstack"); - final ProcessBuilder pb = new ProcessBuilder(JSTACK, String.valueOf(pid)); - OutputAnalyzer output = new OutputAnalyzer(pb.start()); - output.outputTo(System.out); - output.shouldHaveExitValue(0); - return output; - } catch (IOException e) { - throw new RuntimeException("Launching jstack failed", e); - } - } -} diff --git a/test/jdk/jb/java/api/frontend/TestSetRoundedCorners.java b/test/jdk/jb/java/api/frontend/TestSetRoundedCorners.java deleted file mode 100644 index 972266cebd80..000000000000 --- a/test/jdk/jb/java/api/frontend/TestSetRoundedCorners.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2023 JetBrains s.r.o. - * 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 com.jetbrains.JBR; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.lang.reflect.InvocationTargetException; - -/** - * @test - * @summary JBR-4563 Rounded corners for native Window on Mac OS - * @summary JBR-4787 Rounded corners for native Window on Windows 11 - * @author Alexander Lobas - * @requires (os.family == "mac" | os.family == "windows") - * @run main/manual TestSetRoundedCorners - */ -public class TestSetRoundedCorners { - private static final int TD_RED = 50; - private static final int TD = 0; - private static final int DELAY = 1000; - - private static TestSetRoundedCorners theTest; - - private final Robot robot; - private JFrame frame; - private JFrame testFrame; - - public TestSetRoundedCorners() { - try { - robot = new Robot(); - } catch (AWTException ex) { - throw new RuntimeException(ex); - } - } - - public void performTest(Object roundParams) { - if (!JBR.isRoundedCornersManagerSupported()) { - throw new RuntimeException("JBR Rounded API is not available"); - } - - runSwing(() -> { - frame = new JFrame(""); - frame.setUndecorated(true); - JPanel panel = new JPanel(); - panel.setOpaque(true); - panel.setBackground(Color.RED); - frame.setContentPane(panel); - frame.setBounds(100, 100, 300, 300); - frame.setVisible(true); - - testFrame = new JFrame(""); - testFrame.setUndecorated(true); - JPanel testPanel = new JPanel(); - testPanel.setOpaque(true); - testPanel.setBackground(Color.GREEN); - testFrame.setContentPane(testPanel); - testFrame.setBounds(150, 150, 50, 50); - testFrame.setVisible(true); - }); - - robot.delay(DELAY); - - // check that window without rounded corners - validateColor(51, 51, Color.GREEN, "0"); - validateColor(51, 99, Color.GREEN, "0"); - validateColor(99, 51, Color.GREEN, "0"); - validateColor(99, 99, Color.GREEN, "0"); - - runSwing(() -> testFrame.setVisible(false)); - robot.delay(DELAY); - - runSwing(() -> { - JBR.getRoundedCornersManager().setRoundedCorners(testFrame, roundParams); - testFrame.setVisible(true); - }); - robot.delay(DELAY); - - // check that window with rounded corners - validateColor(51, 51, Color.RED, "1"); - validateColor(51, 99, Color.RED, "1"); - validateColor(99, 51, Color.RED, "1"); - validateColor(99, 99, Color.RED, "1"); - - runSwing(() -> dispose()); - } - - private Color getTestPixel(int x, int y) { - Rectangle bounds = frame.getBounds(); - BufferedImage screenImage = robot.createScreenCapture(bounds); - int rgb = screenImage.getRGB(x, y); - int red = (rgb >> 16) & 0xFF; - int green = (rgb >> 8) & 0xFF; - int blue = rgb & 0xFF; - return new Color(red, green, blue); - } - - private static boolean validateColor(Color actual, Color expected) { - return Math.abs(actual.getRed() - expected.getRed()) <= TD_RED && - Math.abs(actual.getGreen() - expected.getGreen()) <= TD && - Math.abs(actual.getBlue() - expected.getBlue()) <= TD; - } - - private void validateColor(int x, int y, Color expected, String place) { - Color actual = getTestPixel(x, y); - if (!validateColor(actual, expected)) { - throw new RuntimeException( - "Test failed [" + place + "]. Incorrect color " + actual + " instead " + expected + " at (" + x + "," + y + ")"); - } - } - - private static void runSwing(Runnable r) { - try { - SwingUtilities.invokeAndWait(r); - } catch (InterruptedException e) { - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - public void dispose() { - if (frame != null) { - frame.dispose(); - frame = null; - } - if (testFrame != null) { - testFrame.dispose(); - testFrame = null; - } - } - - public static void runTest(Object roundParams) { - try { - runSwing(() -> theTest = new TestSetRoundedCorners()); - theTest.performTest(roundParams); - } finally { - if (theTest != null) { - runSwing(() -> theTest.dispose()); - } - } - } - - public static void main(String[] args) { - String osName = System.getProperty("os.name"); - if (osName.contains("Windows 11")) { - runTest("full"); - } else if (osName.contains("OS X")) { - runTest(10F); - } else { - System.out.println("This test is for MacOS or Windows 11 only. Automatically passed on other platforms."); - } - } -} diff --git a/test/jdk/jb/java/api/frontend/WindowMoveTest.java b/test/jdk/jb/java/api/frontend/WindowMoveTest.java deleted file mode 100644 index 7115f4038f1d..000000000000 --- a/test/jdk/jb/java/api/frontend/WindowMoveTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2000-2023 JetBrains s.r.o. - * 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 Verifies that WindowMove test service of JBR is supported - * on Linux and not supported elsewhere. - * @run main WindowMoveTest - * @run main/othervm -Djava.awt.headless=true WindowMoveTest - */ - -import com.jetbrains.JBR; -import java.awt.GraphicsEnvironment; - -public class WindowMoveTest { - - public static void main(String[] args) throws Exception { - final String os = System.getProperty("os.name"); - if ("linux".equalsIgnoreCase(os)) { - final boolean isHeadless = GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadlessInstance(); - if (JBR.isWindowMoveSupported()) { - if (isHeadless) - throw new RuntimeException("JBR.isWindowMoveSupported says it is supported in the headless mode"); - } else { - if (!isHeadless) - throw new RuntimeException("JBR.isWindowMoveSupported says it is NOT supported on Linux"); - } - // Use: JBR.getWindowMove().startMovingTogetherWithMouse(jframe, MouseEvent.BUTTON1); - } else { - if (JBR.isWindowMoveSupported()) { - throw new RuntimeException("JBR.isWindowMoveSupported says it's supported on " + os + "where it is NOT implemented"); - } - } - } -}