JBR-6357 JBR API v3

JBR API frontend is moved into a separate repository.
Rewritten proxy generation, bridges removed, invokedynamic is used instead.
Mapping is now specified using annotations.
Support for extension methods.
Support for arrays and generics.
Added JBR API implementation version.

JBR-7232 Refactor deriveFontWithFeatures & JBRFileDialog JBR API
This commit is contained in:
Nikita Gubarkov
2024-05-30 18:59:18 +02:00
committed by jbrbot
parent cf5d136b3e
commit a4804efa96
112 changed files with 3087 additions and 4890 deletions

1
.gitignore vendored
View File

@@ -20,3 +20,4 @@ NashornProfile.txt
/.settings/
/compile_commands.json
/.cache
/jbr-api/

1
jb/jbr-api.version Normal file
View File

@@ -0,0 +1 @@
0.0.0

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/src/jetbrains.api">
<sourceFolder url="file://$MODULE_DIR$/src/jetbrains.api/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/jetbrains.api/templates" isTestSource="false" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="inheritedJdk" />
</component>
</module>

View File

@@ -4,7 +4,6 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/jdk.iml" filepath="$PROJECT_DIR$/.idea/jdk.iml" />
###MODULE_IMLS###
<module fileurl="file://$PROJECT_DIR$/.idea/jetbrains.api.iml" filepath="$PROJECT_DIR$/.idea/jetbrains.api.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/test.iml" filepath="$PROJECT_DIR$/.idea/test.iml" />
</modules>
</component>

View File

@@ -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']"

View File

@@ -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 $?

View File

@@ -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 $?

View File

@@ -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 $?

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -100,6 +100,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), \

View File

@@ -87,7 +87,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 \
@@ -100,13 +100,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)
################################################################################

View File

@@ -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 := \
<?xml version="1.0" encoding="UTF-8"?> \
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" \
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> \
<modelVersion>4.0.0</modelVersion> \
<groupId>com.jetbrains</groupId> \
<artifactId>jbr-api</artifactId> \
<version>SNAPSHOT</version> \
</project> \
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

View File

@@ -170,6 +170,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
@@ -293,11 +294,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), )
@@ -493,7 +503,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) $$@

View File

@@ -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<String, TypeBinding> typeBindings = new HashMap<>();
final List<MethodBinding> 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<String, Type> types = new HashMap<>();
final Map<StaticDescriptor, StaticMethod> methods = new HashMap<>();
final Set<Object> 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<String> addBindings() {
List<String> unresolvedErrors = new ArrayList<>();
List<TypeBinding> addedTypes = new ArrayList<>();
List<MethodBinding> addedMethods = new ArrayList<>();
Set<Object> 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<String, String> 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<StaticDescriptor, StaticMethod> 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<Void, CompilationUnitTree>() {
@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);
}
}
}
});
}
}

View File

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

View File

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

View File

@@ -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> T internalService() {
var caller = (Class<T>) 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); }
}
}

View File

@@ -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<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation,
Map<Enum<?>, Class[]> knownExtensions,
Function<Method, Enum<?>> 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();
}
}

View File

@@ -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", "<init>", "(Ljava/lang/String;)V", false);
p.visitLdcInsn(message);
p.visitMethodInsn(INVOKESPECIAL, type, "<init>", "(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", "<init>", "(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;

View File

@@ -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<Class<?>, Boolean> accessibleClasses = new HashMap<>();
final Map<Proxy, Boolean> dependencies = new HashMap<>(); // true for required, false for optional
final List<DynamicCallTarget> dynamicCallTargets = new ArrayList<>();
final Lookup caller;
AccessContext(Lookup caller) {
this.caller = caller;
}
record DynamicCallTarget(String name, String descriptor, Supplier<MethodHandle> 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<MethodHandle> 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;
}
}

View File

