Compare commits

...

18 Commits
106 ... 120

Author SHA1 Message Date
Vitaly Provodin
d3e2151a05 move the script comparing performance results from jdk8u_test 2021-10-14 09:24:28 +07:00
Vitaly Provodin
9141bec0d9 exclude java/awt/Frame/HugeFrame/HugeFrame.java crashing GNOME on Ubuntu-21.04 2021-10-13 09:58:43 +07:00
Vitaly Provodin
3c5524a0ce exclude java/awt/Frame/HugeFrame/HugeFrame.java crashing GNOME on Ubuntu-21.04 2021-10-12 13:52:49 +07:00
Vitaly Provodin
da6e21563c exclude sun/java2d/ClassCastExceptionForInvalidSurface.java crashing GNOME on Ubuntu-21.04 2021-10-12 07:03:14 +07:00
Nikita Provotorov
10c7694d60 fixup! JBR-3838 AltGr on Polish keyboard triggers Ctrl+Alt shortcut.
Workaround for the regression test when it misses a focus for the awt.Component.
2021-10-06 18:09:50 +07:00
Maxim Kartashev
59ea9d809d JBR-3833 ServerSocketChannel.open(StandardProtocolFamily.UNIX) fails with IAE with non-standard filesystems on JBR17
Lazy-initialize the static member UNNAMED of UnixDomainSockets so that this
initialization doesn't throw unless actually used. This is only
necessary when using a non-default file system.
2021-10-05 16:00:27 +03:00
Alexey Ushakov
ab146e18bd JBR-3740 JBR17: Font metrics problem in text fields
Aligned code with openjdk (JDK-8263583)
2021-10-05 13:45:03 +02:00
Nikita Gubarkov
0ca9b169b0 Added JBR API 2021-10-05 02:21:20 +03:00
Maxim Kartashev
289f756f70 JBR-3835 Cropped messages in all Message Dialogs in Idea on Ubuntu 18.04.5 LTS with swing alerts enabled
The _NET_FRAME_EXTENTS property that is used to obtain the initial
insets of a dialog window does not immediately get its value and may be
returned as 0 if queried too soon after the window creation.

In order to avoid (incorrect) guessing of dialog's insets, make 3
attempts at getting the insets with a small but increasing pause
in between them.
2021-10-04 12:15:04 -07:00
Nikita Provotorov
ba84093c7d JBR-3838 AltGr on Polish keyboard triggers Ctrl+Alt shortcut.
Add regression test.

(cherry picked from commit 8df43eef4b)
2021-10-03 13:24:39 +07:00
Vitaly Provodin
46702cad3c fix name for -windows-test-x64 tarball 2021-10-03 05:44:17 +07:00
Artem Semenov
ac8ffa0ba7 8274381: missing CAccessibility definitions in JNI code
Reviewed-by: pbansal, ant, kizune
2021-10-02 08:55:36 +07:00
Alexey Ushakov
fae062d45f JBR-3820 Gamma correction for grayscale text in Metal rendering pipeline
Implemented gamma correction using the same approach that we did for OGL grayscale text rendering (OGLTextRenderer.c). Applied some optimisations to shader code.
2021-10-01 14:11:10 +02:00
Artem Semenov
0e87047b7e 8274383: JNI call of getAccessibleSelection on a wrong thread
Reviewed-by: kizune, ant
2021-09-30 16:17:18 +07:00
Vitaly Provodin
0106ef8fef add the link to the latest JBR release 2021-09-29 11:27:53 +07:00
Vitaly Provodin
a69031c54b a11y: exclude tests crashing JBR in the runs with enabled Voice Over 2021-09-29 05:49:39 +07:00
Nikita Gubarkov
8b96b1e115 Remove old JBR API 2021-09-29 00:04:48 +03:00
Maxim Kartashev
48186ebbfb JBR-3813 Regression after fix for JBR-3688
1. Cached bounds and insets must be cloned before return because they
aren't immutable objects.
2. Fixed the deadlock in resetBoundsCache() by synchronizing on a dedicated
lock.

(cherry picked from commit cd5314db8b)
2021-09-27 11:53:08 -07:00
77 changed files with 3973 additions and 449 deletions

View File

