Added JBR API

with fix for JBR-5300 Change source code and test files to use GPL license

(cherry picked from commit 2ce0a876c5)
This commit is contained in:
Nikita Gubarkov
2021-09-15 16:11:03 +03:00
committed by jbrbot
parent 020672efb3
commit c31c9f4e21
41 changed files with 3516 additions and 2 deletions

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,18 @@
#!/bin//bash
set -euo pipefail
# $1 - Boot JDK
# $2 - JBR part of API version
cd "`dirname "$0"`/../../../../.."
PWD="`pwd`"
CONF="$PWD/build/jbr-api.conf"
./configure --with-debug-level=release --with-boot-jdk=$1 || exit $?
make jbr-api CONF=release MAKEOVERRIDES= "JBR_API_CONF_FILE=$CONF" JBR_API_JBR_VERSION=$2 || exit $?
. $CONF || exit $?
echo "##teamcity[buildNumber '$VERSION']"
cp "$JAR" ./jbr-api-${VERSION}.jar || exit $?
cp "$SOURCES_JAR" ./jbr-api-${VERSION}-sources.jar || exit $?
echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}.jar']"
echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}-sources.jar']"

93
make/JBRApi.gmk Normal file
View File

@@ -0,0 +1,93 @@
#
# Copyright 2000-2023 JetBrains s.r.o.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
include $(SPEC)
include MakeBase.gmk
include JavaCompilation.gmk
JBR_API_ROOT_DIR := $(TOPDIR)/src/jetbrains.api
JBR_API_TOOLS_DIR := $(JBR_API_ROOT_DIR)/tools
JBR_API_SRC_DIR := $(JBR_API_ROOT_DIR)/src
JBR_API_OUTPUT_DIR := $(OUTPUTDIR)/jbr-api
JBR_API_GENSRC_DIR := $(JBR_API_OUTPUT_DIR)/gensrc
JBR_API_BIN_DIR := $(JBR_API_OUTPUT_DIR)/bin
JBR_API_VERSION_PROPERTIES := $(JBR_API_ROOT_DIR)/version.properties
JBR_API_VERSION_GENSRC := $(JBR_API_OUTPUT_DIR)/jbr-api.version
JBR_API_GENSRC_BATCH := $(JBR_API_VERSION_GENSRC)
JBR_API_SRC_FILES := $(call FindFiles, $(JBR_API_SRC_DIR))
JBR_API_GENSRC_FILES := $(foreach f, $(call FindFiles, $(JBR_API_SRC_DIR)), \
$(JBR_API_GENSRC_DIR)/$(call RelativePath, $f, $(JBR_API_SRC_DIR)))
ifeq ($(JBR_API_JBR_VERSION),)
JBR_API_JBR_VERSION := <DEVELOPMENT>
JBR_API_FAIL_ON_HASH_MISMATCH := false
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), \
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), \
SRCS := $(JBR_API_GENSRC_DIR), \
JAR := $(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar, \
SUFFIXES := .java, \
BIN := $(JBR_API_BIN_DIR), \
))
# Grouped targets may not be supported, so hack dependencies: sources -> version file -> generated sources
$(JBR_API_VERSION_GENSRC): $(JBR_API_SRC_FILES) $(JBR_API_VERSION_PROPERTIES) $(JBR_API_TOOLS_DIR)/Gensrc.java
$(ECHO) Generating sources for JBR API
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/Gensrc.java" \
"$(TOPDIR)/src" "$(JBR_API_OUTPUT_DIR)" "$(JBR_API_JBR_VERSION)"
$(JBR_API_GENSRC_FILES): $(JBR_API_VERSION_GENSRC)
$(TOUCH) $@
jbr-api-check-version: $(JBR_API_GENSRC_FILES) $(JBR_API_VERSION_PROPERTIES)
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/CheckVersion.java" \
"$(JBR_API_ROOT_DIR)" "$(JBR_API_GENSRC_DIR)" "$(JBR_API_FAIL_ON_HASH_MISMATCH)"
jbr-api: $(BUILD_JBR_API) $(BUILD_JBR_API_SOURCES_JAR) jbr-api-check-version
.PHONY: jbr-api jbr-api-check-version
ifneq ($(JBR_API_CONF_FILE),)
$(JBR_API_CONF_FILE): $(JBR_API_GENSRC_FILES)
$(ECHO) "VERSION=`$(CAT) $(JBR_API_VERSION_GENSRC)`" > $(JBR_API_CONF_FILE)
$(ECHO) "JAR=$(JBR_API_OUTPUT_DIR)/jbr-api.jar" >> $(JBR_API_CONF_FILE)
$(ECHO) "SOURCES_JAR=$(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar" >> $(JBR_API_CONF_FILE)
jbr-api: $(JBR_API_CONF_FILE)
.PHONY: $(JBR_API_CONF_FILE)
endif

