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"); - } - } - } -}