@@ -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.
* <p>
* This class has most basic methods for working with JBR API and cache of generated proxies.
* <p>
* <h2>How to add a new service</h2>
* <ol>
* <li>Create your service interface in module {@link com.jetbrains.JBR jetbrains.api}:
* <blockquote><pre>{@code
* package com.jetbrains;
*
* interface StringOptimizer {
* void optimize(String string);
* }
* }</pre></blockquote>
* </li>
* <li>Create an implementation inside JBR:
* <blockquote><pre>{@link java.lang.String java.lang.String}:{@code
* private static void optimizeInternal(String string) {
* string.hash = 0;
* string.hashIsZero = true;
* }
* }</pre></blockquote>
* </li>
* <li>Register your service in corresponding
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES module registry class}:
* <blockquote><pre>{@link com.jetbrains.base.JBRApiModule}:{@code
* .service("com.jetbrains.StringOptimizer", null)
* .withStatic("optimize", "java.lang.String", "optimizeInternal")
* }</pre></blockquote>
* </li>
* <li>You can also bind the interface to implementation class
* (without actually implementing the interface):
* <blockquote><pre>{@link java.lang.String java.lang.String}:{@code
* private static class StringOptimizerImpl {
*
* private void optimize(String string) {
* string.hash = 0;
* string.hashIsZero = true;
* }
* }
* }</pre></blockquote>
* <blockquote><pre>{@link com.jetbrains.base.JBRApiModule}:{@code
* .service("com.jetbrains.StringOptimizer", "java.lang.String$StringOptimizerImpl")
* }</pre></blockquote>
* </li>
* </ol>
* <h2>How to add a new proxy</h2>
* Registering a proxy is similar to registering a service:
* <blockquote><pre>{@code
* .proxy("com.jetbrains.SomeProxy", "a.b.c.SomeProxyImpl")
* }</pre></blockquote>
* Note that unlike service, proxy <b>must</b> 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<String, RegisteredProxyInfo> registeredProxyInfoByInterfaceName = new HashMap<>();
private static final Map<String, RegisteredProxyInfo> registeredProxyInfoByTargetName = new HashMap<>();
private static final ConcurrentMap<Class<?>, 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<String> 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<DynamicCallTargetKey, Supplier<MethodHandle>> dynamicCallTargets = new ConcurrentHashMap<>();
private static final ProxyRepository proxyRepository = new ProxyRepository();
private static Boolean[] supportedExtensions;
private static long[] emptyExtensionsBitfield;
@SuppressWarnings("rawtypes")
private static Map<Enum<?>, Class[]> knownExtensions;
static Function<Method, Enum<?>> extensionExtractor;
@SuppressWarnings("rawtypes")
public static void init(InputStream extendedRegistryStream,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation,
Map<Enum<?>, Class[]> knownExtensions,
Function<Method, Enum<?>> 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> T getService(Class<T> 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> T getService(Class<T> interFace) {
Proxy<T> p = getProxy(interFace);
return p != null && p.isSupported() ? p.getInstance() : null;
return getService(interFace, emptyExtensionsBitfield, true);
}
public static <T> T getInternalService(Class<T> interFace) {
return getService(interFace, emptyExtensionsBitfield, false);
}
/**
* @return proxy for the given interface, or {@code null}
*/
@SuppressWarnings("unchecked")
static <T> Proxy<T> getProxy(Class<T> interFace) {
return (Proxy<T>) 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> T getService(Class<T> 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<Object>) () -> {
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<String, Module, Module> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}

View File

@@ -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<TypeVariable<?>, Mapping> tvMappings = new HashMap<>();
Context(ProxyRepository proxyRepository) {
this.proxyRepository = proxyRepository;
}
void initTypeParameters(Class<?> type, Stream<Mapping> typeParameters) {
if (type == null) return;
if (typeParameters != null) {
TypeVariable<?>[] tvs = type.getTypeParameters();
Iterator<Mapping> 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<Class<?>> 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;
}
}
}

View File

@@ -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.
* <p>
* There are 3 type of proxy objects:
* <ol>
* <li>{@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.</li>
* <li>{@linkplain ProxyInfo.Type#SERVICE Service} - singleton {@linkplain ProxyInfo.Type#PROXY proxy},
* may delegate calls only to static methods, without target object.</li>
* <li>{@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.</li>
* </ol>
* Mapping between interfaces and implementation code is defined using
* {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations.
* <p>
* 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:
* <blockquote><pre>{@code
* interface A {
* B foo(B b);
* }
* interface B {
* void bar();
* }
* class AImpl {
* BImpl foo(BImpl b) {}
* }
* class BImpl {
* void bar() {}
* }
* }</pre></blockquote>
* {@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:
* <blockquote><pre>{@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();
* }
* }
* }</pre></blockquote>
* <p>
* 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.
* <p>
* Mapping between interfaces and implementation code is defined in
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}.
* @param <INTERFACE> 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<INTERFACE> {
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<Proxy> directDependencies = Set.of();
private volatile Set<Enum<?>> 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<Class<?>> 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<Proxy> deps = new HashSet<>(directDependencies);
supported = generator.generate();
for (Map.Entry<Proxy, Boolean> 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.
* <ul>
* <li>For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.</li>
* <li>For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
* expecting target object to which it would delegate method calls.</li>
* </ul>
* 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<Proxy<?>> actualUsages) {
defineClasses();
if (generator != null) {
actualUsages.addAll(generator.getDirectProxyDependencies());
generator.init();
generator = null;
}
}
private synchronized void initDependencyGraph() {
defineClasses();
if (generator == null) return;
Set<Class<?>> dependencyClasses = ProxyDependencyManager.getProxyDependencies(info.interFace);
Set<Proxy<?>> dependencies = new HashSet<>();
Set<Proxy<?>> 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<StaticMethod, MethodHandle> 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));
}
}
}

View File

@@ -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.
* <p>
* 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.
* <p>
* Dependencies allow JBR to validate whole set of interfaces for
* a particular feature instead of treating them as separate entities.
* <h2>Example</h2>
* Suppose we implemented some feature and added some API for it:
* <blockquote><pre>{@code
* interface SomeFeature {
* SomeOtherObject createSomeObject(int magicNumber);
* }
* interface SomeOtherObject {
* int getMagicNumber();
* }
* }</pre></blockquote>
* And then used it:
* <blockquote><pre>{@code
* if (JBR.isSomeFeatureSupported()) {
* SomeOtherObject object = JBR.getSomeFeature().createSomeObject(123);
* int magic = object.getMagicNumber();
* }
* }</pre></blockquote>
* 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.
* <p>
* 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<Class<?>, Set<Class<?>>> cache = new ConcurrentHashMap<>();
/**
* @return all proxy interfaces that are used (directly or indirectly) by given interface, including itself.
*/
static Set<Class<?>> getProxyDependencies(Class<?> interFace) {
Set<Class<?>> 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<Class<?>> 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<Class<?>> members = new HashSet<>();
private final Set<Class<?>> 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<Class<?>> 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<Class<?>> 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<Class<?>> action) {
for (java.lang.reflect.Type t : types) collect(t, action);
}
}
}

