mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 01:19:28 +01:00
JBR API frontend is moved into a separate repository.
Rewritten proxy generation, bridges removed, invokedynamic is used instead.
Mapping is now specified using annotations.
Support for extension methods.
Support for arrays and generics.
Added JBR API implementation version.
JBR-7232 Refactor deriveFontWithFeatures & JBRFileDialog JBR API
(cherry picked from commit a4804efa96)
228 lines
10 KiB
Java
228 lines
10 KiB
Java
/*
|
|
* 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 com.jetbrains.exported.JBRApi.Provides;
|
|
|
|
import java.io.InputStream;
|
|
import java.lang.annotation.Annotation;
|
|
import java.lang.invoke.MethodHandle;
|
|
import java.lang.invoke.MethodType;
|
|
import java.lang.reflect.Method;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentMap;
|
|
import java.util.function.Function;
|
|
import java.util.function.Supplier;
|
|
|
|
import static java.lang.invoke.MethodHandles.Lookup;
|
|
|
|
/**
|
|
* JBR API is a collection of JBR-specific features that are accessed by client though
|
|
* {@link com.jetbrains.JBR jetbrains.runtime.api} module. Actual implementation is linked by
|
|
* JBR at runtime by generating {@linkplain Proxy proxy objects}.
|
|
* Mapping between interfaces and implementation code is defined using
|
|
* {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations.
|
|
* <p>
|
|
* This class is an entry point into JBR API backend.
|
|
* @see Proxy
|
|
*/
|
|
public class JBRApi {
|
|
/**
|
|
* Enable JBR API, it wouldn't init when disabled. Enabled by default.
|
|
*/
|
|
public static final boolean ENABLED = Utils.property("jetbrains.runtime.api.enabled", true);
|
|
/**
|
|
* Enable API extensions. When disabled, extension methods are treated like any other method,
|
|
* {@link JBRApi#isExtensionSupported} always returns false, {@link JBRApi#getService(Class, Enum[])}
|
|
* behaves the same as {@link JBRApi#getService(Class)}. Enabled by default.
|
|
*/
|
|
static final boolean EXTENSIONS_ENABLED = Utils.property("jetbrains.runtime.api.extensions.enabled", true);
|
|
/**
|
|
* Enable extensive debugging logging. Disabled by default.
|
|
*/
|
|
public static final boolean VERBOSE = Utils.property("jetbrains.runtime.api.verbose", false);
|
|
/**
|
|
* Print warnings about usage of deprecated interfaces and methods to {@link System#err}. Enabled by default.
|
|
*/
|
|
static final boolean LOG_DEPRECATED = Utils.property("jetbrains.runtime.api.logDeprecated", true);
|
|
/**
|
|
* Enable additional verification of generated bytecode. Disabled by default.
|
|
*/
|
|
static final boolean VERIFY_BYTECODE = Utils.property("jetbrains.runtime.api.verifyBytecode", false);
|
|
/**
|
|
* Allow extending registry. Disabled by default, used for tests.
|
|
*/
|
|
private static final boolean EXTEND_REGISTRY = Utils.property("jetbrains.runtime.api.extendRegistry", false);
|
|
|
|
record DynamicCallTargetKey(Class<?> proxy, String name, String descriptor) {}
|
|
static final ConcurrentMap<DynamicCallTargetKey, Supplier<MethodHandle>> dynamicCallTargets = new ConcurrentHashMap<>();
|
|
private static final ProxyRepository proxyRepository = new ProxyRepository();
|
|
|
|
private static Boolean[] supportedExtensions;
|
|
private static long[] emptyExtensionsBitfield;
|
|
@SuppressWarnings("rawtypes")
|
|
private static Map<Enum<?>, Class[]> knownExtensions;
|
|
static Function<Method, Enum<?>> extensionExtractor;
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
public static void init(InputStream extendedRegistryStream,
|
|
Class<? extends Annotation> serviceAnnotation,
|
|
Class<? extends Annotation> providedAnnotation,
|
|
Class<? extends Annotation> providesAnnotation,
|
|
Map<Enum<?>, Class[]> knownExtensions,
|
|
Function<Method, Enum<?>> extensionExtractor) {
|
|
if (extendedRegistryStream != null && !EXTEND_REGISTRY) {
|
|
throw new Error("Extending JBR API registry is not supported");
|
|
}
|
|
proxyRepository.init(extendedRegistryStream, serviceAnnotation, providedAnnotation, providesAnnotation);
|
|
|
|
if (EXTENSIONS_ENABLED) {
|
|
JBRApi.knownExtensions = knownExtensions;
|
|
JBRApi.extensionExtractor = extensionExtractor;
|
|
supportedExtensions = new Boolean[
|
|
knownExtensions.keySet().stream().mapToInt(Enum::ordinal).max().orElse(-1) + 1];
|
|
emptyExtensionsBitfield = new long[(supportedExtensions.length + 63) / 64];
|
|
}
|
|
|
|
if (VERBOSE) {
|
|
System.out.println("JBR API init\n knownExtensions = " + (EXTENSIONS_ENABLED ? knownExtensions.keySet() : "DISABLED"));
|
|
}
|
|
}
|
|
|
|
public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) {
|
|
if (VERBOSE) {
|
|
System.out.println("Binding call site " + caller.lookupClass().getName() + "#" + name + ": " + type);
|
|
}
|
|
if (!caller.hasFullPrivilegeAccess()) throw new Error("Caller lookup must have full privilege access"); // Authenticity check.
|
|
return dynamicCallTargets.get(new DynamicCallTargetKey(caller.lookupClass(), name, type.descriptorString())).get().asType(type);
|
|
}
|
|
|
|
/**
|
|
* @return JBR API version supported by current implementation.
|
|
*/
|
|
@Provides("JBR.ServiceApi")
|
|
public static String getImplVersion() {
|
|
return proxyRepository.getVersion();
|
|
}
|
|
|
|
/**
|
|
* @param extension extension name
|
|
* @return true if all methods belonging to given extension are supported
|
|
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
|
|
* service, but is not directly exposed to user.
|
|
*/
|
|
@Provides("JBR.ServiceApi")
|
|
public static boolean isExtensionSupported(Enum<?> extension) {
|
|
if (!EXTENSIONS_ENABLED) return false;
|
|
int i = extension.ordinal();
|
|
if (supportedExtensions[i] == null) {
|
|
synchronized (JBRApi.class) {
|
|
if (supportedExtensions[i] == null) {
|
|
boolean result = true;
|
|
for (Class<?> c : knownExtensions.get(extension)) {
|
|
result &= proxyRepository.getProxy(c, null).isExtensionSupported(extension);
|
|
}
|
|
supportedExtensions[i] = result;
|
|
}
|
|
}
|
|
}
|
|
return supportedExtensions[i];
|
|
}
|
|
|
|
/**
|
|
* @return fully supported service implementation for the given interface with specified extensions, or null
|
|
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
|
|
* service, but is not directly exposed to user.
|
|
*/
|
|
@Provides("JBR.ServiceApi")
|
|
public static <T> T getService(Class<T> interFace, Enum<?>... extensions) {
|
|
if (!EXTENSIONS_ENABLED) return getService(interFace);
|
|
|
|
long[] bitfield = new long[emptyExtensionsBitfield.length];
|
|
for (Enum<?> e : extensions) {
|
|
if (isExtensionSupported(e)) {
|
|
int i = e.ordinal() / 64;
|
|
int j = e.ordinal() % 64;
|
|
bitfield[i] |= 1L << j;
|
|
} else {
|
|
if (VERBOSE) {
|
|
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Extension not supported: " + e.name());
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return getService(interFace, bitfield, true);
|
|
}
|
|
|
|
/**
|
|
* @return fully supported service implementation for the given interface, or null
|
|
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
|
|
* service, but is not directly exposed to user.
|
|
*/
|
|
@Provides("JBR.ServiceApi")
|
|
public static <T> T getService(Class<T> interFace) {
|
|
return getService(interFace, emptyExtensionsBitfield, true);
|
|
}
|
|
|
|
public static <T> T getInternalService(Class<T> interFace) {
|
|
return getService(interFace, emptyExtensionsBitfield, false);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private static <T> T getService(Class<T> interFace, long[] extensions, boolean publicService) {
|
|
Proxy p = proxyRepository.getProxy(interFace, null);
|
|
if ((p.getFlags() & Proxy.SERVICE) == 0 || (publicService && (p.getFlags() & Proxy.INTERNAL) != 0)) {
|
|
if (VERBOSE) {
|
|
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Not allowed as a service: " + interFace.getCanonicalName());
|
|
}
|
|
return null;
|
|
}
|
|
if (!p.init()) {
|
|
if (VERBOSE) {
|
|
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not supported: " + interFace.getCanonicalName());
|
|
}
|
|
return null;
|
|
}
|
|
try {
|
|
MethodHandle constructor = p.getConstructor();
|
|
return (T) (EXTENSIONS_ENABLED ? constructor.invoke(extensions) : constructor.invoke());
|
|
} catch (com.jetbrains.exported.JBRApi.ServiceNotAvailableException | NullPointerException e) {
|
|
if (VERBOSE) {
|
|
synchronized (System.err) {
|
|
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not available: " + interFace.getCanonicalName());
|
|
System.err.print("Caused by: ");
|
|
e.printStackTrace(System.err);
|
|
}
|
|
}
|
|
} catch (Throwable e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
return null;
|
|
}
|
|
}
|