@@ -5,6 +5,14 @@
JetBrains Runtime is a fork of [OpenJDK](https://github.com/openjdk/jdk) available for Windows, Mac OS X, and Linux.
It includes a number enhancements in font rendering, HiDPI support, ligatures, performance improvements, and bugfixes.
## Releases
Download the latest releases of JetBrains Runtime to use with JetBrains IDEs. The full list
can be found on the [releases page](https://github.com/JetBrains/JetBrainsRuntime/releases).
| IDE Version | Latest JBR | Date Released |
| --- | --- | --- |
| 2021.3 | [17-b106.1](https://github.com/JetBrains/JetBrainsRuntime/releases/tag/jbr17b106.1) | 28-Sep-2021 |
## Contents
- [Welcome to JetBrains Runtime](#jetbrains-runtime)
- [Products Built on JetBrains Runtime](#products-built-on-jetbrains-runtime)

View File

@@ -0,0 +1,13 @@
<?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

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<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>
</project>

View File

@@ -0,0 +1,16 @@
#!/bin/sh
# $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

@@ -1,56 +0,0 @@
#!/bin/bash -x
# How to call this script:
# eval $(jb/project/tools/mkjbrapi.sh)
# It is used to build jetbrains.api module
# After properly calling this script, you can use following variables:
# JBR_API_JAR - absolute path to resulting JAR
# JBR_API_SOURCES_JAR - absolute path to JAR with sources
# JBR_API_VERSION - JBR API version in form <major>.<minor>
# JBR_API_VERSION_MAJOR, JBR_API_VERSION_MINOR - JBR API version components
# JBR_BUILD_DIR - absolute path to JBR build directory
# JBR_BOOT_JDK - absolute path to used boot JDK
ROOT=$(pwd)
sh configure --with-debug-level=release --disable-warnings-as-errors 1>&2 || exit $?
# Get boot JDK & build directory using make script
make -f $ROOT/make/JBRApi.gmk -I $ROOT jbr-api MAKEOVERRIDES= CONF=release OUT="$ROOT/build/jbr-api.cfg" 1>&2 || exit $?
source "$ROOT/build/jbr-api.cfg" || exit $?
# Build module
make jetbrains.api 1>&2 || exit $?
# Get JBR API version from compiled class
JSHELL_COMMAND='
System.out.println("\nVERSION_MAJOR=" + com.jetbrains.JBRApi.getMajorVersion());
System.out.println("\nVERSION_MINOR=" + com.jetbrains.JBRApi.getMinorVersion());
/exit'
VERSION_VARIABLES=$("$BOOT_JDK/bin/jshell" -s --module-path "$BUILD_DIR/jdk/modules/jetbrains.api" \
--add-modules jetbrains.api <<< "$JSHELL_COMMAND" | grep "^VERSION\|^|") || exit $?
eval "$VERSION_VARIABLES" || exit $?
# Create JAR
(
cd "$BUILD_DIR/jdk/modules/jetbrains.api"
"$BOOT_JDK/bin/jar" -cf "$BUILD_DIR/jbr-api.jar" * 1>&2
) || exit $?
# Create source JAR
(
cd "src/jetbrains.api/share/classes"
"$BOOT_JDK/bin/jar" -cf "$BUILD_DIR/jbr-api-sources.jar" * 1>&2
) || exit $?
# Print output values
echo "JBR_API_JAR=$BUILD_DIR/jbr-api.jar"
echo "JBR_API_SOURCES_JAR=$BUILD_DIR/jbr-api-sources.jar"
echo "JBR_API_VERSION=$VERSION_MAJOR.$VERSION_MINOR"
echo "JBR_API_VERSION_MAJOR=$VERSION_MAJOR"
echo "JBR_API_VERSION_MINOR=$VERSION_MINOR"
echo "JBR_BUILD_DIR=$BUILD_DIR"
echo "JBR_BOOT_JDK=$BOOT_JDK"
echo "Success!" 1>&2

View File

@@ -1,11 +1,11 @@
diff --git modules.list modules.list
index dcf610a6a56..f8797505c23 100644
index 054f21d1ee0..d9a121f0273 100644
--- modules.list
+++ modules.list
@@ -51,4 +51,7 @@ jdk.zipfs,
@@ -49,4 +49,7 @@ jdk.unsupported,
jdk.xml.dom,
jdk.zipfs,
jdk.hotspot.agent,
jetbrains.api,
jetbrains.api.impl,
-jdk.jcmd
+jdk.jcmd,
+jcef,

View File

@@ -0,0 +1,83 @@
#!/bin/bash -x
usage ()
{
echo "Usage: perfcmp.sh [options] <test_results_cur> <test_results_ref> <results> <test_prefix> <noHeaders>"
echo "Options:"
echo -e " -h, --help\tdisplay this help"
echo -e " -tc\tprint teacmity statistic"
echo -e "test_results_cur - the file with metrics values for the current measuring"
echo -e "test_results_ref - the file with metrics values for the reference measuring"
echo -e "results - results of comaprison"
echo -e "test_prefix - specifys measuring type, makes sense for enabled -tc, by default no prefixes"
echo -e "noHeaders - by default 1-st line contains headers"
echo -e ""
echo -e "test_results_* files content should be in csv format with header and tab separator:"
echo -e "The 1-st column is the test name"
echo -e "The 2-st column is the test value"
echo -e ""
echo -e "Example:"
echo -e "Test Value"
echo -e "Testname 51.54"
}
while [ -n "$1" ]
do
case "$1" in
-h | --help) usage
exit 1 ;;
-tc) tc=1
shift
break ;;
*) break;;
esac
done
if [[ "$#" < "3" ]]; then
echo "Error: Invalid arguments"
usage
exit 1
fi
curFile=$1
refFile=$2
resFile=$3
testNamePrefix=$4
noHeaders=$5
echo $curFile
echo $refFile
echo $resFile
curValues=`cat "$curFile" | cut -f 2 | tr -d '\t'`
if [ -z noHeaders ]; then
curValuesHeader=`echo "$curValues" | head -n +1`_cur
header=`cat "$refFile" | head -n +1 | awk -F'\t' -v x=$curValuesHeader '{print " "$1"\t"$2"_ref\t"x"\tratio"}'`
testContent=`paste -d '\t' $refFile <(echo "$curValues") | tail -n +2`
else
testContent=`paste -d '\t' $refFile <(echo "$curValues") | tail -n +1`
fi
testContent=`echo "$testContent" | awk -F'\t' '{ if ($3>$2+$2*0.1) {print "* "$1"\t"$2"\t"$3"\t"(($2==0)?"-":$3/$2)} else {print " "$1"\t"$2"\t"$3"\t"(($2==0)?"-":$3/$2)} }'`
if [ -z noHeaders ]; then
echo "$header" > $resFile
fi
echo "$testContent" >> $resFile
cat "$resFile" | tr '\t' ';' | column -t -s ';' | tee $resFile
if [ -z $tc ]; then
exit 0
fi
echo "$testContent" 2>&1 | (
while read -r s; do
testname=`echo "$s" | cut -f 1 | tr -d "[:space:]" | tr -d "*"`
duration=`echo "$s" | cut -f 3`
failed=`echo "$s" | cut -c1 | grep -c "*"`
echo \#\#teamcity[testStarted name=\'$testNamePrefix$testname\']
echo "===>$s"
echo \#\#teamcity[buildStatisticValue key=\'$testNamePrefix$testname\' value=\'$duration\']
[ $failed -eq 1 ] && echo \#\#teamcity[testFailed name=\'$testNamePrefix$testname\' message=\'$s\']
echo \#\#teamcity[testFinished name=\'$testNamePrefix$testname\' duration=\'$duration\']
failed=0
done
)

View File

@@ -42,7 +42,6 @@ function pack_jbr {
JBRSDK_BUNDLE=jbrsdk
RELEASE_NAME=windows-x86_64-server-release
IMAGES_DIR=build/$RELEASE_NAME/images
JBSDK=$JBRSDK_BASE_NAME-windows-x64-b$build_number
BASE_DIR=.
if [ "$bundle_type" == "jcef" ] || [ "$bundle_type" == "dcevm" ] || [ "$bundle_type" == "fd" ]; then
@@ -53,7 +52,7 @@ pack_jbr jbr${jbr_name_postfix} jbr
pack_jbr jbrsdk${jbr_name_postfix} jbrsdk
if [ -z "$bundle_type" ]; then
JBRSDK_TEST=$JBRSDK_BASE_NAME-windows-test-x64-b$build_number
JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-x64-b$build_number
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

@@ -1,20 +1,80 @@
include Makefile
include make/MainSupport.gmk
#
# Copyright 2000-2021 JetBrains s.r.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
.PHONY: jbr-api
include $(SPEC)
include MakeBase.gmk
include JavaCompilation.gmk
ifeq ($(SPEC),)
ifneq ($(words $(SPECS)),1)
@echo "Error: Multiple build specification files found. Please select one explicitly."
@exit 2
endif
jbr-api:
@cd $(topdir)
@$(MAKE) $(MFLAGS) $(MAKE_LOG_FLAGS) -r -R -j 1 -f $(topdir)/make/JBRApi.gmk SPEC=$(SPECS) HAS_SPEC=true ACTUAL_TOPDIR=$(topdir) MODULES="$(MODULES)" jbr-api
else #with SPEC
JBR_API_ROOT_DIR := $(TOPDIR)/src/jetbrains.api
JBR_API_GENSRC_TEMPLATES := $(JBR_API_ROOT_DIR)/templates
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:
$(ECHO) "BUILD_DIR=$(OUTPUTDIR)" > $(OUT)
$(ECHO) "BOOT_JDK=\"$(BOOT_JDK)\"" >> $(OUT)
JBR_API_GENSRC_SOURCES := $(call FindFiles, $(JBR_API_GENSRC_TEMPLATES))
JBR_API_GENSRC_FILES := $(foreach f, $(call FindFiles, $(JBR_API_GENSRC_TEMPLATES)/com), \
$(JBR_API_GENSRC_DIR)/$(call RelativePath, $f, $(JBR_API_GENSRC_TEMPLATES)))
JBR_API_SRC_FILES := $(call FindFiles, $(JBR_API_SRC_DIR))
ifeq ($(JBR_API_JBR_VERSION),)
JBR_API_JBR_VERSION := <DEVELOPMENT>
JBR_API_FAIL_ON_HASH_MISMATCH := false
else
.PHONY: $(JBR_API_VERSION_PROPERTIES)
JBR_API_FAIL_ON_HASH_MISMATCH := true
endif
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) $(JBR_API_SRC_DIR), \
EXTRA_FILES := $(JBR_API_GENSRC_FILES), \
BIN := $(JBR_API_BIN_DIR), \
JAR := $(JBR_API_OUTPUT_DIR)/jbr-api.jar, \
))
$(eval $(call SetupJarArchive, BUILD_JBR_API_SOURCES_JAR, \
DEPENDENCIES := $(JBR_API_GENSRC_FILES) $(JBR_API_SRC_FILES), \
SRCS := $(JBR_API_GENSRC_DIR) $(JBR_API_SRC_DIR), \
JAR := $(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar, \
SUFFIXES := .java, \
BIN := $(JBR_API_BIN_DIR), \
))
$(JBR_API_GENSRC_FILES): $(JBR_API_GENSRC_SOURCES) $(JBR_API_SRC_FILES) $(JBR_API_VERSION_PROPERTIES)
$(ECHO) Generating sources for JBR API
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_GENSRC_TEMPLATES)/Gensrc.java" \
"$(TOPDIR)/src" "$(JBR_API_OUTPUT_DIR)" "$(JBR_API_JBR_VERSION)"
jbr-api-check-version: $(JBR_API_GENSRC_FILES) $(JBR_API_SRC_FILES) $(JBR_API_VERSION_PROPERTIES)
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_GENSRC_TEMPLATES)/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_OUTPUT_DIR)/jbr-api.version`" > $(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

View File

@@ -1325,6 +1325,14 @@ create-main-targets-include:
@$(ECHO) ALL_MAIN_TARGETS := $(sort $(ALL_TARGETS)) > \
$(MAKESUPPORT_OUTPUTDIR)/main-targets.gmk
################################################################################
# JBR API
$(eval $(call SetupTarget, jbr-api, \
MAKEFILE := JBRApi, \
TARGET := jbr-api \
))
################################################################################
# Hook to include the corresponding custom file, if present.
$(eval $(call IncludeCustomExtension, Main-post.gmk))

View File

@@ -55,8 +55,6 @@ BOOT_MODULES= \
jdk.sctp \
jdk.unsupported \
jdk.naming.rmi \
jetbrains.api \
jetbrains.api.impl \
#
# Modules that directly or indirectly requiring upgradeable modules

View File

@@ -49,6 +49,4 @@ jdk.unsupported,
jdk.xml.dom,
jdk.zipfs,
jdk.hotspot.agent,
jetbrains.api,
jetbrains.api.impl,
jdk.jcmd

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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", null)
.withStatic("getService", "com.jetbrains.internal.JBRApi");
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.bootstrap;
import com.jetbrains.internal.JBRApi;
import jdk.internal.loader.ClassLoaders;
import java.lang.invoke.MethodHandles;
/**
* Bootstrap class, used to initialize {@linkplain JBRApi JBR API}.
*/
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}.
* @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);
}
}
}

View File

@@ -0,0 +1,132 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.internal;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Type;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
* Utility class that helps with bytecode generation
*/
class ASMUtils {
private static final MethodHandle genericSignatureGetter;
static {
try {
genericSignatureGetter = MethodHandles.privateLookupIn(Method.class, MethodHandles.lookup())
.findVirtual(Method.class, "getGenericSignature", MethodType.methodType(String.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new Error(e);
}
}
/**
* Replaced with {@code VM.classFileVersion()} in more recent JDK
* @see java.lang.invoke.InnerClassLambdaMetafactory#CLASSFILE_VERSION
*/
public static final int CLASSFILE_VERSION = 59;
public static void generateUnsupportedMethod(ClassVisitor writer, Method interfaceMethod) {
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.visitInsn(DUP);
p.visitLdcInsn("No implementation found for this method");
p.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<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,
String[] exceptionNames) {}
public static InternalMethodInfo getInternalMethodInfo(Method method) {
try {
return new InternalMethodInfo(
method.getName(),
Type.getMethodDescriptor(method),
(String) genericSignatureGetter.invoke(method),
getExceptionNames(method));
} catch (Throwable e) {
throw new Error(e);
}
}
private static String[] getExceptionNames(Method method) {
Class<?>[] exceptionTypes = method.getExceptionTypes();
String[] exceptionNames = new String[exceptionTypes.length];
for (int i = 0; i < exceptionTypes.length; i++) {
exceptionNames[i] = Type.getInternalName(exceptionTypes[i]);
}
return exceptionNames;
}
public static int getParameterSize(Class<?> c) {
if (c == Void.TYPE) {
return 0;
} else if (c == Long.TYPE || c == Double.TYPE) {
return 2;
}
return 1;
}
public static int getLoadOpcode(Class<?> c) {
if (c == Void.TYPE) {
throw new InternalError("Unexpected void type of load opcode");
}
return ILOAD + getOpcodeOffset(c);
}
public static int getReturnOpcode(Class<?> c) {
if (c == Void.TYPE) {
return RETURN;
}
return IRETURN + getOpcodeOffset(c);
}
public static int getOpcodeOffset(Class<?> c) {
if (c.isPrimitive()) {
if (c == Long.TYPE) {
return 1;
} else if (c == Float.TYPE) {
return 2;
} else if (c == Double.TYPE) {
return 3;
}
return 0;
} else {
return 4;
}
}
}

View File

@@ -0,0 +1,280 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.internal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiFunction;
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
* 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}.
* <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.
*/
public class JBRApi {
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
*/
static Lookup outerLookup;
/**
* Known service and proxy interfaces extracted from {@link com.jetbrains.JBR.Metadata}
*/
static Set<String> knownServices, knownProxies;
public static void init(Lookup outerLookup) {
JBRApi.outerLookup = outerLookup;
try {
Class<?> metadataClass = outerLookup.findClass("com.jetbrains.JBR$Metadata");
knownServices = Set.of((String[]) outerLookup.findStaticVarHandle(metadataClass,
"KNOWN_SERVICES", String[].class).get());
knownProxies = Set.of((String[]) outerLookup.findStaticVarHandle(metadataClass,
"KNOWN_PROXIES", String[].class).get());
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
knownServices = Set.of();
knownProxies = Set.of();
}
}
/**
* @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.
*/
public static <T> T getService(Class<T> interFace) {
Proxy<T> p = getProxy(interFace);
return p != null && p.isSupported() ? p.getInstance() : null;
}
/**
* @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);
return resolved != null ? new Proxy<>(resolved) : null;
});
}
/**
* @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 (info.type() == ProxyInfo.Type.CLIENT_PROXY ? info.apiModule() : outerLookup)
.findClass(info.interfaceName());
} catch (ClassNotFoundException | IllegalAccessException e) {
return null;
}
}
/**
* 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(String interfaceName, String target, ProxyInfo.Type type) {
lastProxy = new RegisteredProxyInfo(lookup, interfaceName, target, type, new ArrayList<>());
registeredProxyInfoByInterfaceName.put(interfaceName, lastProxy);
if (target != null) {
registeredProxyInfoByTargetName.put(target, lastProxy);
validate2WayMapping(lastProxy, registeredProxyInfoByInterfaceName.get(target));
validate2WayMapping(lastProxy, registeredProxyInfoByTargetName.get(interfaceName));
}
return this;
}
private static void validate2WayMapping(RegisteredProxyInfo p, RegisteredProxyInfo reverse) {
if (reverse != null &&
(!p.interfaceName().equals(reverse.target()) || !p.target().equals(reverse.interfaceName()))) {
throw new IllegalArgumentException("Invalid 2-way proxy mapping: " +
p.interfaceName() + " -> " + p.target() + " & " +
reverse.interfaceName() + " -> " + reverse.target());
}
}
/**
* 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 target corresponding class/interface name in current JBR module.
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
*/
public ModuleRegistry proxy(String interFace, String target) {
Objects.requireNonNull(target);
return addProxy(interFace, target, ProxyInfo.Type.PROXY);
}
/**
* 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 target corresponding implementation class name in current JBR module, or null.
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
*/
public ModuleRegistry service(String interFace, String target) {
return addProxy(interFace, target, ProxyInfo.Type.SERVICE);
}
/**
* 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(interFace, target, ProxyInfo.Type.CLIENT_PROXY);
}
/**
* 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 interface "{@code methodName}" calls to static "{@code methodName}" in "{@code clazz}".
* @see #withStatic(String, String, String)
*/
public ModuleRegistry withStatic(String methodName, String clazz) {
return withStatic(methodName, clazz, methodName);
}
/**
* Delegate "{@code interfaceMethodName}" method calls to static "{@code methodName}" in "{@code clazz}".
*/
public ModuleRegistry withStatic(String interfaceMethodName, String clazz, String methodName) {
lastProxy.staticMethods().add(
new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, clazz, methodName));
return this;
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.internal;
import java.lang.invoke.MethodHandle;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 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}.
* <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>
* <p>
* Method signatures of proxy interfaces 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}.
* <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.
*/
class Proxy<INTERFACE> {
private final ProxyInfo info;
private volatile ProxyGenerator generator;
private volatile Boolean allMethodsImplemented;
private volatile Boolean supported;
private volatile Class<?> proxyClass;
private volatile MethodHandle constructor;
private volatile MethodHandle targetExtractor;
private volatile INTERFACE instance;
Proxy(ProxyInfo info) {
this.info = info;
}
/**
* @return {@link ProxyInfo} structure of this proxy
*/
ProxyInfo getInfo() {
return info;
}
private synchronized void initGenerator() {
if (generator != null) return;
generator = new ProxyGenerator(info);
allMethodsImplemented = generator.areAllMethodsImplemented();
}
/**
* 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;
}
}
/**
* Checks if all methods are {@linkplain #areAllMethodsImplemented() implemented}
* for this proxy and all proxies it {@linkplain ProxyDependencyManager uses}.
*/
boolean isSupported() {
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;
}
return supported;
}
}
private synchronized void defineClasses() {
if (constructor != null) return;
initGenerator();
generator.defineClasses();
proxyClass = generator.getProxyClass();
constructor = generator.findConstructor();
targetExtractor = generator.findTargetExtractor();
}
/**
* @return generated proxy class
*/
Class<?> getProxyClass() {
if (proxyClass != null) return proxyClass;
synchronized (this) {
if (proxyClass == null) defineClasses();
return proxyClass;
}
}
/**
* @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>
*/
MethodHandle getConstructor() {
if (constructor != null) return constructor;
synchronized (this) {
if (constructor == null) defineClasses();
return constructor;
}
}
/**
* @return method handle for that extracts target object of the proxy, or null.
*/
MethodHandle getTargetExtractor() {
// targetExtractor may be null, so check constructor instead
if (constructor != null) return targetExtractor;
synchronized (this) {
if (constructor == null) defineClasses();
return targetExtractor;
}
}
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(", ")));
}
}
/**
* @return instance for this {@linkplain ProxyInfo.Type#SERVICE service},
* returns {@code null} for other proxy types.
*/
@SuppressWarnings("unchecked")
INTERFACE getInstance() {
if (instance != null) return instance;
if (info.type != ProxyInfo.Type.SERVICE) return null;
synchronized (this) {
if (instance == null) {
initDependencyGraph();
try {
instance = (INTERFACE) getConstructor().invoke();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
return instance;
}
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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")) 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);
}
}
}
/**
* 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

@@ -0,0 +1,460 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.internal;
import jdk.internal.org.objectweb.asm.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import static com.jetbrains.internal.ASMUtils.*;
import static java.lang.invoke.MethodHandles.Lookup;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
* This class generates {@linkplain Proxy proxy} classes.
* Each proxy is just a generated class implementing some interface and
* delegating method calls to method handles.
* <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.
*/
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", "true").equalsIgnoreCase("true");
private static final AtomicInteger nameCounter = new AtomicInteger();
private final ProxyInfo info;
private final boolean generateBridge;
private final String proxyName, bridgeName;
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;
/**
* 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.
*/
ProxyGenerator(ProxyInfo info) {
this.info = info;
generateBridge = info.type != ProxyInfo.Type.CLIENT_PROXY;
int nameId = nameCounter.getAndIncrement();
proxyName = Type.getInternalName(info.interFace) + "$$JBRApiProxy$" + nameId;
bridgeName = generateBridge ? info.apiModule.lookupClass().getPackageName().replace('.', '/') + "/" +
info.interFace.getSimpleName() + "$$JBRApiBridge$" + nameId : null;
class ClassWriter extends jdk.internal.org.objectweb.asm.ClassWriter {
ClassWriter() { super(ClassWriter.COMPUTE_FRAMES); }
ClassVisitor createEmptyVisitor() {
return new ClassVisitor(api) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
return new MethodVisitor(api) {};
}
};
}
}
ClassWriter proxyClassWriter = new ClassWriter();
proxyWriter = proxyClassWriter;
bridgeWriter = generateBridge ? new ClassWriter() : proxyClassWriter.createEmptyVisitor();
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();
}
boolean areAllMethodsImplemented() {
return allMethodsImplemented;
}
Set<Proxy<?>> getDirectProxyDependencies() {
return directProxyDependencies;
}
/**
* Insert all method handles and class references into static fields, so that proxy can call implementation methods.
*/
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);
}
}
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>
*/
MethodHandle findConstructor() {
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 == ProxyInfo.Type.SERVICE) {
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) {
throw new RuntimeException(e);
}
}
/**
* @return method handle that receives proxy and returns its target, or null
*/
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);
}
}
/**
* Loads generated classes.
*/
void defineClasses() {
try {
Lookup bridge = !generateBridge ? null : MethodHandles.privateLookupIn(
info.apiModule.defineClass(((ClassWriter) bridgeWriter).toByteArray()), info.apiModule);
generatedProxy = info.interFaceLookup.defineHiddenClass(
((ClassWriter) proxyWriter).toByteArray(), true, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
generatedHandlesHolder = generateBridge ? bridge : generatedProxy;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void generateConstructor() {
if (info.target != null) {
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", OBJECT_DESCRIPTOR, null, null);
}
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());
}
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(-1, -1);
}
private void generateMethods() {
for (Method method : info.interFace.getMethods()) {
int mod = method.getModifiers();
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);
generateUnsupportedMethod(proxyWriter, method);
allMethodsImplemented = false;
}
}
private void generateMethod(Method interfaceMethod, MethodHandle handle, MethodMapping mapping, boolean passInstance) {
InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod);
String bridgeMethodDescriptor = mapping.getBridgeDescriptor(passInstance);
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());
}
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(-1, -1);
b.visitMaxs(-1, -1);
}
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;
}
};
}
/**
* 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());
}
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);
}
String getBridgeDescriptor() {
if (conversion == TypeConversion.IDENTITY) return Type.getDescriptor(from);
else return "Ljava/lang/Object;";
}
}
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
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.internal;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
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 = i.target() == null ? null : lookup(getTargetLookup(), i.target());
for (RegisteredProxyInfo.StaticMethodMapping m : i.staticMethods()) {
Lookup l = lookup(getTargetLookup(), m.clazz());
if (l != null) {
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 ? apiModule : JBRApi.outerLookup;
}
Lookup getTargetLookup() {
return type == Type.CLIENT_PROXY ? JBRApi.outerLookup : apiModule;
}
private Lookup lookup(Lookup lookup, String clazz) {
try {
return MethodHandles.privateLookupIn(lookup.findClass(clazz), lookup);
} catch (ClassNotFoundException | IllegalAccessException e) {
if (lookup == JBRApi.outerLookup) return null;
else throw new RuntimeException(e);
}
}
record StaticMethodMapping(Lookup lookup, String methodName) {}
/**
* Proxy type, see {@link Proxy}
*/
enum Type {
PROXY,
SERVICE,
CLIENT_PROXY
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 target,
ProxyInfo.Type type,
List<StaticMethodMapping> staticMethods) {
record StaticMethodMapping(String interfaceMethodName, String clazz, String methodName) {}
}

View File

@@ -129,11 +129,14 @@ module java.base {
exports javax.security.auth.spi;
exports javax.security.auth.x500;
exports javax.security.cert;
exports com.jetbrains.bootstrap;
// additional qualified exports may be inserted at build time
// see make/gensrc/GenModuleInfo.gmk
exports com.jetbrains.internal to
java.desktop;
exports com.sun.crypto.provider to
jdk.crypto.cryptoki;
exports sun.invoke.util to

View File

@@ -700,11 +700,11 @@ class SocketChannelImpl
private SocketAddress unixBind(SocketAddress local) throws IOException {
UnixDomainSockets.checkPermission();
if (local == null) {
return UnixDomainSockets.UNNAMED;
return UnixDomainSockets.getUNNAMED();
} else {
Path path = UnixDomainSockets.checkAddress(local).getPath();
if (path.toString().isEmpty()) {
return UnixDomainSockets.UNNAMED;
return UnixDomainSockets.getUNNAMED();
} else {
// bind to non-empty path
UnixDomainSockets.bind(fd, path);

View File

@@ -44,7 +44,9 @@ import sun.nio.fs.AbstractFileSystemProvider;
class UnixDomainSockets {
private UnixDomainSockets() { }
static final UnixDomainSocketAddress UNNAMED = UnixDomainSocketAddress.of("");
private static class UNNAMEDHolder {
static final UnixDomainSocketAddress UNNAMED = UnixDomainSocketAddress.of("");
}
private static final boolean supported;
@@ -71,7 +73,7 @@ class UnixDomainSockets {
// Security check passed
} catch (SecurityException e) {
// Return unnamed address only if security check fails
addr = UNNAMED;
addr = getUNNAMED();
}
return addr;
}
@@ -178,4 +180,8 @@ class UnixDomainSockets {
IOUtil.load();
supported = init();
}
static UnixDomainSocketAddress getUNNAMED() {
return UNNAMEDHolder.UNNAMED;
}
}

View File

@@ -51,6 +51,7 @@ static jclass sjc_CAccessibility = NULL;
NSSize getAxComponentSize(JNIEnv *env, jobject axComponent, jobject component)
{
GET_CACCESSIBILITY_CLASS_RETURN(NSZeroSize);
DECLARE_CLASS_RETURN(jc_Dimension, "java/awt/Dimension", NSZeroSize);
DECLARE_FIELD_RETURN(jf_width, jc_Dimension, "width", "I", NSZeroSize);
DECLARE_FIELD_RETURN(jf_height, jc_Dimension, "height", "I", NSZeroSize);

View File

@@ -35,6 +35,12 @@ static jmethodID sjm_getAccessibleName = NULL;
GET_STATIC_METHOD_RETURN(sjm_getAccessibleName, sjc_CAccessibility, "getAccessibleName", \
"(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;", ret);
static jmethodID sjm_getAccessibleSelection = NULL;
#define GET_ACCESSIBLESELECTION_METHOD_RETURN(ret) \
GET_CACCESSIBILITY_CLASS_RETURN(ret); \
GET_STATIC_METHOD_RETURN(sjm_getAccessibleSelection, sjc_CAccessibility, "getAccessibleSelection", \
"(Ljavax/accessibility/AccessibleContext;Ljava/awt/Component;)Ljavax/accessibility/AccessibleSelection;", ret);
@implementation ComboBoxAccessibility
// NSAccessibilityElement protocol methods
@@ -43,15 +49,21 @@ static jmethodID sjm_getAccessibleName = NULL;
JNIEnv *env = [ThreadUtilities getJNIEnv];
jobject axContext = [self axContextWithEnv:env];
if (axContext == NULL) return nil;
jclass axContextClass = (*env)->GetObjectClass(env, axContext);
DECLARE_METHOD_RETURN(jm_getAccessibleSelection, axContextClass, "getAccessibleSelection", "(I)Ljavax/accessibility/Accessible;", nil);
jobject axSelectedChild = (*env)->CallObjectMethod(env, axContext, jm_getAccessibleSelection, 0);
GET_ACCESSIBLESELECTION_METHOD_RETURN(nil);
jobject axSelection = (*env)->CallStaticObjectMethod(env, sjc_CAccessibility, sjm_getAccessibleSelection, axContext, self->fComponent);
CHECK_EXCEPTION();
if (axSelection == NULL) {
return nil;
}
jclass axSelectionClass = (*env)->GetObjectClass(env, axSelection);
DECLARE_METHOD_RETURN(jm_getAccessibleSelection, axSelectionClass, "getAccessibleSelection", "(I)Ljavax/accessibility/Accessible;", nil);
jobject axSelectedChild = (*env)->CallObjectMethod(env, axSelection, jm_getAccessibleSelection, 0);
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, axSelection);
(*env)->DeleteLocalRef(env, axContext);
if (axSelectedChild == NULL) {
return nil;
}
GET_CACCESSIBILITY_CLASS_RETURN(nil);
GET_ACCESSIBLENAME_METHOD_RETURN(nil);
jobject childName = (*env)->CallStaticObjectMethod(env, sjc_CAccessibility, sjm_getAccessibleName, axSelectedChild, fComponent);
CHECK_EXCEPTION();

View File

@@ -1081,6 +1081,7 @@ static jobject sAccessibilityClass = NULL;
{
JNIEnv* env = [ThreadUtilities getJNIEnv];
GET_CACCESSIBILITY_CLASS_RETURN(nil);
DECLARE_CLASS_RETURN(jc_Container, "java/awt/Container", nil);
DECLARE_STATIC_METHOD_RETURN(jm_accessibilityHitTest, sjc_CAccessibility, "accessibilityHitTest",
"(Ljava/awt/Container;FF)Ljavax/accessibility/Accessible;", nil);

View File

@@ -164,19 +164,7 @@ JNI_COCOA_ENTER(env);
// to indicate we should use CoreText to substitute the character
CGGlyph glyph;
const CTFontRef fallback = CTS_CopyCTFallbackFontAndGlyphForJavaGlyphCode(awtFont, glyphCode, &glyph);
const CGFontRef cgFallback = CTFontCopyGraphicsFont(fallback, NULL);
if (IS_OSX_GT10_13 || IsEmojiFont(cgFallback)) {
CGAffineTransform matrix = awtStrike->fAltTx;
CGFloat fontSize = sqrt(fabs(matrix.a * matrix.d - matrix.b * matrix.c));
CTFontRef font = CTFontCreateWithGraphicsFont(cgFallback, fontSize, NULL, NULL);
CTFontGetAdvancesForGlyphs(font, kCTFontDefaultOrientation, &glyph, &advance, 1);
CFRelease(font);
advance.width /= fontSize;
advance.height /= fontSize;
} else {
CTFontGetAdvancesForGlyphs(fallback, kCTFontDefaultOrientation, &glyph, &advance, 1);
}
CFRelease(cgFallback);
CGGlyphImages_GetGlyphMetrics(fallback, &awtStrike->fAltTx, awtStrike->fStyle, &glyph, 1, NULL, &advance, IS_OSX_GT10_14);
CFRelease(fallback);
advance = CGSizeApplyAffineTransform(advance, awtStrike->fFontTx);
if (!JRSFontStyleUsesFractionalMetrics(awtStrike->fStyle)) {

View File

@@ -86,7 +86,8 @@
isAA:(jboolean)isAA;
- (id<MTLRenderCommandEncoder> _Nonnull)getTextEncoder:(const BMTLSDOps * _Nonnull)dstOps
isSrcOpaque:(bool)isSrcOpaque;
isSrcOpaque:(bool)isSrcOpaque
gammaCorrection:(bool)gmc;
// Base method to obtain any MTLRenderCommandEncoder
- (id<MTLRenderCommandEncoder> _Nonnull) getEncoder:(id<MTLTexture> _Nonnull)dest

View File

@@ -40,19 +40,18 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
- (id)init;
- (void)dealloc;
- (void)reset:(id<MTLTexture>)destination
isDstOpaque:(jboolean)isDstOpaque
isDstPremultiplied:(jboolean)isDstPremultiplied
isAA:(jboolean)isAA
isText:(jboolean)isText
isLCD:(jboolean)isLCD;
- (void) reset:(id<MTLTexture>)destination
isDstOpaque:(jboolean)isDstOpaque
isDstPremultiplied:(jboolean)isDstPremultiplied
isAA:(jboolean)isAA
isGMCText:(jboolean)isGMCText
isLCD:(jboolean)isLCD;
- (void)updateEncoder:(id<MTLRenderCommandEncoder>)encoder
context:(MTLContext *)mtlc
renderOptions:(const RenderOptions *)renderOptions
forceUpdate:(jboolean)forceUpdate;
@property (assign) jboolean aa;
@property (assign) jboolean text;
@property (assign) jboolean lcd;
@property (assign) jboolean aaShader;
@property (retain) MTLPaint* paint;
@@ -67,7 +66,7 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
SurfaceRasterFlags _dstFlags;
jboolean _isAA;
jboolean _isText;
jboolean _isGMCText;
jboolean _isLCD;
jboolean _isAAShader;
@@ -93,7 +92,6 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
MTLTransform * _transform;
}
@synthesize aa = _isAA;
@synthesize text = _isText;
@synthesize lcd = _isLCD;
@synthesize aaShader = _isAAShader;
@synthesize paint = _paint;
@@ -126,13 +124,13 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
isDstOpaque:(jboolean)isDstOpaque
isDstPremultiplied:(jboolean)isDstPremultiplied
isAA:(jboolean)isAA
isText:(jboolean)isText
isGMCText:(jboolean)isGMCText
isLCD:(jboolean)isLCD {
_destination = destination;
_dstFlags.isOpaque = isDstOpaque;
_dstFlags.isPremultiplied = isDstPremultiplied;
_isAA = isAA;
_isText = isText;
_isGMCText = isGMCText;
_isLCD = isLCD;
// NOTE: probably it's better to invalidate/reset all cached states now
}
@@ -182,7 +180,7 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
&& (_isTexture == renderOptions->isTexture && (!renderOptions->isTexture || _interpolationMode == renderOptions->interpolation)) // interpolation is used only in texture mode
&& _isAA == renderOptions->isAA
&& _isAAShader == renderOptions->isAAShader
&& _isText == renderOptions->isText
&& _isGMCText == renderOptions->isGMCText
&& _isLCD == renderOptions->isLCD
&& _srcFlags.isOpaque == renderOptions->srcFlags.isOpaque && _srcFlags.isPremultiplied == renderOptions->srcFlags.isPremultiplied)
return;
@@ -193,7 +191,7 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
_interpolationMode = renderOptions->interpolation;
_isAA = renderOptions->isAA;
_isAAShader = renderOptions->isAAShader;
_isText = renderOptions->isText;
_isGMCText = renderOptions->isGMCText;
_isLCD = renderOptions->isLCD;
_srcFlags = renderOptions->srcFlags;
@@ -352,9 +350,11 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
}
- (id<MTLRenderCommandEncoder> _Nonnull) getTextEncoder:(const BMTLSDOps * _Nonnull)dstOps
isSrcOpaque:(bool)isSrcOpaque
isSrcOpaque:(bool)isSrcOpaque
gammaCorrection:(bool)gmc
{
RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, { isSrcOpaque, JNI_TRUE }, {dstOps->isOpaque, JNI_TRUE}, JNI_TRUE, JNI_FALSE, JNI_FALSE};
RenderOptions roptions = {JNI_TRUE, JNI_FALSE, INTERPOLATION_NEAREST_NEIGHBOR, { isSrcOpaque, JNI_TRUE },
{dstOps->isOpaque, JNI_TRUE}, gmc, JNI_FALSE, JNI_FALSE};
return [self getEncoder:dstOps->pTexture renderOptions:&roptions];
}
@@ -437,11 +437,11 @@ const SurfaceRasterFlags defaultRasterFlags = { JNI_FALSE, JNI_TRUE };
_encoder = [[cbw getCommandBuffer] renderCommandEncoderWithDescriptor:rpd];
[_encoderStates reset:dest
isDstOpaque:renderOptions->dstFlags.isOpaque
isDstPremultiplied:YES
isAA:renderOptions->isAA
isText:renderOptions->isText
isLCD:renderOptions->isLCD];
isDstOpaque:renderOptions->dstFlags.isOpaque
isDstPremultiplied:YES
isAA:renderOptions->isAA
isGMCText:renderOptions->isGMCText
isLCD:renderOptions->isLCD];
}
//

View File

@@ -56,7 +56,8 @@ static MTLRenderPipelineDescriptor * templateLCDPipelineDesc = nil;
static MTLRenderPipelineDescriptor * templateAAPipelineDesc = nil;
static void
setTxtUniforms(MTLContext *mtlc, int color, id <MTLRenderCommandEncoder> encoder, int interpolation, bool repeat,
jfloat extraAlpha, const SurfaceRasterFlags *srcFlags, const SurfaceRasterFlags *dstFlags, int mode);
jfloat extraAlpha, const SurfaceRasterFlags *srcFlags, const SurfaceRasterFlags *dstFlags, int mode,
bool gmcText);
static void initTemplatePipelineDescriptors() {
if (templateRenderPipelineDesc != nil && templateTexturePipelineDesc != nil &&
@@ -189,6 +190,7 @@ jint _color;
NSString *vertShader = @"vert_col";
NSString *fragShader = @"frag_col";
bool gmcText = NO;
if (renderOptions->isTexture) {
vertShader = @"vert_txt";
@@ -198,8 +200,9 @@ jint _color;
fragShader = @"aa_frag_txt";
rpDesc = [[templateAATexturePipelineDesc copy] autorelease];
}
if (renderOptions->isText) {
fragShader = @"frag_text";
if (renderOptions->isGMCText) {
fragShader = @"frag_gmc_text";
gmcText = YES;
}
if (renderOptions->isLCD) {
vertShader = @"vert_txt_lcd";
@@ -208,7 +211,7 @@ jint _color;
}
setTxtUniforms(mtlc, _color, encoder,
renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha], &renderOptions->srcFlags,
&renderOptions->dstFlags, 1);
&renderOptions->dstFlags, 1, gmcText);
} else if (renderOptions->isAAShader) {
vertShader = @"vert_col_aa";
fragShader = @"frag_col_aa";
@@ -251,7 +254,7 @@ jint _color;
setTxtUniforms(mtlc, col, encoder,
renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
&renderOptions->srcFlags, &renderOptions->dstFlags, 1);
&renderOptions->srcFlags, &renderOptions->dstFlags, 1, NO);
[encoder setFragmentBytes:&xorColor length:sizeof(xorColor) atIndex:0];
[encoder setFragmentTexture:dstOps->pTexture atIndex:1];
@@ -792,7 +795,7 @@ jint _color;
const SurfaceRasterFlags srcFlags = {_isOpaque, renderOptions->srcFlags.isPremultiplied};
setTxtUniforms(mtlc, 0, encoder,
renderOptions->interpolation, YES, [mtlc.composite getExtraAlpha],
&srcFlags, &renderOptions->dstFlags, 0);
&srcFlags, &renderOptions->dstFlags, 0, NO);
id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
vertexShaderId:vertShader
@@ -874,9 +877,41 @@ jint _color;
static void
setTxtUniforms(MTLContext *mtlc, int color, id <MTLRenderCommandEncoder> encoder, int interpolation, bool repeat,
jfloat extraAlpha, const SurfaceRasterFlags *srcFlags, const SurfaceRasterFlags *dstFlags, int mode) {
struct TxtFrameUniforms uf = {RGBA_TO_V4(color), mode, srcFlags->isOpaque, dstFlags->isOpaque, extraAlpha};
[encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
jfloat extraAlpha, const SurfaceRasterFlags *srcFlags, const SurfaceRasterFlags *dstFlags, int mode,
bool gmcText)
{
if (gmcText) {
float ca = (((color) >> 24) & 0xFF)/255.0f;
float cr = (((color) >> 16) & (0xFF))/255.0f;
float cg = (((color) >> 8) & 0xFF)/255.0f;
float cb = ((color) & 0xFF)/255.0f;
float inv_light_gamma = 1.666f;
float inv_dark_gamma = 0.333f;
float inv_light_exp = 0.454f;
float inv_dark_exp = 1.4f;
// calculate brightness of the fragment
float b = (cr/3.0f + cg/3.0f + cb/3.0f)*ca;
// adjust fragment coverage
float exp = inv_dark_exp*(1.0f - b) + inv_light_exp*b;
// adjust fragment color and alpha for alpha < 1.0
if (ca < 1.0f) {
float g = inv_dark_gamma*(1.0f - b) + inv_light_gamma*b;
cr = pow(cr, g);
cg = pow(cr, g);
cb = pow(cr, g);
ca = pow(ca, exp);
}
struct GMCFrameUniforms uf = {{cr, cg, cb, ca}, exp};
[encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
} else {
struct TxtFrameUniforms uf =
{RGBA_TO_V4(color), mode, srcFlags->isOpaque, dstFlags->isOpaque, extraAlpha};
[encoder setFragmentBytes:&uf length:sizeof(uf) atIndex:FrameUniformBuffer];
}
[mtlc.samplerManager setSamplerWithEncoder:encoder interpolation:interpolation repeat:repeat];
}
@@ -938,7 +973,7 @@ setTxtUniforms(MTLContext *mtlc, int color, id <MTLRenderCommandEncoder> encoder
setTxtUniforms(mtlc, 0, encoder,
renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
&renderOptions->srcFlags,
&renderOptions->dstFlags, 0);
&renderOptions->dstFlags, 0, NO);
}
id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
@@ -969,7 +1004,7 @@ setTxtUniforms(MTLContext *mtlc, int color, id <MTLRenderCommandEncoder> encoder
const int col = 0 ^ xorColor;
setTxtUniforms(mtlc, col, encoder,
renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
&renderOptions->srcFlags, &renderOptions->dstFlags, 0);
&renderOptions->srcFlags, &renderOptions->dstFlags, 0, NO);
[encoder setFragmentBytes:&xorColor length:sizeof(xorColor) atIndex: 0];
BMTLSDOps *dstOps = MTLRenderQueue_GetCurrentDestination();
@@ -978,7 +1013,7 @@ setTxtUniforms(MTLContext *mtlc, int color, id <MTLRenderCommandEncoder> encoder
setTxtUniforms(mtlc, 0, encoder,
renderOptions->interpolation, NO, [mtlc.composite getExtraAlpha],
&renderOptions->srcFlags,
&renderOptions->dstFlags, 0);
&renderOptions->dstFlags, 0, NO);
id <MTLRenderPipelineState> pipelineState = [pipelineStateStorage getPipelineState:rpDesc
vertexShaderId:vertShader

View File

@@ -297,7 +297,8 @@ J2dTraceLn(J2D_TRACE_INFO, "MTLTR_EnableGlyphVertexCache");
return;
}
}
MTLVertexCache_CreateSamplingEncoder(mtlc, dstOps);
MTLVertexCache_CreateSamplingEncoder(mtlc, dstOps, YES);
}
void

View File

@@ -82,5 +82,5 @@ void
MTLVertexCache_AddGlyphQuad(MTLContext *mtlc,
jfloat tx1, jfloat ty1, jfloat tx2, jfloat ty2,
jfloat dx1, jfloat dy1, jfloat dx2, jfloat dy2);
void MTLVertexCache_CreateSamplingEncoder(MTLContext *mtlc, BMTLSDOps *dstOps);
void MTLVertexCache_CreateSamplingEncoder(MTLContext *mtlc, BMTLSDOps *dstOps, bool gmc);
#endif /* MTLVertexCache_h_Included */

View File

@@ -44,6 +44,7 @@ static jint vertexCacheIndex = 0;
static MTLPooledTextureHandle * maskCacheTex = NULL;
static jint maskCacheIndex = 0;
static bool gammaCorrection = NO;
static id<MTLRenderCommandEncoder> encoder = NULL;
#define MTLVC_ADD_VERTEX(TX, TY, DX, DY, DZ) \
@@ -182,7 +183,7 @@ MTLVertexCache_EnableMaskCache(MTLContext *mtlc, BMTLSDOps *dstOps)
return;
}
}
MTLVertexCache_CreateSamplingEncoder(mtlc, dstOps);
MTLVertexCache_CreateSamplingEncoder(mtlc, dstOps, gammaCorrection);
}
void
@@ -194,15 +195,18 @@ MTLVertexCache_DisableMaskCache(MTLContext *mtlc)
J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_DisableMaskCache");
MTLVertexCache_FlushVertexCache(mtlc);
maskCacheIndex = 0;
gammaCorrection = NO;
free(vertexCache);
vertexCache = NULL;
}
void
MTLVertexCache_CreateSamplingEncoder(MTLContext *mtlc, BMTLSDOps *dstOps) {
MTLVertexCache_CreateSamplingEncoder(MTLContext *mtlc, BMTLSDOps *dstOps, bool gmc) {
J2dTraceLn(J2D_TRACE_INFO, "MTLVertexCache_CreateSamplingEncoder");
gammaCorrection = gmc;
encoder = [mtlc.encoderManager getTextEncoder:dstOps
isSrcOpaque:NO];
isSrcOpaque:NO
gammaCorrection:gmc];
}
void

View File

@@ -36,7 +36,7 @@ typedef struct {
int interpolation;
SurfaceRasterFlags srcFlags;
SurfaceRasterFlags dstFlags;
jboolean isText;
jboolean isGMCText;
jboolean isLCD;
jboolean isAAShader;
} RenderOptions;

View File

@@ -157,6 +157,11 @@ struct LCDFrameUniforms {
vector_float3 invgamma;
};
struct GMCFrameUniforms {
vector_float4 color;
float exp;
};
struct SwizzleUniforms {
unsigned char swizzle[4];
unsigned char hasAlpha;

View File

@@ -332,6 +332,20 @@ fragment half4 frag_text(
return half4(uniforms.color * pixelColor.a);
}
fragment half4 frag_gmc_text(
TxtShaderInOut vert [[stage_in]],
texture2d<float, access::sample> renderTexture [[texture(0)]],
constant GMCFrameUniforms& uniforms [[buffer(1)]],
sampler textureSampler [[sampler(0)]]
) {
float4 pixelColor = renderTexture.sample(textureSampler, vert.texCoords);
float a = uniforms.color.a;
float3 col = uniforms.color.rgb;
// adjust fragment coverage
float frag_cov = pow(pixelColor.a, uniforms.exp);
return half4(col.r*frag_cov, col.g*frag_cov, col.b*frag_cov, a*frag_cov);
}
fragment half4 frag_txt_tp(TxtShaderInOut vert [[stage_in]],
texture2d<float, access::sample> renderTexture [[texture(0)]],
texture2d<float, access::sample> paintTexture [[texture(1)]],

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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", null)
.withStatic("getSubpixelResolution", "sun.font.FontUtilities");
}
}

View File

@@ -122,8 +122,6 @@ module java.desktop {
exports sun.awt.dnd to jdk.unsupported.desktop;
exports sun.swing to jdk.unsupported.desktop;
exports sun.font to jetbrains.api.impl;
opens javax.swing.plaf.basic to
jdk.jconsole;

View File

@@ -136,6 +136,10 @@ public final class FontUtilities {
});
}
public static Dimension getSubpixelResolution() {
return subpixelResolution;
}
/**
* Referenced by code in the JDK which wants to test for the
* minimum char code for which layout may be required.

View File

@@ -1198,7 +1198,7 @@ public final class XToolkit extends UNIXToolkit implements Runnable {
@Override
public Insets getScreenInsets(final GraphicsConfiguration gc) {
if (useCachedInsets) {
return cachedInsets.computeIfAbsent(gc, this::getScreenInsetsImpl);
return (Insets)cachedInsets.computeIfAbsent(gc, this::getScreenInsetsImpl).clone();
} else {
return getScreenInsetsImpl(gc);
}

View File

@@ -1481,9 +1481,21 @@ final class XWM
}
XNETProtocol net_protocol = getWM().getNETProtocol();
if (net_protocol != null && net_protocol.active()) {
Insets insets = getInsetsFromProp(window, XA_NET_FRAME_EXTENTS);
if (insLog.isLoggable(PlatformLogger.Level.FINE)) {
insLog.fine("_NET_FRAME_EXTENTS: {0}", insets);
Insets insets = null;
final int MAX_RETRY_COUNT = 3;
for (int i = 0; i < MAX_RETRY_COUNT; i++) {
insets = getInsetsFromProp(window, XA_NET_FRAME_EXTENTS);
if (insLog.isLoggable(PlatformLogger.Level.FINE)) {
insLog.fine("_NET_FRAME_EXTENTS: {0}", insets);
}
if (insets == null) {
final long timeForInsetExtentToBecomeReadyMs = (i + 1)*5;
insLog.fine("_NET_FRAME_EXTENTS not available (yet?), retrying in {0} ms",
timeForInsetExtentToBecomeReadyMs);
try {
Thread.sleep(timeForInsetExtentToBecomeReadyMs);
} catch (InterruptedException ignored) {}
}
}
if (insets != null) {

View File

@@ -149,21 +149,26 @@ public final class X11GraphicsDevice extends GraphicsDevice
}
private Rectangle boundsCached;
private final Object boundsCacheLock = new Object();
private synchronized Rectangle getBoundsCached() {
if (boundsCached == null) {
boundsCached = getBoundsImpl();
private Rectangle getBoundsCached() {
synchronized (boundsCacheLock) {
if (boundsCached == null) {
boundsCached = getBoundsImpl();
}
return boundsCached;
}
return boundsCached;
}
public synchronized void resetBoundsCache() {
boundsCached = null;
public void resetBoundsCache() {
synchronized (boundsCacheLock) {
boundsCached = null;
}
}
public Rectangle getBounds() {
if (X11GraphicsEnvironment.useBoundsCache()) {
return getBoundsCached();
return new Rectangle(getBoundsCached());
}
else {
return getBoundsImpl();

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2021 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.*;
public interface ExtendedGlyphCache extends JBRService {
interface V1 extends ExtendedGlyphCache {
Dimension getSubpixelResolution();
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2000-2020 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains;
public class JBRApi {
private static final int MAJOR_VERSION = 0;
private static final int MINOR_VERSION = 0;
private JBRApi() {}
/**
* Returns major API version.
* It has nothing to do with {@link #isAvailable() actual implementation availability}.
*/
public static int getMajorVersion() {
return MAJOR_VERSION;
}
/**
* Returns minor API version.
* It has nothing to do with {@link #isAvailable() actual implementation availability}.
*/
public static int getMinorVersion() {
return MINOR_VERSION;
}
/**
* Checks for JBR API implementation availability at runtime.
* {@code true} means we're running on JBR with {@code jetbrains.api.impl} module.
*/
public static boolean isAvailable() {
return JBRService.isApiAvailable();
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright 2000-2020 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.function.Supplier;
/**
* Marker interface, can be used with Java SPI to enumerate JBR services.
* All JBR services must implement this interface
*/
public interface JBRService {
/**
* Tries to load JBR service for given interface.
* Returns null when there's no implementation for such interface, or when given Supplier throws
* NoClassDefFoundError, which usually means that given interface is absent in module path.
*/
@SuppressWarnings("unchecked")
static <T extends JBRService> T load(Supplier<Class<T>> serviceInterface) {
try {
JBRServiceManager.ServiceHolder<T> serviceHolder =
(JBRServiceManager.ServiceHolder<T>) JBRServiceManager.services.get(serviceInterface.get());
return serviceHolder == null ? null : serviceHolder.get();
} catch (NoClassDefFoundError ignore) {
return null;
}
}
static boolean isApiAvailable() {
return JBRServiceManager.services.size() > 0;
}
}
class JBRServiceManager {
static class ServiceHolder<T extends JBRService> {
private ServiceLoader.Provider<T> provider;
private T service;
private ServiceHolder(ServiceLoader.Provider<T> provider) {
this.provider = provider;
}
synchronized T get() {
if(service != null) return service;
service = provider.get();
provider = null;
return service;
}
}
static final Map<Class<? extends JBRService>, ServiceHolder<?>> services;
static {
Map<Class<? extends JBRService>, ServiceHolder<?>> map = new HashMap<>();
ServiceLoader.load(JBRService.class).stream()
.forEach(p -> populateServiceMap(map, new ServiceHolder<>(p), p.type()));
services = Collections.unmodifiableMap(map);
}
/**
* Finds all chains of class/interface hierarchy that leads from given {@code clazz} to {@link JBRService}
* and assigns given {@code serviceHolder} to them
*/
@SuppressWarnings("unchecked")
private static boolean populateServiceMap(Map<Class<? extends JBRService>, ServiceHolder<?>> map,
ServiceHolder<?> serviceHolder, Class<?> clazz) {
if(clazz == null) return false;
if(clazz.equals(JBRService.class)) return true;
boolean isSubtypeOfJBRService = false;
for(Class<?> ifc : clazz.getInterfaces()) {
isSubtypeOfJBRService |= populateServiceMap(map, serviceHolder, ifc);
}
isSubtypeOfJBRService |= populateServiceMap(map, serviceHolder, clazz.getSuperclass());
if(isSubtypeOfJBRService) {
map.put((Class<? extends JBRService>) clazz, serviceHolder);
}
return isSubtypeOfJBRService;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains;
import java.awt.*;
public interface ExtendedGlyphCache {
Dimension getSubpixelResolution();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2020 JetBrains s.r.o.
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,8 @@
* limitations under the License.
*/
import com.jetbrains.JBRService;
module jetbrains.api {
requires transitive java.desktop;
exports com.jetbrains;
uses JBRService;
requires static transitive java.desktop;
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 module, 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 {
module = Path.of(args[0]);
gensrc = Path.of(args[1]);
boolean error = args[2].equals("true");
Path versionFile = module.resolve("version.properties");
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(module.resolve("src"), hash);
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 abs = file.toAbsolutePath();
Path rel = dir.relativize(abs);
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(abs);
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

@@ -0,0 +1,233 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
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, templates, 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");
templates = module.resolve("templates");
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,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
modules = new JBRModules();
JBR.generate();
}
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) {
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) 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 void generate() throws IOException {
String jbrFileName = "com/jetbrains/JBR.java";
Path output = gensrc.resolve(jbrFileName);
Files.createDirectories(output.getParent());
String content = generate(Files.readString(templates.resolve(jbrFileName)));
Files.writeString(output, content, CREATE, WRITE, TRUNCATE_EXISTING);
}
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);
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|\n)*\\*/");
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");
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;
}
return new Service(name, javadoc,
content.substring(javadocEnd, indexOfDeclaration).contains("@Deprecated"));
} 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 = api != null ? api.getService($.class) : null;
}
/**
* @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;
}
"""
.replaceAll("\\$", service.name)
.replace("<JAVADOC>", service.javadoc)
.replaceAll("<DEPRECATED>", service.deprecated ? "\n@Deprecated" : "");
}
private record Service(String name, String javadoc, boolean deprecated) {}
}
/**
* Finds and analyzes JBR API implementation modules and collects proxy definitions.
*/
private static class JBRModules {
private final Set<String> proxies = new HashSet<>(), services = new HashSet<>();
private JBRModules() throws IOException {
String[] moduleNames = findJBRApiModules();
Path[] potentialModules = findPotentialJBRApiContributorModules();
for (String moduleName : moduleNames) {
Path module = findJBRApiModuleFile(moduleName, potentialModules);
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 parameters = matcher.group(2);
String interfaceName = extractFromStringLiteral(parameters.substring(0, parameters.indexOf(',')));
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

@@ -0,0 +1,106 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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() {}
/**
* @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);
}
// ========================== 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

@@ -0,0 +1,14 @@
# 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.0
# Hash is used to track changes to jetbrains.api, so you would not forget to update version when needed.
# When you make any changes, "make jbr-api" will fail and ask you to update hash and version number here.
HASH = 2F1FBCDFD362481FFD12AA12C082DE

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) 2021, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @key headful
* @summary Verifies that JDialog's insets are the same every time the dialog is created.
* @requires (os.family == "linux")
* @run main DialogInsets
*/
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.WindowConstants;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.lang.reflect.InvocationTargetException;
public class DialogInsets {
private static Robot robot;
private static Insets insets;
public static void main(String[] args) throws Exception {
robot = new Robot();
final JFrame frame = new JFrame("Test Dialog Demo");
frame.setBounds(100, 100, 400, 300);
frame.setVisible(true);
robot.delay(300);
for (int i = 0; i < 16; i++) {
runSwing(() -> showDialog(frame));
robot.delay(300);
}
runSwing( () -> frame.dispose() );
}
private static void showDialog(Frame parent) {
final JLabel content = new JLabel("test label", SwingConstants.CENTER);
final JDialog dialog = new JDialog(parent, "Test Dialog", true);
if (insets == null) {
// Slow down the first time in order to increase the chance to get the right insets.
robot.delay(300);
}
dialog.setContentPane(content);
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
dialog.setResizable(false);
dialog.pack();
final Insets localInsets = dialog.getInsets();
System.out.println(localInsets);
if (insets == null) {
insets = localInsets;
} else if (!insets.equals(localInsets)) {
throw new RuntimeException("insets differ (" + insets + " before, " + localInsets + " now)");
}
final Timer closeTimer = new Timer(300, (ActionEvent event) -> { dialog.setVisible(false); dialog.dispose(); } );
closeTimer.setRepeats(false);
closeTimer.start();
dialog.setLocationRelativeTo(parent);
dialog.setVisible(true);
}
private static void runSwing(Runnable r) {
try {
SwingUtilities.invokeAndWait(r);
} catch (InterruptedException ignored) {
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021 JetBrains s.r.o.
* Copyright (c) 2021, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -21,18 +21,20 @@
* questions.
*/
package com.jetbrains.impl;
/**
* @test
* @summary Verifies that UNIX socket doesn't throw when opened with
* a non-default file system installed. NB: the opened socket isn't
* supposed to be useable.
* @library /test/lib
* @build TestProvider UnixSocketInNonDefaultFS
* @run main/othervm -Djava.nio.file.spi.DefaultFileSystemProvider=TestProvider UnixSocketInNonDefaultFS
*/
import java.nio.channels.ServerSocketChannel;
import java.net.StandardProtocolFamily;
import com.jetbrains.ExtendedGlyphCache;
import java.awt.*;
import sun.font.FontUtilities;
public class ExtendedGlyphCacheImpl implements ExtendedGlyphCache.V1 {
@Override
public Dimension getSubpixelResolution() {
return FontUtilities.subpixelResolution;
public class UnixSocketInNonDefaultFS {
public static void main(String args[]) throws java.io.IOException {
ServerSocketChannel server = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
}
}

View File

@@ -1,77 +0,0 @@
/*
* Copyright 2000-2020 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.jetbrains.JBRService;
import com.jetbrains.SampleJBRApi;
import java.util.Objects;
/*
* @test
* @summary JBR API service loading & usage example
*/
public class JBRServiceTest {
/**
* All API members are organized into some kind of 'versioned namespaces', here's the example:
* <ul>
* <li>SampleJBRApi - root of our feature API.</li>
* <li>SampleJBRApi.V1 - first version of our API. It contains all relevant methods, classes, etc.</li>
* <li>SampleJBRApi.V1.SomeDataClass - data class, added by first version of API.</li>
* <li>SampleJBRApi.V2 - second version of our API. It extends V1, adding some more things to API.</li>
* </ul>
*
* Actual interface hierarchy for our API looks like this:
* <ul>
* <li>JBRService: marker interface, root for every feature API</li>
* <ul>
* <li>SampleJBRApi: marker interface, root for that specific feature API</li>
* <ul>
* <li>SampleJBRApi.V1: first version of feature API</li>
* <ul>
* <li>SampleJBRApi.V2: second version of feature API</li>
* </ul>
* </ul>
* </ul>
* </ul>
*
* General rule when using JBR API is to avoid loading classes unless you make sure they're available at runtime.
* To check for service availability you can try loading it with {@link JBRService#load}.
* Also avoid using 'instanceof' to test JBR service implementation against specific interface, because using
* it in 'instanceof' statement will cause loading this interface, which may in turn cause NoClassDefFoundError.
*/
public static void main(String[] args) {
// // Declaring variables is safe
// SampleJBRApi.V1 service;
// // We pass lambda instead of plain class. This allows JBRService#load to deal with NoClassDefFoundErrors
// service = JBRService.load(() -> SampleJBRApi.V1.class);
// // Null-checking variables is safe too
// Objects.requireNonNull(service);
// // When we ensured that SampleJBRApi.V1 is available, we can use anything that's inside
// service.someMethod1(null);
// // We can also create SampleJBRApi.V1.SomeDataClass instances, as we know they're supported with V1
// service.someMethod1(new SampleJBRApi.V1.SomeDataClass());
//
// // But don't try doing 'service instanceof SampleJBRApi.V2' as it may cause NoClassDefFoundErrors!
// SampleJBRApi.V2 service2 = Objects.requireNonNull(JBRService.load(() -> SampleJBRApi.V2.class));
// // Versioned service interfaces are inherited, so V2 gives you access to both V2 and V1 API
// service2.someMethod1(service2.someMethod2());
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @test
* @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

@@ -0,0 +1,71 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.* com.jetbrains.api.MethodMapping com.jetbrains.jbr.MethodMapping
* @run main MethodMappingTest
*/
import com.jetbrains.internal.JBRApi;
import static com.jetbrains.Util.*;
import static com.jetbrains.api.MethodMapping.*;
import static com.jetbrains.jbr.MethodMapping.*;
public class MethodMappingTest {
public static void main(String[] args) throws Throwable {
JBRApi.ModuleRegistry r = init();
// Simple empty interface
r.proxy(SimpleEmpty.class.getName(), SimpleEmptyImpl.class.getName());
requireImplemented(SimpleEmpty.class);
// Plain method mapping
r.proxy(PlainFail.class.getName(), PlainImpl.class.getName());
r.service(Plain.class.getName(), PlainImpl.class.getName())
.withStatic("c", MethodMappingTest.class.getName(), "main");
requireNotImplemented(PlainFail.class);
requireImplemented(Plain.class);
// Callback (client proxy)
r.clientProxy(Callback.class.getName(), ApiCallback.class.getName());
requireImplemented(Callback.class);
// 2-way
r.twoWayProxy(ApiTwoWay.class.getName(), JBRTwoWay.class.getName());
requireImplemented(ApiTwoWay.class);
requireImplemented(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");
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.* com.jetbrains.api.ProxyInfoResolving com.jetbrains.jbr.ProxyInfoResolving
* @run main ProxyInfoResolvingTest
*/
import com.jetbrains.internal.JBRApi;
import java.util.Objects;
import static com.jetbrains.Util.*;
import static com.jetbrains.api.ProxyInfoResolving.*;
import static com.jetbrains.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 -> error
r.proxy(InterfaceWithoutImplementation.class.getName(), "absentImpl");
mustFail(() -> getProxy(InterfaceWithoutImplementation.class), RuntimeException.class, ClassNotFoundException.class);
// Invalid JBR-side target static method mapping -> error
r.service(ServiceWithoutImplementation.class.getName(), null)
.withStatic("someMethod", "NoClass");
mustFail(() -> getProxy(ServiceWithoutImplementation.class), RuntimeException.class, ClassNotFoundException.class);
// Service without target class or static method mapping -> null
r.service(EmptyService.class.getName(), null);
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));
// Valid proxy
r.proxy(ValidApi.class.getName(), ValidApiImpl.class.getName());
Objects.requireNonNull(getProxy(ValidApi.class));
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @test
* @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", null);
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

@@ -0,0 +1,79 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.* com.jetbrains.api.Real com.jetbrains.jbr.Real
* @run main RealTest
*/
import com.jetbrains.internal.JBRApi;
import java.util.Objects;
import static com.jetbrains.Util.*;
import static com.jetbrains.api.Real.*;
import static com.jetbrains.jbr.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());
// Get service
Service service = Objects.requireNonNull(JBRApi.getService(Service.class));
// Test JBR-side proxy wrapping & unwrapping
Api2Way stw = Objects.requireNonNull(service.get2Way());
Api2Way nstw = Objects.requireNonNull(service.passthrough(stw));
// stw and nstw are different proxy objects, because *real* object is on JBR-side
if (stw.getClass() != nstw.getClass()) {
throw new Error("Different classes when passing through the same object");
}
// Test client-side proxy wrapping & unwrapping
TwoWayImpl tw = new TwoWayImpl();
Api2Way ntw = service.passthrough(tw);
if (tw != ntw) {
throw new Error("Client pass through doesn't work, there are probably issues with extracting target object");
}
// Service must have set tw.value by calling accept()
Objects.requireNonNull(tw.value);
// Passing through null object -> null
requireNull(service.passthrough(null));
if (!service.isSelf(service)) {
throw new Error("service.isSelf(service) == false");
}
if (service.sum(() -> 200, () -> 65).get() != 265) {
throw new Error("Lazy numbers conversion error");
}
}
private static class TwoWayImpl implements Api2Way {
private Object value;
@Override
public void accept(Object o) {
value = o;
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @test
* @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

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2020 JetBrains s.r.o.
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,15 +14,20 @@
* limitations under the License.
*/
import com.jetbrains.JBRService;
import com.jetbrains.impl.*;
package com.jetbrains;
module jetbrains.api.impl {
/**
* Special open JBR API frontend for tests.
*/
public class JBR {
requires jetbrains.api;
public interface ServiceApi {
// We probably don't want to add `provides with` for each version of API, so we just provide `JBRService`
// implementation, `JBRService#load` will take care of the rest
provides JBRService with ExtendedGlyphCacheImpl;
<T> T getService(Class<T> interFace);
}
}
static final class Metadata {
static String[] KNOWN_SERVICES = {};
static String[] KNOWN_PROXIES = {};
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains;
import com.jetbrains.internal.JBRApi;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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) {
throw new Error(e);
}
}
public static JBRApi.ModuleRegistry init() {
return init(new String[0], new String[0]);
}
/**
* 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);
public static Object getProxy(Class<?> interFace) throws Throwable {
return getProxy.invoke(null, interFace);
}
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

@@ -0,0 +1,69 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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

@@ -0,0 +1,42 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.api;
public class MethodMapping {
public interface SimpleEmpty {}
public interface Plain {
void a();
boolean b();
void c(String... a);
}
public interface PlainFail extends Plain {}
public interface ApiCallback {
void hello();
}
public interface ApiTwoWay {
void something();
}
public interface Conversion {
SimpleEmpty convert(Plain a, ApiCallback b, ApiTwoWay c);
}
public interface ConversionSelf extends Conversion {
ConversionSelf convert(Object a, Object b, Object c);
}
public interface ConversionFail extends Conversion {
void missingMethod();
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.api;
public class ProxyInfoResolving {
public interface InterfaceWithoutImplementation {}
public interface ServiceWithoutImplementation {}
public interface EmptyService {}
public static class ClientProxyClassImpl {}
public static class ProxyClass {}
public interface ValidApi {}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.api;
import java.util.function.Consumer;
public class Real {
public interface Service {
Api2Way get2Way();
Api2Way passthrough(Api2Way a);
boolean isSelf(Service a);
ApiLazyNumber sum(ApiLazyNumber a, ApiLazyNumber b);
/**
* When generating bridge class, convertible method parameters are changed to Objects,
* which may cause name collisions
*/
void testMethodNameConflict(Api2Way a);
void testMethodNameConflict(ApiLazyNumber a);
}
@FunctionalInterface
public interface Api2Way extends Consumer<Object> {}
@FunctionalInterface
public interface ApiLazyNumber {
int get();
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.jbr;
public class FindDependencies {
public interface C3 {
void f(C4 c);
}
public interface C4 {}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.jbr;
public class MethodMapping {
public static class SimpleEmptyImpl {}
public static class PlainImpl {
public void a() {}
public boolean b() {
return false;
}
}
public interface Callback {
void hello();
}
public interface JBRTwoWay {
void something();
}
public interface ConversionImpl {
default SimpleEmptyImpl convert(PlainImpl a, Callback b, JBRTwoWay c) {
return null;
}
}
public static class ConversionSelfImpl implements ConversionImpl {
public ConversionSelfImpl convert(Object a, Object b, Object c) {
return null;
}
}
public static class ConversionFailImpl implements ConversionImpl {}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.jbr;
public class ProxyInfoResolving {
public static class ClientProxyClass {}
public static class ProxyClassImpl {}
public static class ValidApiImpl {}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.jbr;
import java.util.function.Consumer;
public class Real {
public static class ServiceImpl {
JBR2Way get2Way() {
return a -> {};
}
JBR2Way passthrough(JBR2Way a) {
if (a != null) a.accept(a);
return a;
}
boolean isSelf(ServiceImpl a) {
return a == this;
}
JBRLazyNumber sum(JBRLazyNumber a, JBRLazyNumber b) {
return () -> a.get() + b.get();
}
void testMethodNameConflict(JBR2Way a) {}
void testMethodNameConflict(JBRLazyNumber a) {}
}
@FunctionalInterface
public interface JBR2Way extends Consumer<Object> {}
@FunctionalInterface
public interface JBRLazyNumber {
int get();
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2000-2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @test
* @run shell build.sh
* @run main JBRApiTest
*/
import com.jetbrains.JBR;
import java.awt.*;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
public class JBRApiTest {
public static void main(String[] args) throws Exception {
if (!JBR.getApiVersion().matches("\\w+\\.\\d+\\.\\d+\\.\\d+")) throw new Error("Invalid JBR API version");
if (!JBR.isAvailable()) throw new Error("JBR API is not available");
checkMetadata();
testServices();
}
private static void checkMetadata() throws Exception {
Class<?> metadata = Class.forName(JBR.class.getName() + "$Metadata");
Field field = metadata.getDeclaredField("KNOWN_SERVICES");
field.setAccessible(true);
List<String> knownServices = List.of((String[]) field.get(null));
if (!knownServices.contains("com.jetbrains.JBR$ServiceApi")) {
throw new Error("com.jetbrains.JBR$ServiceApi was not found in known services of com.jetbrains.JBR$Metadata");
}
}
private static void testServices() {
Objects.requireNonNull((Dimension) JBR.getExtendedGlyphCache().getSubpixelResolution());
}
}

View File

@@ -0,0 +1,29 @@
#!/bin/sh
#
# Copyright 2000-2021 JetBrains s.r.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
SRC="$TESTSRC/../../../../../../src"
PWD="`pwd`"
# Generate sources
"$COMPILEJAVA/bin/java" "$SRC/jetbrains.api/templates/Gensrc.java" "$SRC" "$PWD/jbr-api" "TEST" || exit $?
# Validate version
"$COMPILEJAVA/bin/java" "$SRC/jetbrains.api/templates/CheckVersion.java" "$SRC/jetbrains.api" "$PWD/jbr-api/gensrc" "true" || exit $?
# Compile API
find "$SRC/jetbrains.api/src" -name *.java > compile.list
find jbr-api/gensrc -name *.java >> compile.list
"$COMPILEJAVA/bin/javac" $TESTJAVACOPTS -d "$TESTCLASSES" @compile.list || exit $?
rm "$TESTCLASSES/module-info.class"
exit 0

View File

@@ -0,0 +1,96 @@
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* @test
* @key headful
* @summary Test runs two times to generate and compare images with accelerated OGL and Metal text rendering.
* @run main/othervm -Dsun.java2d.metal=False OGLMetalTextRender
* @run main/othervm -Dsun.java2d.metal=True OGLMetalTextRender
*/
public class OGLMetalTextRender {
final static int WIDTH = 210;
final static int HEIGHT = 120;
final static int TD = 3;
final static File mtlImg = new File("t-metal.png");
final static File oglImg = new File("t-ogl.png");
final static File cmpImg = new File("t-mtlogl.png");
public static void main(final String[] args) throws IOException {
GraphicsEnvironment ge =
GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsConfiguration gc =
ge.getDefaultScreenDevice().getDefaultConfiguration();
VolatileImage vi = gc.createCompatibleVolatileImage(WIDTH, HEIGHT);
BufferedImage bi = gc.createCompatibleImage(WIDTH, HEIGHT);
String text = "The quick brown fox jumps over the lazy dog";
int c = 10;
do {
Graphics2D g2d = vi.createGraphics();
Font font = new Font("Serif", Font.PLAIN, 10);
g2d.setFont(font);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
for (int i = 0; i <= 10; i++) {
g2d.setColor(new Color(0, 0, 0, i/10.0f));
g2d.drawString(text, 10, 10*(i + 1));
}
g2d.dispose();
} while (vi.contentsLost() && (--c > 0));
Graphics2D g2d = bi.createGraphics();
g2d.drawImage(vi, 0, 0, null);
g2d.dispose();
int errors = 0;
BufferedImage result = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
String opt = System.getProperty("sun.java2d.metal");
if (opt != null && ("true".equals(opt) || "True".equals(opt))) {
ImageIO.write(bi, "png", mtlImg);
if (oglImg.exists()) {
errors = compare(bi, ImageIO.read(oglImg), result);
}
} else {
ImageIO.write(bi, "png", oglImg);
if (mtlImg.exists()) {
errors = compare(bi, ImageIO.read(mtlImg), result);
}
}
if (errors > 0) {
ImageIO.write(result, "png", cmpImg);
throw new RuntimeException("Metal vs OGL rendering mismatch, errors found: " + errors);
}
}
private static int compare(BufferedImage bi1, BufferedImage bi2,
BufferedImage result)
{
int errors = 0;
for (int i = 0; i < WIDTH; i++) {
for (int j = 0; j < HEIGHT; j++) {
Color c1 = new Color(bi1.getRGB(i, j));
Color c2 = new Color(bi2.getRGB(i, j));
int dr = Math.abs(c1.getRed() - c2.getRed());
int dg = Math.abs(c1.getGreen() - c2.getGreen());
int db = Math.abs(c1.getBlue() - c2.getBlue());
if (dr+dg+db > TD) {
errors++;
result.setRGB(i, j, Color.RED.getRGB());
}
Color r = new Color(dr, dg, db);
}
}
return errors;
}
}

View File

@@ -0,0 +1,391 @@
/*
* Copyright 2021 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @test
* @summary Regression test for JBR-3838 ensures that pressing each layout-free key + AltGr produces AltGr modifier
* @requires (os.family == "windows")
* @key headful
* @run main AltGrMustGenerateAltGrModifierTest3838
* @author Nikita Provotorov
*/
public class AltGrMustGenerateAltGrModifierTest3838 extends Frame {
public static void main(String[] args) throws Exception
{
final AltGrMustGenerateAltGrModifierTest3838 mainWindow = new AltGrMustGenerateAltGrModifierTest3838();
try {
mainWindow.setVisible(true);
mainWindow.toFront();
final Robot robot = new Robot();
forceFocusTo(mainWindow.textArea, robot);
Thread.sleep(PAUSE_MS);
mainWindow.pressAllKeysWithAltGr(robot);
Thread.sleep(PAUSE_MS);
if (mainWindow.allKeysWerePressedAndReleased &&
mainWindow.allModifiersAreCorrect &&
mainWindow.allModifiersExAreCorrect) {
System.out.println("Test passed.");
} else {
throw new Exception("Test failed");
}
} finally {
mainWindow.dispose();
}
}
private AltGrMustGenerateAltGrModifierTest3838()
{
super("AltGr must generate AltGr modifier");
setAlwaysOnTop(true);
textArea = new TextArea();
textArea.setFocusable(true);
add(textArea);
pack();
setSize(250, 250);
textArea.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e)
{
keyHandlerImpl("keyPressed", e, pressedKeyToCheck);
}
@Override
public void keyReleased(KeyEvent e)
{
keyHandlerImpl("keyReleased", e, pressedKeyToCheck);
}
private void keyHandlerImpl(final String eventName, final KeyEvent e, final Integer expectedPressedKey)
{
if (expectedPressedKey == null) {
return;
}
try {
// Old API.
// Not all keyboard layouts have AltGr (e.g. ENG-US).
// On this keyboards right Alt produces only Alt without Ctrl.
final int modifiersOnMask = /*KeyEvent.CTRL_MASK |*/ KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_MASK;
final int modifiers = e.getModifiers();
if ((modifiers & modifiersOnMask) != modifiersOnMask) {
allModifiersAreCorrect = false;
System.err.println(eventName + " {" + keyCodeToText(expectedPressedKey) + "}: wrong Modifiers;" +
" expected: " + modifiersToText(modifiersOnMask) +
", actual: " + modifiersToText(modifiers) + ".");
}
// Modern API.
// Not all keyboard layouts have AltGr (e.g. ENG-US).
// On this keyboards right Alt produces only Alt without Ctrl.
final int modifiersExOnMask = /*KeyEvent.CTRL_DOWN_MASK |*/ KeyEvent.ALT_DOWN_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK;
final int modifiersEx = e.getModifiersEx();
if ((modifiersEx & modifiersExOnMask) != modifiersExOnMask) {
allModifiersExAreCorrect = false;
System.err.println(eventName + " {" + keyCodeToText(expectedPressedKey) + "}: wrong ModifiersEx;" +
" expected: " + modifiersExToText(modifiersExOnMask) +
", actual: " + modifiersExToText(modifiersEx) + ".");
}
} finally {
keyPressedLatch.countDown();
}
}
});
}
private void pressAllKeysWithAltGr(final Robot robot) throws Exception
{
for (final int keyToPress : allLayoutFreeVirtualKeys) {
keyPressedLatch = new CountDownLatch(2);
pressKeyWithAltGr(keyToPress, robot);
if (!keyPressedLatch.await(PAUSE_MS, TimeUnit.MILLISECONDS)) {
allKeysWerePressedAndReleased = false;
System.err.println("Pressing {" + keyCodeToText(KeyEvent.VK_ALT_GRAPH) + " + " + keyCodeToText(keyToPress) + "}" +
": not all keys have been pressed or released.");
}
}
}
private void pressKeyWithAltGr(final int keyToPress, final Robot robot)
{
pressedKeyToCheck = null;
robot.waitForIdle();
robot.keyPress(KeyEvent.VK_ALT_GRAPH);
robot.waitForIdle();
try {
robot.keyPress(pressedKeyToCheck = keyToPress);
robot.keyRelease(keyToPress);
robot.waitForIdle();
pressedKeyToCheck = null;
// restore lock-keys state
if ( (keyToPress == KeyEvent.VK_CAPS_LOCK) ||
(keyToPress == KeyEvent.VK_NUM_LOCK) ||
(keyToPress == KeyEvent.VK_KANA_LOCK) ||
(keyToPress == KeyEvent.VK_SCROLL_LOCK) ) {
robot.keyPress(keyToPress);
robot.keyRelease(keyToPress);
}
} finally {
robot.keyRelease(KeyEvent.VK_ALT_GRAPH);
robot.waitForIdle();
}
}
// Sometimes top-level Frame does not get focus when requestFocus is called.
// For example, when this test is launched after test/.../bug6361367.java:
// jtreg test/jdk/javax/swing/text/JTextComponent/6361367/bug6361367.java test/jdk/jb/java/awt/keyboard/AltGrMustGenerateAltGrModifierTest3838.java
//
// So this method forces the focus acquiring via mouse clicking to the component.
private static void forceFocusTo(final Component component, final Robot robot) {
robot.waitForIdle();
final Point componentTopLeft = component.getLocationOnScreen();
final int componentCenterX = componentTopLeft.x + component.getWidth() / 2;
final int componentCenterY = componentTopLeft.y + component.getHeight() / 2;
robot.mouseMove(componentCenterX, componentCenterY);
robot.waitForIdle();
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
robot.waitForIdle();
component.requestFocus();
robot.waitForIdle();
}
private static String keyCodeToText(int keyCode) {
return "<" + KeyEvent.getKeyText(keyCode) + " (code=" + keyCode + ")>";
}
private static String modifiersToText(int modifiers) {
return "[" + KeyEvent.getKeyModifiersText(modifiers) + " (code=" + modifiers + ")]";
}
private static String modifiersExToText(int modifiersEx) {
return "[" + KeyEvent.getModifiersExText(modifiersEx) + " (code=" + modifiersEx + ")]";
}
private static final int PAUSE_MS = 2000;
private final TextArea textArea;
private volatile boolean allKeysWerePressedAndReleased = true;
private volatile boolean allModifiersAreCorrect = true;
private volatile boolean allModifiersExAreCorrect = true;
private volatile Integer pressedKeyToCheck = null;
private volatile CountDownLatch keyPressedLatch = null;
private final int[] allLayoutFreeVirtualKeys = new int[] {
// Modifier keys
KeyEvent.VK_CAPS_LOCK,
KeyEvent.VK_SHIFT,
/* VK_CONTROL releasing removes SHIFT_MASK, SHIFT_DOWN_MASK - notepad.exe, VSCode have the same behavior */
//KeyEvent.VK_CONTROL,
KeyEvent.VK_ALT,
//KeyEvent.VK_ALT_GRAPH,
KeyEvent.VK_NUM_LOCK,
// Miscellaneous Windows keys
KeyEvent.VK_WINDOWS,
KeyEvent.VK_WINDOWS,
KeyEvent.VK_CONTEXT_MENU,
// Alphabet
KeyEvent.VK_A,
KeyEvent.VK_B,
KeyEvent.VK_C,
KeyEvent.VK_D,
KeyEvent.VK_E,
KeyEvent.VK_F,
KeyEvent.VK_G,
KeyEvent.VK_H,
KeyEvent.VK_I,
KeyEvent.VK_J,
KeyEvent.VK_K,
KeyEvent.VK_L,
KeyEvent.VK_M,
KeyEvent.VK_N,
KeyEvent.VK_O,
KeyEvent.VK_P,
KeyEvent.VK_Q,
KeyEvent.VK_R,
KeyEvent.VK_S,
KeyEvent.VK_T,
KeyEvent.VK_U,
KeyEvent.VK_V,
KeyEvent.VK_W,
KeyEvent.VK_X,
KeyEvent.VK_Y,
KeyEvent.VK_Z,
// Standard numeric row
KeyEvent.VK_0,
KeyEvent.VK_1,
KeyEvent.VK_2,
KeyEvent.VK_3,
KeyEvent.VK_4,
KeyEvent.VK_5,
KeyEvent.VK_6,
KeyEvent.VK_7,
KeyEvent.VK_8,
KeyEvent.VK_9,
// Misc key from main block
KeyEvent.VK_ENTER,
/* avoid SC_KEYMENU event on layouts which don't have AltGr */
//KeyEvent.VK_SPACE,
KeyEvent.VK_BACK_SPACE,
/* avoid focus switching */
//KeyEvent.VK_TAB,
//KeyEvent.VK_ESCAPE,
// NumPad with NumLock off & extended block (rectangular)
KeyEvent.VK_INSERT,
KeyEvent.VK_DELETE,
KeyEvent.VK_HOME,
KeyEvent.VK_END,
KeyEvent.VK_PAGE_UP,
KeyEvent.VK_PAGE_DOWN,
KeyEvent.VK_CLEAR,
// NumPad with NumLock off & extended arrows block (triangular)
KeyEvent.VK_LEFT,
KeyEvent.VK_RIGHT,
KeyEvent.VK_UP,
KeyEvent.VK_DOWN,
// NumPad with NumLock on: numbers
KeyEvent.VK_NUMPAD0,
KeyEvent.VK_NUMPAD1,
KeyEvent.VK_NUMPAD2,
KeyEvent.VK_NUMPAD3,
KeyEvent.VK_NUMPAD4,
KeyEvent.VK_NUMPAD5,
KeyEvent.VK_NUMPAD6,
KeyEvent.VK_NUMPAD7,
KeyEvent.VK_NUMPAD8,
KeyEvent.VK_NUMPAD9,
// NumPad with NumLock on
KeyEvent.VK_MULTIPLY,
KeyEvent.VK_ADD,
KeyEvent.VK_SEPARATOR,
KeyEvent.VK_SUBTRACT,
KeyEvent.VK_DECIMAL,
KeyEvent.VK_DIVIDE,
// Functional keys
KeyEvent.VK_F1,
KeyEvent.VK_F2,
KeyEvent.VK_F3,
KeyEvent.VK_F4,
KeyEvent.VK_F5,
KeyEvent.VK_F6,
KeyEvent.VK_F7,
KeyEvent.VK_F8,
KeyEvent.VK_F9,
KeyEvent.VK_F10,
KeyEvent.VK_F11,
KeyEvent.VK_F12,
KeyEvent.VK_F13,
KeyEvent.VK_F14,
KeyEvent.VK_F15,
KeyEvent.VK_F16,
KeyEvent.VK_F17,
KeyEvent.VK_F18,
KeyEvent.VK_F19,
KeyEvent.VK_F20,
KeyEvent.VK_F21,
KeyEvent.VK_F22,
KeyEvent.VK_F23,
KeyEvent.VK_F24,
/* Windows does not produce WM_KEYDOWN for VK_SNAPSHOT; see JDK-4455060 */
//KeyEvent.VK_PRINTSCREEN,
KeyEvent.VK_SCROLL_LOCK,
KeyEvent.VK_PAUSE,
KeyEvent.VK_CANCEL,
KeyEvent.VK_HELP,
// Japanese
/* -> [VK_ALT] + VK_CONVERT -> [VK_ALT] + VK_ALL_CANDIDATES */
KeyEvent.VK_CONVERT,
KeyEvent.VK_NONCONVERT,
KeyEvent.VK_INPUT_METHOD_ON_OFF,
/* -> [VK_ALT] + VK_ALPHANUMERIC -> [VK_ALT] + VK_CODE_INPUT */
KeyEvent.VK_ALPHANUMERIC,
KeyEvent.VK_KATAKANA,
KeyEvent.VK_HIRAGANA,
KeyEvent.VK_FULL_WIDTH,
KeyEvent.VK_HALF_WIDTH,
KeyEvent.VK_ROMAN_CHARACTERS,
KeyEvent.VK_ALL_CANDIDATES,
/* [VK_ALT] + VK_PREVIOUS_CANDIDATE -> [VK_ALT + VK_SHIFT] + VK_CONVERT -> [VK_ALT + VK_SHIFT] + VK_ALL_CANDIDATES */
KeyEvent.VK_PREVIOUS_CANDIDATE,
KeyEvent.VK_CODE_INPUT,
/* can only be found if is available */
//KeyEvent.VK_KANA_LOCK,
};
}

View File

@@ -0,0 +1,50 @@
java/awt/Focus/ModalDialogInitialFocusTest/ModalDialogInitialFocusTest.java JBR-3730 macosx-all
java/awt/Focus/ModalExcludedWindowClickTest/ModalExcludedWindowClickTest.java JBR-3730 macosx-all
java/awt/Focus/WindowIsFocusableAccessByThreadsTest/WindowIsFocusableAccessByThreadsTest.java JBR-3730 macosx-all
java/awt/Mixing/ValidBounds.java JBR-3730 macosx-all
java/awt/Mixing/Validating.java JBR-3730 macosx-all
sanity/client/SwingSet/src/TableDemoTest.java JBR-3389 macosx-all intermittent
java/awt/Component/7097771/bug7097771.java JBR-3730 macosx-all
java/awt/Component/NoUpdateUponShow/NoUpdateUponShow.java JBR-3730 macosx-all
java/awt/Component/SetComponentsBounds/SetComponentsBounds.java JBR-3722 macosx-all
java/awt/event/ComponentEvent/MovedResizedTwiceTest/MovedResizedTwiceTest.java JBR-3730 macosx-all
java/awt/event/MouseEvent/EnterAsGrabbedEvent/EnterAsGrabbedEvent.java JBR-3730 macosx-all
java/awt/FileDialog/ModalFocus/FileDialogModalFocusTest.java JBR-3730 macosx-all
java/awt/Focus/AppletInitialFocusTest/AppletInitialFocusTest.java JBR-3730 macosx-all
java/awt/Focus/AppletInitialFocusTest/AppletInitialFocusTest1.java JBR-3730 macosx-all
java/awt/Focus/ClearGlobalFocusOwnerTest/ClearGlobalFocusOwnerTest.java JBR-3730 macosx-all
java/awt/Focus/ChildWindowFocusTest/ChildWindowFocusTest.java JBR-3730 macosx-all
java/awt/Focus/DisposedWindow/DisposeDialogNotActivateOwnerTest/DisposeDialogNotActivateOwnerTest.java JBR-3730 macosx-all
java/awt/Focus/FrameMinimizeTest/FrameMinimizeTest.java JBR-3730 macosx-all
java/awt/Focus/NonFocusableWindowTest/NoEventsTest.java JBR-3730 macosx-all
java/awt/Focus/RemoveAfterRequest/RemoveAfterRequest.java JBR-3730 macosx-all
java/awt/Focus/RequestFocusAndHideTest/RequestFocusAndHideTest.java JBR-3730 macosx-all
java/awt/Focus/RequestFocusByCause/RequestFocusByCauseTest.java JBR-3730 macosx-all
java/awt/Focus/RequestOnCompWithNullParent/RequestOnCompWithNullParent1.java JBR-3730 macosx-all
java/awt/Focus/RestoreFocusOnDisabledComponentTest/RestoreFocusOnDisabledComponentTest.java JBR-3730 macosx-all
java/awt/Focus/ShowFrameCheckForegroundTest/ShowFrameCheckForegroundTest.java JBR-3733 macosx-all
java/awt/Focus/ToFrontFocusTest/ToFrontFocus.java JBR-3730 macosx-all
java/awt/Focus/WindowInitialFocusTest/WindowInitialFocusTest.java JBR-3730 macosx-all
java/awt/Focus/WindowUpdateFocusabilityTest/WindowUpdateFocusabilityTest.java JBR-3730 macosx-all
java/awt/Frame/MiscUndecorated/ActiveAWTWindowTest.java JBR-3730 macosx-all
java/awt/Frame/MiscUndecorated/FrameCloseTest.java JBR-3733 macosx-all
java/awt/Frame/ObscuredFrame/ObscuredFrameTest.java JBR-3730 macosx-all
java/awt/GridLayout/ChangeGridSize/ChangeGridSize.java JBR-3730 macosx-all
java/awt/GridLayout/ComponentPreferredSize/ComponentPreferredSize.java JBR-3730 macosx-all
java/awt/Insets/CombinedTestApp1.java JBR-3730 macosx-all
java/awt/KeyboardFocusmanager/TypeAhead/EnqueueWithDialogButtonTest/EnqueueWithDialogButtonTest.java JBR-3730 macosx-all
java/awt/KeyboardFocusmanager/TypeAhead/EnqueueWithDialogTest/EnqueueWithDialogTest.java JBR-3730 macosx-all
java/awt/KeyboardFocusmanager/TypeAhead/FreezeTest/FreezeTest.java JBR-3730 macosx-all
java/awt/Mixing/MixingOnDialog.java JBR-3730 macosx-all
java/awt/Mixing/MixingOnShrinkingHWButton.java JBR-3730 macosx-all
java/awt/MouseAdapter/MouseAdapterUnitTest/MouseAdapterUnitTest.java JBR-3730 macosx-all
java/awt/Paint/ButtonRepaint.java JBR-3730 macosx-all
java/awt/print/PaintSetEnabledDeadlock/PaintSetEnabledDeadlock.java JBR-3730 macosx-all
java/awt/Toolkit/RealSync/Test.java JBR-3730 macosx-all
java/awt/TrayIcon/ActionEventMask/ActionEventMask.java JBR-3730 macosx-all
java/awt/Window/ShapedAndTranslucentWindows/TranslucentChoice.java JBR-3730 macosx-all
java/awt/Window/ShapedAndTranslucentWindows/TranslucentWindowClick.java JBR-3730 macosx-all
java/awt/Window/setLocRelativeTo/SetLocationRelativeToTest.java JBR-3730 macosx-all
javax/swing/ProgressMonitor/ProgressMonitorEscapeKeyPress.java JBR-3730 macosx-all
sanity/client/SwingSet/src/TreeDemoTest.java JBR-3389 macosx-all

View File

@@ -953,4 +953,8 @@ javax/print/PrintServiceLookup/CountPrintServices.java
javax/print/attribute/AttributeTest.java nobug generic-all
javax/print/attribute/GetCopiesSupported.java nobug generic-all
javax/print/attribute/SidesPageRangesTest.java nobug generic-all
javax/print/attribute/SupportedPrintableAreas.java nobug generic-all
javax/print/attribute/SupportedPrintableAreas.java nobug generic-all
# Fedora & ArchLinux (Wayland) & Ubuntu 21.04
sun/java2d/ClassCastExceptionForInvalidSurface.java JBR-3167 linux-5.11.0-37-generic,linux-5.10.12-200.fc33.x86_64,linux-5.11.6-arch1-1
java/awt/Frame/HugeFrame/HugeFrame.java JBR-3167 linux-5.11.0-37-generic,linux-5.10.12-200.fc33.x86_64,linux-5.11.6-arch1-1