View File

@@ -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.
* <p>
* There are 2 proxy dispatch modes:
* <ul>
* <li>interface -> proxy -> {@linkplain #generateBridge bridge} -> method handle -> implementation code</li>
* <li>interface -> proxy -> method handle -> implementation code</li>
* </ul>
* 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).
* <p>
* 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<Supplier<MethodHandle>> handles = new ArrayList<>();
private final List<Supplier<Class<?>>> classReferences = new ArrayList<>();
private final Set<Proxy<?>> directProxyDependencies = new HashSet<>();
private final List<Exception> 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<Enum<?>, 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<Proxy<?>> 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<Proxy, Boolean> getDependencies() {
return accessContext.dependencies;
}
Set<Enum<?>> 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.
* <ul>
* <li>For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.</li>
* <li>For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
* expecting target object to which it would delegate method calls.</li>
* </ul>
* 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, "<init>", "(" + 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, "<init>", "(" +
(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", "<init>", "()V", false);
p.visitInsn(RETURN);
p.visitMaxs(0, 0);
p.visitEnd();
m.visitMethodInsn(INVOKESPECIAL, superclassName, "<init>", "()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<MethodHandle> 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<MethodHandle> 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 <T> TypeMapping getTargetTypeMapping(Class<T> userType) {
TypeMappingMetadata m = new TypeMappingMetadata();
Proxy<T> 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<TypeMapping> {
@Override
public Iterator<TypeMapping> 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();
}
}

View File

@@ -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<String, StaticMethodMapping> 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;
}
}
}