View File

@@ -1477,6 +1477,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 \
))
.PHONY: $(ALL_TARGETS)
FRC: # Force target

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.base;
import com.jetbrains.internal.JBRApi;
import java.lang.invoke.MethodHandles;
/**
* This class contains mapping between JBR API interfaces and implementation in {@code java.base} module.
*/
public class JBRApiModule {
static {
JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports)
.service("com.jetbrains.JBR$ServiceApi", null)
.withStatic("getService", "com.jetbrains.internal.JBRApi");
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.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,138 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import 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.ClassFileFormatVersion;
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);
}
}
public static final int CLASSFILE_VERSION = ClassFileFormatVersion.latest().major();
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,331 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
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");
Lookup lookup = MethodHandles.privateLookupIn(metadataClass, outerLookup);
knownServices = Set.of((String[]) lookup.findStaticVarHandle(metadataClass,
"KNOWN_SERVICES", String[].class).get());
knownProxies = Set.of((String[]) lookup.findStaticVarHandle(metadataClass,
"KNOWN_PROXIES", String[].class).get());
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
knownServices = Set.of();
knownProxies = Set.of();
}
}
/**
* @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().isPublicApi() ? outerLookup : info.apiModule())
.findClass(info.interfaceName());
} catch (ClassNotFoundException | IllegalAccessException e) {
return null;
}
}
public static InternalServiceBuilder internalServiceBuilder(Lookup interFace, String target) {
return new InternalServiceBuilder(new RegisteredProxyInfo(
interFace, interFace.lookupClass().getName(), target, ProxyInfo.Type.INTERNAL_SERVICE, new ArrayList<>()));
}
public static class InternalServiceBuilder {
private final RegisteredProxyInfo info;
private InternalServiceBuilder(RegisteredProxyInfo info) {
this.info = info;
}
public InternalServiceBuilder withStatic(String methodName, String clazz) {
return withStatic(methodName, clazz, methodName);
}
public InternalServiceBuilder withStatic(String interfaceMethodName, String clazz, String methodName) {
info.staticMethods().add(
new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, clazz, methodName));
return this;
}
public Object build() {
ProxyInfo info = ProxyInfo.resolve(this.info);
if (info == null) return null;
ProxyGenerator generator = new ProxyGenerator(info);
if (!generator.areAllMethodsImplemented()) return null;
generator.defineClasses();
MethodHandle constructor = generator.findConstructor();
generator.init();
try {
return constructor.invoke();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
/**
* 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,220 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.invoke.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,207 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.reflect.*;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
/**
* This class collects {@linkplain Proxy proxy} dependencies.
* <p>
* Dependencies of a class {@code C} are other classes that are
* used by {@code C} (i.e. supertypes, classes that appear in method
* parameters, return types) and all their dependencies. Any class
* is also considered a dependency of itself.
* <p>
* Dependencies allow JBR to validate whole set of interfaces for
* a particular feature instead of treating them as separate entities.
* <h2>Example</h2>
* Suppose we implemented some feature and added some API for it:
* <blockquote><pre>{@code
* interface SomeFeature {
* SomeOtherObject createSomeObject(int magicNumber);
* }
* interface SomeOtherObject {
* int getMagicNumber();
* }
* }</pre></blockquote>
* And then used it:
* <blockquote><pre>{@code
* if (JBR.isSomeFeatureSupported()) {
* SomeOtherObject object = JBR.getSomeFeature().createSomeObject(123);
* int magic = object.getMagicNumber();
* }
* }</pre></blockquote>
* Suppose JBR was able to find implementation for {@code SomeFeature.createSomeObject()},
* but not for {@code SomeOtherObject.getMagicNumber()}. So {@code JBR.getSomeFeature()}
* would succeed and return service instance, but {@code createSomeObject()} would fail,
* because JBR wasn't able to find implementation for {@code SomeOtherObject.getMagicNumber()}
* and therefore couldn't create proxy for {@code SomeOtherObject} class.
* <p>
* To avoid such issues, not only proxy interface itself, but all proxies that are accessible
* from current proxy interface must have proper implementation.
*/
class ProxyDependencyManager {
private static final ConcurrentMap<Class<?>, Set<Class<?>>> cache = new ConcurrentHashMap<>();
/**
* @return all proxy interfaces that are used (directly or indirectly) by given interface, including itself.
*/
static Set<Class<?>> getProxyDependencies(Class<?> interFace) {
Set<Class<?>> dependencies = cache.get(interFace);
if (dependencies != null) return dependencies;
step(null, interFace);
return cache.get(interFace);
}
/**
* Collect dependencies for given class and store them into cache.
*/
private static void step(Node parent, Class<?> clazz) {
if (!clazz.getPackageName().startsWith("com.jetbrains")) 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,469 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import 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.isPublicApi();
int nameId = nameCounter.getAndIncrement();
proxyName = Type.getInternalName(info.interFace) + "$$JBRApiProxy$" + nameId;
bridgeName = generateBridge ? info.apiModule.lookupClass().getPackageName().replace('.', '/') + "/" +
info.interFace.getSimpleName() + "$$JBRApiBridge$" + nameId : null;
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.isService()) {
try {
return MethodHandles.foldArguments(c, info.target.findConstructor(info.target.lookupClass(),
MethodType.methodType(void.class)).asType(MethodType.methodType(Object.class)));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException("Service implementation must have no-args constructor: " +
info.target.lookupClass(), e);
}
}
return c;
}
} catch (IllegalAccessException | NoSuchMethodException e) {
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,119 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import 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 || type == Type.INTERNAL_SERVICE ? apiModule : JBRApi.outerLookup;
}
Lookup getTargetLookup() {
return type == Type.CLIENT_PROXY ? JBRApi.outerLookup : apiModule;
}
private Lookup lookup(Lookup lookup, String clazz) {
String[] nestedClasses = clazz.split("\\$");
clazz = "";
for (int i = 0; i < nestedClasses.length; i++) {
try {
if (i != 0) clazz += "$";
clazz += nestedClasses[i];
lookup = MethodHandles.privateLookupIn(Class.forName(clazz, true, lookup.lookupClass().getClassLoader()), lookup);
} catch (ClassNotFoundException | IllegalAccessException ignore) {
return null;
}
}
return lookup;
}
record StaticMethodMapping(Lookup lookup, String methodName) {}
/**
* Proxy type, see {@link Proxy}
*/
enum Type {
PROXY,
SERVICE,
CLIENT_PROXY,
INTERNAL_SERVICE;
public boolean isPublicApi() {
return this == PROXY || this == SERVICE;
}
public boolean isService() {
return this == SERVICE || this == INTERNAL_SERVICE;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.invoke.MethodHandles;
import java.util.List;
/**
* Raw proxy info, as it was registered through {@link JBRApi.ModuleRegistry}.
* Contains all necessary information to create a {@linkplain Proxy proxy}.
*/
record RegisteredProxyInfo(MethodHandles.Lookup apiModule,
String interfaceName,
String target,
ProxyInfo.Type type,
List<StaticMethodMapping> staticMethods) {
record StaticMethodMapping(String interfaceMethodName, String clazz, String methodName) {}
}

View File

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

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.desktop;
import com.jetbrains.internal.JBRApi;
import java.lang.invoke.MethodHandles;
/**
* This class contains mapping between JBR API interfaces and implementation in {@code java.desktop} module.
*/
public class JBRApiModule {
static {
JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports)
.service("com.jetbrains.ExtendedGlyphCache", null)
.withStatic("getSubpixelResolution", "sun.font.FontUtilities");
}
}

View File

@@ -48,7 +48,7 @@ public final class FontUtilities {
public static boolean isWindows;
public static Dimension subpixelResolution;
static Dimension subpixelResolution;
private static boolean debugFonts = false;
private static PlatformLogger logger = null;
@@ -126,6 +126,10 @@ public final class FontUtilities {
}
}
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

@@ -0,0 +1,32 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains;
import java.awt.*;
public interface ExtendedGlyphCache {
Dimension getSubpixelResolution();
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
/**
* This class is an entry point into JBR API.
* JBR API is a collection of services, classes, interfaces, etc.,
* which require tight interaction with JRE and therefore are implemented inside JBR.
* <div>JBR API consists of two parts:</div>
* <ul>
* <li>Client side - {@code jetbrains.api} module, mostly containing interfaces</li>
* <li>JBR side - actual implementation code inside JBR</li>
* </ul>
* Client and JBR side are linked dynamically at runtime and do not have to be of the same version.
* In some cases (e.g. running on different JRE or old JBR) system will not be able to find
* implementation for some services, so you'll need a fallback behavior for that case.
* <h2>Simple usage example:</h2>
* <blockquote><pre>{@code
* if (JBR.isSomeServiceSupported()) {
* JBR.getSomeService().doSomething();
* } else {
* planB();
* }
* }</pre></blockquote>
* @implNote JBR API is initialized on first access to this class (in static initializer).
* Actual implementation is linked on demand, when corresponding service is requested by client.
*/
public class JBR {
private static final ServiceApi api;
private static final Exception bootstrapException;
static {
ServiceApi a = null;
Exception exception = null;
try {
a = (ServiceApi) Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap")
.getMethod("bootstrap", MethodHandles.Lookup.class)
.invoke(null, MethodHandles.lookup());
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof Error error) throw error;
else throw new Error(t);
} catch (IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) {
exception = e;
}
api = a;
bootstrapException = exception;
}
private JBR() {}
private static <T> T getService(Class<T> interFace, FallbackSupplier<T> fallback) {
T service = getService(interFace);
try {
return service != null ? service : fallback != null ? fallback.get() : null;
} catch (Throwable ignore) {
return null;
}
}
static <T> T getService(Class<T> interFace) {
return api == null ? null : api.getService(interFace);
}
/**
* @return true when running on JBR which implements JBR API
*/
public static boolean isAvailable() {
return api != null;
}
/**
* @return JBR API version in form {@code JBR.MAJOR.MINOR.PATCH}
* @implNote This is an API version, which comes with client application,
* it has nothing to do with JRE it runs on.
*/
public static String getApiVersion() {
return "/*API_VERSION*/";
}
/**
* Internal API interface, contains most basic methods for communication between client and JBR.
*/
private interface ServiceApi {
<T> T getService(Class<T> interFace);
}
@FunctionalInterface
private interface FallbackSupplier<T> {
T get() throws Throwable;
}
// ========================== Generated metadata ==========================
/**
* Generated client-side metadata, needed by JBR when linking the implementation.
*/
private static final class Metadata {
private static final String[] KNOWN_SERVICES = {/*KNOWN_SERVICES*/};
private static final String[] KNOWN_PROXIES = {/*KNOWN_PROXIES*/};
}
// ======================= Generated static methods =======================
/*GENERATED_METHODS*/
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
module jetbrains.api {
exports com.jetbrains;
requires static transitive java.desktop;
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.function.Function;
public class CheckVersion {
private static final Map<String, Function<String, String>> FILE_TRANSFORMERS = Map.of(
"com/jetbrains/JBR.java", c -> {
// Exclude API version from hash calculation
int versionMethodIndex = c.indexOf("getApiVersion()");
int versionStartIndex = c.indexOf("\"", versionMethodIndex) + 1;
int versionEndIndex = c.indexOf("\"", versionStartIndex);
return c.substring(0, versionStartIndex) + c.substring(versionEndIndex);
}
);
private static Path gensrc;
/**
* <ul>
* <li>$0 - absolute path to {@code JetBrainsRuntime/src/jetbrains.api} dir</li>
* <li>$1 - absolute path to gensrc dir ({@code JetBrainsRuntime/build/<conf>/jbr-api/gensrc})</li>
* <li>$2 - true if hash mismatch is an error</li>
* </ul>
*/
public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
Path versionFile = Path.of(args[0]).resolve("version.properties");
gensrc = Path.of(args[1]);
boolean error = args[2].equals("true");
Properties props = new Properties();
props.load(Files.newInputStream(versionFile));
String hash = SourceHash.calculate();
if (hash.equals(props.getProperty("HASH"))) return;
System.err.println("================================================================================");
if (error) {
System.err.println("Error: jetbrains.api code was changed, hash and API version must be updated in " + versionFile);
} else {
System.err.println("Warning: jetbrains.api code was changed, " +
"update hash and increment API version in " + versionFile + " before committing these changes");
}
System.err.println("HASH = " + hash);
if (!error) System.err.println("DO NOT COMMIT YOUR CHANGES WITH THIS WARNING");
System.err.println("================================================================================");
if (error) System.exit(-1);
}
private static class SourceHash {
private static String calculate() throws NoSuchAlgorithmException, IOException {
MessageDigest hash = MessageDigest.getInstance("MD5");
calculate(gensrc, hash);
byte[] digest = hash.digest();
StringBuilder result = new StringBuilder();
for (byte b : digest) {
result.append(String.format("%X", b));
}
return result.toString();
}
private static void calculate(Path dir, MessageDigest hash) throws IOException {
for (Entry f : findFiles(dir)) {
hash.update(f.name.getBytes(StandardCharsets.UTF_8));
hash.update(f.content.getBytes(StandardCharsets.UTF_8));
}
}
private static List<Entry> findFiles(Path dir) throws IOException {
List<Entry> files = new ArrayList<>();
FileVisitor<Path> fileFinder = new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
try {
Path rel = dir.relativize(file);
StringBuilder name = new StringBuilder();
for (int i = 0; i < rel.getNameCount(); i++) {
if (!name.isEmpty()) name.append('/');
name.append(rel.getName(i));
}
String content = Files.readString(file);
String fileName = name.toString();
files.add(new Entry(FILE_TRANSFORMERS.getOrDefault(fileName, c -> c).apply(content), fileName));
return FileVisitResult.CONTINUE;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
};
Files.walkFileTree(dir, fileFinder);
files.sort(Comparator.comparing(Entry::name));
return files;
}
private record Entry(String content, String name) {}
}
}

View File

@@ -0,0 +1,299 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.nio.file.StandardOpenOption.*;
import static java.util.regex.Pattern.compile;
/**
* Codegen script for {@code jetbrains.api} module.
* It produces "main" {@link com.jetbrains.JBR} class from template by
* inspecting interfaces and implementation code and inserting
* static utility methods for public services as well as some metadata
* needed by JBR at runtime.
*/
public class Gensrc {
private static Path srcroot, src, gensrc;
private static String apiVersion;
private static JBRModules modules;
/**
* <ul>
* <li>$0 - absolute path to {@code JetBrainsRuntime/src} dir</li>
* <li>$1 - absolute path to jbr-api output dir ({@code JetBrainsRuntime/build/<conf>/jbr-api})</li>
* <li>$2 - {@code JBR} part of API version</li>
* </ul>
*/
public static void main(String[] args) throws IOException {
srcroot = Path.of(args[0]);
Path module = srcroot.resolve("jetbrains.api");
src = module.resolve("src");
Path output = Path.of(args[1]);
gensrc = output.resolve("gensrc");
Files.createDirectories(gensrc);
Properties props = new Properties();
props.load(Files.newInputStream(module.resolve("version.properties")));
apiVersion = args[2] + "." + props.getProperty("VERSION");
Files.writeString(output.resolve("jbr-api.version"), apiVersion,
CREATE, WRITE, TRUNCATE_EXISTING);
modules = new JBRModules();
generateFiles();
}
private static void generateFiles() throws IOException {
Files.walkFileTree(src, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
try {
Path rel = src.relativize(file);
Path output = gensrc.resolve(rel);
Files.createDirectories(output.getParent());
String content = generateContent(file.getFileName().toString(), Files.readString(file));
Files.writeString(output, content, CREATE, WRITE, TRUNCATE_EXISTING);
return FileVisitResult.CONTINUE;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
});
}
private static String generateContent(String fileName, String content) throws IOException {
return switch (fileName) {
case "JBR.java" -> JBR.generate(content);
default -> generate(content);
};
}
private static String generate(String content) throws IOException {
Pattern pattern = compile("/\\*CONST ((?:[a-zA-Z0-9]+\\.)+)([a-zA-Z0-9_*]+)\\s*\\*/");
for (;;) {
Matcher matcher = pattern.matcher(content);
if (!matcher.find()) return content;
String placeholder = matcher.group(0), file = matcher.group(1), name = matcher.group(2);
file = file.substring(0, file.length() - 1).replace('.', '/') + ".java";
List<String> statements = new ArrayList<>();
for (Path module : modules.paths) {
Path f = module.resolve("share/classes").resolve(file);
if (Files.exists(f)) {
Pattern namePattern = compile(name.replaceAll("\\*", "\\\\w+"));
Pattern statementPattern = compile(
"((?:(?:MODS) ){2,3})([a-zA-Z0-9]+) (FIELD(?:, FIELD)*);"
.replaceAll("MODS", "public|protected|private|static|final")
.replaceAll("FIELD", "\\\\w+ = [\\\\w\"']+ ")
.replaceAll(" ", "\\\\s+")
.replaceAll(" ", "\\\\s*")
);
Matcher statementMatcher = statementPattern.matcher(Files.readString(f));
while (statementMatcher.find()) {
String mods = statementMatcher.group(1);
if (!mods.contains("static") || !mods.contains("final")) continue;
for (String s : statementMatcher.group(3).split(",")) {
s = s.strip();
String nm = s.substring(0, s.indexOf('=')).strip();
if (!namePattern.matcher(nm).matches()) continue;
statements.add("public static final " + statementMatcher.group(2) + " " + s + ";");
}
}
break;
}
}
if (statements.isEmpty()) throw new RuntimeException("Constant not found: " + placeholder);
content = replaceTemplate(content, placeholder, statements, true);
}
}
private static String findRegex(String src, Pattern regex) {
Matcher matcher = regex.matcher(src);
if (!matcher.find()) throw new IllegalArgumentException("Regex not found: " + regex.pattern());
return matcher.group(1);
}
private static String replaceTemplate(String src, String placeholder, Iterable<String> statements, boolean compact) {
int placeholderIndex = src.indexOf(placeholder);
int indent = 0;
while (placeholderIndex - indent >= 1 && src.charAt(placeholderIndex - indent - 1) == ' ') indent++;
int nextLineIndex = src.indexOf('\n', placeholderIndex + placeholder.length()) + 1;
if (nextLineIndex == 0) nextLineIndex = placeholderIndex + placeholder.length();
String before = src.substring(0, placeholderIndex - indent), after = src.substring(nextLineIndex);
StringBuilder sb = new StringBuilder(before);
boolean firstStatement = true;
for (String s : statements) {
if (!firstStatement && !compact) sb.append('\n');
sb.append(s.indent(indent));
firstStatement = false;
}
sb.append(after);
return sb.toString();
}
/**
* Code for generating {@link com.jetbrains.JBR} class.
*/
private static class JBR {
private static String generate(String content) {
Service[] interfaces = findPublicServiceInterfaces();
List<String> statements = new ArrayList<>();
for (Service i : interfaces) statements.add(generateMethodsForService(i));
content = replaceTemplate(content, "/*GENERATED_METHODS*/", statements, false);
content = content.replace("/*KNOWN_SERVICES*/",
modules.services.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
content = content.replace("/*KNOWN_PROXIES*/",
modules.proxies.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
content = content.replace("/*API_VERSION*/", apiVersion);
return content;
}
private static Service[] findPublicServiceInterfaces() {
Pattern javadocPattern = Pattern.compile("/\\*\\*((?:.|\n)*?)\\s*\\*/");
return modules.services.stream()
.map(fullName -> {
if (fullName.indexOf('$') != -1) return null; // Only top level services can be public
Path path = src.resolve(fullName.replace('.', '/') + ".java");
if (!Files.exists(path)) return null;
String name = fullName.substring(fullName.lastIndexOf('.') + 1);
try {
String content = Files.readString(path);
int indexOfDeclaration = content.indexOf("public interface " + name);
if (indexOfDeclaration == -1) return null;
Matcher javadocMatcher = javadocPattern.matcher(content.substring(0, indexOfDeclaration));
String javadoc;
int javadocEnd;
if (javadocMatcher.find()) {
javadoc = javadocMatcher.group(1);
javadocEnd = javadocMatcher.end();
} else {
javadoc = "";
javadocEnd = 0;
}
return new Service(name, javadoc,
content.substring(javadocEnd, indexOfDeclaration).contains("@Deprecated"),
content.contains("__Fallback"));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
})
.filter(Objects::nonNull).toArray(Service[]::new);
}
private static String generateMethodsForService(Service service) {
return """
private static class $__Holder {<DEPRECATED>
private static final $ INSTANCE = getService($.class, <FALLBACK>);
}
/**
* @return true if current runtime has implementation for all methods in {@link $}
* and its dependencies (can fully implement given service).
* @see #get$()
*/<DEPRECATED>
public static boolean is$Supported() {
return $__Holder.INSTANCE != null;
}
/**<JAVADOC>
* @return full implementation of {@link $} service if any, or {@code null} otherwise
*/<DEPRECATED>
public static $ get$() {
return $__Holder.INSTANCE;
}
"""
.replace("<FALLBACK>", service.hasFallback ? "$.__Fallback::new" : "null")
.replaceAll("\\$", service.name)
.replace("<JAVADOC>", service.javadoc)
.replaceAll("<DEPRECATED>", service.deprecated ? "\n@Deprecated" : "");
}
private record Service(String name, String javadoc, boolean deprecated, boolean hasFallback) {}
}
/**
* Finds and analyzes JBR API implementation modules and collects proxy definitions.
*/
private static class JBRModules {
private final Path[] paths;
private final Set<String> proxies = new HashSet<>(), services = new HashSet<>();
private JBRModules() throws IOException {
String[] moduleNames = findJBRApiModules();
paths = findPotentialJBRApiContributorModules();
for (String moduleName : moduleNames) {
Path module = findJBRApiModuleFile(moduleName, paths);
findInModule(Files.readString(module));
}
}
private void findInModule(String content) {
Pattern servicePattern = compile("(service|proxy|twoWayProxy)\\s*\\(([^)]+)");
Matcher matcher = servicePattern.matcher(content);
while (matcher.find()) {
String type = matcher.group(1);
String 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,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 = 92C0C6E35DC69EE86FD74E4083E1668

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.* com.jetbrains.api.FindDependencies com.jetbrains.jbr.FindDependencies
* @run main FindDependenciesTest
*/
import com.jetbrains.internal.JBRApi;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.jetbrains.Util.*;
import static com.jetbrains.api.FindDependencies.*;
import static com.jetbrains.jbr.FindDependencies.*;
public class FindDependenciesTest {
public static void main(String[] args) throws Throwable {
JBRApi.ModuleRegistry r = init(new String[] {}, names("""
A AL AR ARL /*ARR is skipped*/ ARRL ARRR
BV B1 B2 B3 B4 B5 B6 B7 B8 B9
C1 !C3 C5 C6
""").toArray(String[]::new));
// Simple tree with non-proxy type ARR
validateDependencies(AR.class, cs("AR ARL /*ARR is skipped*/ ARRL ARRR"));
validateDependencies(A.class, cs("A AL AR ARL /*ARR is skipped*/ ARRL ARRR"));
validateDependencies(ARRR.class, ARRR.class);
// Complex graph with many cycles
for (Class<?> c : cs("B4 B6 B2 B3 B5")) {
validateDependencies(c, cs("BV B2 B3 B4 B5 B6 B7 B8 B9"));
}
validateDependencies(B1.class, cs("BV B1 B2 B3 B4 B5 B6 B7 B8 B9"));
validateDependencies(B7.class, B7.class, BV.class);
validateDependencies(B8.class, B8.class, BV.class);
validateDependencies(B9.class, B9.class);
validateDependencies(BV.class, BV.class);
// Client proxy dependencies
r.clientProxy(C3.class.getName(), C2.class.getName());
r.proxy(C5.class.getName(), C4.class.getName());
validateDependencies(C1.class, C1.class, C3.class, C5.class, C6.class);
validateDependencies(C5.class, C5.class, C6.class);
validateDependencies(C6.class, C6.class);
validateDependencies(C3.class, C3.class, C5.class, C6.class);
}
private static Class<?>[] cs(String interfaces) {
return names(interfaces).map(c -> {
try {
return Class.forName(c);
} catch (ClassNotFoundException e) {
throw new Error(e);
}
}).toArray(Class[]::new);
}
private static Stream<String> names(String interfaces) {
return Stream.of(interfaces.replaceAll("/\\*[^*]*\\*/", "").split("(\s|\n)+")).map(String::strip)
.map(s -> {
if (s.startsWith("!")) return "com.jetbrains.jbr.FindDependencies$" + s.substring(1);
else return "com.jetbrains.api.FindDependencies$" + s;
});
}
private static void validateDependencies(Class<?> src, Class<?>... expected) throws Throwable {
Set<Class<?>> actual = getProxyDependencies(src);
if (actual.size() != expected.length || !actual.containsAll(List.of(expected))) {
throw new Error("Invalid proxy dependencies for class " + src +
". Expected: [" + Stream.of(expected).map(Class::getSimpleName).collect(Collectors.joining(" ")) +
"]. Actual: [" + actual.stream().map(Class::getSimpleName).collect(Collectors.joining(" ")) + "].");
}
}
private static final ReflectedMethod getProxyDependencies =
getMethod("com.jetbrains.internal.ProxyDependencyManager", "getProxyDependencies", Class.class);
@SuppressWarnings("unchecked")
static Set<Class<?>> getProxyDependencies(Class<?> interFace) throws Throwable {
return (Set<Class<?>>) getProxyDependencies.invoke(null, interFace);
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.* com.jetbrains.api.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,65 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.* com.jetbrains.api.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 -> null
r.proxy(InterfaceWithoutImplementation.class.getName(), "absentImpl");
requireNull(getProxy(InterfaceWithoutImplementation.class));
// Invalid JBR-side target static method mapping -> null
r.service(ServiceWithoutImplementation.class.getName(), null)
.withStatic("someMethod", "NoClass");
requireNull(getProxy(ServiceWithoutImplementation.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,49 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.*
* @run main ProxyRegistrationTest
*/
import com.jetbrains.internal.JBRApi;
import static com.jetbrains.Util.*;
public class ProxyRegistrationTest {
public static void main(String[] args) {
JBRApi.ModuleRegistry r = init();
// Only service may not have target type
r.service("s", 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,86 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @build com.jetbrains.* com.jetbrains.api.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,43 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @build com.jetbrains.JBR
* @run main ReflectiveBootstrapTest
*/
import com.jetbrains.JBR;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
public class ReflectiveBootstrapTest {
public static void main(String[] args) throws Exception {
JBR.ServiceApi api = (JBR.ServiceApi) Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap")
.getMethod("bootstrap", MethodHandles.Lookup.class)
.invoke(null, MethodHandles.privateLookupIn(JBR.class, MethodHandles.lookup()));
Objects.requireNonNull(api, "JBR API bootstrap failed");
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains;
/**
* Special open JBR API frontend for tests.
*/
public class JBR {
public interface ServiceApi {
<T> T getService(Class<T> interFace);
}
static final class Metadata {
static String[] KNOWN_SERVICES = {};
static String[] KNOWN_PROXIES = {};
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains;
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,76 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.api;
public class FindDependencies {
public interface A {
void f(AL l, AR r);
}
public interface AL {}
public interface AR {
void f(ARL l, ARR r);
}
public interface ARL {}
public interface ARR {
void f(ARRL l, ARRR r);
}
public interface ARRL {}
public interface ARRR {}
public interface BV {}
public interface B1 {
void f(BV bvs, B2 t, BV bve);
}
public interface B2 {
void f(BV bvs, B3 t, BV bve);
}
public interface B3 {
void f(B8 l, BV bvs, B4 t, B2 b, BV bve, B9 r);
}
public interface B4 {
void f(BV bvs, B2 b, B5 t, BV bve);
}
public interface B5 {
void f(BV bvs, B6 s, B3 b, B7 t, BV bve);
}
public interface B6 {
void f(BV bvs, B5 b, BV bve);
}
public interface B7 {
void f(BV v);
}
public interface B8 {
void f(BV v);
}
public interface B9 {}
public interface C1 {
void f(C2 c);
}
public interface C2 {}
public interface C5 {
void f(C6 c);
}
public interface C6 {}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.api;
public class 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,33 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.api;
public class 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,50 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.api;
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,31 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.jbr;
public class FindDependencies {
public interface C3 {
void f(C4 c);
}
public interface C4 {}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.jbr;
public class 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,30 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.jbr;
public class ProxyInfoResolving {
public static class ClientProxyClass {}
public static class ProxyClassImpl {}
public static class ValidApiImpl {}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.jbr;
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,59 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @run 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,46 @@
#!/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.
#
PATHTOOL=echo
PATHTOOL_WIN=echo
case "`uname -a | tr '[:upper:]' '[:lower:]'`" in
cygwin* )
PATHTOOL="cygpath -au" ; PATHTOOL_WIN="cygpath -aw" ;;
*microsoft* )
PATHTOOL="wslpath -au" ; PATHTOOL_WIN="wslpath -aw" ;;
esac
PWD=`pwd`
PWD=`$PATHTOOL_WIN "$PWD"`
TESTSRC_UNIX=`$PATHTOOL "$TESTSRC"`
COMPILEJAVA_UNIX=`$PATHTOOL "$COMPILEJAVA"`
TESTCLASSES_UNIX=`$PATHTOOL "$TESTCLASSES"`
SRC="$TESTSRC/../../../../../../src"
SRC_UNIX="$TESTSRC_UNIX/../../../../../../src"
# Generate sources
"$COMPILEJAVA_UNIX/bin/java$EXE_SUFFIX" "$SRC/jetbrains.api/tools/Gensrc.java" "$SRC" "$PWD/jbr-api" "TEST" || exit $?
# Validate version
"$COMPILEJAVA_UNIX/bin/java$EXE_SUFFIX" "$SRC/jetbrains.api/tools/CheckVersion.java" "$SRC/jetbrains.api" "$PWD/jbr-api/gensrc" "true" || exit $?
# Compile API
if [ "$PATHTOOL" != "echo" ]; then
where.exe /r "jbr-api\\gensrc" *.java > compile.list
else
find jbr-api/gensrc -name *.java > compile.list
fi
"$COMPILEJAVA_UNIX/bin/javac$EXE_SUFFIX" $TESTJAVACOPTS -d "$TESTCLASSES" @compile.list || exit $?
rm "$TESTCLASSES_UNIX/module-info.class"
exit 0