mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2026-02-03 14:16:30 +01:00
Compare commits
4 Commits
jbr21.1312
...
jbr21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11421db3d5 | ||
|
|
532cc261c0 | ||
|
|
486cd176d7 | ||
|
|
a897ca4c83 |
@@ -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
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,5 +28,7 @@ package com.jetbrains;
|
||||
*/
|
||||
public class JBR {
|
||||
|
||||
@Service
|
||||
@Provided
|
||||
public interface ServiceApi {}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user