View File

@@ -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<Key, Proxy> proxies = new HashMap<>();
void init(InputStream extendedRegistryStream,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> 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<StaticKey, StaticValue> 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<? extends Annotation> serviceAnnotation, providedAnnotation, providesAnnotation;
private Module annotationsModule;
private ClassLoader classLoader;
private final Map<String, Entry> 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<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> 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<? extends Annotation> a) {
return c.getAnnotation(a) != null;
}
}
}

View File

@@ -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<StaticMethodMapping> staticMethods) {
record StaticMethodMapping(String interfaceMethodName, String methodName, String[] classes) {}
}

View File

@@ -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<StackTraceElement>, Stream<StackTraceElement>>
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<StackTraceElement>, Stream<StackTraceElement>>
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<StackTraceElement>, Stream<StackTraceElement>> 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;
}
}

View File

@@ -27,6 +27,8 @@ package java.lang;
import java.io.*;
import java.util.*;
import com.jetbrains.exported.JBRApi;
import jdk.internal.access.SharedSecrets;
import jdk.internal.event.ThrowableTracer;
import jdk.internal.misc.InternalLock;
@@ -1161,7 +1163,7 @@ public class Throwable implements Serializable {
private static volatile java.util.function.Supplier<String> $$jb$additionalInfoSupplier = null;
// JBR API internals
@JBRApi.Provides("Jstack#includeInfoFrom")
private static void $$jb$additionalInfoForJstack(java.util.function.Supplier<String> supplier) {
$$jb$additionalInfoSupplier = supplier;
}

View File

@@ -1640,6 +1640,11 @@ abstract class MethodHandleImpl {
return IMPL_LOOKUP.serializableConstructor(decl, ctorToCall);
}
@Override
public Lookup lookupIn(Class<?> lookupClass) {
return IMPL_LOOKUP.in(lookupClass);
}
});
}

View File

@@ -170,4 +170,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);
}

View File

@@ -139,10 +139,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

View File

@@ -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;
@@ -65,11 +64,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;
@@ -83,7 +82,7 @@ import sun.security.action.GetPropertyAction;
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);
@@ -110,7 +109,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);
@@ -145,7 +144,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
@@ -297,17 +295,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
}
}
},
new Property<CPlatformWindow>(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<CPlatformWindow>(WINDOW_CORNER_RADIUS) {
public void applyProperty(final CPlatformWindow c, final Object value) {
c.setRoundedCorners(value);
@@ -379,8 +366,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
@@ -401,7 +386,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));
}
@@ -416,7 +401,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));
}
@@ -581,14 +566,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) {
@@ -1512,10 +1489,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));
}
// ----------------------------------------------------------------------
@@ -1549,28 +1525,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) {

View File

@@ -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();

View File

@@ -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<String, Integer> getFeatures(Font font);
boolean isComplexRendering(Font font);
boolean isKerning(Font font);
}
public static String featuresToString(Map<String, Integer> features) {
return features.entrySet().stream().map(feature -> (feature.getKey() + "=" + feature.getValue())).
collect(Collectors.joining(";"));
}
public static TreeMap<String, Integer> 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);
}
}

View File

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

View File

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

View File

@@ -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);

View File

