Compare commits

..

4 Commits

Author SHA1 Message Date
Nikita Gubarkov
11421db3d5 JBR-9942 Vulkan: Glyph position rounding errors 2026-02-02 16:48:12 +01:00
Nikita Gubarkov
532cc261c0 JBR-9552 Isolated JBR API context per classloader
Backport to 21 (ASM) from main (ClassFile API)
2026-02-02 16:30:50 +01:00
Nikita Gubarkov
486cd176d7 JBR-9943 JBR API: Synchronize with main 2026-02-02 16:30:49 +01:00
Nikita Provotorov
a897ca4c83 JBR-9534 Wayland: text-input-unstable-v3: don't bind to zwp_text_input_manager_v3 if IM support is disabled
* Introducing a new WLToolkit method isNativeInputMethodSupportEnabled
* Deciding whether to bind to a zwp_text_input_manager_v3 based on the value returned by the new method
2026-02-02 14:23:57 +01:00
26 changed files with 657 additions and 359 deletions

View File

@@ -453,7 +453,7 @@ define SetupJavaCompilationBody
ifeq ($$($1_PROCESS_JBR_API), true)
# Automatic path conversion doesn't work for two arguments, so call fixpath manually
$1_JBR_API_FLAGS := -Xplugin:"jbr-api $$(call FixPath, $$($1_BIN)/java.base/META-INF/jbrapi.registry) $$(call FixPath, $(TOPDIR)/jb/jbr-api.version)"
$1_JBR_API_FLAGS := -Xplugin:"jbr-api $$(call FixPath, $$($1_BIN)/java.base/META-INF/jbrapi) $$(call FixPath, $(TOPDIR)/jb/jbr-api.version)"
$1_EXTRA_DEPS := $$($1_EXTRA_DEPS) $$(BUILDTOOLS_OUTPUTDIR)/plugins/_the.COMPILE_JBR_API_PLUGIN_batch
endif

View File