@@ -49,6 +49,7 @@ import java.util.Objects;
import javax.swing.JMenuBar;
import com.jetbrains.exported.JBRApi;
import sun.awt.SunToolkit;
import sun.security.util.SecurityConstants;
@@ -1074,6 +1075,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;
@@ -1106,7 +1108,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) {

View File

@@ -48,17 +48,12 @@ import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
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.CreatedFontTracker;
import sun.font.Font2D;
@@ -284,6 +279,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 {
@@ -455,10 +472,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<String, Integer> features = new TreeMap<String, Integer>();
private String[] featureArray;
@Deprecated
private TreeMap<String, Integer> features; // Kept for compatibility
/**
* The platform specific font information.
@@ -561,10 +579,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.
@@ -628,18 +642,19 @@ public class Font implements java.io.Serializable
this.pointSize = size;
}
private Font(String name, int style, float sizePts, TreeMap<String, Integer> 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<String, Integer> features) {
Font2DHandle handle, boolean useOldHandle, String[] features) {
this(name, style, sizePts, features);
this.createdFont = created;
this.withFallback = withFallback;
@@ -704,9 +719,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<String, Integer> features) {
Font2DHandle handle, boolean useOldHandle, String[] features) {
this.features = features;
this.featureArray = features;
this.createdFont = created;
this.withFallback = withFallback;
if (created || withFallback) {
@@ -740,6 +755,7 @@ public class Font implements java.io.Serializable
this.font2DHandle = handle;
}
initFromValues(values);
this.hasLayoutAttributes |= this.featureArray != null;
}
/**
@@ -768,6 +784,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 {
@@ -776,15 +797,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<String, Integer> features) {
this(font);
this.features = features;
}
/**
@@ -834,7 +850,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);
}
/**
@@ -915,7 +931,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<String, Integer>());
font.font2DHandle, false, null);
}
return new Font(attributes);
}
@@ -927,7 +943,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<String, Integer>());
font.font2DHandle, false, null);
}
return font;
@@ -1648,17 +1664,7 @@ public class Font implements java.io.Serializable
* @since 1.6
*/
public boolean hasLayoutAttributes() {
return anyEnabledFeatures() || hasLayoutAttributes;
}
private static TreeMap<String, Integer> getFeatures(Font font) {
TreeMap<String, Integer> 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;
}
/**
@@ -1866,7 +1872,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
@@ -1905,7 +1911,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
@@ -2011,6 +2017,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
@@ -2029,17 +2040,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;
}
/**
@@ -2143,14 +2151,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);
}
/**
@@ -2170,7 +2178,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);
}
/**
@@ -2183,12 +2191,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);
}
/**
@@ -2205,7 +2213,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);
}
/**
@@ -2218,13 +2226,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);
}
/*
@@ -2262,15 +2270,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<String, Integer> getAsTreeMap();
}
@JBRApi.Provides("FontExtensions#deriveFontWithFeatures")
@Deprecated(forRemoval = true)
private static Font deriveFont(Font font, Features features) {
return new Font(font, features.getAsTreeMap());
TreeMap<String, Integer> map = features.getAsTreeMap();
String[] array = new String[map.size()];
int i = 0;
for (Map.Entry<String, Integer> 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);
}
/**
@@ -2724,20 +2779,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<String> getAvailableFeatures(Font font) {
return SunLayoutEngine.getAvailableFeatures(FontUtilities.getFont2D(font));
}

View File

@@ -31,6 +31,7 @@ import java.security.PrivilegedAction;
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;
@@ -114,12 +115,12 @@ public abstract class GraphicsEnvironment {
return LocalGE.INSTANCE;
}
// JBR API internals
@JBRApi.Provides("ProjectorUtils")
private static void setLocalGraphicsEnvironmentProvider(Supplier<GraphicsEnvironment> geProvider) {
graphicsEnvironmentProvider = geProvider;
}
// JBR API internals
@JBRApi.Provides("ProjectorUtils#overrideGraphicsEnvironment")
private static void overrideLocalGraphicsEnvironment(GraphicsEnvironment overriddenGE) {
setLocalGraphicsEnvironmentProvider(() -> overriddenGE);
}

View File

@@ -47,7 +47,6 @@ 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.security.AccessController;
@@ -55,7 +54,6 @@ 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;
@@ -71,7 +69,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.AWTPermissions;
import sun.awt.AppContext;
@@ -4022,7 +4020,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); }
@@ -4030,7 +4030,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;
@@ -4125,8 +4126,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);
}
@@ -4162,28 +4162,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;
}
@@ -4195,111 +4173,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<Map.Entry<Shape, Integer>> 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<Map.Entry<Shape, Integer>> spots) {
window.customDecorHitTestSpots = List.copyOf(spots);
}
List<Map.Entry<Shape, Integer>> 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;

View File

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

View File

@@ -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<FontDesignMetrics> 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);
}
}

View File

@@ -33,6 +33,7 @@ import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.swing.plaf.FontUIResource;
import com.jetbrains.exported.JBRApi;
import sun.awt.OSInfo;
import sun.util.logging.PlatformLogger;
@@ -136,7 +137,8 @@ public final class FontUtilities {
});
}
static Dimension getSubpixelResolution() {
@JBRApi.Provides("FontExtensions")
private static Dimension getSubpixelResolution() {
return subpixelResolution;
}

View File

@@ -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<String, Integer> 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<String, Integer> features) {
void layout(boolean ltrDirection, String[] features) {
_textRecord.start = start;
_textRecord.limit = limit;
engine.layout(_sd, _mat, ptSize, slot, slotShift, start - _offset, _textRecord,

View File

@@ -30,7 +30,6 @@
package sun.font;
import com.jetbrains.desktop.FontExtensions;
import sun.font.GlyphLayout.*;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
@@ -41,7 +40,6 @@ import java.lang.ref.SoftReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.WeakHashMap;
@@ -187,7 +185,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<String, Integer> features,
int baseIndex, TextRecord tr, boolean ltrDirection, String[] features,
Point2D.Float pt, GVData data) {
Font2D font = key.font();
@@ -198,7 +196,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 {
@@ -207,7 +205,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);
}
}

View File

@@ -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) {

View File

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

View File

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

View File

@@ -34,7 +34,6 @@ import java.awt.peer.WindowPeer;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import com.jetbrains.internal.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.AWTAccessor.ComponentAccessor;
import sun.awt.DisplayChangedListener;
@@ -2662,42 +2661,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");
}
}
}
}

View File

@@ -35,6 +35,7 @@ import java.awt.peer.ComponentPeer;
import java.awt.peer.FramePeer;
import java.security.AccessController;
import com.jetbrains.exported.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.im.InputMethodManager;
import sun.security.action.GetPropertyAction;
@@ -265,7 +266,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,

View File

@@ -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;
@@ -839,7 +839,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) {

View File

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

View File

@@ -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:
* <ul>
* <li>{@link #NO_HIT_SPOT} - default title bar area, can be dragged to move a window, maximizes on double-click</li>
* <li>{@link #OTHER_HIT_SPOT} - generic hit spot, window cannot be moved with drag or maximized on double-click</li>
* <li>{@link #MINIMIZE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip on Windows when hovered</li>
* <li>{@link #MAXIMIZE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip / snap layout menu on Windows when hovered</li>
* <li>{@link #CLOSE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip on Windows when hovered</li>
* <li>{@link #MENU_BAR} - currently no different from {@link #OTHER_HIT_SPOT}</li>
* <li>{@link #DRAGGABLE_AREA} - special type, moves window when dragged, but doesn't maximize on double-click</li>
* </ul>
* @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<Map.Entry<Shape, Integer>> spots);
/**
* @see #setCustomDecorationHitTestSpots(Window, List)
*/
List<Map.Entry<Shape, Integer>> 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);
}

View File

@@ -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(); }
}
}

View File

@@ -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();
}

View File

@@ -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
* <a href=https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist>documentation</a> <br>
* 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}:
* <ul>
* <li>Attribute {@link java.awt.font.TextAttribute#KERNING} manages KERN feature</li>
* <li>Attribute {@link java.awt.font.TextAttribute#LIGATURES} manages LIGA and CALT features</li>
* </ul>
*/
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<FeatureTag> getFeatureTag(String str) {
try {
return Optional.of(FeatureTag.valueOf(str.toUpperCase()));
} catch (IllegalArgumentException ignored) {
return Optional.empty();
}
}
}
final class Features extends TreeMap<FeatureTag, Integer> {
@Serial
private static final long serialVersionUID = 1L;
public Features(Map<FontExtensions.FeatureTag, Integer> map) {
super(map);
}
public Features(FontExtensions.FeatureTag... features) {
Arrays.stream(features).forEach(tag -> put(tag, FontExtensions.FEATURE_ON));
}
private TreeMap<String, Integer> getAsTreeMap() {
TreeMap<String, Integer> 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();
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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() {}
}
}

View File

@@ -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<String> getAvailableFeatures(Font font);
}

View File

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

View File

@@ -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.
* <div>JBR API consists of two parts:</div>
* <ul>
* <li>Client side - {@code jetbrains.api} module, mostly containing interfaces</li>
* <li>JBR side - actual implementation code inside JBR</li>
* </ul>
* 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.
* <h2>Simple usage example:</h2>
* <blockquote><pre>{@code
* if (JBR.isSomeServiceSupported()) {
* JBR.getSomeService().doSomething();
* } else {
* planB();
* }
* }</pre></blockquote>
* @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> T getService(Class<T> interFace, FallbackSupplier<T> fallback) {
T service = getService(interFace);
try {
return service != null ? service : fallback != null ? fallback.get() : null;
} catch (Throwable ignore) {
return null;
}
}
static <T> T getService(Class<T> 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> T getService(Class<T> interFace);
}
@FunctionalInterface
private interface FallbackSupplier<T> {
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*/
}