@@ -198,34 +198,35 @@ public class JBRApiPlugin implements Plugin {
return unresolvedErrors;
}
void read(RandomAccessFile file) throws IOException {
void read(RandomAccessFile file, boolean internal) throws IOException {
String s;
while ((s = file.readLine()) != null) {
String[] tokens = s.split(" ");
switch (tokens[0]) {
case "TYPE" -> {
types.put(tokens[1], new Type(tokens[2], Binding.valueOf(tokens[3])));
if (tokens.length > 4 && tokens[4].equals("INTERNAL")) internal.add(tokens[1]);
}
case "VERSION" -> {}
case "STATIC" -> {
StaticDescriptor descriptor = new StaticDescriptor(new StaticMethod(
tokens[1], tokens[2]), tokens[3]);
methods.put(descriptor, new StaticMethod(tokens[4], tokens[5]));
if (tokens.length > 6 && tokens[6].equals("INTERNAL")) internal.add(descriptor);
if (internal) this.internal.add(descriptor);
}
default -> {
types.put(tokens[1], new Type(tokens[2], Binding.valueOf(tokens[0])));
if (internal) this.internal.add(tokens[1]);
}
}
}
}
void write(RandomAccessFile file) throws IOException {
void write(RandomAccessFile pub, RandomAccessFile priv) throws IOException {
for (var t : types.entrySet()) {
file.writeBytes("TYPE " + t.getKey() + " " + t.getValue().type + " " + t.getValue().binding +
(internal.contains(t.getKey()) ? " INTERNAL\n" : "\n"));
(internal.contains(t.getKey()) ? priv : pub).writeBytes(
t.getValue().binding + " " + t.getKey() + " " + t.getValue().type + "\n");
}
for (var t : methods.entrySet()) {
file.writeBytes("STATIC " + t.getKey().method.type + " " + t.getKey().method.name + " " +
t.getKey().descriptor + " " + t.getValue().type + " " + t.getValue().name +
(internal.contains(t.getKey()) ? " INTERNAL\n" : "\n"));
(internal.contains(t.getKey()) ? priv : pub).writeBytes(
"STATIC " + t.getKey().method.type + " " + t.getKey().method.name + " " +
t.getKey().descriptor + " " + t.getValue().type + " " + t.getValue().name + "\n");
}
}
}
@@ -399,7 +400,7 @@ public class JBRApiPlugin implements Plugin {
@Override
public void init(JavacTask jt, String... args) {
Path output = Path.of(args[0]);
Path pubPath = Path.of(args[0] + ".public"), privPath = Path.of(args[0] + ".private");
String implVersion;
try {
implVersion = Files.readString(Path.of(args[1])).strip();
@@ -426,18 +427,21 @@ public class JBRApiPlugin implements Plugin {
}
}.scan(te.getTypeElement(), te.getCompilationUnit());
} else if (te.getKind() == TaskEvent.Kind.COMPILATION) {
try (RandomAccessFile file = new RandomAccessFile(output.toFile(), "rw");
FileChannel channel = file.getChannel()) {
try (RandomAccessFile pub = new RandomAccessFile(pubPath.toFile(), "rw");
RandomAccessFile priv = new RandomAccessFile(privPath.toFile(), "rw");
FileChannel channel = pub.getChannel()) {
for (;;) {
try { if (channel.lock() != null) break; } catch (OverlappingFileLockException ignore) {}
LockSupport.parkNanos(10_000000);
}
Registry r = new Registry();
r.read(file);
r.read(pub, false);
r.read(priv, true);
var unresolvedErrors = r.addBindings();
file.setLength(0);
file.writeBytes("VERSION " + implVersion + "\n");
r.write(file);
priv.setLength(0);
pub.setLength(0);
pub.writeBytes("VERSION " + implVersion + "\n");
r.write(pub, priv);
if (!unresolvedErrors.isEmpty()) {
throw new RuntimeException(String.join("\n", unresolvedErrors));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* Copyright 2025 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
@@ -25,7 +25,7 @@
package com.jetbrains.bootstrap;
import com.jetbrains.internal.JBRApi;
import com.jetbrains.internal.jbrapi.JBRApi;
import java.lang.invoke.MethodHandles;
import java.util.Map;
@@ -45,10 +45,7 @@ public class JBRApiBootstrap {
* @return implementation for {@link com.jetbrains.JBR.ServiceApi} interface
*/
public static synchronized Object bootstrap(MethodHandles.Lookup outerLookup) {
if (!JBRApi.ENABLED) return null;
if (JBRApi.VERBOSE) {
System.out.println("JBR API bootstrap in compatibility mode: Object bootstrap(MethodHandles.Lookup)");
}
System.out.println("JBR API bootstrap in compatibility mode: Object bootstrap(MethodHandles.Lookup)");
Class<?> apiInterface;
try {
apiInterface = outerLookup.findClass("com.jetbrains.JBR$ServiceApi");

View File

@@ -1,3 +1,28 @@
/*
* Copyright 2025 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.exported;
import jdk.internal.reflect.CallerSensitive;
@@ -66,7 +91,7 @@ public class JBRApi {
public static <T> T internalService() {
var caller = (Class<T>) Reflection.getCallerClass();
if (caller == null) throw new IllegalCallerException("No caller frame");
return com.jetbrains.internal.JBRApi.getInternalService(caller);
return com.jetbrains.internal.jbrapi.JBRApi.getInternalService(caller);
}
/**

View File

@@ -1,5 +1,32 @@
/*
* Copyright 2025 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.exported;
import com.jetbrains.internal.jbrapi.JBRApi;
import java.lang.annotation.Annotation;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
@@ -25,29 +52,27 @@ public class JBRApiSupport {
* @param extensionExtractor receives method, returns its extension enum, or null
* @return implementation for {@code JBR.ServiceApi} interface
*/
@SuppressWarnings("rawtypes")
public static synchronized Object bootstrap(Class<?> apiInterface,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation,
Map<Enum<?>, Class[]> knownExtensions,
Map<Enum<?>, Class<?>[]> knownExtensions,
Function<Method, Enum<?>> extensionExtractor) {
if (!com.jetbrains.internal.JBRApi.ENABLED) return null;
com.jetbrains.internal.JBRApi.init(
return JBRApi.init(
null,
apiInterface,
serviceAnnotation,
providedAnnotation,
providesAnnotation,
knownExtensions,
extensionExtractor);
return com.jetbrains.internal.JBRApi.getService(apiInterface);
}
/**
* Bootstrap method for JBR API dynamic invocations.
*/
public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
return new ConstantCallSite(com.jetbrains.internal.JBRApi.bindDynamic(caller, name, type));
return new ConstantCallSite(JBRApi.bindDynamic(caller, name, type));
}
/**

View File

@@ -1,4 +1,29 @@
package com.jetbrains.internal;
/*
* Copyright 2026 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.jbrapi;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.MethodVisitor;
@@ -20,17 +45,26 @@ import static jdk.internal.org.objectweb.asm.Type.getInternalName;
*/
class AccessContext {
static final int DYNAMIC_CALL_TARGET_NAME_OFFSET = 128;
@SuppressWarnings("unchecked")
static Supplier<MethodHandle>[] getDynamicCallTargets(Lookup target) {
try {
return (Supplier<MethodHandle>[]) target.findStaticVarHandle(
target.lookupClass(), "dynamicCallTargets", Supplier[].class).get();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private final Map<Class<?>, Boolean> accessibleClasses = new HashMap<>();
final Map<Proxy, Boolean> dependencies = new HashMap<>(); // true for required, false for optional
final List<DynamicCallTarget> dynamicCallTargets = new ArrayList<>();
final List<Supplier<MethodHandle>> dynamicCallTargets = new ArrayList<>();
final Lookup caller;
AccessContext(Lookup caller) {
this.caller = caller;
}
record DynamicCallTarget(String name, String descriptor, Supplier<MethodHandle> futureHandle) {}
class Method {
final MethodVisitor writer;
private final boolean methodRequired;
@@ -54,10 +88,10 @@ class AccessContext {
}
void invokeDynamic(MethodType type, Supplier<MethodHandle> futureHandle) {
String name = String.valueOf((char) (dynamicCallTargets.size() + DYNAMIC_CALL_TARGET_NAME_OFFSET));
String descriptor = erase(type).descriptorString();
DynamicCallTarget t = new DynamicCallTarget("dynamic" + dynamicCallTargets.size(), descriptor, futureHandle);
dynamicCallTargets.add(t);
writer.visitInvokeDynamicInsn(t.name, descriptor,
dynamicCallTargets.add(futureHandle);
writer.visitInvokeDynamicInsn(name, descriptor,
new Handle(H_INVOKESTATIC, "com/jetbrains/exported/JBRApiSupport", "bootstrapDynamic",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false));
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* Copyright 2000-2026 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
@@ -23,7 +23,7 @@
* questions.
*/
package com.jetbrains.internal;
package com.jetbrains.internal.jbrapi;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
@@ -40,7 +40,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
* Utility class that helps with bytecode generation
*/
class ASMUtils {
class BytecodeUtils {
private static final MethodHandle genericSignatureGetter;
static {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* Copyright 2000-2026 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
@@ -23,7 +23,7 @@
* questions.
*/
package com.jetbrains.internal;
package com.jetbrains.internal.jbrapi;
import com.jetbrains.exported.JBRApi.Provides;
@@ -33,10 +33,7 @@ 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;
@@ -50,11 +47,13 @@ import static java.lang.invoke.MethodHandles.Lookup;
* This class is an entry point into JBR API backend.
* @see Proxy
*/
// Root is not considered a service for proxy generation purposes, as its instantiation follows custom rules.
@Provides("JBR.ServiceApi")
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);
private 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[])}
@@ -64,7 +63,7 @@ public class JBRApi {
/**
* Enable extensive debugging logging. Disabled by default.
*/
public static final boolean VERBOSE = Utils.property("jetbrains.runtime.api.verbose", false);
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.
*/
@@ -78,54 +77,68 @@ public class JBRApi {
*/
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);
private final ProxyRepository proxyRepository;
private final Boolean[] supportedExtensions;
private final long[] emptyExtensionsBitfield;
private final Map<Enum<?>, Class<?>[]> knownExtensions;
private JBRApi(ProxyRepository proxyRepository, Map<Enum<?>, Class<?>[]> knownExtensions) {
this.proxyRepository = proxyRepository;
if (EXTENSIONS_ENABLED) {
JBRApi.knownExtensions = knownExtensions;
JBRApi.extensionExtractor = extensionExtractor;
this.knownExtensions = knownExtensions;
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"));
} else {
this.knownExtensions = null;
supportedExtensions = null;
emptyExtensionsBitfield = null;
}
}
public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) {
public static Object init(InputStream extendedRegistryStream,
Class<?> apiInterface,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation,
Map<Enum<?>, Class<?>[]> knownExtensions,
Function<Method, Enum<?>> extensionExtractor) {
if (!ENABLED) return null;
if (VERBOSE) {
System.out.println("Binding call site " + caller.lookupClass().getName() + "#" + name + ": " + type);
System.out.println("JBR API init\n knownExtensions = " + (EXTENSIONS_ENABLED ? knownExtensions.keySet() : "DISABLED"));
}
ProxyRepository.Registry registry;
if (extendedRegistryStream != null) {
if (!EXTEND_REGISTRY) throw new Error("Extending JBR API registry is not supported");
registry = new ProxyRepository.Registry(extendedRegistryStream);
} else registry = ProxyRepository.Registry.Builtin.PUBLIC;
ProxyRepository proxyRepository = new ProxyRepository(registry, apiInterface.getClassLoader(),
serviceAnnotation, providedAnnotation, providesAnnotation, extensionExtractor);
JBRApi api = new JBRApi(proxyRepository, knownExtensions);
try {
Proxy p = proxyRepository.getProxy(apiInterface, null);
if (!p.init()) throw new Error("Proxy initialization failed");
MethodHandle constructor = p.getConstructor();
return EXTENSIONS_ENABLED ? constructor.invoke(api, api.emptyExtensionsBitfield) : constructor.invoke(api);
} catch (Throwable e) {
if (VERBOSE) {
synchronized (System.err) {
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: JBR API is not supported");
System.err.print("Caused by: ");
e.printStackTrace(System.err);
}
}
return null;
}
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() {
public String getImplVersion() {
return proxyRepository.getVersion();
}
@@ -135,8 +148,7 @@ public class JBRApi {
* @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) {
public boolean isExtensionSupported(Enum<?> extension) {
if (!EXTENSIONS_ENABLED) return false;
int i = extension.ordinal();
if (supportedExtensions[i] == null) {
@@ -153,30 +165,13 @@ public class JBRApi {
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;
}
public static <T> T getInternalService(Class<T> interFace) {
class Holder {
private static final JBRApi INSTANCE = new JBRApi(
new ProxyRepository(ProxyRepository.Registry.Builtin.PRIVATE, JBRApi.class.getClassLoader(),
null, null, null, null), Map.of());
}
return getService(interFace, bitfield, true);
return Holder.INSTANCE.getService(interFace);
}
/**
@@ -184,19 +179,36 @@ public class JBRApi {
* @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);
public <T> T getService(Class<T> interFace) {
return getService(interFace, new Enum<?>[0]);
}
/**
* @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.
*/
@SuppressWarnings("unchecked")
private static <T> T getService(Class<T> interFace, long[] extensions, boolean publicService) {
public <T> T getService(Class<T> interFace, Enum<?>... extensions) {
long[] bitfield;
if (extensions.length > 0 && EXTENSIONS_ENABLED) {
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;
}
}
} else bitfield = emptyExtensionsBitfield;
Proxy p = proxyRepository.getProxy(interFace, null);
if ((p.getFlags() & Proxy.SERVICE) == 0 || (publicService && (p.getFlags() & Proxy.INTERNAL) != 0)) {
if ((p.getFlags() & Proxy.SERVICE) == 0) {
if (VERBOSE) {
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Not allowed as a service: " + interFace.getCanonicalName());
}
@@ -210,7 +222,7 @@ public class JBRApi {
}
try {
MethodHandle constructor = p.getConstructor();
return (T) (EXTENSIONS_ENABLED ? constructor.invoke(extensions) : constructor.invoke());
return (T) (EXTENSIONS_ENABLED ? constructor.invoke(bitfield) : constructor.invoke());
} catch (com.jetbrains.exported.JBRApi.ServiceNotAvailableException | NullPointerException e) {
if (VERBOSE) {
synchronized (System.err) {
@@ -224,4 +236,15 @@ public class JBRApi {
}
return null;
}
public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) {
int index = name.charAt(0) - AccessContext.DYNAMIC_CALL_TARGET_NAME_OFFSET;
if (VERBOSE) {
System.out.println("Binding call site " + caller.lookupClass().getName() + " #" + index);
}
if (!caller.hasFullPrivilegeAccess()) {
throw new Error("Caller lookup must have full privilege access"); // Authenticity check.
}
return AccessContext.getDynamicCallTargets(caller)[index].get().asType(type);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 JetBrains s.r.o.
* Copyright 2023-2026 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
@@ -23,7 +23,7 @@
* questions.
*/
package com.jetbrains.internal;
package com.jetbrains.internal.jbrapi;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
@@ -141,6 +141,26 @@ abstract class Mapping {
}
}
static class Cast extends Nesting {
private Cast(Mapping component) {
super(component.from, component.to, component);
}
static Mapping wrap(Mapping m) {
return m == null || m instanceof Cast ? m : new Cast(m);
}
@Override
void convert(AccessContext.Method context) {
if (context.access().canAccess(from)) context.writer.visitTypeInsn(CHECKCAST, getInternalName(from));
component.convert(context);
}
@Override
void convertNonNull(AccessContext.Method context) { convert(context); }
@Override
Mapping inverse() { return wrap(component.inverse()); }
@Override
public String toString() { return component.toString(); }
}
static class Array extends Nesting {
private Array(Mapping component) {
super(component.from.arrayType(), component.to.arrayType(), component);
@@ -160,7 +180,7 @@ abstract class Mapping {
MethodVisitor m = context.writer;
Label loopStart = new Label(), loopEnd = new Label();
// Stack: fromArray -> toArray, fromArray, i=length
if (!context.access().canAccess(from)) m.visitTypeInsn(CHECKCAST, "[Ljava/lang/Object;");
if (!context.access().canAccess(from)) m.visitTypeInsn(CHECKCAST, getInternalName(Object[].class));
m.visitInsn(DUP);
m.visitInsn(ARRAYLENGTH);
if (context.access().canAccess(to)) {
@@ -344,7 +364,7 @@ abstract class Mapping {
for (int i = 0;; i++) {
if ((i < tvs.length) ^ tpIterator.hasNext()) throw new RuntimeException("Number of type parameters doesn't match");
if (i >= tvs.length) break;
tvMappings.put(tvs[i], tpIterator.next());
tvMappings.put(tvs[i], Cast.wrap(tpIterator.next()));
}
}
initTypeParameters(type.getGenericSuperclass());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* Copyright 2000-2026 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
@@ -23,7 +23,7 @@
* questions.
*/
package com.jetbrains.internal;
package com.jetbrains.internal.jbrapi;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
@@ -96,9 +96,7 @@ class Proxy {
/**
* @see Proxy.Info#flags
*/
static final int
INTERNAL = 1,
SERVICE = 2;
static final int SERVICE = 1;
private final Proxy inverse;
private final Class<?> interFace, target;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* Copyright 2000-2026 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
@@ -23,7 +23,7 @@
* questions.
*/
package com.jetbrains.internal;
package com.jetbrains.internal.jbrapi;
import jdk.internal.org.objectweb.asm.*;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
@@ -39,8 +39,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.jetbrains.exported.JBRApi.ServiceNotAvailableException;
import static com.jetbrains.internal.ASMUtils.*;
import static com.jetbrains.internal.JBRApi.EXTENSIONS_ENABLED;
import static com.jetbrains.internal.jbrapi.BytecodeUtils.*;
import static com.jetbrains.internal.jbrapi.JBRApi.EXTENSIONS_ENABLED;
import static java.lang.invoke.MethodHandles.Lookup;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import static jdk.internal.org.objectweb.asm.Type.getInternalName;
@@ -65,7 +65,15 @@ import static jdk.internal.org.objectweb.asm.Type.getInternalName;
*/
class ProxyGenerator {
private static final String PROXY_INTERFACE_NAME = getInternalName(com.jetbrains.exported.JBRApiSupport.Proxy.class);
private static final Map<MethodSignature, Method> OBJECT_METHODS = Stream.of(Object.class.getMethods())
.filter(ProxyGenerator::isValidInterfaceMethod)
.collect(Collectors.toUnmodifiableMap(MethodSignature::of, m -> m));
private static boolean isValidInterfaceMethod(Method method) {
return (method.getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0;
}
private final ProxyRepository proxyRepository;
private final Proxy.Info info;
private final Class<?> interFace;
private final Lookup proxyGenLookup;
@@ -80,6 +88,7 @@ class ProxyGenerator {
private final ClassVisitor proxyWriter;
private final Map<Enum<?>, Boolean> supportedExtensions = new HashMap<>();
private Map<MethodSignature, Method> remainingObjectMethods;
private boolean supported = true;
private Lookup generatedProxy;
@@ -88,6 +97,7 @@ class ProxyGenerator {
* Creates new proxy generator from given {@link Proxy.Info},
*/
ProxyGenerator(ProxyRepository proxyRepository, Proxy.Info info, Mapping[] specialization) {
this.proxyRepository = proxyRepository;
this.info = info;
this.interFace = info.interfaceLookup.lookupClass();
this.specialization = specialization;
@@ -181,10 +191,9 @@ class ProxyGenerator {
if (JBRApi.VERBOSE) {
System.out.println("Initializing proxy " + interFace.getName());
}
for (var t : accessContext.dynamicCallTargets) {
JBRApi.dynamicCallTargets.put(new JBRApi.DynamicCallTargetKey(
generatedProxy.lookupClass(), t.name(), t.descriptor()
), t.futureHandle());
if (!accessContext.dynamicCallTargets.isEmpty()) {
var table = AccessContext.getDynamicCallTargets(generatedProxy);
for (int i = 0; i < table.length; i++) table[i] = accessContext.dynamicCallTargets.get(i);
}
}
@@ -196,7 +205,7 @@ class ProxyGenerator {
try {
Object sericeTarget = service && info.targetLookup != null ? createServiceTarget() : null;
generatedProxy = proxyGenLookup.defineHiddenClass(
originalProxyWriter.toByteArray(), false, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
originalProxyWriter.toByteArray(), false, Lookup.ClassOption.NESTMATE);
MethodHandle constructor = findConstructor();
if (sericeTarget != null) constructor = MethodHandles.insertArguments(constructor, 0, sericeTarget);
return constructor;
@@ -228,11 +237,10 @@ class ProxyGenerator {
mappingContext.initTypeParameters(interFace, Stream.of(specialization));
}
mappingContext.initTypeParameters(interFace);
generateFields();
generateConstructor();
generateTargetGetter();
generateMethods(interFace);
if (interFace.isInterface()) generateMethods(Object.class);
generateMethods();
generateFields();
proxyWriter.visitEnd();
return supported;
}
@@ -244,6 +252,20 @@ class ProxyGenerator {
if (EXTENSIONS_ENABLED) {
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "extensions", "[J", null, null);
}
if (!accessContext.dynamicCallTargets.isEmpty()) {
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, "dynamicCallTargets", Supplier[].class.descriptorString(), null, null);
MethodVisitor m = proxyWriter.visitMethod(ACC_PRIVATE | ACC_STATIC, "<clinit>", "()V", null, null);
m.visitCode();
m.visitLdcInsn(accessContext.dynamicCallTargets.size());
m.visitTypeInsn(ANEWARRAY, getInternalName(Supplier.class));
m.visitFieldInsn(PUTSTATIC, proxyName, "dynamicCallTargets", Supplier[].class.descriptorString());
m.visitInsn(RETURN);
m.visitMaxs(0, 0);
m.visitEnd();
}
}
private void generateConstructor() {
@@ -280,59 +302,78 @@ class ProxyGenerator {
m.visitEnd();
}
private void generateMethods(Class<?> interFace) {
private void generateMethods() {
boolean isInterface = interFace.isInterface();
// Generate implementation for interface methods.
for (Method method : interFace.getMethods()) {
int mod = method.getModifiers();
if ((mod & (Modifier.STATIC | Modifier.FINAL)) != 0) continue;
Exception exception = null;
Enum<?> extension = EXTENSIONS_ENABLED && JBRApi.extensionExtractor != null ?
JBRApi.extensionExtractor.apply(method) : null;
Mapping.Method methodMapping = mappingContext.getMapping(method);
MethodHandle handle;
boolean passInstance;
if (methodMapping.query().valid) {
// Try static method.
handle = info.getStaticMethod(method.getName(), methodMapping.type());
passInstance = false;
// Try target class.
if (handle == null && info.targetLookup != null) {
try {
handle = interFace.equals(info.targetLookup.lookupClass()) ?
info.targetLookup.unreflect(method) : info.targetLookup.findVirtual(
info.targetLookup.lookupClass(), method.getName(), methodMapping.type());
passInstance = true;
} catch (NoSuchMethodException | IllegalAccessException e) {
exception = e;
if (isValidInterfaceMethod(method)) {
if (isInterface) {
// Remember Object methods which we already encountered, they may be not included in getMethods().
MethodSignature signature = MethodSignature.of(method);
if (remainingObjectMethods == null && OBJECT_METHODS.containsKey(signature)) {
remainingObjectMethods = new HashMap<>(OBJECT_METHODS);
}
if (remainingObjectMethods != null) remainingObjectMethods.remove(signature);
}
if (handle != null) {
// Method found.
generateMethod(method, handle, methodMapping, extension, passInstance);
if (extension != null) supportedExtensions.putIfAbsent(extension, true);
continue;
}
} else {
exception = new Exception("Method mapping is invalid: " + methodMapping);
generateMethod(method);
}
// Skip if possible.
if (!Modifier.isAbstract(mod)) continue;
// Generate unsupported stub.
generateUnsupportedMethod(proxyWriter, method);
if (JBRApi.VERBOSE) {
synchronized (System.err) {
System.err.println("Couldn't generate method " + method.getName());
if (exception != null) exception.printStackTrace(System.err);
}
}
if (extension == null) supported = false;
else supportedExtensions.put(extension, false);
}
if (isInterface) {
// Generate implementation for the remaining Object methods.
for (Method method : (remainingObjectMethods != null ? remainingObjectMethods : OBJECT_METHODS).values()) {
generateMethod(method);
}
}
}
private void generateMethod(Method method) {
Exception exception = null;
Enum<?> extension = EXTENSIONS_ENABLED && proxyRepository.extensionExtractor != null ?
proxyRepository.extensionExtractor.apply(method) : null;
Mapping.Method methodMapping = mappingContext.getMapping(method);
MethodHandle handle;
boolean passInstance;
if (methodMapping.query().valid) {
// Try static method.
handle = info.getStaticMethod(method.getName(), methodMapping.type());
passInstance = false;
// Try target class.
if (handle == null && info.targetLookup != null) {
try {
handle = interFace.equals(info.targetLookup.lookupClass()) ?
info.targetLookup.unreflect(method) : info.targetLookup.findVirtual(
info.targetLookup.lookupClass(), method.getName(), methodMapping.type());
passInstance = true;
} catch (NoSuchMethodException | IllegalAccessException e) {
exception = e;
}
}
if (handle != null) {
// Method found.
generateMethod(method, handle, methodMapping, extension, passInstance);
if (extension != null) supportedExtensions.putIfAbsent(extension, true);
return;
}
} else {
exception = new Exception("Method mapping is invalid: " + methodMapping);
}
// Skip if possible.
if (!Modifier.isAbstract(method.getModifiers())) return;
// Generate unsupported stub.
generateUnsupportedMethod(proxyWriter, method);
if (JBRApi.VERBOSE) {
synchronized (System.err) {
System.err.println("Couldn't generate method " + method.getName());
if (exception != null) exception.printStackTrace(System.err);
}
}
if (extension == null) supported = false;
else supportedExtensions.put(extension, false);
}
private void generateMethod(Method interfaceMethod, MethodHandle handle, Mapping.Method mapping, Enum<?> extension, boolean passInstance) {
@@ -417,4 +458,20 @@ class ProxyGenerator {
m.visitMaxs(0, 0);
m.visitEnd();
}
private record MethodSignature(String name, Class<?>[] parameters) {
private static MethodSignature of(Method method) {
return new MethodSignature(method.getName(), method.getParameterTypes());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return (o instanceof MethodSignature(String n, Class<?>[] p)) &&
name.equals(n) && Arrays.equals(parameters, p);
}
@Override
public int hashCode() {
return name.hashCode() + 31 * Arrays.hashCode(parameters);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2023 JetBrains s.r.o.
* Copyright 2023-2026 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
@@ -23,7 +23,7 @@
* questions.
*/
package com.jetbrains.internal;
package com.jetbrains.internal.jbrapi;
import jdk.internal.access.SharedSecrets;
import jdk.internal.loader.BootLoader;
@@ -35,8 +35,10 @@ import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import static java.lang.invoke.MethodHandles.Lookup;
@@ -48,15 +50,26 @@ import static java.lang.invoke.MethodHandles.Lookup;
class ProxyRepository {
private static final Proxy NONE = Proxy.empty(null), INVALID = Proxy.empty(false);
private final Registry registry = new Registry();
private final Map<Key, Proxy> proxies = new HashMap<>();
private final Registry registry;
private final ClassLoader classLoader;
private final Class<? extends Annotation> serviceAnnotation, providedAnnotation, providesAnnotation;
private final Module annotationsModule;
final Function<Method, Enum<?>> extensionExtractor;
void init(InputStream extendedRegistryStream,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation) {
registry.initAnnotations(serviceAnnotation, providedAnnotation, providesAnnotation);
if (extendedRegistryStream != null) registry.readEntries(extendedRegistryStream);
ProxyRepository(Registry registry,
ClassLoader classLoader,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation,
Function<Method, Enum<?>> extensionExtractor) {
this.registry = registry;
this.classLoader = classLoader;
this.serviceAnnotation = serviceAnnotation;
this.providedAnnotation = providedAnnotation;
this.providesAnnotation = providesAnnotation;
this.extensionExtractor = extensionExtractor;
annotationsModule = serviceAnnotation == null ? null : serviceAnnotation.getModule();
}
String getVersion() {
@@ -67,15 +80,14 @@ class ProxyRepository {
Key key = new Key(clazz, specialization);
Proxy p = proxies.get(key);
if (p == null) {
registry.updateClassLoader(clazz.getClassLoader());
Mapping[] inverseSpecialization = specialization == null ? null :
Stream.of(specialization).map(m -> m == null ? null : m.inverse()).toArray(Mapping[]::new);
Key inverseKey = null;
Registry.Entry entry = registry.entries.get(key.clazz().getCanonicalName());
if (entry != null) { // This is a registered proxy
Proxy.Info infoByInterface = entry.resolve(),
infoByTarget = entry.inverse != null ? entry.inverse.resolve() : null;
Proxy.Info infoByInterface = entry.resolve(this),
infoByTarget = entry.inverse != null ? entry.inverse.resolve(this) : null;
inverseKey = infoByTarget != null && infoByTarget.interfaceLookup != null ?
new Key(infoByTarget.interfaceLookup.lookupClass(), inverseSpecialization) : null;
if ((infoByInterface == null && infoByTarget == null) ||
@@ -123,9 +135,9 @@ class ProxyRepository {
/**
* Registry contains all information about mapping between JBR API interfaces and implementation.
* This mapping information can be {@linkplain Entry#resolve() resolved} into {@link Proxy.Info}.
* This mapping information can be {@linkplain Entry#resolve(ProxyRepository) resolved} into {@link Proxy.Info}.
*/
private static class Registry {
static class Registry {
private record StaticKey(String methodName, String targetMethodDescriptor) {}
private record StaticValue(String targetType, String targetMethodName) {}
@@ -140,12 +152,12 @@ class ProxyRepository {
private Entry(String type) { this.type = type; }
private Proxy.Info resolve() {
private Proxy.Info resolve(ProxyRepository repository) {
if (type == null) return null;
Lookup l, t;
try {
l = resolveType(type, classLoader);
t = target != null ? resolveType(target, classLoader) : null;
l = resolveType(type, repository.classLoader);
t = target != null ? resolveType(target, repository.classLoader) : null;
} catch (ClassNotFoundException e) {
if (JBRApi.VERBOSE) {
System.err.println(type + " not eligible");
@@ -166,18 +178,18 @@ class ProxyRepository {
return INVALID;
}
if (target == null) flags |= Proxy.SERVICE;
if (needsAnnotation(l.lookupClass())) {
if (!hasAnnotation(l.lookupClass(), providedAnnotation)) {
if (needsAnnotation(repository, l.lookupClass())) {
if (!hasAnnotation(l.lookupClass(), repository.providedAnnotation)) {
if (JBRApi.VERBOSE) {
System.err.println(type + " not eligible: no @Provided annotation");
}
return INVALID;
}
if (!hasAnnotation(l.lookupClass(), serviceAnnotation)) flags &= ~Proxy.SERVICE;
if (!hasAnnotation(l.lookupClass(), repository.serviceAnnotation)) flags &= ~Proxy.SERVICE;
}
Proxy.Info info;
if (t != null) {
if (needsAnnotation(t.lookupClass()) && !hasAnnotation(t.lookupClass(), providesAnnotation)) {
if (needsAnnotation(repository, t.lookupClass()) && !hasAnnotation(t.lookupClass(), repository.providesAnnotation)) {
if (JBRApi.VERBOSE) {
System.err.println(target + " not eligible: no @Provides annotation");
}
@@ -191,8 +203,8 @@ class ProxyRepository {
String targetType = method.getValue().targetType;
String targetMethodName = method.getValue().targetMethodName;
try {
Lookup lookup = resolveType(targetType, classLoader);
MethodType mt = MethodType.fromMethodDescriptorString(targetMethodDescriptor, classLoader);
Lookup lookup = resolveType(targetType, repository.classLoader);
MethodType mt = MethodType.fromMethodDescriptorString(targetMethodDescriptor, repository.classLoader);
MethodHandle handle = lookup.findStatic(lookup.lookupClass(), targetMethodName, mt);
info.addStaticMethod(methodName, handle);
} catch (ClassNotFoundException | IllegalArgumentException | TypeNotPresentException |
@@ -230,38 +242,41 @@ class ProxyRepository {
public String toString() { return type; }
}
private Class<? extends Annotation> serviceAnnotation, providedAnnotation, providesAnnotation;
private Module annotationsModule;
private ClassLoader classLoader;
private final Map<String, Entry> entries = new HashMap<>();
private final String version;
private Registry() {
try (InputStream registryStream = BootLoader.findResourceAsStream("java.base", "META-INF/jbrapi.registry")) {
version = readEntries(registryStream);
} catch (IOException e) {
throw new RuntimeException(e);
static class Builtin {
static final Registry PRIVATE, PUBLIC;
static {
try {
PRIVATE = new Registry(BootLoader.findResourceAsStream("java.base", "META-INF/jbrapi.private"));
PUBLIC = new Registry(BootLoader.findResourceAsStream("java.base", "META-INF/jbrapi.public"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private void initAnnotations(Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation) {
this.serviceAnnotation = serviceAnnotation;
this.providedAnnotation = providedAnnotation;
this.providesAnnotation = providesAnnotation;
annotationsModule = serviceAnnotation == null ? null : serviceAnnotation.getModule();
if (annotationsModule != null) classLoader = annotationsModule.getClassLoader();
}
private final Map<String, Entry> entries = new HashMap<>();
private final String version;
private String readEntries(InputStream inputStream) {
Registry(InputStream inputStream) {
String version = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String s;
while ((s = reader.readLine()) != null) {
String[] tokens = s.split(" ");
switch (tokens[0]) {
case "TYPE" -> {
case "VERSION" -> version = tokens[1];
case "STATIC" -> {
Entry entry = entries.computeIfAbsent(tokens[4], Entry::new);
StaticValue target = new StaticValue(tokens[1], tokens[2]);
StaticValue prev = entry.staticMethods.put(new StaticKey(tokens[5], tokens[3]), target);
if (prev != null && !prev.equals(target)) {
throw new RuntimeException("Conflicting mapping: " +
target.targetType + "#" + target.targetMethodName + " <- " +
tokens[4] + "#" + tokens[5] + " -> " +
prev.targetType + "#" + prev.targetMethodName);
}
}
default -> {
Entry a = entries.computeIfAbsent(tokens[1], Entry::new);
Entry b = entries.computeIfAbsent(tokens[2], Entry::new);
if ((a.inverse != null || b.inverse != null) && (a.inverse != b || b.inverse != a)) {
@@ -274,7 +289,7 @@ class ProxyRepository {
b.inverse = a;
a.target = tokens[2];
b.target = tokens[1];
switch (tokens[3]) {
switch (tokens[0]) {
case "SERVICE" -> {
a.type = null;
b.flags |= Proxy.SERVICE;
@@ -282,56 +297,17 @@ class ProxyRepository {
case "PROVIDES" -> a.type = null;
case "PROVIDED" -> b.type = null;
}
if (tokens.length > 4 && tokens[4].equals("INTERNAL")) {
a.flags |= Proxy.INTERNAL;
b.flags |= Proxy.INTERNAL;
}
}
case "STATIC" -> {
Entry entry = entries.computeIfAbsent(tokens[4], Entry::new);
StaticValue target = new StaticValue(tokens[1], tokens[2]);
StaticValue prev = entry.staticMethods.put(new StaticKey(tokens[5], tokens[3]), target);
if (prev != null && !prev.equals(target)) {
throw new RuntimeException("Conflicting mapping: " +
target.targetType + "#" + target.targetMethodName + " <- " +
tokens[4] + "#" + tokens[5] + " -> " +
prev.targetType + "#" + prev.targetMethodName);
}
if (tokens.length > 6 && tokens[6].equals("INTERNAL")) entry.flags |= Proxy.INTERNAL;
}
case "VERSION" -> version = tokens[1];
}
}
} catch (IOException e) {
entries.clear();
throw new RuntimeException(e);
} catch (RuntimeException | Error e) {
entries.clear();
throw e;
}
return version;
this.version = version;
}
private synchronized void updateClassLoader(ClassLoader newLoader) {
// New loader is descendant of current one -> update
for (ClassLoader cl = newLoader;; cl = cl.getParent()) {
if (cl == classLoader) {
classLoader = newLoader;
return;
}
if (cl == null) break;
}
// Current loader is descendant of the new one -> leave
for (ClassLoader cl = classLoader;; cl = cl.getParent()) {
if (cl == newLoader) return;
if (cl == null) break;
}
// Independent classloaders -> error? Or maybe reset cache and start fresh?
throw new RuntimeException("Incompatible classloader");
}
private boolean needsAnnotation(Class<?> c) {
return annotationsModule != null && annotationsModule.equals(c.getModule());
private boolean needsAnnotation(ProxyRepository repository, Class<?> c) {
return repository.annotationsModule != null && repository.annotationsModule.equals(c.getModule());
}
private static boolean hasAnnotation(Class<?> c, Class<? extends Annotation> a) {

View File

@@ -1,4 +1,29 @@
package com.jetbrains.internal;
/*
* Copyright 2026 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.jbrapi;
import jdk.internal.misc.VM;

View File

@@ -24,6 +24,7 @@
* questions.
*/
#include <math.h>
#include "sun_font_StrikeCache.h"
#include "sun_java2d_pipe_BufferedOpCodes.h"
#include "sun_java2d_pipe_BufferedRenderPipe.h"
@@ -315,11 +316,23 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
}
if (ginfo->format != sun_font_StrikeCache_PIXEL_FORMAT_GREYSCALE) continue;
if (ginfo->height*ginfo->rowBytes == 0) continue;
VKRenderer_MaskFill((int) glyphx, (int) glyphy,
// Calculate subpixel offset.
int rx = ginfo->subpixelResolutionX, ry = ginfo->subpixelResolutionY;
int x = floor(glyphx), y = floor(glyphy);
int xOffset, yOffset;
if ((rx == 1 && ry == 1) || rx <= 0 || ry <= 0) {
xOffset = yOffset = 0;
} else {
xOffset = (int) ((glyphx - (float) x) * (float) rx);
yOffset = (int) ((glyphy - (float) y) * (float) ry);
}
VKRenderer_MaskFill(x, y,
ginfo->width, ginfo->height,
0, ginfo->rowBytes,
ginfo->height * ginfo->rowBytes,
ginfo->image);
ginfo->image + (ginfo->rowBytes * ginfo->height) * (xOffset + yOffset * rx));
}
SKIP_BYTES(b, numGlyphs * bytesPerGlyph);
}

View File

@@ -39,6 +39,7 @@ import sun.awt.datatransfer.DataTransferer;
import sun.awt.wl.im.WLInputMethodMetaDescriptor;
import sun.java2d.vulkan.VKEnv;
import sun.java2d.vulkan.VKRenderQueue;
import sun.security.action.GetPropertyAction;
import sun.util.logging.PlatformLogger;
import java.awt.*;
@@ -152,6 +153,9 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
private static native void initIDs(long displayPtr);
static {
// This field must be initialized BEFORE initIDs is called because it'll be read there
ENABLE_NATIVE_IM_SUPPORT = obtainWhetherToEnableNativeIMSupport();
if (!GraphicsEnvironment.isHeadless()) {
keyboard = new WLKeyboard();
long display = WLDisplay.getInstance().getDisplayPtr();
@@ -270,6 +274,43 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
return peer;
}
// This method is directly used by native code at the class loading stage, be careful with changing it in any way.
public static boolean isNativeInputMethodSupportEnabled() {
return ENABLE_NATIVE_IM_SUPPORT;
}
/**
* This flag allows disabling ALL integrations with native IMs. The idea is to allow users to disable
* unnecessary for them functionality if they face any problems because of it.
* Therefore, if it's {@code false}, the Toolkit code shouldn't use (directly or indirectly)
* any of Wayland's input methods-related APIs (e.g. the "text-input" protocol).
*/
private final static boolean ENABLE_NATIVE_IM_SUPPORT;
private static boolean obtainWhetherToEnableNativeIMSupport() {
// NB: make sure this default value is synchronized with the one used in the native function
// isNativeInputMethodSupportEnabled() in WLToolkit.c
boolean result = true;
try {
@SuppressWarnings("removal") // still needed for JBR21
final boolean propertyVal = Boolean.parseBoolean(
AccessController.doPrivileged(new GetPropertyAction("sun.awt.wl.im.enabled", "true"))
);
result = propertyVal;
} catch (Exception err) {
log.severe(
String.format(
"Failed to read the value of the system property \"sun.awt.wl.im.enabled\". Assuming the default value(=%b).",
result
),
err
);
}
return result;
}
/**
* Wayland events coming to queues other that the default are handled here.
* The code is executed on a separate thread and must not call any user code.
@@ -918,7 +959,15 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
*/
@Override
public InputMethodDescriptor getInputMethodAdapterDescriptor() {
return WLInputMethodMetaDescriptor.getInstanceIfAvailableOnPlatform();
final InputMethodDescriptor result = ENABLE_NATIVE_IM_SUPPORT
? WLInputMethodMetaDescriptor.getInstanceIfAvailableOnPlatform()
: null;
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine("getInputMethodAdapterDescriptor(): ENABLE_NATIVE_IM_SUPPORT={0}, result={1}.", ENABLE_NATIVE_IM_SUPPORT, result);
}
return result;
}
/**

View File

@@ -25,8 +25,8 @@
package sun.awt.wl.im;
import sun.awt.wl.WLToolkit;
import sun.awt.wl.im.text_input_unstable_v3.WLInputMethodDescriptorZwpTextInputV3;
import sun.security.action.GetPropertyAction;
import sun.util.logging.PlatformLogger;
import java.awt.AWTException;
@@ -35,7 +35,6 @@ import java.awt.font.TextAttribute;
import java.awt.im.InputMethodHighlight;
import java.awt.im.spi.InputMethod;
import java.awt.im.spi.InputMethodDescriptor;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
@@ -90,8 +89,9 @@ public final class WLInputMethodMetaDescriptor implements InputMethodDescriptor
public static WLInputMethodMetaDescriptor getInstanceIfAvailableOnPlatform() {
final WLInputMethodMetaDescriptor result;
final boolean enableNativeImSupport = WLToolkit.isNativeInputMethodSupportEnabled();
if (!ENABLE_NATIVE_IM_SUPPORT) {
if (!enableNativeImSupport) {
result = null;
} else {
// For now there's only 1 possible implementation of IM,
@@ -106,10 +106,6 @@ public final class WLInputMethodMetaDescriptor implements InputMethodDescriptor
}
}
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine("getInstanceIfAvailableOnPlatform(): result={0}, ENABLE_NATIVE_IM_SUPPORT={1}.", result, ENABLE_NATIVE_IM_SUPPORT);
}
return result;
}
@@ -188,29 +184,6 @@ public final class WLInputMethodMetaDescriptor implements InputMethodDescriptor
TextAttribute.SWAP_COLORS, TextAttribute.SWAP_COLORS_ON
);
/**
* This flag allows disabling ALL integrations with native IMs. The idea is to allow users to disable
* unnecessary for them functionality if they face any problems because of it.
* Therefore, if it's {@code false}, the Toolkit code shouldn't use (directly or indirectly)
* any of Wayland's input methods-related APIs (e.g. the "text-input" protocol).
*/
private final static boolean ENABLE_NATIVE_IM_SUPPORT;
static {
boolean enableNativeImSupportInitializer = true;
try {
@SuppressWarnings("removal") // still needed for JBR21
final boolean enableNativeImSupportPropertyValue = Boolean.parseBoolean(
AccessController.doPrivileged(new GetPropertyAction("sun.awt.wl.im.enabled", "true"))
);
enableNativeImSupportInitializer = enableNativeImSupportPropertyValue;
} catch (Exception err) {
log.severe("Failed to read the value of the system property \"sun.awt.wl.im.enabled\". Assuming the default value(=true).", err);
}
ENABLE_NATIVE_IM_SUPPORT = enableNativeImSupportInitializer;
}
private final InputMethodDescriptor realImDescriptor;

View File

@@ -132,6 +132,8 @@ static jmethodID dispatchKeyboardLeaveEventMID;
static jmethodID dispatchRelativePointerEventMID;
static jmethodID handleToplevelIconSizeMID;
static jmethodID isNativeInputMethodSupportEnabledMID;
JNIEnv *getEnv() {
JNIEnv *env;
// assuming we're always called from a Java thread
@@ -595,6 +597,29 @@ static const struct xdg_toplevel_icon_manager_v1_listener xdg_toplevel_icon_mana
.done = xdg_toplevel_icon_manager_icon_size_done,
};
static bool isNativeInputMethodSupportEnabled(void) {
// NB: make sure this default value is synchronized with the one used in
// sun.awt.wl.WLToolkit#obtainWhetherToEnableNativeIMSupport
const bool deflt = true;
JNIEnv * const env = getEnv();
jboolean upcallResult;
if (env == NULL) {
return deflt;
}
upcallResult = (*env)->CallStaticBooleanMethod(env, tkClass, isNativeInputMethodSupportEnabledMID);
if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
return deflt;
}
return (upcallResult == JNI_TRUE) ? true : false;
}
static void
registry_global(void *data, struct wl_registry *wl_registry,
uint32_t name, const char *interface, uint32_t version)
@@ -651,8 +676,11 @@ registry_global(void *data, struct wl_registry *wl_registry,
// the event loop may shut down as soon as it gets launched (wl_display_dispatch will return -1),
// so let's protect from this since the component being obtained is not vital for work.
const uint32_t versionToBind = 1;
if (versionToBind <= version) {
zwp_text_input_manager = wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, versionToBind);
if (isNativeInputMethodSupportEnabled()) {
if (versionToBind <= version) {
zwp_text_input_manager = wl_registry_bind(wl_registry, name, &zwp_text_input_manager_v3_interface, versionToBind);
}
}
} else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
xdg_decoration_manager = wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1);
@@ -805,6 +833,11 @@ initJavaRefs(JNIEnv *env, jclass clazz)
"(DD)V"),
JNI_FALSE);
CHECK_NULL_RETURN(isNativeInputMethodSupportEnabledMID = (*env)->GetStaticMethodID(env, tkClass,
"isNativeInputMethodSupportEnabled",
"()Z"),
JNI_FALSE);
jclass wlgeClass = (*env)->FindClass(env, "sun/awt/wl/WLGraphicsEnvironment");
CHECK_NULL_RETURN(wlgeClass, JNI_FALSE);

View File

@@ -24,8 +24,8 @@
/*
* @test
* @build com.jetbrains.JBR
* @run main BootstrapTest new
* @run main BootstrapTest old
* @run main/othervm -Djetbrains.runtime.api.verbose=true -Djetbrains.runtime.api.verifyBytecode=true BootstrapTest new
* @run main/othervm -Djetbrains.runtime.api.verbose=true -Djetbrains.runtime.api.verifyBytecode=true BootstrapTest old
*/
import com.jetbrains.JBR;

View File

@@ -23,9 +23,9 @@
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @modules java.base/com.jetbrains.internal.jbrapi:+open
* @build com.jetbrains.* com.jetbrains.test.api.MethodMapping com.jetbrains.test.jbr.MethodMapping
* @run main/othervm -Djetbrains.runtime.api.extendRegistry=true MethodMappingTest
* @run main/othervm -Djetbrains.runtime.api.verbose=true -Djetbrains.runtime.api.verifyBytecode=true -Djetbrains.runtime.api.extendRegistry=true MethodMappingTest
*/
import java.util.Map;

View File

@@ -1,11 +1,11 @@
TYPE com.jetbrains.test.jbr.MethodMapping.SimpleEmptyImpl com.jetbrains.test.api.MethodMapping.SimpleEmpty PROVIDES
TYPE com.jetbrains.test.jbr.MethodMapping.PlainImpl com.jetbrains.test.api.MethodMapping.Plain SERVICE
PROVIDES com.jetbrains.test.jbr.MethodMapping.SimpleEmptyImpl com.jetbrains.test.api.MethodMapping.SimpleEmpty
SERVICE com.jetbrains.test.jbr.MethodMapping.PlainImpl com.jetbrains.test.api.MethodMapping.Plain
STATIC MethodMappingTest main ([Ljava/lang/String;)V com.jetbrains.test.api.MethodMapping.Plain c
TYPE com.jetbrains.test.jbr.MethodMapping.PlainFailImpl com.jetbrains.test.api.MethodMapping.PlainFail SERVICE
TYPE com.jetbrains.test.jbr.MethodMapping.Callback com.jetbrains.test.api.MethodMapping.ApiCallback PROVIDED
TYPE com.jetbrains.test.jbr.MethodMapping.JBRTwoWay com.jetbrains.test.api.MethodMapping.ApiTwoWay TWO_WAY
TYPE com.jetbrains.test.jbr.MethodMapping.ConversionImpl com.jetbrains.test.api.MethodMapping.Conversion TWO_WAY
TYPE com.jetbrains.test.jbr.MethodMapping.ConversionSelfImpl com.jetbrains.test.api.MethodMapping.ConversionSelf PROVIDES
TYPE com.jetbrains.test.jbr.MethodMapping.ConversionFailImpl com.jetbrains.test.api.MethodMapping.ConversionFail PROVIDES
TYPE com.jetbrains.test.jbr.MethodMapping.ArrayConversionImpl com.jetbrains.test.api.MethodMapping.ArrayConversion PROVIDES
TYPE com.jetbrains.test.jbr.MethodMapping.GenericConversionImpl com.jetbrains.test.api.MethodMapping.GenericConversion PROVIDES
SERVICE com.jetbrains.test.jbr.MethodMapping.PlainFailImpl com.jetbrains.test.api.MethodMapping.PlainFail
PROVIDED com.jetbrains.test.jbr.MethodMapping.Callback com.jetbrains.test.api.MethodMapping.ApiCallback
TWO_WAY com.jetbrains.test.jbr.MethodMapping.JBRTwoWay com.jetbrains.test.api.MethodMapping.ApiTwoWay
TWO_WAY com.jetbrains.test.jbr.MethodMapping.ConversionImpl com.jetbrains.test.api.MethodMapping.Conversion
PROVIDES com.jetbrains.test.jbr.MethodMapping.ConversionSelfImpl com.jetbrains.test.api.MethodMapping.ConversionSelf
PROVIDES com.jetbrains.test.jbr.MethodMapping.ConversionFailImpl com.jetbrains.test.api.MethodMapping.ConversionFail
PROVIDES com.jetbrains.test.jbr.MethodMapping.ArrayConversionImpl com.jetbrains.test.api.MethodMapping.ArrayConversion
PROVIDES com.jetbrains.test.jbr.MethodMapping.GenericConversionImpl com.jetbrains.test.api.MethodMapping.GenericConversion

View File

@@ -23,9 +23,9 @@
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @modules java.base/com.jetbrains.internal.jbrapi:+open
* @build com.jetbrains.* com.jetbrains.test.api.ProxyInfoResolving com.jetbrains.test.jbr.ProxyInfoResolving
* @run main/othervm -Djetbrains.runtime.api.extendRegistry=true ProxyInfoResolvingTest
* @run main/othervm -Djetbrains.runtime.api.verbose=true -Djetbrains.runtime.api.verifyBytecode=true -Djetbrains.runtime.api.extendRegistry=true ProxyInfoResolvingTest
*/
import java.util.Map;

View File

@@ -1,7 +1,7 @@
TYPE absentImpl com.jetbrains.test.api.ProxyInfoResolving.InterfaceWithoutImplementation PROVIDES
PROVIDES absentImpl com.jetbrains.test.api.ProxyInfoResolving.InterfaceWithoutImplementation
STATIC NoClass foo ()V com.jetbrains.test.api.ProxyInfoResolving.ServiceWithoutImplementation foo
TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ValidApiImpl com.jetbrains.test.api.ProxyInfoResolving.ValidApi PROVIDES
TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ProxyClassImpl com.jetbrains.test.api.ProxyInfoResolving.ProxyClass PROVIDES
TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ClientProxyClass com.jetbrains.test.api.ProxyInfoResolving.ClientProxyClassImpl PROVIDED
TYPE com.jetbrains.test.jbr.ProxyInfoResolving.ServiceWithoutAnnotationImpl com.jetbrains.test.api.ProxyInfoResolving.ServiceWithoutAnnotation SERVICE
PROVIDES com.jetbrains.test.jbr.ProxyInfoResolving.ValidApiImpl com.jetbrains.test.api.ProxyInfoResolving.ValidApi
PROVIDES com.jetbrains.test.jbr.ProxyInfoResolving.ProxyClassImpl com.jetbrains.test.api.ProxyInfoResolving.ProxyClass
PROVIDED com.jetbrains.test.jbr.ProxyInfoResolving.ClientProxyClass com.jetbrains.test.api.ProxyInfoResolving.ClientProxyClassImpl
SERVICE com.jetbrains.test.jbr.ProxyInfoResolving.ServiceWithoutAnnotationImpl com.jetbrains.test.api.ProxyInfoResolving.ServiceWithoutAnnotation
STATIC NoClass foo ()V com.jetbrains.test.api.ProxyInfoResolving.ServiceWithExtension foo

View File

@@ -23,14 +23,16 @@
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
* @modules java.base/com.jetbrains.internal.jbrapi:+open
* @build com.jetbrains.* com.jetbrains.test.api.Real com.jetbrains.test.jbr.Real
* @run main/othervm -Djetbrains.runtime.api.extendRegistry=true RealTest
* @run main/othervm -Djetbrains.runtime.api.verbose=true -Djetbrains.runtime.api.verifyBytecode=true -Djetbrains.runtime.api.extendRegistry=true RealTest
*/
import com.jetbrains.Extensions;
import com.jetbrains.internal.JBRApi;
import com.jetbrains.internal.jbrapi.JBRApi;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -41,11 +43,25 @@ import static com.jetbrains.test.api.Real.*;
public class RealTest {
public static void main(String[] args) {
init("RealTest", Map.of(Extensions.FOO, new Class[] {Proxy.class}, Extensions.BAR, new Class[] {Proxy.class}));
public static void main(String[] args) throws Throwable {
// Plain run.
run();
// Run in an isolated classloader at least 2 times until the proxy gets GC'ed.
WeakReference<?> weak = null;
for (int i = 0; i < 2 || weak.get() != null; i++) {
System.gc();
new IsolatedLoader().loadClass(RealTest.class.getName()).getMethod("run").invoke(null);
if (weak == null) weak = new WeakReference<>(getProxy(Proxy.class));
if (i > 300) throw new Error("Proxy was not collected after 300 iterations");
}
}
public static void run() {
init("RealTest", Map.of(Extensions.FOO, new Class<?>[] {Proxy.class}, Extensions.BAR, new Class<?>[] {Proxy.class}));
// Get service
Service service = Objects.requireNonNull(JBRApi.getService(Service.class));
Service service = Objects.requireNonNull(api.getService(Service.class));
// Proxy passthrough
Proxy p = Objects.requireNonNull(service.getProxy());
@@ -86,10 +102,10 @@ public class RealTest {
}
// Check extensions
if (!JBRApi.isExtensionSupported(Extensions.FOO)) {
if (!api.isExtensionSupported(Extensions.FOO)) {
throw new Error("FOO extension must be supported");
}
if (JBRApi.isExtensionSupported(Extensions.BAR)) {
if (api.isExtensionSupported(Extensions.BAR)) {
throw new Error("BAR extension must not be supported");
}
try {
@@ -101,10 +117,10 @@ public class RealTest {
throw new Error("BAR extension was disabled but call succeeded");
} catch (UnsupportedOperationException ignore) {}
// foo() must succeed when enabled
JBRApi.getService(Service.class, Extensions.FOO).getProxy().foo();
api.getService(Service.class, Extensions.FOO).getProxy().foo();
// Asking for BAR must return null, as it is not supported
requireNull(JBRApi.getService(Service.class, Extensions.FOO, Extensions.BAR));
requireNull(JBRApi.getService(Service.class, Extensions.BAR));
requireNull(api.getService(Service.class, Extensions.FOO, Extensions.BAR));
requireNull(api.getService(Service.class, Extensions.BAR));
// Test specialized (implicit) List proxy
List<Api2Way> list = Objects.requireNonNull(service.testList(null));
@@ -133,4 +149,25 @@ public class RealTest {
value = o;
}
}
private static class IsolatedLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.equals("RealClassloadersTest") ||
(name.startsWith("com.jetbrains.") && !name.startsWith("com.jetbrains.test.jbr.") &&
!name.startsWith("com.jetbrains.exported.") && !name.startsWith("com.jetbrains.internal."))) {
Class<?> c = findLoadedClass(name);
if (c != null) return c;
String path = name.replace('.', '/').concat(".class");
try (var stream = getResourceAsStream(path)) {
if (stream == null) throw new ClassNotFoundException(name);
byte[] b = stream.readAllBytes();
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return super.loadClass(name, resolve);
}
}
}

View File

@@ -1,5 +1,5 @@
TYPE com.jetbrains.test.jbr.Real.ServiceImpl com.jetbrains.test.api.Real.Service SERVICE
TYPE com.jetbrains.test.jbr.Real.ProxyImpl com.jetbrains.test.api.Real.Proxy PROVIDES
TYPE com.jetbrains.test.jbr.Real.Client com.jetbrains.test.api.Real.ClientImpl PROVIDED
TYPE com.jetbrains.test.jbr.Real.JBR2Way com.jetbrains.test.api.Real.Api2Way TWO_WAY
TYPE com.jetbrains.test.jbr.Real.JBRLazyNumber com.jetbrains.test.api.Real.ApiLazyNumber TWO_WAY
SERVICE com.jetbrains.test.jbr.Real.ServiceImpl com.jetbrains.test.api.Real.Service
PROVIDES com.jetbrains.test.jbr.Real.ProxyImpl com.jetbrains.test.api.Real.Proxy
PROVIDED com.jetbrains.test.jbr.Real.Client com.jetbrains.test.api.Real.ClientImpl
TWO_WAY com.jetbrains.test.jbr.Real.JBR2Way com.jetbrains.test.api.Real.Api2Way
TWO_WAY com.jetbrains.test.jbr.Real.JBRLazyNumber com.jetbrains.test.api.Real.ApiLazyNumber

View File

@@ -28,5 +28,7 @@ package com.jetbrains;
*/
public class JBR {
@Service
@Provided
public interface ServiceApi {}
}

View File

@@ -23,7 +23,7 @@
package com.jetbrains;
import com.jetbrains.internal.JBRApi;
import com.jetbrains.internal.jbrapi.JBRApi;
import java.io.*;
import java.lang.reflect.Field;
@@ -32,42 +32,50 @@ import java.util.Map;
public class Util {
public static JBRApi api;
private static Object proxyRepository;
private static Method getProxy, inverse, generate;
/**
* Invoke internal {@link JBRApi#init} bypassing {@link com.jetbrains.exported.JBRApiSupport#bootstrap}.
*/
public static void init(String registryName, Map<Enum<?>, Class[]> extensionClasses) {
try (InputStream in = new FileInputStream(new File(System.getProperty("test.src", "."), registryName + ".registry"))) {
JBRApi.init(in, Service.class, Provided.class, Provides.class, extensionClasses, m -> {
public static void init(String registryName, Map<Enum<?>, Class<?>[]> extensionClasses) {
try (InputStream in = new SequenceInputStream(
new ByteArrayInputStream("PROVIDES com.jetbrains.internal.jbrapi.JBRApi com.jetbrains.JBR.ServiceApi\n".getBytes()),
new FileInputStream(new File(System.getProperty("test.src", "."), registryName + ".registry")))) {
Object api = JBRApi.init(in, JBR.ServiceApi.class, Service.class, Provided.class, Provides.class, extensionClasses, m -> {
Extension e = m.getAnnotation(Extension.class);
return e == null ? null : e.value();
});
} catch (IOException e) {
Field f = api.getClass().getDeclaredField("target");
f.setAccessible(true);
Util.api = (JBRApi) f.get(api);
proxyRepository = null;
getProxy = inverse = generate = null;
} catch (IOException | NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
private static Object proxyRepository;
public static Object getProxyRepository() throws Throwable {
if (proxyRepository == null) {
Field f = JBRApi.class.getDeclaredField("proxyRepository");
f.setAccessible(true);
proxyRepository = f.get(null);
proxyRepository = f.get(api);
}
return proxyRepository;
}
private static Method getProxy;
public static Object getProxy(Class<?> interFace) throws Throwable {
var repo = getProxyRepository();
if (getProxy == null) {
getProxy = repo.getClass()
.getDeclaredMethod("getProxy", Class.class, Class.forName("com.jetbrains.internal.Mapping").arrayType());
.getDeclaredMethod("getProxy", Class.class, Class.forName("com.jetbrains.internal.jbrapi.Mapping").arrayType());
getProxy.setAccessible(true);
}
return getProxy.invoke(repo, interFace, null);
}
private static Method inverse;
public static Object inverse(Object proxy) throws Throwable {
if (inverse == null) {
inverse = proxy.getClass().getDeclaredMethod("inverse");
@@ -76,7 +84,6 @@ public class Util {
return inverse.invoke(proxy);
}
private static Method generate;
public static boolean isSupported(Object proxy) throws Throwable {
if (generate == null) {
generate = proxy.getClass().getDeclaredMethod("generate");