View File

@@ -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:
* <ul>
* <li>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</li>
* <li>CREATE_DIRECTORIES_HINT - whether to allow creating directories or not (macOS)</li>
* </ul>
*/
void setHints(int hints);
/**
* @see #setHints(int)
*/
int getHints();
/*CONST com.jetbrains.desktop.JBRFileDialog.*_KEY*/
/**
* Change text of UI elements (Windows).
* Supported keys:
* <ul>
* <li>OPEN_FILE_BUTTON_KEY - "open" button when a file is selected in the list</li>
* <li>OPEN_DIRECTORY_BUTTON_KEY - "open" button when a directory is selected in the list</li>
* <li>ALL_FILES_COMBO_KEY - "all files" item in the file filter combo box</li>
* </ul>
*/
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);
}

View File

@@ -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<String> infoSupplier);
}

View File

@@ -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<GraphicsEnvironment> geProvider);
void overrideGraphicsEnvironment(GraphicsEnvironment overriddenGE);
}

View File

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

View File

@@ -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<String, Object> getProperties();
/**
* @see #putProperty(String, Object)
*/
void putProperties(Map<String, ?> m);
/**
* Windows & macOS properties:
* <ul>
* <li>{@code controls.visible} : {@link Boolean} - whether title bar controls
* (minimize/maximize/close buttons) are visible, default = true.</li>
* </ul>
* Windows properties:
* <ul>
* <li>{@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.</li>
* <li>{@code controls.dark} : {@link Boolean} - whether to use dark or light color theme
* (light or dark icons respectively).</li>
* <li>{@code controls.<layer>.<state>} : {@link Color} - precise control over button colors,
* where {@code <layer>} is one of:
* <ul><li>{@code foreground}</li><li>{@code background}</li></ul>
* and {@code <state>} is one of:
* <ul>
* <li>{@code normal}</li>
* <li>{@code hovered}</li>
* <li>{@code pressed}</li>
* <li>{@code disabled}</li>
* <li>{@code inactive}</li>
* </ul>
* </ul>
*/
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.
* <p>
* This method gives you precise control of whether to allow native title bar actions or not.
* <ul>
* <li>{@code client=true} means that mouse is currently over a client area. Native title bar behavior is disabled.</li>
* <li>{@code client=false} means that mouse is currently over a non-client area. Native title bar behavior is enabled.</li>
* </ul>
* <em>Intended usage:
* <ul>
* <li>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}.</li>
* <li>This method is called per-event, i.e. when component has multiple listeners, you only need to call it once.</li>
* <li>If this method hadn't been called, title bar behavior is reverted back to default upon processing the event.</li>
* </ul></em>
* 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.
*
* <h2>Example:</h2>
* 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.
* <pre>
* 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);
* </pre>
*/
void forceHitTest(boolean client);
Window getContainingWindow();
}
}

View File

@@ -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:
* <ul>
* <li>WM supports _NET_WM_MOVE_RESIZE (this is checked automatically when an implementation
* of this interface is obtained).</li>
* <li>Mouse pointer is within this window's bounds.</li>
* <li>The mouse button specified by {@code mouseButton} is pressed.</li>
* </ul>
*
* 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);
}

View File

@@ -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;
}

View File

@@ -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<String, Function<String, String>> 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;
/**
* <ul>
* <li>$0 - absolute path to {@code JetBrainsRuntime/src/jetbrains.api} dir</li>
* <li>$1 - absolute path to gensrc dir ({@code JetBrainsRuntime/build/<conf>/jbr-api/gensrc})</li>
* <li>$2 - true if hash mismatch is an error</li>
* </ul>
*/
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<Entry> findFiles(Path dir) throws IOException {
List<Entry> files = new ArrayList<>();
FileVisitor<Path> 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) {}
}
}

View File

@@ -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;
/**
* <ul>
* <li>$0 - absolute path to {@code JetBrainsRuntime/src} dir</li>
* <li>$1 - absolute path to jbr-api output dir ({@code JetBrainsRuntime/build/<conf>/jbr-api})</li>
* <li>$2 - {@code JBR} part of API version</li>
* </ul>
*/
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<String> 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<String> 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<String> 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 {<DEPRECATED>
private static final $ INSTANCE = getService($.class, <FALLBACK>);
}
/**
* @return true if current runtime has implementation for all methods in {@link $}
* and its dependencies (can fully implement given service).
* @see #get$()
*/<DEPRECATED>
public static boolean is$Supported() {
return $__Holder.INSTANCE != null;
}
/**<JAVADOC>
* @return full implementation of {@link $} service if any, or {@code null} otherwise
*/<DEPRECATED>
public static $ get$() {
return $__Holder.INSTANCE;
}
"""
.replace("<FALLBACK>", service.hasFallback ? "$.__Fallback::new" : "null")
.replaceAll("\\$", service.name)
.replace("<JAVADOC>", service.javadoc)
.replaceAll("<DEPRECATED>", 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<String> 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);
}
}
}

View File

@@ -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

View File

@@ -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<Method, Enum<?>>) 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");
}
}

View File

@@ -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<String> 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<Class<?>> 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<Class<?>> getProxyDependencies(Class<?> interFace) throws Throwable {
return (Set<Class<?>>) getProxyDependencies.invoke(null, interFace);
}
}

View File

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

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View File

@@ -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 -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<Api2Way> 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 {

View File

@@ -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

View File

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

View File

@@ -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();
}

View File

@@ -0,0 +1,6 @@
package com.jetbrains;
public enum Extensions {
FOO,
BAR
}

View File

@@ -28,13 +28,5 @@ package com.jetbrains;
*/
public class JBR {
public interface ServiceApi {
<T> T getService(Class<T> interFace);
}
static final class Metadata {
static String[] KNOWN_SERVICES = {};
static String[] KNOWN_PROXIES = {};
}
public interface ServiceApi {}
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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<Enum<?>, 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<? extends Throwable>... exceptionTypeChain) {
try {
action.run();
} catch(Throwable exception) {
Throwable e = exception;
error: {
for (Class<? extends Throwable> 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;
}
}

View File

@@ -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 {}
}

View File

@@ -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 {}
}

View File

@@ -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<Conversion, ArrayConversion> convert();
}
public interface ImplicitGeneric<R, P> {
R apply(P p);
}
}

View File

@@ -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();
}
}

Some files were not shown because too many files have changed in this diff Show More