addExports) {
- addExports.apply(lookup.lookupClass().getPackageName(), outerLookup.lookupClass().getModule());
- return new ModuleRegistry(lookup);
- }
-
- public static class ModuleRegistry {
-
- private final Lookup lookup;
- private RegisteredProxyInfo lastProxy;
-
- private ModuleRegistry(Lookup lookup) {
- this.lookup = lookup;
- }
-
- private ModuleRegistry addProxy(ProxyInfo.Type type, String interfaceName, String... targets) {
- lastProxy = new RegisteredProxyInfo(lookup, interfaceName, targets, type, new ArrayList<>());
- registeredProxyInfoByInterfaceName.put(interfaceName, lastProxy);
- for (String target : targets) {
- registeredProxyInfoByTargetName.put(target, lastProxy);
}
- if (targets.length == 1) {
- validate2WayMapping(lastProxy, registeredProxyInfoByInterfaceName.get(targets[0]));
- validate2WayMapping(lastProxy, registeredProxyInfoByTargetName.get(interfaceName));
- }
- return this;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
}
- private static void validate2WayMapping(RegisteredProxyInfo p, RegisteredProxyInfo reverse) {
- if (reverse != null &&
- (!p.interfaceName().equals(reverse.targets()[0]) || !p.targets()[0].equals(reverse.interfaceName()))) {
- throw new IllegalArgumentException("Invalid 2-way proxy mapping: " +
- p.interfaceName() + " -> " + p.targets()[0] + " & " +
- reverse.interfaceName() + " -> " + reverse.targets()[0]);
- }
- }
-
- /**
- * Register new {@linkplain ProxyInfo.Type#PROXY proxy} mapping.
- *
- * When {@code target} object is passed from JBR to client through service or any other proxy,
- * it's converted to corresponding {@code interFace} type by creating a proxy object
- * that implements {@code interFace} and delegates method calls to {@code target}.
- * @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module.
- * @param targets corresponding class/interface names in current JBR module, first found will be used. This must not be empty.
- * @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
- */
- public ModuleRegistry proxy(String interFace, String... targets) {
- if (targets.length == 0) throw new IllegalArgumentException("Proxy must have at least one target");
- return addProxy(ProxyInfo.Type.PROXY, interFace, targets);
- }
-
- /**
- * Register new {@linkplain ProxyInfo.Type#SERVICE service} mapping.
- *
- * Service is a singleton, which may be accessed by client using {@link com.jetbrains.JBR} class.
- * @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module.
- * @param targets corresponding implementation class names in current JBR module, first found will be used.
- * @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
- */
- public ModuleRegistry service(String interFace, String... targets) {
- return addProxy(ProxyInfo.Type.SERVICE, interFace, targets);
- }
-
- /**
- * Register new {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} mapping.
- * This mapping type allows implementation of callbacks.
- *
- * When {@code target} object is passed from client to JBR through service or any other proxy,
- * it's converted to corresponding {@code interFace} type by creating a proxy object
- * that implements {@code interFace} and delegates method calls to {@code target}.
- * @param interFace interface name in current JBR module.
- * @param target corresponding class/interface name in {@link com.jetbrains.JBR jetbrains.api} module.
- * @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
- */
- public ModuleRegistry clientProxy(String interFace, String target) {
- Objects.requireNonNull(target);
- return addProxy(ProxyInfo.Type.CLIENT_PROXY, interFace, target);
- }
-
- /**
- * Register new 2-way mapping.
- * It creates both {@linkplain ProxyInfo.Type#PROXY proxy} and
- * {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} between given interfaces.
- *
- * It links together two given interfaces and allows passing such objects back and forth
- * between JBR and {@link com.jetbrains.JBR jetbrains.api} module through services and other proxy methods.
- * @param apiInterface interface name in {@link com.jetbrains.JBR jetbrains.api} module.
- * @param jbrInterface interface name in current JBR module.
- * @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
- */
- public ModuleRegistry twoWayProxy(String apiInterface, String jbrInterface) {
- clientProxy(jbrInterface, apiInterface);
- proxy(apiInterface, jbrInterface);
- return this;
- }
-
- /**
- * Delegate "{@code interfaceMethodName}" method calls to first found static "{@code methodName}" in "{@code classes}".
- */
- public ModuleRegistry withStatic(String interfaceMethodName, String methodName, String... classes) {
- lastProxy.staticMethods().add(
- new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, methodName, classes));
- return this;
- }
- }
-
- /**
- * Thrown by service implementations indicating that the service is not available for some reason
- */
- public static class ServiceNotAvailableException extends RuntimeException {
- @Serial
- private static final long serialVersionUID = 1L;
- public ServiceNotAvailableException() { super(); }
- public ServiceNotAvailableException(String message) { super(message); }
- public ServiceNotAvailableException(String message, Throwable cause) { super(message, cause); }
- public ServiceNotAvailableException(Throwable cause) { super(cause); }
+ return null;
}
}
diff --git a/src/java.base/share/classes/com/jetbrains/internal/Mapping.java b/src/java.base/share/classes/com/jetbrains/internal/Mapping.java
new file mode 100644
index 000000000000..8a0792ab42db
--- /dev/null
+++ b/src/java.base/share/classes/com/jetbrains/internal/Mapping.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2023 JetBrains s.r.o.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.jetbrains.internal;
+
+import jdk.internal.org.objectweb.asm.Label;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.stream.Stream;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+import static jdk.internal.org.objectweb.asm.Type.getInternalName;
+
+/**
+ * Mapping defines conversion of parameters and return types between source and destination method.
+ */
+abstract class Mapping {
+
+ static class Query {
+ boolean valid = true;
+ boolean needsExtensions = false;
+ }
+
+ final Class> from, to;
+
+ private Mapping(Class> from, Class> to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ void convert(AccessContext.Method context) {
+ MethodVisitor m = context.writer;
+ Label skipConvert = new Label();
+ m.visitInsn(DUP);
+ m.visitJumpInsn(IFNULL, skipConvert);
+ convertNonNull(context);
+ m.visitLabel(skipConvert);
+ }
+
+ abstract void convertNonNull(AccessContext.Method context);
+
+ void cast(AccessContext.Method context) {
+ if (context.access().canAccess(to)) context.writer.visitTypeInsn(CHECKCAST, getInternalName(to));
+ }
+
+ abstract Mapping inverse();
+
+ void query(Query q) {}
+
+ @Override
+ public abstract boolean equals(Object obj);
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public abstract String toString();
+
+ static class Identity extends Mapping {
+ private Identity(Class> c) {
+ super(c, c);
+ }
+ @Override
+ void convert(AccessContext.Method context) {}
+ @Override
+ void convertNonNull(AccessContext.Method context) {}
+ @Override
+ Mapping inverse() { return this; }
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Identity i = (Identity) o;
+ return from.equals(i.from);
+ }
+ @Override
+ public int hashCode() { return from.hashCode(); }
+ @Override
+ public String toString() { return from.getName(); }
+ }
+
+ static class Invalid extends Identity {
+ private Invalid(Class> c) {
+ super(c);
+ }
+ @Override
+ void query(Query q) { q.valid = false; }
+ @Override
+ public String toString() { return "INVALID(" + from.getName() + ")"; }
+ }
+
+ static abstract class Nesting extends Mapping {
+ final Mapping component;
+ Nesting(Class> from, Class> to, Mapping component) {
+ super(from, to);
+ this.component = component;
+ }
+ @Override
+ void query(Query q) { component.query(q); }
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Nesting nesting = (Nesting) o;
+ if (!Objects.equals(from, nesting.from)) return false;
+ if (!Objects.equals(to, nesting.to)) return false;
+ return component.equals(nesting.component);
+ }
+ @Override
+ public int hashCode() {
+ int result = from != null ? from.hashCode() : 0;
+ result = 31 * result + (to != null ? to.hashCode() : 0);
+ result = 31 * result + component.hashCode();
+ return result;
+ }
+ }
+
+ static class Array extends Nesting {
+ private Array(Mapping component) {
+ super(component.from.arrayType(), component.to.arrayType(), component);
+ }
+ static Mapping wrap(Mapping m) {
+ if (m instanceof Identity) return new Identity(m.from.arrayType());
+ else return new Array(m);
+ }
+ @Override
+ void convert(AccessContext.Method context) {
+ super.convert(context);
+ cast(context); // Explicitly cast to result type after non-null branch
+ }
+ @Override
+ void convertNonNull(AccessContext.Method context) {
+ final int TEMP_COUNTER_SLOT = 1; // Warning! We overwrite 1st local slot.
+ 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;");
+ m.visitInsn(DUP);
+ m.visitInsn(ARRAYLENGTH);
+ if (context.access().canAccess(to)) {
+ m.visitTypeInsn(ANEWARRAY, getInternalName(Objects.requireNonNull(to.componentType())));
+ } else context.invokeDynamic(MethodHandles.arrayConstructor(to));
+ m.visitInsn(SWAP);
+ m.visitInsn(DUP);
+ m.visitInsn(ARRAYLENGTH);
+ // Check loop conditions
+ m.visitLabel(loopStart);
+ m.visitInsn(DUP);
+ m.visitJumpInsn(IFLE, loopEnd);
+ // Stack: toArray, fromArray, i -> toArray, fromArray, i, toArray, i, from
+ m.visitVarInsn(ISTORE, TEMP_COUNTER_SLOT);
+ m.visitInsn(DUP2);
+ m.visitIincInsn(TEMP_COUNTER_SLOT, -1);
+ m.visitVarInsn(ILOAD, TEMP_COUNTER_SLOT);
+ m.visitInsn(DUP_X2);
+ m.visitInsn(DUP_X1);
+ m.visitInsn(AALOAD);
+ // Stack from -> to
+ component.convert(context);
+ // Stack: toArray, fromArray, i, toArray, i, to -> toArray, fromArray, i
+ m.visitInsn(AASTORE);
+ m.visitJumpInsn(GOTO, loopStart);
+ m.visitLabel(loopEnd);
+ // Stack: toArray, fromArray, i -> toArray
+ m.visitInsn(POP2);
+ }
+ @Override
+ Mapping inverse() { return new Array(component.inverse()); }
+ @Override
+ public String toString() { return "[" + component + "]"; }
+ }
+
+ private static abstract class ProxyConversion extends Mapping {
+ final Proxy fromProxy, toProxy;
+ private ProxyConversion(Class> from, Class> to, Proxy fromProxy, Proxy toProxy) {
+ super(from, to);
+ this.fromProxy = fromProxy;
+ this.toProxy = toProxy;
+ }
+ void wrapNonNull(AccessContext.Method context) {
+ context.addDependency(toProxy);
+ MethodType mt;
+ if (JBRApi.EXTENSIONS_ENABLED) {
+ context.writer.visitVarInsn(ALOAD, 0);
+ mt = MethodType.methodType(to, from, long[].class);
+ } else {
+ mt = MethodType.methodType(to, from);
+ }
+ context.invokeDynamic(mt, toProxy::getConstructor);
+ }
+ void extractNonNull(AccessContext.Method context) {
+ context.addDependency(fromProxy);
+ context.writer.visitMethodInsn(INVOKEINTERFACE,
+ "com/jetbrains/exported/JBRApiSupport$Proxy", "$getProxyTarget", "()Ljava/lang/Object;", true);
+ }
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ProxyConversion i = (ProxyConversion) o;
+ return from.equals(i.from) && to.equals(i.to) &&
+ Objects.equals(fromProxy, i.fromProxy) && Objects.equals(toProxy, i.toProxy);
+ }
+ @Override
+ public int hashCode() {
+ int result = from.hashCode();
+ result = 31 * result + to.hashCode();
+ result = 31 * result + Objects.hashCode(fromProxy);
+ result = 31 * result + Objects.hashCode(toProxy);
+ return result;
+ }
+ }
+
+ private static class Wrap extends ProxyConversion {
+ private Wrap(Class> from, Class> to, Proxy proxy) {
+ super(from, to, null, proxy);
+ }
+ @Override
+ void convertNonNull(AccessContext.Method context) { wrapNonNull(context); }
+ @Override
+ Mapping inverse() { return new Extract(to, from, toProxy); }
+ @Override
+ void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; }
+ @Override
+ public String toString() { return from.getName() + " --wrap-> " + to.getName(); }
+ }
+
+ private static class Extract extends ProxyConversion {
+ private Extract(Class> from, Class> to, Proxy proxy) {
+ super(from, to, proxy, null);
+ }
+ @Override
+ void convert(AccessContext.Method context) {
+ super.convert(context);
+ cast(context); // Explicitly cast to result type after non-null branch
+ }
+ @Override
+ void convertNonNull(AccessContext.Method context) { extractNonNull(context); }
+ @Override
+ Mapping inverse() { return new Wrap(to, from, fromProxy); }
+ @Override
+ public String toString() { return from.getName() + " --extract-> " + to.getName(); }
+ }
+
+ private static class Dynamic2Way extends ProxyConversion {
+ private Dynamic2Way(Class> from, Class> to, Proxy fromProxy, Proxy toProxy) {
+ super(from, to, fromProxy, toProxy);
+ }
+ @Override
+ void convert(AccessContext.Method context) {
+ Label elseBranch = new Label(), afterBranch = new Label();
+ MethodVisitor m = context.writer;
+ m.visitInsn(DUP);
+ m.visitJumpInsn(IFNULL, afterBranch);
+ m.visitInsn(DUP);
+ m.visitTypeInsn(INSTANCEOF, "com/jetbrains/exported/JBRApiSupport$Proxy");
+ m.visitJumpInsn(IFEQ, elseBranch);
+ extractNonNull(context);
+ m.visitJumpInsn(GOTO, afterBranch);
+ m.visitLabel(elseBranch);
+ wrapNonNull(context);
+ m.visitLabel(afterBranch);
+ cast(context); // Explicitly cast to result type after non-null branch
+ }
+ @Override
+ void convertNonNull(AccessContext.Method context) {}
+ @Override
+ Mapping inverse() { return new Dynamic2Way(to, from, toProxy, fromProxy); }
+ @Override
+ void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; }
+ @Override
+ public String toString() { return from.getName() + " --2way-> " + to.getName(); }
+ }
+
+ private static class CustomOptional extends Nesting {
+ private CustomOptional(Mapping component) {
+ super(Optional.class, Optional.class, component);
+ }
+ static Mapping wrap(Mapping m) {
+ if (m instanceof Identity) return new Identity(Optional.class);
+ else return new CustomOptional(m);
+ }
+ @Override
+ void convertNonNull(AccessContext.Method context) {
+ MethodVisitor m = context.writer;
+ m.visitInsn(ACONST_NULL);
+ m.visitMethodInsn(INVOKEVIRTUAL, "java/util/Optional", "orElse", "(Ljava/lang/Object;)Ljava/lang/Object;", false);
+ component.convert(context);
+ m.visitMethodInsn(INVOKESTATIC, "java/util/Optional", "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;", false);
+ }
+ @Override
+ Mapping inverse() { return new CustomOptional(component.inverse()); }
+ @Override
+ public String toString() { return "Optional<" + component + ">"; }
+ }
+
+ record Method(MethodType type, Mapping returnMapping, Mapping[] parameterMapping, Query query) {
+ @Override
+ public String toString() {
+ return returnMapping + "(" + Arrays.toString(parameterMapping) + ")";
+ }
+ }
+
+ static class Context {
+
+ private final ProxyRepository proxyRepository;
+ private final Map, Mapping> tvMappings = new HashMap<>();
+
+ Context(ProxyRepository proxyRepository) {
+ this.proxyRepository = proxyRepository;
+ }
+
+ void initTypeParameters(Class> type, Stream typeParameters) {
+ if (type == null) return;
+ if (typeParameters != null) {
+ TypeVariable>[] tvs = type.getTypeParameters();
+ Iterator tpIterator = typeParameters.iterator();
+ 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());
+ }
+ }
+ initTypeParameters(type.getGenericSuperclass());
+ for (Type t : type.getGenericInterfaces()) initTypeParameters(t);
+ }
+
+ void initTypeParameters(Type supertype) {
+ if (supertype instanceof ParameterizedType type) {
+ initTypeParameters((Class>) type.getRawType(),
+ Stream.of(type.getActualTypeArguments()).map(this::getMapping));
+ } else if (supertype instanceof Class> c) {
+ initTypeParameters(c, null);
+ } else if (supertype != null) {
+ throw new RuntimeException("Unknown supertype kind: " + supertype.getClass());
+ }
+ }
+
+ Method getMapping(java.lang.reflect.Method interfaceMethod) {
+ Type[] params = interfaceMethod.getGenericParameterTypes();
+ List> ptypes = new ArrayList<>(params.length);
+ Mapping[] paramMappings = new Mapping[params.length];
+ for (int i = 0; i < params.length; i++) {
+ paramMappings[i] = getMapping(params[i]);
+ ptypes.add(paramMappings[i].to);
+ }
+ Mapping returnMapping = getMapping(interfaceMethod.getGenericReturnType()).inverse();
+ return new Method(MethodType.methodType(returnMapping.from, ptypes), returnMapping, paramMappings,
+ query(returnMapping, paramMappings));
+ }
+
+ private Mapping getMapping(Type userType) {
+ if (userType instanceof Class> t) {
+ return getMapping(t, null);
+ } else if (userType instanceof GenericArrayType t) {
+ return Array.wrap(getMapping(t.getGenericComponentType()));
+ } else if (userType instanceof ParameterizedType t) {
+ return getMapping(t);
+ } else if (userType instanceof TypeVariable> t) {
+ Mapping tvMapping = tvMappings.get(t);
+ if (tvMapping != null) return tvMapping;
+ Type[] bounds = t.getBounds();
+ return getMapping(bounds.length > 0 ? bounds[0] : Object.class);
+ } else if (userType instanceof WildcardType t) {
+ Type[] bounds = t.getUpperBounds();
+ return getMapping(bounds.length > 0 ? bounds[0] : Object.class);
+ } else {
+ throw new RuntimeException("Unknown type kind: " + userType.getClass());
+ }
+ }
+
+ private Mapping getMapping(ParameterizedType userType) {
+ Type[] actual = userType.getActualTypeArguments();
+ Mapping[] specialization = null;
+ for (int i = 0; i < actual.length; i++) {
+ Mapping m = getMapping(actual[i]);
+ if (m instanceof Identity) continue;
+ if (specialization == null) specialization = new Mapping[actual.length];
+ specialization[i] = m;
+ }
+ return getMapping((Class>) userType.getRawType(), specialization);
+ }
+
+ private Mapping getMapping(Class> userType, Mapping[] specialization) {
+ if (userType.isArray()) {
+ return Array.wrap(getMapping(userType.componentType()));
+ }
+ if (specialization != null && specialization.length == 1 && specialization[0] != null &&
+ userType.equals(Optional.class)) {
+ return CustomOptional.wrap(specialization[0]);
+ }
+ Proxy p = proxyRepository.getProxy(userType, specialization);
+ if (p.supported() != Boolean.FALSE && p.inverse().supported() != Boolean.FALSE &&
+ (p.getFlags() & Proxy.SERVICE) == 0 && (p.inverse().getFlags() & Proxy.SERVICE) == 0) {
+ if (p.getInterface() != null && p.inverse().getInterface() != null) {
+ return new Dynamic2Way(userType, p.inverse().getInterface(), p, p.inverse());
+ } else if (p.getInterface() != null) {
+ if (p.getTarget() != null) return new Extract(userType, p.getTarget(), p);
+ } else if (p.inverse().getInterface() != null) {
+ return new Wrap(userType, p.inverse().getInterface(), p.inverse());
+ } else {
+ return new Identity(userType);
+ }
+ }
+ return new Invalid(userType);
+ }
+
+ private static Query query(Mapping returnMapping, Mapping[] parameterMapping) {
+ Query q = new Query();
+ returnMapping.query(q);
+ for (Mapping p : parameterMapping) p.query(q);
+ return q;
+ }
+ }
+}
diff --git a/src/java.base/share/classes/com/jetbrains/internal/Proxy.java b/src/java.base/share/classes/com/jetbrains/internal/Proxy.java
index 29195c3f5621..0a70b7792a4d 100644
--- a/src/java.base/share/classes/com/jetbrains/internal/Proxy.java
+++ b/src/java.base/share/classes/com/jetbrains/internal/Proxy.java
@@ -26,203 +26,255 @@
package com.jetbrains.internal;
import java.lang.invoke.MethodHandle;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
+import java.lang.invoke.MethodType;
+import java.util.*;
+
+import static java.lang.invoke.MethodHandles.Lookup;
/**
* Proxy is needed to dynamically link JBR API interfaces and implementation at runtime.
- * It implements user-side interfaces and delegates method calls to actual implementation
- * code through {@linkplain java.lang.invoke.MethodHandle method handles}.
+ * Proxy implements or extends a base interface or class and delegates method calls to
+ * target object or static methods. Calls may be delegated even to methods inaccessible by base type.
*
- * There are 3 type of proxy objects:
- *
- * - {@linkplain ProxyInfo.Type#PROXY Proxy} - implements client-side interface from
- * {@code jetbrains.api} and delegates calls to JBR-side target object and optionally static methods.
- * - {@linkplain ProxyInfo.Type#SERVICE Service} - singleton {@linkplain ProxyInfo.Type#PROXY proxy},
- * may delegate calls only to static methods, without target object.
- * - {@linkplain ProxyInfo.Type#CLIENT_PROXY Client proxy} - reverse proxy, implements JBR-side interface
- * and delegates calls to client-side target object by interface defined in {@code jetbrains.api}.
- * May be used to implement callbacks which are created by client and called by JBR.
- *
+ * Mapping between interfaces and implementation code is defined using
+ * {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations.
*
- * Method signatures of proxy interfaces and implementation are validated to ensure that proxy can
+ * When JBR API interface or imeplementation type is used as a method parameter or return type,
+ * JBR API backend performs conversions to ensure each side receives object or correct type,
+ * for example, given following types:
+ *
{@code
+ * interface A {
+ * B foo(B b);
+ * }
+ * interface B {
+ * void bar();
+ * }
+ * class AImpl {
+ * BImpl foo(BImpl b) {}
+ * }
+ * class BImpl {
+ * void bar() {}
+ * }
+ * }
+ * {@code A#foo(B)} will be mapped to {@code AImpl#foo(BImpl)}.
+ * Conversion between {@code B} and {@code BImpl} will be done by JBR API backend.
+ * Here's what generated proxies would look like:
+ * {@code
+ * final class AProxy implements A {
+ * final AImpl target;
+ * AProxy(AImpl t) {
+ * target = t;
+ * }
+ * @Override
+ * B foo(B b) {
+ * BImpl ret = t.foo(b.target);
+ * return new BProxy(ret);
+ * }
+ * }
+ * final class BProxy implements B {
+ * final BImpl target;
+ * BProxy(BImpl t) {
+ * target = t;
+ * }
+ * @Override
+ * void bar() {
+ * t.bar();
+ * }
+ * }
+ * }
+ *
+ * Method signatures of base type and implementation are validated to ensure that proxy can
* properly delegate call to the target implementation code. If there's no implementation found for some
* interface methods, corresponding proxy is considered unsupported. Proxy is also considered unsupported
- * if any proxy used by it is unsupported, more about it at {@link ProxyDependencyManager}.
+ * if any proxy used by it is unsupported.
*
- * Mapping between interfaces and implementation code is defined in
- * {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}.
- * @param interface type for this proxy.
+ * Extensions are an exception to this rule. Methods of base type may be marked as extension methods,
+ * which makes them optional for support checks. Moreover, extension must be explicitly enabled for a
+ * proxy instance to enable usage of corresponding methods.
*/
-class Proxy {
- private final ProxyInfo info;
+class Proxy {
+ /**
+ * @see Proxy.Info#flags
+ */
+ static final int
+ INTERNAL = 1,
+ SERVICE = 2;
+ private final Proxy inverse;
+ private final Class> interFace, target;
+ private final int flags;
private volatile ProxyGenerator generator;
- private volatile Boolean allMethodsImplemented;
-
private volatile Boolean supported;
- private volatile Class> proxyClass;
+ private volatile Set directDependencies = Set.of();
+ private volatile Set> supportedExtensions = Set.of();
+
private volatile MethodHandle constructor;
- private volatile MethodHandle targetExtractor;
- private volatile boolean instanceInitialized;
- private volatile INTERFACE instance;
-
- Proxy(ProxyInfo info) {
- this.info = info;
+ /**
+ * Creates empty proxy.
+ */
+ static Proxy empty(Boolean supported) {
+ return new Proxy(supported);
}
/**
- * @return {@link ProxyInfo} structure of this proxy
+ * Creates a new proxy (and possibly an inverse one) from {@link Info}.
*/
- ProxyInfo getInfo() {
- return info;
+ static Proxy create(ProxyRepository repository,
+ Info info, Mapping[] specialization,
+ Info inverseInfo, Mapping[] inverseSpecialization) {
+ return new Proxy(repository, info, specialization, null, inverseInfo, inverseSpecialization);
}
- private synchronized void initGenerator() {
- if (generator != null) return;
- generator = new ProxyGenerator(info);
- allMethodsImplemented = generator.areAllMethodsImplemented();
+ private Proxy(Boolean supported) {
+ interFace = target = null;
+ flags = 0;
+ inverse = this;
+ this.supported = supported;
}
- /**
- * Checks if implementation is found for all abstract interface methods of this proxy.
- */
- boolean areAllMethodsImplemented() {
- if (allMethodsImplemented != null) return allMethodsImplemented;
- synchronized (this) {
- if (allMethodsImplemented == null) initGenerator();
- return allMethodsImplemented;
+ private Proxy(ProxyRepository repository, Info info, Mapping[] specialization,
+ Proxy inverseProxy, Info inverseInfo, Mapping[] inverseSpecialization) {
+ if (info != null) {
+ interFace = info.interfaceLookup.lookupClass();
+ target = info.targetLookup == null ? null : info.targetLookup.lookupClass();
+ flags = info.flags;
+ generator = new ProxyGenerator(repository, info, specialization);
+ } else {
+ interFace = target = null;
+ flags = 0;
}
+ inverse = inverseProxy == null ? new Proxy(repository, inverseInfo, inverseSpecialization, this, null, null) : inverseProxy;
+ if (inverse.getInterface() != null) directDependencies = Set.of(inverse);
}
/**
- * Checks if all methods are {@linkplain #areAllMethodsImplemented() implemented}
- * for this proxy and all proxies it {@linkplain ProxyDependencyManager uses}.
+ * @return inverse proxy
*/
- boolean isSupported() {
+ Proxy inverse() {
+ return inverse;
+ }
+
+ /**
+ * @return interface class
+ */
+ Class> getInterface() {
+ return interFace;
+ }
+
+ /**
+ * @return target class
+ */
+ Class> getTarget() {
+ return target;
+ }
+
+ /**
+ * @return flags
+ */
+ int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Checks if all methods are implemented for this proxy and all proxies it uses.
+ * {@code null} means not known yet.
+ */
+ Boolean supported() {
+ return supported;
+ }
+
+ private synchronized boolean generate() {
if (supported != null) return supported;
- synchronized (this) {
- if (supported == null) {
- Set> dependencies = ProxyDependencyManager.getProxyDependencies(info.interFace);
- for (Class> d : dependencies) {
- Proxy> p = JBRApi.getProxy(d);
- if (p == null || !p.areAllMethodsImplemented()) {
- supported = false;
- return false;
- }
- }
- supported = true;
+ if (generator == null) return false;
+ Set deps = new HashSet<>(directDependencies);
+ supported = generator.generate();
+ for (Map.Entry e : generator.getDependencies().entrySet()) {
+ if (e.getKey().generate()) deps.add(e.getKey());
+ else if (e.getValue()) {
+ supported = false;
+ break;
}
- return supported;
}
- }
-
- private synchronized void defineClasses() {
- if (constructor != null) return;
- initGenerator();
- generator.defineClasses();
- proxyClass = generator.getProxyClass();
- constructor = generator.findConstructor();
- targetExtractor = generator.findTargetExtractor();
+ if (supported) {
+ directDependencies = deps;
+ supportedExtensions = generator.getSupportedExtensions();
+ } else generator = null; // Release for gc
+ return supported;
}
/**
- * @return generated proxy class
+ * Checks if specified extension is implemented by this proxy.
+ * Implicitly runs bytecode generation.
*/
- Class> getProxyClass() {
- if (proxyClass != null) return proxyClass;
- synchronized (this) {
- if (proxyClass == null) defineClasses();
- return proxyClass;
+ boolean isExtensionSupported(Enum> extension) {
+ if (supported == null) generate();
+ return supportedExtensions.contains(extension);
+ }
+
+ private synchronized boolean define() {
+ if (constructor != null) return true;
+ if (!generate()) return false;
+ constructor = generator.define((flags & SERVICE) != 0);
+ if (constructor == null) {
+ supported = false;
+ return false;
}
+ for (Proxy p : directDependencies) {
+ if (!p.define()) return false;
+ }
+ return true;
+ }
+
+ synchronized boolean init() {
+ if (!define()) return false;
+ if (generator != null) {
+ ProxyGenerator gen = generator;
+ generator = null;
+ gen.init();
+ for (Proxy p : directDependencies) p.init();
+ }
+ return supported;
}
/**
* @return method handle for the constructor of this proxy.
- *
- * - For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.
- * - For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
- * expecting target object to which it would delegate method calls.
- *
+ * First parameter is target object to which it would delegate method calls.
+ * Second parameter is extensions bitfield (if extensions are enabled).
*/
- MethodHandle getConstructor() {
- if (constructor != null) return constructor;
- synchronized (this) {
- if (constructor == null) defineClasses();
- return constructor;
+ MethodHandle getConstructor() throws NullPointerException {
+ if (JBRApi.LOG_DEPRECATED && interFace.isAnnotationPresent(Deprecated.class)) {
+ Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err,
+ "Warning: using deprecated JBR API interface " + interFace.getCanonicalName());
}
+ return constructor;
}
/**
- * @return method handle for that extracts target object of the proxy, or null.
+ * Proxy descriptor with all classes and lookup contexts resolved.
+ * Contains all necessary information to create a {@linkplain Proxy proxy}.
*/
- MethodHandle getTargetExtractor() {
- // targetExtractor may be null, so check constructor instead
- if (constructor != null) return targetExtractor;
- synchronized (this) {
- if (constructor == null) defineClasses();
- return targetExtractor;
- }
- }
+ static class Info {
+ private record StaticMethod(String name, MethodType targetType) {}
- private synchronized void initClass(Set> actualUsages) {
- defineClasses();
- if (generator != null) {
- actualUsages.addAll(generator.getDirectProxyDependencies());
- generator.init();
- generator = null;
- }
- }
- private synchronized void initDependencyGraph() {
- defineClasses();
- if (generator == null) return;
- Set> dependencyClasses = ProxyDependencyManager.getProxyDependencies(info.interFace);
- Set> dependencies = new HashSet<>();
- Set> actualUsages = new HashSet<>();
- for (Class> d : dependencyClasses) {
- Proxy> p = JBRApi.getProxy(d);
- if (p != null) {
- dependencies.add(p);
- p.initClass(actualUsages);
- }
- }
- actualUsages.removeAll(dependencies);
- if (!actualUsages.isEmpty()) {
- // Should never happen, this is a sign of broken dependency search
- throw new RuntimeException("Some proxies are not in dependencies of " + info.interFace.getName() +
- ", but are actually used by it: " +
- actualUsages.stream().map(p -> p.info.interFace.getName()).collect(Collectors.joining(", ")));
- }
- }
+ private final Map staticMethods = new HashMap<>();
+ final Lookup interfaceLookup;
+ final Lookup targetLookup;
+ private final int flags;
- /**
- * @return instance for this {@linkplain ProxyInfo.Type#SERVICE service},
- * returns {@code null} for other proxy types.
- */
- @SuppressWarnings("unchecked")
- INTERFACE getInstance() {
- if (instanceInitialized) return instance;
- if (info.type != ProxyInfo.Type.SERVICE) return null;
- synchronized (this) {
- if (instance == null) {
- initDependencyGraph();
- try {
- instance = (INTERFACE) getConstructor().invoke();
- } catch (JBRApi.ServiceNotAvailableException e) {
- if (JBRApi.VERBOSE) {
- System.err.println("Service not available: " + info.interFace.getName());
- e.printStackTrace();
- }
- } catch (Throwable e) {
- throw new RuntimeException(e);
- } finally {
- instanceInitialized = true;
- }
- }
- return instance;
+ Info(Lookup interfaceLookup, Lookup targetLookup, int flags) {
+ this.interfaceLookup = interfaceLookup;
+ this.targetLookup = targetLookup;
+ this.flags = flags;
+ }
+
+ void addStaticMethod(String name, MethodHandle target) {
+ staticMethods.put(new StaticMethod(name, target.type()), target);
+ }
+
+ MethodHandle getStaticMethod(String name, MethodType targetType) {
+ return staticMethods.get(new StaticMethod(name, targetType));
}
}
}
\ No newline at end of file
diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyDependencyManager.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyDependencyManager.java
deleted file mode 100644
index b4ac4035b28f..000000000000
--- a/src/java.base/share/classes/com/jetbrains/internal/ProxyDependencyManager.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains.internal;
-
-import java.lang.reflect.*;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.function.Consumer;
-
-/**
- * This class collects {@linkplain Proxy proxy} dependencies.
- *
- * Dependencies of a class {@code C} are other classes that are
- * used by {@code C} (i.e. supertypes, classes that appear in method
- * parameters, return types) and all their dependencies. Any class
- * is also considered a dependency of itself.
- *
- * Dependencies allow JBR to validate whole set of interfaces for
- * a particular feature instead of treating them as separate entities.
- *
Example
- * Suppose we implemented some feature and added some API for it:
- * {@code
- * interface SomeFeature {
- * SomeOtherObject createSomeObject(int magicNumber);
- * }
- * interface SomeOtherObject {
- * int getMagicNumber();
- * }
- * }
- * And then used it:
- * {@code
- * if (JBR.isSomeFeatureSupported()) {
- * SomeOtherObject object = JBR.getSomeFeature().createSomeObject(123);
- * int magic = object.getMagicNumber();
- * }
- * }
- * Suppose JBR was able to find implementation for {@code SomeFeature.createSomeObject()},
- * but not for {@code SomeOtherObject.getMagicNumber()}. So {@code JBR.getSomeFeature()}
- * would succeed and return service instance, but {@code createSomeObject()} would fail,
- * because JBR wasn't able to find implementation for {@code SomeOtherObject.getMagicNumber()}
- * and therefore couldn't create proxy for {@code SomeOtherObject} class.
- *
- * To avoid such issues, not only proxy interface itself, but all proxies that are accessible
- * from current proxy interface must have proper implementation.
- */
-class ProxyDependencyManager {
-
- private static final ConcurrentMap, Set>> cache = new ConcurrentHashMap<>();
-
- /**
- * @return all proxy interfaces that are used (directly or indirectly) by given interface, including itself.
- */
- static Set> getProxyDependencies(Class> interFace) {
- Set> dependencies = cache.get(interFace);
- if (dependencies != null) return dependencies;
- step(null, interFace);
- return cache.get(interFace);
- }
-
- /**
- * Collect dependencies for given class and store them into cache.
- */
- private static void step(Node parent, Class> clazz) {
- if (!clazz.getPackageName().startsWith("com.jetbrains") && !JBRApi.isKnownProxyInterface(clazz)) return;
- if (parent != null && parent.findAndMergeCycle(clazz) != null) {
- return;
- }
- Set> cachedDependencies = cache.get(clazz);
- if (cachedDependencies != null) {
- if (parent != null) parent.cycle.dependencies.addAll(cachedDependencies);
- return;
- }
- Node node = new Node(parent, clazz);
- ClassUsagesFinder.visitUsages(clazz, c -> step(node, c));
- Class> correspondingProxyInterface = JBRApi.getProxyInterfaceByTargetName(clazz.getName());
- if (correspondingProxyInterface != null) {
- step(node, correspondingProxyInterface);
- }
- if (parent != null && parent.cycle != node.cycle) {
- parent.cycle.dependencies.addAll(node.cycle.dependencies);
- }
- if (node.cycle.origin.equals(clazz)) {
- // Put collected dependencies into cache only when we exit from the cycle
- // Otherwise cache will contain incomplete data
- for (Class> c : node.cycle.members) {
- cache.put(c, node.cycle.dependencies);
- if (JBRApi.VERBOSE) {
- System.out.println("Found dependencies for " + c.getName() + ": " + node.cycle.dependencies);
- }
- }
- }
- }
-
- /**
- * Graph node, one per visited class
- */
- private static class Node {
- private final Node parent;
- private final Class> clazz;
- private Cycle cycle;
-
- private Node(Node parent, Class> clazz) {
- this.parent = parent;
- this.clazz = clazz;
- cycle = new Cycle(clazz);
- }
-
- /**
- * When classes form dependency cycle, they share all their dependencies.
- * If cycle was found, merge all found dependencies for nodes that form the cycle.
- */
- private Cycle findAndMergeCycle(Class> clazz) {
- if (this.clazz.equals(clazz)) return cycle;
- if (parent == null) return null;
- Cycle c = parent.findAndMergeCycle(clazz);
- if (c != null && c != cycle) {
- c.members.addAll(cycle.members);
- c.dependencies.addAll(cycle.dependencies);
- cycle = c;
- }
- return c;
- }
- }
-
- /**
- * Cycle info. For the sake of elegant code, single node
- * also forms a cycle with itself as a single member and dependency.
- */
- private static class Cycle {
- /**
- * Origin is the first visited class from that cycle.
- */
- private final Class> origin;
- private final Set> members = new HashSet<>();
- private final Set> dependencies = new HashSet<>();
-
- private Cycle(Class> origin) {
- this.origin = origin;
- members.add(origin);
- if (JBRApi.isKnownProxyInterface(origin)) {
- dependencies.add(origin);
- }
- }
- }
-
- /**
- * Utility class that collects direct class usages using reflection
- */
- private static class ClassUsagesFinder {
-
- private static void visitUsages(Class> c, Consumer> action) {
- collect(c.getGenericSuperclass(), action);
- for (java.lang.reflect.Type t : c.getGenericInterfaces()) collect(t, action);
- for (Field f : c.getDeclaredFields()) collect(f.getGenericType(), action);
- for (Method m : c.getDeclaredMethods()) {
- collect(m.getGenericParameterTypes(), action);
- collect(m.getGenericReturnType(), action);
- collect(m.getGenericExceptionTypes(), action);
- }
- }
-
- private static void collect(java.lang.reflect.Type type, Consumer> action) {
- if (type instanceof Class> c) {
- while (c.isArray()) c = Objects.requireNonNull(c.getComponentType());
- if (!c.isPrimitive()) action.accept(c);
- } else if (type instanceof TypeVariable> v) {
- collect(v.getBounds(), action);
- } else if (type instanceof WildcardType w) {
- collect(w.getUpperBounds(), action);
- collect(w.getLowerBounds(), action);
- } else if (type instanceof ParameterizedType p) {
- collect(p.getActualTypeArguments(), action);
- collect(p.getRawType(), action);
- collect(p.getOwnerType(), action);
- } else if (type instanceof GenericArrayType a) {
- collect(a.getGenericComponentType(), action);
- }
- }
-
- private static void collect(java.lang.reflect.Type[] types, Consumer> action) {
- for (java.lang.reflect.Type t : types) collect(t, action);
- }
- }
-}
diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java
index ca0b37e233f0..d5bcfb740a98 100644
--- a/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java
+++ b/src/java.base/share/classes/com/jetbrains/internal/ProxyGenerator.java
@@ -29,457 +29,392 @@ import jdk.internal.org.objectweb.asm.*;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.lang.reflect.*;
import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
+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 java.lang.invoke.MethodHandles.Lookup;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
+import static jdk.internal.org.objectweb.asm.Type.getInternalName;
/**
- * This class generates {@linkplain Proxy proxy} classes.
- * Each proxy is just a generated class implementing some interface and
- * delegating method calls to method handles.
+ * Generates {@linkplain Proxy proxy} classes.
*
- * There are 2 proxy dispatch modes:
- *
- * - interface -> proxy -> {@linkplain #generateBridge bridge} -> method handle -> implementation code
- * - interface -> proxy -> method handle -> implementation code
- *
- * Generated proxy is always located in the same package with its interface and optional bridge is located in the
- * same module with target implementation code. Bridge allows proxy to safely call hidden non-static implementation
- * methods and is only needed for {@code jetbrains.api} -> JBR calls. For JBR -> {@code jetbrains.api} calls, proxy can
- * invoke method handle directly.
+ * Proxy class always implements/extends a base interface/class. It's defined as a
+ * {@linkplain MethodHandles.Lookup#defineHiddenClass(byte[], boolean, Lookup.ClassOption...) hidden class}
+ * sharing the nest with either interface or target implementation class.
+ * Defining proxy as a nestmate of a target class is preferred,
+ * as in this case proxy can call implementation methods directly.
+ * However, this may not be possible if interface is not accessible by target
+ * (thus making it impossible for proxy to implement it), or if there is no target class at all
+ * (e.g. a service may delegate all calls exclusively to static methods).
+ *
+ * Proxy invokes target methods in two ways: either directly, when accessible
+ * ({@code invokestatic}, {@code invokevirtual}, {@code invokeinterface}),
+ * or via {@code invokedynamic} in cases when target method is inaccessible
+ * (see {@link com.jetbrains.exported.JBRApiSupport#bootstrapDynamic(Lookup, String, MethodType)}).
+ * @see Proxy
*/
class ProxyGenerator {
- private static final String OBJECT_DESCRIPTOR = "Ljava/lang/Object;";
- private static final String MH_NAME = "java/lang/invoke/MethodHandle";
- private static final String MH_DESCRIPTOR = "Ljava/lang/invoke/MethodHandle;";
- private static final String CONVERSION_DESCRIPTOR = "(Ljava/lang/Object;)Ljava/lang/Object;";
- /**
- * Print warnings about usage of deprecated interfaces and methods to {@link System#err}.
- */
- private static final boolean LOG_DEPRECATED = System.getProperty("jetbrains.api.logDeprecated", String.valueOf(JBRApi.VERBOSE)).equalsIgnoreCase("true");
- private static final boolean VERIFY_BYTECODE = Boolean.getBoolean("jetbrains.api.verifyBytecode");
+ private static final String PROXY_INTERFACE_NAME = getInternalName(com.jetbrains.exported.JBRApiSupport.Proxy.class);
- private static final AtomicInteger nameCounter = new AtomicInteger();
+ private final Proxy.Info info;
+ private final Class> interFace;
+ private final Lookup proxyGenLookup;
+ private final Mapping[] specialization;
+ private final Mapping.Context mappingContext;
+ private final AccessContext accessContext;
- private final ProxyInfo info;
- private final boolean generateBridge;
- private final String proxyName, bridgeName;
- private final ClassWriter originalProxyWriter, originalBridgeWriter;
- private final ClassVisitor proxyWriter, bridgeWriter;
- private final List> handles = new ArrayList<>();
- private final List>> classReferences = new ArrayList<>();
- private final Set> directProxyDependencies = new HashSet<>();
- private final List exceptions = new ArrayList<>();
- private int bridgeMethodCounter;
- private boolean allMethodsImplemented = true;
- private Lookup generatedHandlesHolder, generatedProxy;
+ private final Class> constructorTargetParameterType;
+ private final String targetDescriptor, proxyName, superclassName;
+ private final String[] superinterfaceNames;
+ private final ClassWriter originalProxyWriter;
+ private final ClassVisitor proxyWriter;
+
+ private final Map, Boolean> supportedExtensions = new HashMap<>();
+
+ private boolean supported = true;
+ private Lookup generatedProxy;
/**
- * Creates new proxy generator from given {@link ProxyInfo},
- * looks for abstract interface methods, corresponding implementation methods
- * and generates proxy bytecode. However, it doesn't actually load generated
- * classes until {@link #defineClasses()} is called.
+ * Creates new proxy generator from given {@link Proxy.Info},
*/
- ProxyGenerator(ProxyInfo info) {
- if (JBRApi.VERBOSE) {
- System.out.println("Generating proxy " + info.interFace.getName());
- }
+ ProxyGenerator(ProxyRepository proxyRepository, Proxy.Info info, Mapping[] specialization) {
this.info = info;
- generateBridge = info.type.isPublicApi();
- int nameId = nameCounter.getAndIncrement();
- proxyName = Type.getInternalName(info.interFace) + "$$JBRApiProxy$" + nameId;
- bridgeName = generateBridge ? info.apiModule.lookupClass().getPackageName().replace('.', '/') + "/" +
- info.interFace.getSimpleName() + "$$JBRApiBridge$" + nameId : null;
+ this.interFace = info.interfaceLookup.lookupClass();
+ this.specialization = specialization;
- originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
- proxyWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter;
- originalBridgeWriter = generateBridge ? new ClassWriter(ClassWriter.COMPUTE_FRAMES) : null;
- if (generateBridge) {
- bridgeWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalBridgeWriter, true) : originalBridgeWriter;
- } else bridgeWriter = new ClassVisitor(Opcodes.ASM9) { // Empty visitor
+ // Placing our proxy implementation into the target's nest is preferred, as it gives us direct method calls.
+ this.proxyGenLookup = info.targetLookup != null && AccessContext.canAccess(info.targetLookup, interFace) ?
+ info.targetLookup : info.interfaceLookup;
+
+ this.mappingContext = new Mapping.Context(proxyRepository);
+ this.accessContext = new AccessContext(proxyGenLookup);
+
+ constructorTargetParameterType = info.targetLookup == null ? null :
+ accessContext.canAccess(info.targetLookup.lookupClass()) ? info.targetLookup.lookupClass() : Object.class;
+ targetDescriptor = constructorTargetParameterType == null ? "" : constructorTargetParameterType.descriptorString();
+
+ // Even though generated proxy is hidden and therefore has no qualified name,
+ // it can reference itself via internal name, which can lead to name collisions.
+ // Let's consider specialized proxy for java/util/List - if we name proxy similarly,
+ // methods calls to java/util/List will be treated by VM as calls to proxy class,
+ // not standard library interface. Therefore we append $$$ to proxy name to avoid name collision.
+ proxyName = proxyGenLookup.lookupClass().getPackageName().replace('.', '/') + "/" + interFace.getSimpleName() + "$$$";
+
+ originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES) {
@Override
- public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
- return new MethodVisitor(api) {};
+ protected ClassLoader getClassLoader() {
+ return ProxyGenerator.this.proxyGenLookup.lookupClass().getClassLoader();
}
};
- proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null,
- "java/lang/Object", new String[] {Type.getInternalName(info.interFace)});
- bridgeWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC | ACC_PUBLIC, bridgeName, null,
- "java/lang/Object", null);
- generateConstructor();
- generateMethods();
- proxyWriter.visitEnd();
- bridgeWriter.visitEnd();
- }
-
- boolean areAllMethodsImplemented() {
- return allMethodsImplemented;
- }
-
- Set> getDirectProxyDependencies() {
- return directProxyDependencies;
+ proxyWriter = JBRApi.VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter;
+ if (interFace.isInterface()) {
+ superclassName = "java/lang/Object";
+ superinterfaceNames = new String[] {getInternalName(interFace), PROXY_INTERFACE_NAME};
+ } else {
+ superclassName = getInternalName(interFace);
+ superinterfaceNames = new String[] {PROXY_INTERFACE_NAME};
+ }
}
/**
- * Insert all method handles and class references into static fields, so that proxy can call implementation methods.
+ * Direct (non-transitive) only. These are the proxies accessed by current one.
+ * True for required and false for optional.
+ */
+ Map getDependencies() {
+ return accessContext.dependencies;
+ }
+
+ Set> getSupportedExtensions() {
+ return supportedExtensions.entrySet().stream()
+ .filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
+ }
+
+ /**
+ * @return service target instance, if any
+ */
+ private Object createServiceTarget() throws Throwable {
+ Exception exception = null;
+ MethodHandle constructor = null;
+ try {
+ constructor = info.targetLookup.findStatic(
+ info.targetLookup.lookupClass(), "create", MethodType.methodType(info.targetLookup.lookupClass()));
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ exception = e;
+ }
+ try {
+ if (constructor == null) constructor = info.targetLookup.findConstructor(
+ info.targetLookup.lookupClass(), MethodType.methodType(void.class));
+ } catch (NoSuchMethodException | IllegalAccessException e) {
+ if (exception == null) exception = e;
+ else exception.addSuppressed(e);
+ }
+ if (constructor == null) throw new ServiceNotAvailableException("No service factory method or constructor found", exception);
+ return constructor.invoke();
+ }
+
+ /**
+ * First parameter is target object to which it would delegate method calls (if target exists).
+ * Second parameter is extensions bitfield (if extensions are enabled).
+ * @return method handle to constructor of generated proxy class, or null.
+ */
+ private MethodHandle findConstructor() throws NoSuchMethodException, IllegalAccessException {
+ MethodType constructorType = MethodType.methodType(void.class);
+ if (info.targetLookup != null) constructorType = constructorType.appendParameterTypes(constructorTargetParameterType);
+ if (EXTENSIONS_ENABLED) constructorType = constructorType.appendParameterTypes(long[].class);
+ return generatedProxy.findConstructor(generatedProxy.lookupClass(), constructorType);
+ }
+
+ /**
+ * Initialize method handles used by generated class via {@code invokedynamic}.
*/
void init() {
- try {
- for (int i = 0; i < handles.size(); i++) {
- generatedHandlesHolder
- .findStaticVarHandle(generatedHandlesHolder.lookupClass(), "h" + i, MethodHandle.class)
- .set(handles.get(i).get());
- }
- for (int i = 0; i < classReferences.size(); i++) {
- generatedHandlesHolder
- .findStaticVarHandle(generatedHandlesHolder.lookupClass(), "c" + i, Class.class)
- .set(classReferences.get(i).get());
- }
- } catch (NoSuchFieldException | IllegalAccessException e) {
- throw new RuntimeException(e);
+ 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());
}
}
- Class> getProxyClass() {
- return generatedProxy.lookupClass();
- }
-
/**
- * @return method handle to constructor of generated proxy class.
- *
- * - For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.
- * - For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
- * expecting target object to which it would delegate method calls.
- *
+ * Define generated class.
+ * @return method handle to constructor of generated proxy class, or null.
*/
- MethodHandle findConstructor() {
+ MethodHandle define(boolean service) {
try {
- if (info.target == null) {
- return generatedProxy.findConstructor(generatedProxy.lookupClass(), MethodType.methodType(void.class));
- } else {
- MethodHandle c = generatedProxy.findConstructor(generatedProxy.lookupClass(),
- MethodType.methodType(void.class, Object.class));
- if (info.type.isService()) {
- try {
- return MethodHandles.foldArguments(c, info.target.findConstructor(info.target.lookupClass(),
- MethodType.methodType(void.class)).asType(MethodType.methodType(Object.class)));
- } catch (NoSuchMethodException | IllegalAccessException e) {
- throw new RuntimeException("Service implementation must have no-args constructor: " +
- info.target.lookupClass(), e);
- }
- }
- return c;
- }
- } catch (IllegalAccessException | NoSuchMethodException e) {
+ Object sericeTarget = service && info.targetLookup != null ? createServiceTarget() : null;
+ generatedProxy = proxyGenLookup.defineHiddenClass(
+ originalProxyWriter.toByteArray(), false, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
+ MethodHandle constructor = findConstructor();
+ if (sericeTarget != null) constructor = MethodHandles.insertArguments(constructor, 0, sericeTarget);
+ return constructor;
+ } catch (ServiceNotAvailableException e) {
+ if (JBRApi.VERBOSE) e.printStackTrace(System.err);
+ return null;
+ } catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
- * @return method handle that receives proxy and returns its target, or null
+ * Generate bytecode for class.
+ * @return true if generated proxy is considered supported
*/
- MethodHandle findTargetExtractor() {
- if (info.target == null) return null;
- try {
- return generatedProxy.findGetter(generatedProxy.lookupClass(), "target", Object.class);
- } catch (NoSuchFieldException | IllegalAccessException e) {
- throw new RuntimeException(e);
+ boolean generate() {
+ if (!supported) return false;
+ proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null,
+ superclassName, superinterfaceNames);
+ if (JBRApi.VERBOSE) {
+ synchronized (System.out) {
+ System.out.print("Generating proxy " + interFace.getName());
+ if (specialization != null) System.out.print(" <" +
+ Stream.of(specialization).map(t -> t == null ? "?" : t.toString()).collect(Collectors.joining(", ")) + ">");
+ System.out.println();
+ }
}
+ if (specialization != null) {
+ mappingContext.initTypeParameters(interFace, Stream.of(specialization));
+ }
+ mappingContext.initTypeParameters(interFace);
+ generateFields();
+ generateConstructor();
+ generateTargetGetter();
+ generateMethods(interFace);
+ if (interFace.isInterface()) generateMethods(Object.class);
+ proxyWriter.visitEnd();
+ return supported;
}
- /**
- * Loads generated classes.
- */
- void defineClasses() {
- try {
- Lookup bridge = !generateBridge ? null : MethodHandles.privateLookupIn(
- info.apiModule.defineClass(originalBridgeWriter.toByteArray()), info.apiModule);
- generatedProxy = info.interFaceLookup.defineHiddenClass(
- originalProxyWriter.toByteArray(), true, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
- generatedHandlesHolder = generateBridge ? bridge : generatedProxy;
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
+ private void generateFields() {
+ if (info.targetLookup != null) {
+ proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", targetDescriptor, null, null);
+ }
+ if (EXTENSIONS_ENABLED) {
+ proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "extensions", "[J", null, null);
}
}
private void generateConstructor() {
- if (info.target != null) {
- proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", OBJECT_DESCRIPTOR, null, null);
+ MethodVisitor m = proxyWriter.visitMethod(ACC_PRIVATE, "", "(" + targetDescriptor +
+ (EXTENSIONS_ENABLED ? "[J" : "") + ")V", null, null);
+ m.visitCode();
+ m.visitVarInsn(ALOAD, 0);
+ if (info.targetLookup != null) {
+ m.visitInsn(DUP);
+ m.visitVarInsn(ALOAD, 1);
+ m.visitFieldInsn(PUTFIELD, proxyName, "target", targetDescriptor);
}
- MethodVisitor p = proxyWriter.visitMethod(ACC_PRIVATE, "", "(" +
- (info.target == null ? "" : OBJECT_DESCRIPTOR) + ")V", null, null);
- if (LOG_DEPRECATED && info.interFace.isAnnotationPresent(Deprecated.class)) {
- logDeprecated(p, "Warning: using deprecated JBR API interface " + info.interFace.getName());
+ if (EXTENSIONS_ENABLED) {
+ m.visitInsn(DUP);
+ m.visitVarInsn(ALOAD, info.targetLookup != null ? 2 : 1);
+ m.visitFieldInsn(PUTFIELD, proxyName, "extensions", "[J");
}
- p.visitCode();
- p.visitVarInsn(ALOAD, 0);
- if (info.target != null) {
- p.visitInsn(DUP);
- p.visitVarInsn(ALOAD, 1);
- p.visitFieldInsn(PUTFIELD, proxyName, "target", OBJECT_DESCRIPTOR);
- }
- p.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
- p.visitInsn(RETURN);
- p.visitMaxs(0, 0);
- p.visitEnd();
+ m.visitMethodInsn(INVOKESPECIAL, superclassName, "", "()V", false);
+ m.visitInsn(RETURN);
+ m.visitMaxs(0, 0);
+ m.visitEnd();
}
- private void generateMethods() {
- for (Method method : info.interFace.getMethods()) {
+ private void generateTargetGetter() {
+ MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, "$getProxyTarget", "()" +
+ Object.class.descriptorString(), null, null);
+ m.visitCode();
+ if (info.targetLookup != null) {
+ m.visitVarInsn(ALOAD, 0);
+ m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor);
+ } else m.visitInsn(ACONST_NULL);
+ m.visitInsn(ARETURN);
+ m.visitMaxs(0, 0);
+ m.visitEnd();
+ }
+
+ private void generateMethods(Class> interFace) {
+ 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 (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);
+ }
+
+ // Skip if possible.
if (!Modifier.isAbstract(mod)) continue;
- MethodMapping methodMapping = getTargetMethodMapping(method);
- Exception e1 = null;
- if (info.target != null) {
- try {
- MethodHandle handle = info.target.findVirtual(
- info.target.lookupClass(), method.getName(), methodMapping.type());
- generateMethod(method, handle, methodMapping, true);
- continue;
- } catch (NoSuchMethodException | IllegalAccessException e) {
- e1 = e;
- }
- }
-
- Exception e2 = null;
- ProxyInfo.StaticMethodMapping mapping = info.staticMethods.get(method.getName());
- if (mapping != null) {
- try {
- MethodHandle staticHandle =
- mapping.lookup().findStatic(mapping.lookup().lookupClass(), mapping.methodName(), methodMapping.type());
- generateMethod(method, staticHandle, methodMapping, false);
- continue;
- } catch (NoSuchMethodException | IllegalAccessException e) {
- e2 = e;
- }
- }
-
- if (e1 != null) exceptions.add(e1);
- if (e2 != null) exceptions.add(e2);
+ // Generate unsupported stub.
generateUnsupportedMethod(proxyWriter, method);
if (JBRApi.VERBOSE) {
- System.err.println("Couldn't generate method " + method.getName());
- if (e1 != null) e1.printStackTrace();
- if (e2 != null) e2.printStackTrace();
+ synchronized (System.err) {
+ System.err.println("Couldn't generate method " + method.getName());
+ if (exception != null) exception.printStackTrace(System.err);
+ }
}
- allMethodsImplemented = false;
+ if (extension == null) supported = false;
+ else supportedExtensions.put(extension, false);
}
}
- private void generateMethod(Method interfaceMethod, MethodHandle handle, MethodMapping mapping, boolean passInstance) {
+ private void generateMethod(Method interfaceMethod, MethodHandle handle, Mapping.Method mapping, Enum> extension, boolean passInstance) {
+ boolean passExtensions = mapping.query().needsExtensions;
InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod);
- String bridgeMethodDescriptor = mapping.getBridgeDescriptor(passInstance);
+ MethodHandleInfo directCall = accessContext.resolveDirect(handle);
+ Supplier futureHandle = () -> handle;
- ClassVisitor handleWriter = generateBridge ? bridgeWriter : proxyWriter;
- String bridgeOrProxyName = generateBridge ? bridgeName : proxyName;
- String handleName = addHandle(handleWriter, () -> handle);
- for (TypeMapping m : mapping) {
- if (m.conversion() == TypeConversion.EXTRACT_TARGET ||
- m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
- Proxy> from = m.fromProxy();
- m.metadata.extractTargetHandle = addHandle(handleWriter, from::getTargetExtractor);
- directProxyDependencies.add(from);
- }
- if (m.conversion() == TypeConversion.WRAP_INTO_PROXY ||
- m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
- Proxy> to = m.toProxy();
- m.metadata.proxyConstructorHandle = addHandle(handleWriter, to::getConstructor);
- directProxyDependencies.add(to);
- }
- if (m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
- String classField = "c" + classReferences.size();
- m.metadata.extractableClassField = classField;
- classReferences.add(m.fromProxy()::getProxyClass);
- handleWriter.visitField(ACC_PRIVATE | ACC_STATIC, classField, "Ljava/lang/Class;", null, null);
- }
- }
- String bridgeMethodName = methodInfo.name() + "$bridge$" + bridgeMethodCounter;
- bridgeMethodCounter++;
-
- MethodVisitor p = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
- methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
- MethodVisitor b = bridgeWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, bridgeMethodName,
- bridgeMethodDescriptor, null, null);
- if (LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) {
- logDeprecated(p, "Warning: using deprecated JBR API method " +
- interfaceMethod.getDeclaringClass().getName() + "#" + interfaceMethod.getName());
- }
- p.visitCode();
- b.visitCode();
- MethodVisitor bp = generateBridge ? b : p;
- bp.visitFieldInsn(GETSTATIC, bridgeOrProxyName, handleName, MH_DESCRIPTOR);
- if (passInstance) {
- p.visitVarInsn(ALOAD, 0);
- p.visitFieldInsn(GETFIELD, proxyName, "target", OBJECT_DESCRIPTOR);
- b.visitVarInsn(ALOAD, 0);
- }
- int lvIndex = 1;
- for (TypeMapping param : mapping.parameterMapping) {
- int opcode = getLoadOpcode(param.from());
- p.visitVarInsn(opcode, lvIndex);
- b.visitVarInsn(opcode, lvIndex - (passInstance ? 0 : 1));
- lvIndex += getParameterSize(param.from());
- convertValue(bp, bridgeOrProxyName, param);
- }
- if (generateBridge) {
- p.visitMethodInsn(INVOKESTATIC, bridgeName, bridgeMethodName, bridgeMethodDescriptor, false);
- }
- bp.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", bridgeMethodDescriptor, false);
- convertValue(bp, bridgeOrProxyName, mapping.returnMapping());
- int returnOpcode = getReturnOpcode(mapping.returnMapping().to());
- p.visitInsn(returnOpcode);
- b.visitInsn(returnOpcode);
- p.visitMaxs(0, 0);
- b.visitMaxs(0, 0);
- p.visitEnd();
- b.visitEnd();
- }
-
- private String addHandle(ClassVisitor classWriter, Supplier handleSupplier) {
- String handleName = "h" + handles.size();
- handles.add(handleSupplier);
- classWriter.visitField(ACC_PRIVATE | ACC_STATIC, handleName, MH_DESCRIPTOR, null, null);
- return handleName;
- }
-
- private static void convertValue(MethodVisitor m, String handlesHolderName, TypeMapping mapping) {
- if (mapping.conversion() == TypeConversion.IDENTITY) return;
- Label skipConvert = new Label();
- m.visitInsn(DUP);
- m.visitJumpInsn(IFNULL, skipConvert);
- switch (mapping.conversion()) {
- case EXTRACT_TARGET ->
- m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR);
- case WRAP_INTO_PROXY ->
- m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR);
- case DYNAMIC_2_WAY -> {
- m.visitInsn(DUP);
- m.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
- m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractableClassField, "Ljava/lang/Class;");
- Label elseBranch = new Label(), afterBranch = new Label();
- m.visitJumpInsn(IF_ACMPNE, elseBranch);
- m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR);
- m.visitJumpInsn(GOTO, afterBranch);
- m.visitLabel(elseBranch);
- m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR);
- m.visitLabel(afterBranch);
- }
- }
- m.visitInsn(SWAP);
- m.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", CONVERSION_DESCRIPTOR, false);
- m.visitLabel(skipConvert);
- }
-
- private static MethodMapping getTargetMethodMapping(Method interfaceMethod) {
- Class>[] params = interfaceMethod.getParameterTypes();
- TypeMapping[] paramMappings = new TypeMapping[params.length];
- for (int i = 0; i < params.length; i++) {
- paramMappings[i] = getTargetTypeMapping(params[i]);
- params[i] = paramMappings[i].to();
- }
- TypeMapping returnMapping = getTargetTypeMapping(interfaceMethod.getReturnType()).inverse();
- return new MethodMapping(MethodType.methodType(returnMapping.from(), params), returnMapping, paramMappings);
- }
-
- private static TypeMapping getTargetTypeMapping(Class userType) {
- TypeMappingMetadata m = new TypeMappingMetadata();
- Proxy p = JBRApi.getProxy(userType);
- if (p != null && p.getInfo().target != null) {
- Proxy> r = JBRApi.getProxy(p.getInfo().target.lookupClass());
- if (r != null && r.getInfo().target != null) {
- return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.DYNAMIC_2_WAY, p, r, m);
- }
- return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.EXTRACT_TARGET, p, null, m);
- }
- Class> interFace = JBRApi.getProxyInterfaceByTargetName(userType.getName());
- if (interFace != null) {
- Proxy> r = JBRApi.getProxy(interFace);
- if (r != null) {
- return new TypeMapping(userType, interFace, TypeConversion.WRAP_INTO_PROXY, null, r, m);
- }
- }
- return new TypeMapping(userType, userType, TypeConversion.IDENTITY, null, null, m);
- }
-
- private record MethodMapping(MethodType type, TypeMapping returnMapping, TypeMapping[] parameterMapping)
- implements Iterable {
- @Override
- public Iterator iterator() {
- return new Iterator<>() {
- private int index = -1;
- @Override
- public boolean hasNext() {
- return index < parameterMapping.length;
- }
- @Override
- public TypeMapping next() {
- TypeMapping m = index == -1 ? returnMapping : parameterMapping[index];
- index++;
- return m;
- }
+ // Check usage of deprecated API.
+ if (JBRApi.LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) {
+ directCall = null; // Force invokedynamic.
+ futureHandle = () -> { // Log warning when binding the call site.
+ Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err, "Warning: using deprecated JBR API method " +
+ interfaceMethod.getDeclaringClass().getCanonicalName() + "." + interfaceMethod.getName());
+ return handle;
};
}
- /**
- * Every convertable parameter type is replaced with {@link Object} for bridge descriptor.
- * Optional {@link Object} is added as first parameter for instance methods.
- */
- String getBridgeDescriptor(boolean passInstance) {
- StringBuilder bd = new StringBuilder("(");
- if (passInstance) bd.append(OBJECT_DESCRIPTOR);
- for (TypeMapping m : parameterMapping) {
- bd.append(m.getBridgeDescriptor());
+ if (JBRApi.VERBOSE) {
+ System.out.println(" " +
+ mapping.returnMapping() + " " +
+ interfaceMethod.getName() + "(" +
+ Stream.of(mapping.parameterMapping()).map(Mapping::toString).collect(Collectors.joining(", ")) +
+ ")" + (directCall != null ? " (direct)" : "")
+ );
+ }
+
+ MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
+ methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
+ AccessContext.Method methodContext = accessContext.new Method(m, extension == null);
+ m.visitCode();
+ // Load `this`.
+ if (passInstance || passExtensions || extension != null) {
+ m.visitVarInsn(ALOAD, 0);
+ if (passInstance && (passExtensions || extension != null)) m.visitInsn(DUP);
+ }
+ // Check enabled extension.
+ if (passExtensions || extension != null) {
+ m.visitFieldInsn(GETFIELD, proxyName, "extensions", "[J");
+ if (passExtensions && extension != null) m.visitInsn(DUP);
+ if (passExtensions) {
+ // If this method converts any parameters, we need to store extensions for later use.
+ // We overwrite `this` slot, but we have it on stack, so it's ok.
+ m.visitVarInsn(ASTORE, 0);
+ }
+ if (extension != null) {
+ // Check the specific bit inside long[].
+ Label afterExtensionCheck = new Label();
+ m.visitIntInsn(extension.ordinal() < 8192 ? BIPUSH : SIPUSH, extension.ordinal() / 64);
+ m.visitInsn(LALOAD);
+ m.visitLdcInsn(1L << (extension.ordinal() % 64));
+ m.visitInsn(LAND);
+ m.visitInsn(LCONST_0);
+ m.visitInsn(LCMP);
+ m.visitJumpInsn(IFNE, afterExtensionCheck);
+ throwException(m, "java/lang/UnsupportedOperationException",
+ interFace.getCanonicalName() + '.' + interfaceMethod.getName() +
+ " - extension " + extension.name() + " is disabled");
+ m.visitLabel(afterExtensionCheck);
}
- bd.append(')');
- bd.append(returnMapping.getBridgeDescriptor());
- return bd.toString();
}
- }
-
- private record TypeMapping(Class> from, Class> to, TypeConversion conversion,
- Proxy> fromProxy, Proxy> toProxy, TypeMappingMetadata metadata) {
- TypeMapping inverse() {
- return new TypeMapping(to, from, switch (conversion) {
- case EXTRACT_TARGET -> TypeConversion.WRAP_INTO_PROXY;
- case WRAP_INTO_PROXY -> TypeConversion.EXTRACT_TARGET;
- default -> conversion;
- }, toProxy, fromProxy, metadata);
+ // Extract target from `this`.
+ if (passInstance) {
+ // We already have `this` on stack.
+ m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor);
}
- String getBridgeDescriptor() {
- if (conversion == TypeConversion.IDENTITY) return Type.getDescriptor(from);
- else return "Ljava/lang/Object;";
+ // Load and convert parameters.
+ for (int lvIndex = 1, i = 0; i < mapping.parameterMapping().length; i++) {
+ Mapping param = mapping.parameterMapping()[i];
+ int opcode = getLoadOpcode(param.from);
+ m.visitVarInsn(opcode, lvIndex);
+ lvIndex += getParameterSize(param.from);
+ param.convert(methodContext);
}
- }
-
- private static class TypeMappingMetadata {
- private String extractTargetHandle, proxyConstructorHandle, extractableClassField;
- }
-
- private enum TypeConversion {
- /**
- * No conversion.
- */
- IDENTITY,
- /**
- * Take a proxy object and extract its target implementation object.
- */
- EXTRACT_TARGET,
- /**
- * Create new proxy targeting given implementation object.
- */
- WRAP_INTO_PROXY,
- /**
- * Decide between {@link #EXTRACT_TARGET} and {@link #WRAP_INTO_PROXY} at runtime, depending on actual object.
- */
- DYNAMIC_2_WAY
+ // Invoke target method.
+ if (directCall != null) methodContext.invokeDirect(directCall);
+ else methodContext.invokeDynamic(handle.type(), futureHandle);
+ // Convert return value.
+ mapping.returnMapping().convert(methodContext);
+ int opcode = getReturnOpcode(mapping.returnMapping().to);
+ m.visitInsn(opcode);
+ m.visitMaxs(0, 0);
+ m.visitEnd();
}
}
diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyInfo.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyInfo.java
deleted file mode 100644
index 649747496da7..000000000000
--- a/src/java.base/share/classes/com/jetbrains/internal/ProxyInfo.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains.internal;
-
-import java.lang.invoke.MethodHandles;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Stream;
-
-import static java.lang.invoke.MethodHandles.Lookup;
-
-/**
- * Proxy info, like {@link RegisteredProxyInfo}, but with all classes and lookup contexts resolved.
- * Contains all necessary information to create a {@linkplain Proxy proxy}.
- */
-class ProxyInfo {
-
- final Lookup apiModule;
- final Type type;
- final Lookup interFaceLookup;
- final Class> interFace;
- final Lookup target;
- final Map staticMethods = new HashMap<>();
-
- private ProxyInfo(RegisteredProxyInfo i) {
- this.apiModule = i.apiModule();
- type = i.type();
- interFaceLookup = lookup(getInterfaceLookup(), i.interfaceName());
- interFace = interFaceLookup == null ? null : interFaceLookup.lookupClass();
- target = Stream.of(i.targets())
- .map(t -> lookup(getTargetLookup(), t))
- .filter(Objects::nonNull).findFirst().orElse(null);
- for (RegisteredProxyInfo.StaticMethodMapping m : i.staticMethods()) {
- Stream.of(m.classes())
- .map(t -> lookup(getTargetLookup(), t))
- .filter(Objects::nonNull).findFirst()
- .ifPresent(l -> staticMethods.put(m.interfaceMethodName(), new StaticMethodMapping(l, m.methodName())));
- }
- }
-
- /**
- * Resolve all classes and lookups for given {@link RegisteredProxyInfo}.
- */
- static ProxyInfo resolve(RegisteredProxyInfo i) {
- ProxyInfo info = new ProxyInfo(i);
- if (info.interFace == null || (info.target == null && info.staticMethods.isEmpty())) return null;
- if (!info.interFace.isInterface()) {
- if (info.type == Type.CLIENT_PROXY) {
- throw new RuntimeException("Tried to create client proxy for non-interface: " + info.interFace);
- } else {
- return null;
- }
- }
- return info;
- }
-
- Lookup getInterfaceLookup() {
- return type == Type.CLIENT_PROXY || type == Type.INTERNAL_SERVICE ? apiModule : JBRApi.outerLookup;
- }
-
- Lookup getTargetLookup() {
- return type == Type.CLIENT_PROXY ? JBRApi.outerLookup : apiModule;
- }
-
- private Lookup lookup(Lookup lookup, String clazz) {
- String[] nestedClasses = clazz.split("\\$");
- clazz = "";
- for (int i = 0; i < nestedClasses.length; i++) {
- try {
- if (i != 0) clazz += "$";
- clazz += nestedClasses[i];
- lookup = MethodHandles.privateLookupIn(Class.forName(clazz, false, lookup.lookupClass().getClassLoader()), lookup);
- } catch (ClassNotFoundException | IllegalAccessException ignore) {
- return null;
- }
- }
- return lookup;
- }
-
- record StaticMethodMapping(Lookup lookup, String methodName) {}
-
- /**
- * Proxy type, see {@link Proxy}
- */
- enum Type {
- PROXY,
- SERVICE,
- CLIENT_PROXY,
- INTERNAL_SERVICE;
-
- public boolean isPublicApi() {
- return this == PROXY || this == SERVICE;
- }
-
- public boolean isService() {
- return this == SERVICE || this == INTERNAL_SERVICE;
- }
- }
-}
diff --git a/src/java.base/share/classes/com/jetbrains/internal/ProxyRepository.java b/src/java.base/share/classes/com/jetbrains/internal/ProxyRepository.java
new file mode 100644
index 000000000000..c59744da2744
--- /dev/null
+++ b/src/java.base/share/classes/com/jetbrains/internal/ProxyRepository.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2023 JetBrains s.r.o.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.jetbrains.internal;
+
+import jdk.internal.access.SharedSecrets;
+import jdk.internal.loader.BootLoader;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.annotation.Annotation;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.stream.Stream;
+
+import static java.lang.invoke.MethodHandles.Lookup;
+
+/**
+ * Proxy repository keeps track of all generated proxies.
+ * @see ProxyRepository#getProxy(Class, Mapping[])
+ */
+class ProxyRepository {
+ private static final Proxy NONE = Proxy.empty(null), INVALID = Proxy.empty(false);
+
+ private final Registry registry = new Registry();
+ private final Map proxies = new HashMap<>();
+
+ 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);
+ }
+
+ String getVersion() {
+ return registry.version;
+ }
+
+ synchronized Proxy getProxy(Class> clazz, Mapping[] specialization) {
+ 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;
+ inverseKey = infoByTarget != null && infoByTarget.interfaceLookup != null ?
+ new Key(infoByTarget.interfaceLookup.lookupClass(), inverseSpecialization) : null;
+ if ((infoByInterface == null && infoByTarget == null) ||
+ infoByInterface == Registry.Entry.INVALID ||
+ infoByTarget == Registry.Entry.INVALID) {
+ p = INVALID;
+ } else {
+ p = Proxy.create(this, infoByInterface, specialization, infoByTarget, inverseSpecialization);
+ }
+ } else if (!Arrays.equals(specialization, inverseSpecialization)) { // Try implicit proxy
+ inverseKey = new Key(clazz, inverseSpecialization);
+ Lookup lookup = SharedSecrets.getJavaLangInvokeAccess().lookupIn(clazz);
+ Proxy.Info info = new Proxy.Info(lookup, lookup, 0);
+ p = Proxy.create(this, info, specialization, info, inverseSpecialization);
+ } else p = NONE;
+ proxies.put(key, p);
+ if (inverseKey != null) proxies.put(inverseKey, p.inverse());
+ }
+ return p;
+ }
+
+ private record Key(Class> clazz, Mapping[] specialization) {
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Key key = (Key) o;
+ if (!clazz.equals(key.clazz)) return false;
+ return Arrays.equals(specialization, key.specialization);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = clazz.hashCode();
+ result = 31 * result + Arrays.hashCode(specialization);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return clazz.getName() + "<" + Arrays.toString(specialization) + ">";
+ }
+ }
+
+ /**
+ * 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}.
+ */
+ private static class Registry {
+
+ private record StaticKey(String methodName, String targetMethodDescriptor) {}
+ private record StaticValue(String targetType, String targetMethodName) {}
+
+ private class Entry {
+ private static final Proxy.Info INVALID = new Proxy.Info(null, null, 0);
+
+ private final Map staticMethods = new HashMap<>();
+ private String type, target;
+ private Entry inverse;
+ private int flags;
+
+ private Entry(String type) { this.type = type; }
+
+ private Proxy.Info resolve() {
+ if (type == null) return null;
+ Lookup l, t;
+ try {
+ l = resolveType(type, classLoader);
+ t = target != null ? resolveType(target, classLoader) : null;
+ } catch (ClassNotFoundException e) {
+ if (JBRApi.VERBOSE) {
+ System.err.println(type + " not eligible");
+ e.printStackTrace(System.err);
+ }
+ return INVALID;
+ }
+ if (l.lookupClass().isAnnotation() || l.lookupClass().isEnum() || l.lookupClass().isRecord()) {
+ if (JBRApi.VERBOSE) {
+ System.err.println(type + " not eligible: not a class or interface");
+ }
+ return INVALID;
+ }
+ if (Modifier.isFinal(l.lookupClass().getModifiers()) || l.lookupClass().isSealed()) {
+ if (JBRApi.VERBOSE) {
+ System.err.println(type + " not eligible: invalid type (final/sealed)");
+ }
+ return INVALID;
+ }
+ if (target == null) flags |= Proxy.SERVICE;
+ if (needsAnnotation(l.lookupClass())) {
+ if (!hasAnnotation(l.lookupClass(), providedAnnotation)) {
+ if (JBRApi.VERBOSE) {
+ System.err.println(type + " not eligible: no @Provided annotation");
+ }
+ return INVALID;
+ }
+ if (!hasAnnotation(l.lookupClass(), serviceAnnotation)) flags &= ~Proxy.SERVICE;
+ }
+ Proxy.Info info;
+ if (t != null) {
+ if (needsAnnotation(t.lookupClass()) && !hasAnnotation(t.lookupClass(), providesAnnotation)) {
+ if (JBRApi.VERBOSE) {
+ System.err.println(target + " not eligible: no @Provides annotation");
+ }
+ return INVALID;
+ }
+ info = new Proxy.Info(l, t, flags);
+ } else info = new Proxy.Info(l, null, flags);
+ for (var method : staticMethods.entrySet()) {
+ String methodName = method.getKey().methodName;
+ String targetMethodDescriptor = method.getKey().targetMethodDescriptor;
+ String targetType = method.getValue().targetType;
+ String targetMethodName = method.getValue().targetMethodName;
+ try {
+ Lookup lookup = resolveType(targetType, classLoader);
+ MethodType mt = MethodType.fromMethodDescriptorString(targetMethodDescriptor, classLoader);
+ MethodHandle handle = lookup.findStatic(lookup.lookupClass(), targetMethodName, mt);
+ info.addStaticMethod(methodName, handle);
+ } catch (ClassNotFoundException | IllegalArgumentException | TypeNotPresentException |
+ NoSuchMethodException | IllegalAccessException | ClassCastException e) {
+ if (JBRApi.VERBOSE) {
+ System.err.println(targetType + "#" + targetMethodName + " cannot be resolved");
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+ return info;
+ }
+
+ private static Lookup resolveType(String type, ClassLoader classLoader) throws ClassNotFoundException {
+ Class> c = null;
+ try {
+ c = Class.forName(type, false, classLoader);
+ } catch (ClassNotFoundException e) {
+ String dollars = type.replace('.', '$');
+ for (int i = 0;; i++) {
+ i = type.indexOf('.', i);
+ if (i == -1) break;
+ String name = type.substring(0, i) + dollars.substring(i);
+ try {
+ c = Class.forName(name, false, classLoader);
+ break;
+ } catch (ClassNotFoundException ignore) {}
+ }
+ if (c == null) throw e;
+ }
+ return SharedSecrets.getJavaLangInvokeAccess().lookupIn(c);
+ }
+
+ @Override
+ public String toString() { return type; }
+ }
+
+ private Class extends Annotation> serviceAnnotation, providedAnnotation, providesAnnotation;
+ private Module annotationsModule;
+ private ClassLoader classLoader;
+ private final Map 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);
+ }
+ }
+
+ 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 String readEntries(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" -> {
+ 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)) {
+ throw new RuntimeException("Conflicting mapping: " +
+ tokens[1] + " <-> " + tokens[2] +
+ ", " + a + " -> " + a.inverse +
+ ", " + b + " -> " + b.inverse);
+ }
+ a.inverse = b;
+ b.inverse = a;
+ a.target = tokens[2];
+ b.target = tokens[1];
+ switch (tokens[3]) {
+ case "SERVICE" -> {
+ a.type = null;
+ b.flags |= Proxy.SERVICE;
+ }
+ 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;
+ }
+
+ 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 static boolean hasAnnotation(Class> c, Class extends Annotation> a) {
+ return c.getAnnotation(a) != null;
+ }
+ }
+}
diff --git a/src/java.base/share/classes/com/jetbrains/internal/RegisteredProxyInfo.java b/src/java.base/share/classes/com/jetbrains/internal/RegisteredProxyInfo.java
deleted file mode 100644
index ec636e82b39f..000000000000
--- a/src/java.base/share/classes/com/jetbrains/internal/RegisteredProxyInfo.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains.internal;
-
-import java.lang.invoke.MethodHandles;
-import java.util.List;
-
-/**
- * Raw proxy info, as it was registered through {@link JBRApi.ModuleRegistry}.
- * Contains all necessary information to create a {@linkplain Proxy proxy}.
- */
-record RegisteredProxyInfo(MethodHandles.Lookup apiModule,
- String interfaceName,
- String[] targets,
- ProxyInfo.Type type,
- List staticMethods) {
-
- record StaticMethodMapping(String interfaceMethodName, String methodName, String[] classes) {}
-}
diff --git a/src/java.base/share/classes/com/jetbrains/internal/Utils.java b/src/java.base/share/classes/com/jetbrains/internal/Utils.java
new file mode 100644
index 000000000000..8660ae8e3c89
--- /dev/null
+++ b/src/java.base/share/classes/com/jetbrains/internal/Utils.java
@@ -0,0 +1,49 @@
+package com.jetbrains.internal;
+
+import jdk.internal.misc.VM;
+
+import java.io.PrintStream;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+class Utils {
+
+ static final Function, Stream>
+ BEFORE_BOOTSTRAP_DYNAMIC = st -> st
+ .dropWhile(e -> !e.getClassName().equals("com.jetbrains.exported.JBRApiSupport") ||
+ !e.getMethodName().equals("bootstrapDynamic")).skip(1)
+ .dropWhile(e -> e.getClassName().startsWith("java.lang.invoke."));
+ static final Function, Stream>
+ BEFORE_JBR = st -> st
+ .dropWhile(e -> !e.getClassName().equals("com.jetbrains.JBR"))
+ .dropWhile(e -> e.getClassName().equals("com.jetbrains.JBR"));
+
+ static void log(Function, Stream> preprocessor,
+ PrintStream out, String message) {
+ StackTraceElement[] st = Thread.currentThread().getStackTrace();
+ synchronized (out) {
+ out.println(message);
+ preprocessor.apply(Stream.of(st)).forEach(e -> out.println("\tat " + e));
+ }
+ }
+
+ /**
+ * System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}.
+ */
+ static boolean property(String name, boolean defaultValue) {
+ String value = VM.getSavedProperty(name);
+ if (value != null) {
+ if (value.equalsIgnoreCase("true")) return true;
+ if (value.equalsIgnoreCase("false")) return false;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}.
+ */
+ static String property(String name, String defaultValue) {
+ String value = VM.getSavedProperty(name);
+ return value != null ? value : defaultValue;
+ }
+}
diff --git a/src/java.base/share/classes/java/lang/Throwable.java b/src/java.base/share/classes/java/lang/Throwable.java
index db240023476c..a57058051bf6 100644
--- a/src/java.base/share/classes/java/lang/Throwable.java
+++ b/src/java.base/share/classes/java/lang/Throwable.java
@@ -27,6 +27,8 @@ package java.lang;
import java.io.*;
import java.util.*;
+
+import com.jetbrains.exported.JBRApi;
import jdk.internal.event.ThrowableTracer;
/**
@@ -1138,7 +1140,7 @@ public class Throwable implements Serializable {
private static volatile java.util.function.Supplier $$jb$additionalInfoSupplier = null;
- // JBR API internals
+ @JBRApi.Provides("Jstack#includeInfoFrom")
private static void $$jb$additionalInfoForJstack(java.util.function.Supplier supplier) {
$$jb$additionalInfoSupplier = supplier;
}
diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
index 5b8a4478be57..c9f15b7b3212 100644
--- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
+++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java
@@ -1645,6 +1645,11 @@ abstract class MethodHandleImpl {
return IMPL_LOOKUP.serializableConstructor(decl, ctorToCall);
}
+ @Override
+ public Lookup lookupIn(Class> lookupClass) {
+ return IMPL_LOOKUP.in(lookupClass);
+ }
+
});
}
diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java
index 722447eece67..05588e692f8f 100644
--- a/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java
+++ b/src/java.base/share/classes/jdk/internal/access/JavaLangInvokeAccess.java
@@ -29,6 +29,7 @@ import jdk.internal.foreign.abi.NativeEntryPoint;
import java.lang.foreign.MemoryLayout;
import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Constructor;
@@ -170,4 +171,11 @@ public interface JavaLangInvokeAccess {
* This method should only be used by ReflectionFactory::newConstructorForSerialization.
*/
MethodHandle serializableConstructor(Class> decl, Constructor> ctorToCall) throws IllegalAccessException;
+
+ /**
+ * Returns a lookup object corresponding to given class with full access privileges.
+ * @param lookupClass lookup class
+ * @return full-privileged lookup object
+ */
+ Lookup lookupIn(Class> lookupClass);
}
diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java
index 932bfa0e43bb..74600eb65dee 100644
--- a/src/java.base/share/classes/module-info.java
+++ b/src/java.base/share/classes/module-info.java
@@ -138,10 +138,9 @@ module java.base {
// additional qualified exports may be inserted at build time
// see make/gensrc/GenModuleInfo.gmk
+ exports com.jetbrains.exported;
opens com.jetbrains.bootstrap;
- exports com.jetbrains.internal to
- java.desktop;
exports com.sun.crypto.provider to
jdk.crypto.cryptoki;
exports sun.invoke.util to
diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java
index 65af936820d9..71b210382c4e 100644
--- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java
+++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPlatformWindow.java
@@ -44,7 +44,6 @@ import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.FocusEvent;
-import java.awt.event.PaintEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.awt.peer.ComponentPeer;
@@ -64,11 +63,11 @@ import javax.swing.SwingUtilities;
import com.apple.laf.ClientPropertyApplicator;
import com.apple.laf.ClientPropertyApplicator.Property;
+import com.jetbrains.exported.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.AWTAccessor.ComponentAccessor;
import sun.awt.AWTAccessor.WindowAccessor;
import sun.awt.AWTThreading;
-import sun.awt.PaintEventDispatcher;
import sun.java2d.SunGraphicsEnvironment;
import sun.java2d.SurfaceData;
import sun.lwawt.LWKeyboardFocusManagerPeer;
@@ -81,7 +80,7 @@ import sun.lwawt.PlatformWindow;
import sun.util.logging.PlatformLogger;
public class CPlatformWindow extends CFRetainedResource implements PlatformWindow {
- private native long nativeCreateNSWindow(long nsViewPtr,long ownerPtr, long styleBits, double x, double y, double w, double h, double transparentTitleBarHeight);
+ private native long nativeCreateNSWindow(long nsViewPtr,long ownerPtr, long styleBits, double x, double y, double w, double h);
private static native void nativeSetNSWindowStyleBits(long nsWindowPtr, int mask, int data);
private static native void nativeSetNSWindowAppearance(long nsWindowPtr, String appearanceName);
private static native void nativeSetNSWindowMenuBar(long nsWindowPtr, long menuBarPtr);
@@ -108,7 +107,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
static native CPlatformWindow nativeGetTopmostPlatformWindowUnderMouse();
private static native void nativeRaiseLevel(long nsWindowPtr, boolean popup, boolean onlyIfParentIsActive);
private static native boolean nativeDelayShowing(long nsWindowPtr);
- private static native void nativeSetTransparentTitleBarHeight(long nsWindowPtr, float height);
+ private static native void nativeUpdateCustomTitleBar(long nsWindowPtr);
private static native void nativeCallDeliverMoveResizeEvent(long nsWindowPtr);
private static native void nativeSetRoundedCorners(long nsWindowPrt, float radius, int borderWidth, int borderColor);
@@ -143,7 +142,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
public static final String WINDOW_TRANSPARENT_TITLE_BAR = "apple.awt.transparentTitleBar";
public static final String WINDOW_TITLE_VISIBLE = "apple.awt.windowTitleVisible";
public static final String WINDOW_APPEARANCE = "apple.awt.windowAppearance";
- public static final String WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT = "apple.awt.windowTransparentTitleBarHeight";
public static final String WINDOW_CORNER_RADIUS = "apple.awt.windowCornerRadius";
// This system property is named as jdk.* because it is not specific to AWT
@@ -293,17 +291,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
}
}
},
- new Property(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT) {
- public void applyProperty(final CPlatformWindow c, final Object value) {
- if (value != null && (value instanceof Float)) {
- boolean enabled = (float) value != 0f;
- c.setStyleBits(FULL_WINDOW_CONTENT, enabled);
- c.setStyleBits(TRANSPARENT_TITLE_BAR, enabled);
- c.setStyleBits(TITLE_VISIBLE, !enabled);
- c.execute(ptr -> AWTThreading.executeWaitToolkit(wait -> nativeSetTransparentTitleBarHeight(ptr, (float) value)));
- }
- }
- },
new Property(WINDOW_CORNER_RADIUS) {
public void applyProperty(final CPlatformWindow c, final Object value) {
c.setRoundedCorners(value);
@@ -374,8 +361,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
responder = createPlatformResponder();
contentView.initialize(peer, responder);
- float transparentTitleBarHeight = getTransparentTitleBarHeight(_target);
-
Rectangle bounds;
if (!IS(DECORATED, styleBits)) {
// For undecorated frames the move/resize event does not come if the frame is centered on the screen
@@ -396,7 +381,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
+ ", bounds=" + bounds);
}
long windowPtr = createNSWindow(viewPtr, ownerPtr, styleBits,
- bounds.x, bounds.y, bounds.width, bounds.height, transparentTitleBarHeight);
+ bounds.x, bounds.y, bounds.width, bounds.height);
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
logger.fine("window created: " + Long.toHexString(windowPtr));
}
@@ -411,7 +396,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
+ ", bounds=" + bounds);
}
long windowPtr = createNSWindow(viewPtr, 0, styleBits,
- bounds.x, bounds.y, bounds.width, bounds.height, transparentTitleBarHeight);
+ bounds.x, bounds.y, bounds.width, bounds.height);
if (logger.isLoggable(PlatformLogger.Level.FINE)) {
logger.fine("window created: " + Long.toHexString(windowPtr));
}
@@ -575,14 +560,6 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
if (prop != null) {
styleBits = SET(styleBits, TITLE_VISIBLE, Boolean.parseBoolean(prop.toString()));
}
-
- prop = rootpane.getClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT);
- if (prop != null) {
- boolean enabled = Float.parseFloat(prop.toString()) != 0f;
- styleBits = SET(styleBits, FULL_WINDOW_CONTENT, enabled);
- styleBits = SET(styleBits, TRANSPARENT_TITLE_BAR, enabled);
- styleBits = SET(styleBits, TITLE_VISIBLE, !enabled);
- }
}
if (isDialog) {
@@ -1506,10 +1483,9 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
double x,
double y,
double w,
- double h,
- double transparentTitleBarHeight) {
+ double h) {
return AWTThreading.executeWaitToolkit(() ->
- nativeCreateNSWindow(nsViewPtr, ownerPtr, styleBits, x, y, w, h, transparentTitleBarHeight));
+ nativeCreateNSWindow(nsViewPtr, ownerPtr, styleBits, x, y, w, h));
}
// ----------------------------------------------------------------------
@@ -1543,28 +1519,15 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
isFullScreenAnimationOn = false;
}
- private float getTransparentTitleBarHeight(Window target) {
- if (target instanceof javax.swing.RootPaneContainer) {
- final javax.swing.JRootPane rootpane = ((javax.swing.RootPaneContainer)target).getRootPane();
- if (rootpane != null) {
- Object transparentTitleBarHeightProperty = rootpane.getClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT);
- if (transparentTitleBarHeightProperty != null) {
- return Float.parseFloat(transparentTitleBarHeightProperty.toString());
- }
- }
- }
- return 0f;
- }
-
- // JBR API internals
- private static void setCustomDecorationTitleBarHeight(Window target, ComponentPeer peer, float height) {
- if (target instanceof javax.swing.RootPaneContainer) {
- final javax.swing.JRootPane rootpane = ((javax.swing.RootPaneContainer)target).getRootPane();
- if (rootpane != null) rootpane.putClientProperty(WINDOW_TRANSPARENT_TITLE_BAR_HEIGHT, height);
+ @JBRApi.Provides("java.awt.Window.CustomTitleBarPeer#update")
+ private static void updateCustomTitleBar(ComponentPeer peer) {
+ if (peer instanceof LWWindowPeer lwwp &&
+ lwwp.getPlatformWindow() instanceof CPlatformWindow cpw) {
+ cpw.execute(CPlatformWindow::nativeUpdateCustomTitleBar);
}
}
- // JBR API internals
+ @JBRApi.Provides("RoundedCornersManager")
private static void setRoundedCorners(Window window, Object params) {
Object peer = AWTAccessor.getComponentAccessor().getPeer(window);
if (peer instanceof CPlatformWindow) {
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java b/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java
index 6751da518bea..93732116070e 100644
--- a/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java
+++ b/src/java.desktop/share/classes/com/jetbrains/desktop/ConstrainableGraphics2D.java
@@ -23,10 +23,12 @@
package com.jetbrains.desktop;
+import com.jetbrains.exported.JBRApi;
import sun.awt.ConstrainableGraphics;
import java.awt.geom.Rectangle2D;
+@JBRApi.Provided("GraphicsUtils.ConstrainableGraphics2D")
public interface ConstrainableGraphics2D extends ConstrainableGraphics {
public void constrain(Rectangle2D region);
public Object getDestination();
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java b/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java
deleted file mode 100644
index 4d300d9fe366..000000000000
--- a/src/java.desktop/share/classes/com/jetbrains/desktop/FontExtensions.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains.desktop;
-
-import com.jetbrains.internal.JBRApi;
-
-import java.awt.*;
-import java.lang.invoke.MethodHandles;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-
-public class FontExtensions {
- private interface FontExtension {
- FontExtension INSTANCE = (FontExtension) JBRApi.internalServiceBuilder(MethodHandles.lookup())
- .withStatic("getFeatures", "getFeatures", "java.awt.Font")
- .withStatic("isComplexRendering", "isComplexRendering", "java.awt.Font")
- .withStatic("isKerning", "isKerning", "java.awt.Font")
- .build();
-
- TreeMap getFeatures(Font font);
- boolean isComplexRendering(Font font);
- boolean isKerning(Font font);
- }
-
- public static String featuresToString(Map features) {
- return features.entrySet().stream().map(feature -> (feature.getKey() + "=" + feature.getValue())).
- collect(Collectors.joining(";"));
- }
-
- public static TreeMap getFeatures(Font font) {
- return FontExtension.INSTANCE.getFeatures(font);
- }
-
- public static boolean isComplexRendering(Font font) {
- return FontExtension.INSTANCE.isComplexRendering(font);
- }
-
- public static boolean isKerning(Font font) {
- return FontExtension.INSTANCE.isKerning(font);
- }
-}
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java
deleted file mode 100644
index 32e5cf6ffa4f..000000000000
--- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRApiModule.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains.desktop;
-
-import com.jetbrains.internal.JBRApi;
-
-import java.lang.invoke.MethodHandles;
-
-/**
- * This class contains mapping between JBR API interfaces and implementation in {@code java.desktop} module.
- */
-public class JBRApiModule {
- static {
- JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports)
- .service("com.jetbrains.ExtendedGlyphCache")
- .withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities")
- .service("com.jetbrains.JBRFileDialogService")
- .withStatic("getFileDialog", "get", "com.jetbrains.desktop.JBRFileDialog")
- .proxy("com.jetbrains.JBRFileDialog", "com.jetbrains.desktop.JBRFileDialog")
- .service("com.jetbrains.CustomWindowDecoration", "java.awt.Window$CustomWindowDecoration")
- .service("com.jetbrains.RoundedCornersManager")
- .withStatic("setRoundedCorners", "setRoundedCorners", "sun.lwawt.macosx.CPlatformWindow",
- "sun.awt.windows.WWindowPeer")
- .service("com.jetbrains.DesktopActions")
- .withStatic("setHandler", "setDesktopActionsHandler", "java.awt.Desktop")
- .clientProxy("java.awt.Desktop$DesktopActionsHandler", "com.jetbrains.DesktopActions$Handler")
- .service("com.jetbrains.ProjectorUtils")
- .withStatic("overrideGraphicsEnvironment", "overrideLocalGraphicsEnvironment", "java.awt.GraphicsEnvironment")
- .withStatic("setLocalGraphicsEnvironmentProvider", "setLocalGraphicsEnvironmentProvider", "java.awt.GraphicsEnvironment")
- .service("com.jetbrains.AccessibleAnnouncer")
- .withStatic("announce", "announce", "sun.swing.AccessibleAnnouncer")
- .service("com.jetbrains.GraphicsUtils")
- .withStatic("createConstrainableGraphics", "create", "com.jetbrains.desktop.JBRGraphicsDelegate")
- .clientProxy("com.jetbrains.desktop.ConstrainableGraphics2D", "com.jetbrains.GraphicsUtils$ConstrainableGraphics2D")
- .service("com.jetbrains.WindowDecorations", "java.awt.Window$WindowDecorations")
- .proxy("com.jetbrains.WindowDecorations$CustomTitleBar", "java.awt.Window$CustomTitleBar")
- .service("com.jetbrains.WindowMove", "java.awt.Window$WindowMoveService")
- .service("com.jetbrains.FontExtensions")
- .withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities")
- .withStatic("deriveFontWithFeatures", "deriveFont", "java.awt.Font")
- .withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font")
- .service("com.jetbrains.FontOpenTypeFeatures")
- .withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font")
- .clientProxy("java.awt.Font$Features", "com.jetbrains.FontExtensions$Features")
- .service("com.jetbrains.FontMetricsAccessor", "sun.font.FontDesignMetrics$Accessor")
- .clientProxy("sun.font.FontDesignMetrics$Overrider", "com.jetbrains.FontMetricsAccessor$Overrider");
- }
-}
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java
index 7f6a960e38ad..a41cb1175610 100644
--- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java
+++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRFileDialog.java
@@ -1,5 +1,7 @@
package com.jetbrains.desktop;
+import com.jetbrains.exported.JBRApi;
+
import java.io.Serial;
import java.io.Serializable;
import java.lang.annotation.Native;
@@ -8,7 +10,8 @@ import java.lang.invoke.VarHandle;
import java.awt.*;
import java.util.Objects;
-public class JBRFileDialog implements Serializable {
+@JBRApi.Provides("JBRFileDialog")
+public final class JBRFileDialog implements Serializable {
@Serial
private static final long serialVersionUID = -9154712118353824660L;
@@ -22,6 +25,8 @@ public class JBRFileDialog implements Serializable {
throw new Error(e);
}
}
+
+ @JBRApi.Provides("JBRFileDialogService#getFileDialog")
public static JBRFileDialog get(FileDialog dialog) {
return (JBRFileDialog) getter.get(dialog);
}
diff --git a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java
index 6f35e1eb2f8e..62c686b16aef 100644
--- a/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java
+++ b/src/java.desktop/share/classes/com/jetbrains/desktop/JBRGraphicsDelegate.java
@@ -23,6 +23,8 @@
package com.jetbrains.desktop;
+import com.jetbrains.exported.JBRApi;
+
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
@@ -38,6 +40,7 @@ import java.util.Map;
public class JBRGraphicsDelegate extends Graphics2D implements ConstrainableGraphics2D {
+ @JBRApi.Provides("GraphicsUtils#createConstrainableGraphics")
public static Graphics2D create(Graphics2D graphics2D,
ConstrainableGraphics2D constrainable) {
return new JBRGraphicsDelegate(graphics2D, constrainable);
diff --git a/src/java.desktop/share/classes/java/awt/Desktop.java b/src/java.desktop/share/classes/java/awt/Desktop.java
index 0a48c739f19d..1e1a714654a4 100644
--- a/src/java.desktop/share/classes/java/awt/Desktop.java
+++ b/src/java.desktop/share/classes/java/awt/Desktop.java
@@ -46,6 +46,7 @@ import java.util.Objects;
import javax.swing.JMenuBar;
+import com.jetbrains.exported.JBRApi;
import sun.awt.SunToolkit;
/**
@@ -858,6 +859,7 @@ public class Desktop {
return peer.moveToTrash(file);
}
+ @JBRApi.Provided("DesktopActions.Handler")
private interface DesktopActionsHandler {
void open(File file) throws IOException;
void edit(File file) throws IOException;
@@ -890,7 +892,8 @@ public class Desktop {
}
private static volatile DesktopActions actions;
- static void setDesktopActionsHandler(DesktopActionsHandler h) {
+ @JBRApi.Provides("DesktopActions#setHandler")
+ private static void setDesktopActionsHandler(DesktopActionsHandler h) {
try {
actions = new DesktopActions(h);
} catch (Exception e) {
diff --git a/src/java.desktop/share/classes/java/awt/Font.java b/src/java.desktop/share/classes/java/awt/Font.java
index 8e8fed68d8c2..fa9b371c59ac 100644
--- a/src/java.desktop/share/classes/java/awt/Font.java
+++ b/src/java.desktop/share/classes/java/awt/Font.java
@@ -45,17 +45,12 @@ import java.lang.ref.SoftReference;
import java.nio.file.Files;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.CharacterIterator;
-import java.util.EventListener;
-import java.util.Hashtable;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
+import java.util.*;
+import com.jetbrains.exported.JBRApi;
import sun.awt.ComponentFactory;
import sun.font.AttributeMap;
import sun.font.AttributeValues;
-import sun.font.CompositeFont;
import sun.font.CoreMetrics;
import sun.font.Font2D;
import sun.font.Font2DHandle;
@@ -280,6 +275,28 @@ public class Font implements java.io.Serializable
public FontPeer getFontPeer(final Font font) {
return font.getFontPeer();
}
+
+ @Override
+ public String[] getFeatures(Font font) {
+ int size = font.featureArray == null ? 0 : font.featureArray.length;
+ String[] fs = new String[size + 3];
+ if (size > 0) System.arraycopy(font.featureArray, 0, fs, 0, size);
+ fs[size++] = KERN_FEATURE + "=" + font.getAttributeValues().getKerning();
+ fs[size++] = LIGA_FEATURE + "=" + font.getAttributeValues().getLigatures();
+ fs[size] = CALT_FEATURE + "=" + font.getAttributeValues().getLigatures();
+ return fs;
+ }
+
+ @Override
+ public boolean isComplexRendering(Font font) {
+ return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 ||
+ font.values.getBaselineTransform() != null)) || font.featureArray != null;
+ }
+
+ @Override
+ public boolean isKerning(Font font) {
+ return font.values != null && (font.values.getKerning() != 0);
+ }
}
static {
@@ -451,10 +468,11 @@ public class Font implements java.io.Serializable
* Ordered map choose intentionally as field's type. It allows to correctly comparing two Font objects
*
* @serial
- * @see #getFeatures
* @see #deriveFont(Font, Features)
*/
- private TreeMap features = new TreeMap();
+ private String[] featureArray;
+ @Deprecated
+ private TreeMap features; // Kept for compatibility
/**
* The platform specific font information.
@@ -557,10 +575,6 @@ public class Font implements java.io.Serializable
return font2DHandle.font2D;
}
- private boolean anyEnabledFeatures() {
- return features.values().stream().anyMatch(x -> x != 0);
- }
-
/**
* Creates a new {@code Font} from the specified name, style and
* point size.
@@ -624,18 +638,19 @@ public class Font implements java.io.Serializable
this.pointSize = size;
}
- private Font(String name, int style, float sizePts, TreeMap features) {
+ private Font(String name, int style, float sizePts, String[] features) {
this.name = (name != null) ? name : "Default";
this.style = (style & ~0x03) == 0 ? style : 0;
this.size = (int)(sizePts + 0.5);
this.pointSize = sizePts;
- this.features = features;
+ this.featureArray = features;
+ this.hasLayoutAttributes |= this.featureArray != null;
}
/* This constructor is used by deriveFont when attributes is null */
private Font(String name, int style, float sizePts,
boolean created, boolean withFallback,
- Font2DHandle handle, boolean useOldHandle, TreeMap features) {
+ Font2DHandle handle, boolean useOldHandle, String[] features) {
this(name, style, sizePts, features);
this.createdFont = created;
this.withFallback = withFallback;
@@ -699,9 +714,9 @@ public class Font implements java.io.Serializable
*/
private Font(AttributeValues values, String oldName, int oldStyle,
boolean created, boolean withFallback,
- Font2DHandle handle, boolean useOldHandle, TreeMap features) {
+ Font2DHandle handle, boolean useOldHandle, String[] features) {
- this.features = features;
+ this.featureArray = features;
this.createdFont = created;
this.withFallback = withFallback;
if (created || withFallback) {
@@ -735,6 +750,7 @@ public class Font implements java.io.Serializable
this.font2DHandle = handle;
}
initFromValues(values);
+ this.hasLayoutAttributes |= this.featureArray != null;
}
/**
@@ -763,6 +779,11 @@ public class Font implements java.io.Serializable
* @since 1.6
*/
protected Font(Font font) {
+ this(font, font.featureArray);
+ }
+
+ private Font(Font font, String[] features) {
+ this.featureArray = features;
if (font.values != null) {
initFromValues(font.getAttributeValues().clone());
} else {
@@ -771,15 +792,10 @@ public class Font implements java.io.Serializable
this.size = font.size;
this.pointSize = font.pointSize;
}
+ this.hasLayoutAttributes |= this.featureArray != null;
this.font2DHandle = font.font2DHandle;
this.createdFont = font.createdFont;
this.withFallback = font.withFallback;
- this.features = font.features;
- }
-
- private Font(Font font, TreeMap features) {
- this(font);
- this.features = features;
}
/**
@@ -829,7 +845,7 @@ public class Font implements java.io.Serializable
if (values.getPosture() >= .2f) this.style |= ITALIC; // not == .2f
this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK);
- this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
+ this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
}
/**
@@ -910,7 +926,7 @@ public class Font implements java.io.Serializable
values.merge(attributes, SECONDARY_MASK);
return new Font(values, font.name, font.style,
font.createdFont, font.withFallback,
- font.font2DHandle, false, new TreeMap());
+ font.font2DHandle, false, null);
}
return new Font(attributes);
}
@@ -922,7 +938,7 @@ public class Font implements java.io.Serializable
values.merge(attributes, SECONDARY_MASK);
return new Font(values, font.name, font.style,
font.createdFont, font.withFallback,
- font.font2DHandle, false, new TreeMap());
+ font.font2DHandle, false, null);
}
return font;
@@ -1511,17 +1527,7 @@ public class Font implements java.io.Serializable
* @since 1.6
*/
public boolean hasLayoutAttributes() {
- return anyEnabledFeatures() || hasLayoutAttributes;
- }
-
- private static TreeMap getFeatures(Font font) {
- TreeMap res = new TreeMap<>();
- res.putAll(font.features);
- res.put(KERN_FEATURE, font.getAttributeValues().getKerning());
- res.put(LIGA_FEATURE, font.getAttributeValues().getLigatures());
- res.put(CALT_FEATURE, font.getAttributeValues().getLigatures());
-
- return res;
+ return hasLayoutAttributes;
}
/**
@@ -1723,7 +1729,7 @@ public class Font implements java.io.Serializable
*/
public int hashCode() {
if (hash == 0) {
- hash = name.hashCode() ^ style ^ size ^ features.hashCode();
+ hash = name.hashCode() ^ style ^ size ^ Arrays.hashCode(featureArray);
/* It is possible many fonts differ only in transform.
* So include the transform in the hash calculation.
* nonIdentityTx is set whenever there is a transform in
@@ -1762,7 +1768,7 @@ public class Font implements java.io.Serializable
pointSize == font.pointSize &&
withFallback == font.withFallback &&
name.equals(font.name) &&
- features.equals(font.features)) {
+ Arrays.equals(featureArray, font.featureArray)) {
/* 'values' is usually initialized lazily, except when
* the font is constructed from a Map, or derived using
@@ -1868,6 +1874,11 @@ public class Font implements java.io.Serializable
pointSize = (float)size;
}
+ if (features != null) {
+ featureArray = features.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).toArray(String[]::new);
+ features = null;
+ }
+
// Handle fRequestedAttributes.
// in 1.5, we always streamed out the font values plus
// TRANSFORM, SUPERSCRIPT, and WIDTH, regardless of whether the
@@ -1886,17 +1897,14 @@ public class Font implements java.io.Serializable
}
values = getAttributeValues().merge(extras);
this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK);
- this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
+ this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
} catch (Throwable t) {
throw new IOException(t);
} finally {
fRequestedAttributes = null; // don't need it any more
}
}
-
- if (features == null) {
- features = new TreeMap<>();
- }
+ this.hasLayoutAttributes |= this.featureArray != null;
}
/**
@@ -2000,14 +2008,14 @@ public class Font implements java.io.Serializable
public Font deriveFont(int style, float size){
if (values == null) {
return new Font(name, style, size, createdFont, withFallback,
- font2DHandle, false, features);
+ font2DHandle, false, featureArray);
}
AttributeValues newValues = getAttributeValues().clone();
int oldStyle = (this.style != style) ? this.style : -1;
applyStyle(style, newValues);
newValues.setSize(size);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
- font2DHandle, false, features);
+ font2DHandle, false, featureArray);
}
/**
@@ -2027,7 +2035,7 @@ public class Font implements java.io.Serializable
applyStyle(style, newValues);
applyTransform(trans, newValues);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
- font2DHandle, false, features);
+ font2DHandle, false, featureArray);
}
/**
@@ -2040,12 +2048,12 @@ public class Font implements java.io.Serializable
public Font deriveFont(float size){
if (values == null) {
return new Font(name, style, size, createdFont, withFallback,
- font2DHandle, true, features);
+ font2DHandle, true, featureArray);
}
AttributeValues newValues = getAttributeValues().clone();
newValues.setSize(size);
return new Font(newValues, null, -1, createdFont, withFallback,
- font2DHandle, true, features);
+ font2DHandle, true, featureArray);
}
/**
@@ -2062,7 +2070,7 @@ public class Font implements java.io.Serializable
AttributeValues newValues = getAttributeValues().clone();
applyTransform(trans, newValues);
return new Font(newValues, null, -1, createdFont, withFallback,
- font2DHandle, true, features);
+ font2DHandle, true, featureArray);
}
/**
@@ -2075,13 +2083,13 @@ public class Font implements java.io.Serializable
public Font deriveFont(int style){
if (values == null) {
return new Font(name, style, size, createdFont, withFallback,
- font2DHandle, false, features);
+ font2DHandle, false, featureArray);
}
AttributeValues newValues = getAttributeValues().clone();
int oldStyle = (this.style != style) ? this.style : -1;
applyStyle(style, newValues);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
- font2DHandle, false, features);
+ font2DHandle, false, featureArray);
}
/*
@@ -2119,15 +2127,62 @@ public class Font implements java.io.Serializable
}
}
return new Font(newValues, name, style, createdFont, withFallback,
- font2DHandle, keepFont2DHandle, features);
+ font2DHandle, keepFont2DHandle, featureArray);
}
+ @JBRApi.Provided("FontExtensions.Features")
+ @Deprecated(forRemoval = true)
private interface Features {
TreeMap getAsTreeMap();
}
+ @JBRApi.Provides("FontExtensions#deriveFontWithFeatures")
+ @Deprecated(forRemoval = true)
private static Font deriveFont(Font font, Features features) {
- return new Font(font, features.getAsTreeMap());
+ TreeMap map = features.getAsTreeMap();
+ String[] array = new String[map.size()];
+ int i = 0;
+ for (Map.Entry e : map.entrySet()) {
+ validateFeature(array[i++] = e.getKey() + "=" + e.getValue());
+ }
+ return new Font(font, array);
+ }
+
+ private static void validateFeature(String f) {
+ int len = f.length();
+ invalid:if ((len == 4 || len > 5) &&
+ Character.isLetterOrDigit(f.charAt(0)) &&
+ Character.isLetterOrDigit(f.charAt(1)) &&
+ Character.isLetterOrDigit(f.charAt(2)) &&
+ Character.isLetterOrDigit(f.charAt(3))) {
+ if (len > 5 && f.charAt(4) == '=') {
+ for (int i = 5; i < len; i++) {
+ if (!Character.isDigit(f.charAt(i))) break invalid;
+ }
+ if (f.startsWith("kern")) throw new IllegalArgumentException(
+ "\"kern\" feature is not allowed here, use java.awt.font.TextAttribute.KERNING instead");
+ if (f.startsWith("liga") || f.startsWith("calt")) throw new IllegalArgumentException(
+ "\"liga\" and \"calt\" features are not allowed here, use java.awt.font.TextAttribute.LIGATURES instead");
+ }
+ return;
+ }
+ throw new IllegalArgumentException("Invalid feature: \"" + f + "\", allowed syntax: \"kern\", or \"aalt=2\"");
+ }
+
+ @JBRApi.Provides("FontExtensions#deriveFontWithFeatures")
+ private static Font deriveFont(Font font, String... features) {
+ if (features == null || features.length == 0) return new Font(font, null);
+ for (String f : features) validateFeature(f);
+ return new Font(font, Arrays.copyOf(features, features.length));
+ }
+
+ /**
+ * Returns a list of OpenType's features enabled on the current Font.
+ * @return array of enabled OpenType's features
+ */
+ @JBRApi.Provides("FontExtensions")
+ private static String[] getEnabledFeatures(Font font) {
+ return font.featureArray == null ? new String[0] : Arrays.copyOf(font.featureArray, font.featureArray.length);
}
/**
@@ -2581,20 +2636,12 @@ public class Font implements java.io.Serializable
return metrics.charsBounds(chars, beginIndex, limit - beginIndex);
}
- private static boolean isComplexRendering(Font font) {
- return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 ||
- font.values.getBaselineTransform() != null)) || font.anyEnabledFeatures();
- }
-
- private static boolean isKerning(Font font) {
- return font.values != null && (font.values.getKerning() != 0);
- }
-
/**
* Returns a list of OpenType's features supported by current Font.
* Implementation of such logic goes to HarfBuzz library.
- * @return list of OpenType's features concatenated to String
+ * @return set of available OpenType's features
*/
+ @JBRApi.Provides("FontExtensions")
private static Set getAvailableFeatures(Font font) {
return SunLayoutEngine.getAvailableFeatures(FontUtilities.getFont2D(font));
}
diff --git a/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java b/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java
index 1f8cdaf6d38d..15886a6e2869 100644
--- a/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java
+++ b/src/java.desktop/share/classes/java/awt/GraphicsEnvironment.java
@@ -29,6 +29,7 @@ import java.awt.image.BufferedImage;
import java.util.Locale;
import java.util.function.Supplier;
+import com.jetbrains.exported.JBRApi;
import sun.awt.PlatformGraphicsInfo;
import sun.font.FontManager;
import sun.font.FontManagerFactory;
@@ -112,12 +113,12 @@ public abstract class GraphicsEnvironment {
return LocalGE.INSTANCE;
}
- // JBR API internals
+ @JBRApi.Provides("ProjectorUtils")
private static void setLocalGraphicsEnvironmentProvider(Supplier geProvider) {
graphicsEnvironmentProvider = geProvider;
}
- // JBR API internals
+ @JBRApi.Provides("ProjectorUtils#overrideGraphicsEnvironment")
private static void overrideLocalGraphicsEnvironment(GraphicsEnvironment overriddenGE) {
setLocalGraphicsEnvironmentProvider(() -> overriddenGE);
}
diff --git a/src/java.desktop/share/classes/java/awt/Window.java b/src/java.desktop/share/classes/java/awt/Window.java
index fbc61a87e356..36bba6387667 100644
--- a/src/java.desktop/share/classes/java/awt/Window.java
+++ b/src/java.desktop/share/classes/java/awt/Window.java
@@ -47,14 +47,12 @@ import java.io.OptionalDataException;
import java.io.Serial;
import java.io.Serializable;
import java.lang.annotation.Native;
-import java.lang.invoke.MethodHandles;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventListener;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.HashMap;
@@ -70,7 +68,7 @@ import javax.accessibility.AccessibleRole;
import javax.accessibility.AccessibleState;
import javax.accessibility.AccessibleStateSet;
-import com.jetbrains.internal.JBRApi;
+import com.jetbrains.exported.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.AppContext;
import sun.awt.DebugSettings;
@@ -3904,7 +3902,9 @@ public class Window extends Container implements Accessible {
// ************************** Custom title bar support *******************************
- private static class WindowDecorations {
+ @JBRApi.Service
+ @JBRApi.Provides("WindowDecorations")
+ private static final class WindowDecorations {
WindowDecorations() { CustomTitleBar.assertSupported(); }
void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar) { ((Window) frame).setCustomTitleBar(customTitleBar); }
void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar) { ((Window) dialog).setCustomTitleBar(customTitleBar); }
@@ -3912,7 +3912,8 @@ public class Window extends Container implements Accessible {
}
- private static class CustomTitleBar implements Serializable {
+ @JBRApi.Provides("WindowDecorations.CustomTitleBar")
+ private static final class CustomTitleBar implements Serializable {
@Serial
private static final long serialVersionUID = -2330620200902241173L;
@@ -4007,8 +4008,7 @@ public class Window extends Container implements Accessible {
}
private interface CustomTitleBarPeer {
- CustomTitleBarPeer INSTANCE = (CustomTitleBarPeer) JBRApi.internalServiceBuilder(MethodHandles.lookup())
- .withStatic("update", "updateCustomTitleBar", "sun.awt.windows.WFramePeer", "sun.lwawt.macosx.CPlatformWindow").build();
+ CustomTitleBarPeer INSTANCE = JBRApi.internalService();
void update(ComponentPeer peer);
}
@@ -4044,28 +4044,6 @@ public class Window extends Container implements Accessible {
Window updateCustomTitleBarHitTest(boolean allowNativeActions) {
if (customTitleBar == null) return null;
pendingCustomTitleBarHitTest = allowNativeActions ? CustomTitleBar.HIT_TITLEBAR : CustomTitleBar.HIT_CLIENT;
- if (customDecorHitTestSpots != null) { // Compatibility bridge, to be removed with old API
- Point p = getMousePosition(true);
- if (p == null) return this;
- // Perform old-style hit test
- int result = CustomWindowDecoration.NO_HIT_SPOT;
- for (var spot : customDecorHitTestSpots) {
- if (spot.getKey().contains(p.x, p.y)) {
- result = spot.getValue();
- break;
- }
- }
- // Convert old hit test value to new one
- pendingCustomTitleBarHitTest = switch (result) {
- case CustomWindowDecoration.MINIMIZE_BUTTON -> CustomTitleBar.HIT_MINIMIZE_BUTTON;
- case CustomWindowDecoration.MAXIMIZE_BUTTON -> CustomTitleBar.HIT_MAXIMIZE_BUTTON;
- case CustomWindowDecoration.CLOSE_BUTTON -> CustomTitleBar.HIT_CLOSE_BUTTON;
- case CustomWindowDecoration.NO_HIT_SPOT, CustomWindowDecoration.DRAGGABLE_AREA -> CustomTitleBar.HIT_TITLEBAR;
- default -> CustomTitleBar.HIT_CLIENT;
- };
- // Overwrite hit test value
- applyCustomTitleBarHitTest();
- }
return this;
}
@@ -4077,111 +4055,6 @@ public class Window extends Container implements Accessible {
customTitleBarHitTest = pendingCustomTitleBarHitTest;
}
- // *** Following custom decorations code is kept for backward compatibility and will be removed soon. ***
-
- @Deprecated
- private transient volatile boolean hasCustomDecoration;
- @Deprecated
- private transient volatile List> customDecorHitTestSpots;
- @Deprecated
- private transient volatile int customDecorTitleBarHeight = -1; // 0 can be a legal value when no title bar is expected
- @Deprecated
- private static class CustomWindowDecoration {
-
- CustomWindowDecoration() { CustomTitleBar.assertSupported(); }
-
- @Native public static final int
- NO_HIT_SPOT = 0,
- OTHER_HIT_SPOT = 1,
- MINIMIZE_BUTTON = 2,
- MAXIMIZE_BUTTON = 3,
- CLOSE_BUTTON = 4,
- MENU_BAR = 5,
- DRAGGABLE_AREA = 6;
-
- void setCustomDecorationEnabled(Window window, boolean enabled) {
- window.hasCustomDecoration = enabled;
- setTitleBar(window, enabled ? Math.max(window.customDecorTitleBarHeight, 0.01f) : 0);
- }
- boolean isCustomDecorationEnabled(Window window) {
- return window.hasCustomDecoration;
- }
-
- void setCustomDecorationHitTestSpots(Window window, List> spots) {
- window.customDecorHitTestSpots = List.copyOf(spots);
- }
- List> getCustomDecorationHitTestSpots(Window window) {
- return window.customDecorHitTestSpots;
- }
-
- void setCustomDecorationTitleBarHeight(Window window, int height) {
- window.customDecorTitleBarHeight = height;
- setTitleBar(window, window.hasCustomDecoration ? Math.max(height, 0.01f) : 0);
- }
- int getCustomDecorationTitleBarHeight(Window window) {
- return window.customDecorTitleBarHeight;
- }
-
- // Bridge from old to new API
- private static void setTitleBar(Window window, float height) {
- if (height <= 0.0f) window.setCustomTitleBar(null);
- else {
- CustomTitleBar t = new CustomTitleBar();
- // Old API accepts title bar height with insets, subtract it for new API.
- // We use bottom insets here because top insets may change when toggling custom title bar, they are usually equal.
- if (window instanceof Frame f && (f.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0) {
- height -= window.getInsets().bottom;
- }
- t.setHeight(Math.max(height, 0.01f));
- // In old API versions there were no control buttons on Windows.
- if (System.getProperty("os.name").toLowerCase().contains("win")) t.putProperty("controls.visible", false);
- window.setCustomTitleBar(t);
- }
- }
- }
-
- private interface WindowMovePeer {
- void startMovingWindowTogetherWithMouse(Window window, int mouseButton);
- }
-
- private interface WindowMovePeerX11 extends WindowMovePeer {
- WindowMovePeerX11 INSTANCE = (WindowMovePeerX11) JBRApi.internalServiceBuilder(MethodHandles.lookup())
- .withStatic("startMovingWindowTogetherWithMouse",
- "startMovingWindowTogetherWithMouse",
- "sun.awt.X11.XWindowPeer")
- .build();
- }
-
- private static class WindowMoveService {
- WindowMovePeer windowMovePeer;
-
- WindowMoveService() {
- var toolkit = Toolkit.getDefaultToolkit();
- var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
- if (toolkit == null || ge == null) {
- throw new JBRApi.ServiceNotAvailableException("Supported only with a Toolkit present");
- }
-
- if (!objectIsInstanceOf(toolkit, "sun.awt.X11.XToolkit")
- || !objectIsInstanceOf(ge, "sun.awt.X11GraphicsEnvironment")) {
- throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment");
- }
-
- // This will throw if the service is not supported by the underlying WM
- windowMovePeer = WindowMovePeerX11.INSTANCE;
- }
-
- boolean objectIsInstanceOf(Object o, String className) {
- Objects.requireNonNull(o);
- return o.getClass().getName().equals(className);
- }
-
- void startMovingTogetherWithMouse(Window window, int mouseButton) {
- Objects.requireNonNull(window);
- windowMovePeer.startMovingWindowTogetherWithMouse(window, mouseButton);
- }
- }
-
// ************************** JBR stuff *******************************
private volatile boolean ignoreMouseEvents;
diff --git a/src/java.desktop/share/classes/sun/font/FontAccess.java b/src/java.desktop/share/classes/sun/font/FontAccess.java
index 70359e237809..7343917e39ad 100644
--- a/src/java.desktop/share/classes/sun/font/FontAccess.java
+++ b/src/java.desktop/share/classes/sun/font/FontAccess.java
@@ -47,4 +47,7 @@ public abstract class FontAccess {
public abstract void setWithFallback(Font f);
public abstract boolean isCreatedFont(Font f);
public abstract FontPeer getFontPeer(Font f);
+ public abstract String[] getFeatures(Font font);
+ public abstract boolean isComplexRendering(Font font);
+ public abstract boolean isKerning(Font font);
}
diff --git a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java
index 844c16b1f6f2..8e05fdb9cb51 100644
--- a/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java
+++ b/src/java.desktop/share/classes/sun/font/FontDesignMetrics.java
@@ -43,7 +43,7 @@ import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import com.jetbrains.desktop.FontExtensions;
+import com.jetbrains.exported.JBRApi;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
@@ -481,7 +481,7 @@ public final class FontDesignMetrics extends FontMetrics {
assert (data instanceof String || data instanceof char[]);
float width = 0;
- if (overrider == null && FontExtensions.isComplexRendering(font) && len > 0) {
+ if (overrider == null && FontAccess.getFontAccess().isComplexRendering(font) && len > 0) {
return textLayoutBounds(data, off, len);
}
@@ -492,7 +492,7 @@ public final class FontDesignMetrics extends FontMetrics {
return new Rectangle2D.Float(0f, -ascent, width, height);
}
- boolean isKerning = FontExtensions.isKerning(font);
+ boolean isKerning = FontAccess.getFontAccess().isKerning(font);
float consecutiveDoubleCharacterWidth = 0f;
char prev = 0;
for (int i = off; i < off + len; i++) {
@@ -608,7 +608,9 @@ public final class FontDesignMetrics extends FontMetrics {
return height;
}
- private static class Accessor { // used by JBR API
+ @JBRApi.Service
+ @JBRApi.Provides("FontMetricsAccessor")
+ private static final class Accessor {
// Keeping metrics instances here prevents them from being garbage collected
// and being re-created by FontDesignMetrics.getMetrics method
private final Set PINNED_METRICS = new HashSet<>();
@@ -647,7 +649,8 @@ public final class FontDesignMetrics extends FontMetrics {
}
}
- private interface Overrider { // used by JBR API
+ @JBRApi.Provided("FontMetricsAccessor.Overrider")
+ private interface Overrider {
float charWidth(int codePoint);
}
}
diff --git a/src/java.desktop/share/classes/sun/font/FontUtilities.java b/src/java.desktop/share/classes/sun/font/FontUtilities.java
index 1f71d1c26fcd..7e2f3b73ed70 100644
--- a/src/java.desktop/share/classes/sun/font/FontUtilities.java
+++ b/src/java.desktop/share/classes/sun/font/FontUtilities.java
@@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
import javax.swing.plaf.FontUIResource;
+import com.jetbrains.exported.JBRApi;
import sun.awt.OSInfo;
import sun.util.logging.PlatformLogger;
@@ -126,7 +127,8 @@ public final class FontUtilities {
}
}
- static Dimension getSubpixelResolution() {
+ @JBRApi.Provides("FontExtensions")
+ private static Dimension getSubpixelResolution() {
return subpixelResolution;
}
diff --git a/src/java.desktop/share/classes/sun/font/GlyphLayout.java b/src/java.desktop/share/classes/sun/font/GlyphLayout.java
index 87094b617074..9e0e8565847e 100644
--- a/src/java.desktop/share/classes/sun/font/GlyphLayout.java
+++ b/src/java.desktop/share/classes/sun/font/GlyphLayout.java
@@ -68,8 +68,6 @@
package sun.font;
-import com.jetbrains.desktop.FontExtensions;
-
import java.lang.ref.SoftReference;
import java.awt.Font;
import java.awt.font.FontRenderContext;
@@ -176,7 +174,7 @@ public final class GlyphLayout {
* leave pt and the gvdata unchanged.
*/
public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int slot, int slotShift, int baseIndex, TextRecord text,
- boolean ltrDirection, Map features, Point2D.Float pt, GVData data);
+ boolean ltrDirection, String[] features, Point2D.Float pt, GVData data);
}
/**
@@ -435,7 +433,7 @@ public final class GlyphLayout {
EngineRecord er = _erecords.get(ix);
for (;;) {
try {
- er.layout(ltrDirection, FontExtensions.getFeatures(font));
+ er.layout(ltrDirection, FontAccess.getFontAccess().getFeatures(font));
break;
}
catch (IndexOutOfBoundsException e) {
@@ -643,7 +641,7 @@ public final class GlyphLayout {
this.engine = _lef.getEngine(key); // flags?
}
- void layout(boolean ltrDirection, Map features) {
+ void layout(boolean ltrDirection, String[] features) {
_textRecord.start = start;
_textRecord.limit = limit;
engine.layout(_sd, _mat, ptSize, slot, slotShift, start - _offset, _textRecord,
diff --git a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java
index 65bcab0d17ef..2c5d1a491d90 100644
--- a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java
+++ b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java
@@ -30,7 +30,6 @@
package sun.font;
-import com.jetbrains.desktop.FontExtensions;
import sun.font.GlyphLayout.*;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
@@ -39,7 +38,6 @@ import java.awt.geom.Point2D;
import java.lang.foreign.MemorySegment;
import java.lang.ref.SoftReference;
import java.util.Arrays;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.WeakHashMap;
@@ -182,7 +180,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
}
public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int slot, int slotShift,
- int baseIndex, TextRecord tr, boolean ltrDirection, Map features,
+ int baseIndex, TextRecord tr, boolean ltrDirection, String[] features,
Point2D.Float pt, GVData data) {
Font2D font = key.font();
@@ -193,7 +191,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
HBShaper.shape(font, strike, ptSize, mat, face,
tr.text, data, key.script(),
tr.start, tr.limit, baseIndex, pt,
- ltrDirection, FontExtensions.featuresToString(features),
+ ltrDirection, String.join(";", features),
slot, slotShift);
}
} else {
@@ -202,7 +200,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
SunLayoutEngine.shape(font, strike, ptSize, mat, pFace,
tr.text, data, key.script(),
tr.start, tr.limit, baseIndex, pt,
- ltrDirection, FontExtensions.featuresToString(features),
+ ltrDirection, String.join(";", features),
slot, slotShift);
}
}
diff --git a/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java b/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java
index e40082d97b3e..c5dc69873758 100644
--- a/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java
+++ b/src/java.desktop/share/classes/sun/swing/AccessibleAnnouncer.java
@@ -26,6 +26,7 @@
package sun.swing;
+import com.jetbrains.exported.JBRApi;
import jdk.internal.misc.InnocuousThread;
import sun.awt.AWTThreading;
@@ -72,7 +73,8 @@ public class AccessibleAnnouncer {
* @param str string for announcing
* @param priority priority for announcing
*/
- public static void announce(Accessible a, final String str, final int priority) throws Exception {
+ @JBRApi.Provides("AccessibleAnnouncer")
+ public static void announce(Accessible a, final String str, final int priority) {
if (str == null ||
priority != ANNOUNCE_WITHOUT_INTERRUPTING_CURRENT_OUTPUT &&
priority != ANNOUNCE_WITH_INTERRUPTING_CURRENT_OUTPUT) {
diff --git a/src/java.desktop/unix/classes/sun/awt/WindowMoveService.java b/src/java.desktop/unix/classes/sun/awt/WindowMoveService.java
new file mode 100644
index 000000000000..dd4574a2d820
--- /dev/null
+++ b/src/java.desktop/unix/classes/sun/awt/WindowMoveService.java
@@ -0,0 +1,28 @@
+package sun.awt;
+
+import com.jetbrains.exported.JBRApi;
+import sun.awt.X11.WindowMoveServiceX11;
+
+import java.awt.*;
+
+@JBRApi.Service
+@JBRApi.Provides("WindowMove")
+public interface WindowMoveService {
+
+ private static WindowMoveService create() {
+ JBRApi.ServiceNotAvailableException exception;
+ try {
+ return new WindowMoveServiceX11();
+ } catch (JBRApi.ServiceNotAvailableException e) {
+ exception = e;
+ }
+// try {
+// return new WindowMoveServiceWL();
+// } catch (JBRApi.ServiceNotAvailableException e) {
+// exception.addSuppressed(e);
+// }
+ throw exception;
+ }
+
+ void startMovingTogetherWithMouse(Window window, int mouseButton);
+}
diff --git a/src/java.desktop/unix/classes/sun/awt/X11/WindowMoveServiceX11.java b/src/java.desktop/unix/classes/sun/awt/X11/WindowMoveServiceX11.java
new file mode 100644
index 000000000000..8c6b306509d8
--- /dev/null
+++ b/src/java.desktop/unix/classes/sun/awt/X11/WindowMoveServiceX11.java
@@ -0,0 +1,39 @@
+package sun.awt.X11;
+
+import com.jetbrains.exported.JBRApi;
+import sun.awt.AWTAccessor;
+import sun.awt.WindowMoveService;
+
+import java.awt.*;
+import java.awt.peer.ComponentPeer;
+import java.util.Objects;
+
+public class WindowMoveServiceX11 implements WindowMoveService {
+
+ public WindowMoveServiceX11() {
+ final var toolkit = Toolkit.getDefaultToolkit();
+ final var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+ if (toolkit == null || ge == null
+ || !toolkit.getClass().getName().equals("sun.awt.X11.XToolkit")
+ || !ge.getClass().getName().equals("sun.awt.X11GraphicsEnvironment")) {
+ throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment");
+ }
+
+ if (!((XToolkit)Toolkit.getDefaultToolkit()).isWindowMoveSupported()) {
+ throw new JBRApi.ServiceNotAvailableException("Window manager does not support _NET_WM_MOVE_RESIZE");
+ }
+ }
+
+ @Override
+ public void startMovingTogetherWithMouse(Window window, int mouseButton) {
+ Objects.requireNonNull(window);
+
+ final AWTAccessor.ComponentAccessor acc = AWTAccessor.getComponentAccessor();
+ ComponentPeer peer = acc.getPeer(window);
+ if (peer instanceof XWindowPeer xWindowPeer) {
+ xWindowPeer.startMovingTogetherWithMouse(mouseButton);
+ } else {
+ throw new IllegalArgumentException("AWT window must have XWindowPeer as its peer");
+ }
+ }
+}
diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java b/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java
index 2a27c10fd37e..b17317642288 100644
--- a/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java
+++ b/src/java.desktop/unix/classes/sun/awt/X11/XWindowPeer.java
@@ -37,7 +37,6 @@ import java.util.Set;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
-import com.jetbrains.internal.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.AWTAccessor.ComponentAccessor;
import sun.awt.DisplayChangedListener;
@@ -2573,42 +2572,4 @@ class XWindowPeer extends XPanelPeer implements WindowPeer,
setGrab(false);
XWM.getWM().startMovingWindowTogetherWithMouse(getParentTopLevel().getWindow(), getLastButtonPressAbsLocation(), mouseButton);
}
-
- private static void startMovingWindowTogetherWithMouse(Window window, int mouseButton) {
- final AWTAccessor.ComponentAccessor acc = AWTAccessor.getComponentAccessor();
- ComponentPeer peer = acc.getPeer(window);
- if (peer instanceof XWindowPeer xWindowPeer) {
- xWindowPeer.startMovingTogetherWithMouse(mouseButton);
- } else {
- throw new IllegalArgumentException("AWT window must have XWindowPeer as its peer");
- }
- }
-
- private static class WindowMoveService {
- WindowMoveService() {
- final var toolkit = Toolkit.getDefaultToolkit();
- final var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
- if (toolkit == null || ge == null
- || !toolkit.getClass().getName().equals("sun.awt.X11.XToolkit")
- || !ge.getClass().getName().equals("sun.awt.X11GraphicsEnvironment")) {
- throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment");
- }
-
- if (!((XToolkit)Toolkit.getDefaultToolkit()).isWindowMoveSupported()) {
- throw new JBRApi.ServiceNotAvailableException("Window manager does not support _NET_WM_MOVE_RESIZE");
- }
- }
-
- void startMovingTogetherWithMouse(Window window, int mouseButton) {
- Objects.requireNonNull(window);
-
- final AWTAccessor.ComponentAccessor acc = AWTAccessor.getComponentAccessor();
- ComponentPeer peer = acc.getPeer(window);
- if (peer instanceof XWindowPeer xWindowPeer) {
- xWindowPeer.startMovingTogetherWithMouse(mouseButton);
- } else {
- throw new IllegalArgumentException("AWT window must have XWindowPeer as its peer");
- }
- }
- }
}
diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java
index 57dc477d5811..d310b1829e77 100644
--- a/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java
+++ b/src/java.desktop/windows/classes/sun/awt/windows/WFramePeer.java
@@ -34,6 +34,7 @@ import java.awt.Rectangle;
import java.awt.peer.ComponentPeer;
import java.awt.peer.FramePeer;
+import com.jetbrains.exported.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.im.InputMethodManager;
@@ -260,7 +261,7 @@ class WFramePeer extends WWindowPeer implements FramePeer {
private native void synthesizeWmActivate(boolean activate);
- // JBR API internals
+ @JBRApi.Provides("java.awt.Window.CustomTitleBarPeer#update")
private static void updateCustomTitleBar(ComponentPeer peer) {
// In native code AwtDialog is actually a descendant of AwtFrame,
// so we don't distinguish between WFramePeer and WDialogPeer here,
diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java
index 0bfa0473fe32..06a81de3790e 100644
--- a/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java
+++ b/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java
@@ -48,7 +48,6 @@ import java.awt.event.FocusEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.geom.AffineTransform;
-import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.DataBufferInt;
@@ -61,6 +60,7 @@ import java.util.List;
import javax.swing.JRootPane;
import javax.swing.RootPaneContainer;
+import com.jetbrains.exported.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.AppContext;
import sun.awt.DisplayChangedListener;
@@ -832,7 +832,7 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer,
}
}
- // JBR API internals
+ @JBRApi.Provides("RoundedCornersManager")
private static void setRoundedCorners(Window window, Object params) {
Object peer = AWTAccessor.getComponentAccessor().getPeer(window);
if (peer instanceof WWindowPeer) {
diff --git a/src/jetbrains.api/src/com/jetbrains/AccessibleAnnouncer.java b/src/jetbrains.api/src/com/jetbrains/AccessibleAnnouncer.java
deleted file mode 100644
index 19afe948c270..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/AccessibleAnnouncer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2000-2022 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import javax.accessibility.Accessible;
-
-/**
- * This interface provides the ability to speak a given string using screen readers.
- *
- */
-public interface AccessibleAnnouncer {
-
- /*CONST sun.swing.AccessibleAnnouncer.ANNOUNCE_**/
-
- /**
- * This method makes an announcement with the specified priority from an accessible to which the announcing relates
- *
- * @param a an accessible to which the announcing relates
- * @param str string for announcing
- * @param priority priority for announcing
- */
- void announce(Accessible a, final String str, final int priority);
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/CustomWindowDecoration.java b/src/jetbrains.api/src/com/jetbrains/CustomWindowDecoration.java
deleted file mode 100644
index 6ec882571c09..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/CustomWindowDecoration.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2000-2022 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Custom window decoration allows merging of window content with native title bar,
- * which is usually done by treating title bar as part of client area, but with some
- * special behavior like dragging or maximizing on double click.
- * @implNote Behavior is platform-dependent, only macOS and Windows are supported.
- */
-@Deprecated(forRemoval=true)
-public interface CustomWindowDecoration {
-
- /*CONST java.awt.Window.*_HIT_SPOT*/
- /*CONST java.awt.Window.*_BUTTON*/
- /*CONST java.awt.Window.MENU_BAR*/
- /*CONST java.awt.Window.DRAGGABLE_AREA*/
-
- /**
- * Turn custom decoration on or off, {@link #setCustomDecorationTitleBarHeight(Window, int)}
- * must be called to configure title bar height.
- */
- void setCustomDecorationEnabled(Window window, boolean enabled);
- /**
- * @see #setCustomDecorationEnabled(Window, boolean)
- */
- boolean isCustomDecorationEnabled(Window window);
-
- /**
- * Set list of hit test spots for a custom decoration.
- * Hit test spot is a special area inside custom-decorated title bar.
- * Each hit spot type has its own (probably platform-specific) behavior:
- *
- * - {@link #NO_HIT_SPOT} - default title bar area, can be dragged to move a window, maximizes on double-click
- * - {@link #OTHER_HIT_SPOT} - generic hit spot, window cannot be moved with drag or maximized on double-click
- * - {@link #MINIMIZE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip on Windows when hovered
- * - {@link #MAXIMIZE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip / snap layout menu on Windows when hovered
- * - {@link #CLOSE_BUTTON} - like {@link #OTHER_HIT_SPOT} but displays tooltip on Windows when hovered
- * - {@link #MENU_BAR} - currently no different from {@link #OTHER_HIT_SPOT}
- * - {@link #DRAGGABLE_AREA} - special type, moves window when dragged, but doesn't maximize on double-click
- *
- * @param spots pairs of hit spot shapes with corresponding types.
- * @implNote Hit spots are tested in sequence, so in case of overlapping areas, first found wins.
- */
- void setCustomDecorationHitTestSpots(Window window, List> spots);
- /**
- * @see #setCustomDecorationHitTestSpots(Window, List)
- */
- List> getCustomDecorationHitTestSpots(Window window);
-
- /**
- * Set height of custom window decoration, won't take effect until custom decoration
- * is enabled via {@link #setCustomDecorationEnabled(Window, boolean)}.
- */
- void setCustomDecorationTitleBarHeight(Window window, int height);
- /**
- * @see #setCustomDecorationTitleBarHeight(Window, int)
- */
- int getCustomDecorationTitleBarHeight(Window window);
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/DesktopActions.java b/src/jetbrains.api/src/com/jetbrains/DesktopActions.java
deleted file mode 100644
index f2efcb477a26..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/DesktopActions.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2000-2022 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-
-public interface DesktopActions {
-
- void setHandler(Handler handler);
-
- interface Handler {
- default void open(File file) throws IOException { throw new UnsupportedOperationException(); }
- default void edit(File file) throws IOException { throw new UnsupportedOperationException(); }
- default void print(File file) throws IOException { throw new UnsupportedOperationException(); }
- default void mail(URI mailtoURL) throws IOException { throw new UnsupportedOperationException(); }
- default void browse(URI uri) throws IOException { throw new UnsupportedOperationException(); }
- }
-
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/ExtendedGlyphCache.java b/src/jetbrains.api/src/com/jetbrains/ExtendedGlyphCache.java
deleted file mode 100644
index 32f000d43d71..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/ExtendedGlyphCache.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-
-@Deprecated(forRemoval=true)
-public interface ExtendedGlyphCache {
- Dimension getSubpixelResolution();
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/FontExtensions.java b/src/jetbrains.api/src/com/jetbrains/FontExtensions.java
deleted file mode 100644
index 9c4a6cc679c6..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/FontExtensions.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-import java.io.Serial;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Optional;
-import java.util.TreeMap;
-
-public interface FontExtensions {
- public static final int FEATURE_ON = 1;
- public static final int FEATURE_OFF = 0;
-
- /**
- * The list of all supported features. For feature's description look at
- * documentation
- * The following features: KERN, LIGA, CALT are missing intentionally. These features will be added automatically
- * by adding {@link java.awt.font.TextAttribute} to {@link java.awt.Font}:
- *
- * - Attribute {@link java.awt.font.TextAttribute#KERNING} manages KERN feature
- * - Attribute {@link java.awt.font.TextAttribute#LIGATURES} manages LIGA and CALT features
- *
- */
- enum FeatureTag {
- AALT, ABVF, ABVM, ABVS, AFRC, AKHN, BLWF, BLWM, BLWS, CASE, CCMP, CFAR, CHWS, CJCT, CLIG, CPCT, CPSP, CSWH, CURS,
- CV01, CV02, CV03, CV04, CV05, CV06, CV07, CV08, CV09, CV10, CV11, CV12, CV13, CV14, CV15, CV16, CV17, CV18, CV19,
- CV20, CV21, CV22, CV23, CV24, CV25, CV26, CV27, CV28, CV29, CV30, CV31, CV32, CV33, CV34, CV35, CV36, CV37, CV38,
- CV39, CV40, CV41, CV42, CV43, CV44, CV45, CV46, CV47, CV48, CV49, CV50, CV51, CV52, CV53, CV54, CV55, CV56, CV57,
- CV58, CV59, CV60, CV61, CV62, CV63, CV64, CV65, CV66, CV67, CV68, CV69, CV70, CV71, CV72, CV73, CV74, CV75, CV76,
- CV77, CV78, CV79, CV80, CV81, CV82, CV83, CV84, CV85, CV86, CV87, CV88, CV89, CV90, CV91, CV92, CV93, CV94, CV95,
- CV96, CV97, CV98, CV99, C2PC, C2SC, DIST, DLIG, DNOM, DTLS, EXPT, FALT, FIN2, FIN3, FINA, FLAC, FRAC, FWID, HALF,
- HALN, HALT, HIST, HKNA, HLIG, HNGL, HOJO, HWID, INIT, ISOL, ITAL, JALT, JP78, JP83, JP90, JP04, LFBD, LJMO, LNUM,
- LOCL, LTRA, LTRM, MARK, MED2, MEDI, MGRK, MKMK, MSET, NALT, NLCK, NUKT, NUMR, ONUM, OPBD, ORDN, ORNM, PALT, PCAP,
- PKNA, PNUM, PREF, PRES, PSTF, PSTS, PWID, QWID, RAND, RCLT, RKRF, RLIG, RPHF, RTBD, RTLA, RTLM, RUBY, RVRN, SALT,
- SINF, SIZE, SMCP, SMPL, SS01, SS02, SS03, SS04, SS05, SS06, SS07, SS08, SS09, SS10, SS11, SS12, SS13, SS14, SS15,
- SS16, SS17, SS18, SS19, SS20, SSTY, STCH, SUBS, SUPS, SWSH, TITL, TJMO, TNAM, TNUM, TRAD, TWID, UNIC, VALT, VATU,
- VCHW, VERT, VHAL, VJMO, VKNA, VKRN, VPAL, VRT2, VRTR, ZERO;
-
- public String getName() {
- return toString().toLowerCase();
- }
-
- public static Optional getFeatureTag(String str) {
- try {
- return Optional.of(FeatureTag.valueOf(str.toUpperCase()));
- } catch (IllegalArgumentException ignored) {
- return Optional.empty();
- }
- }
- }
-
- final class Features extends TreeMap {
- @Serial
- private static final long serialVersionUID = 1L;
-
- public Features(Map map) {
- super(map);
- }
-
- public Features(FontExtensions.FeatureTag... features) {
- Arrays.stream(features).forEach(tag -> put(tag, FontExtensions.FEATURE_ON));
- }
-
- private TreeMap getAsTreeMap() {
- TreeMap res = new TreeMap<>();
- forEach((tag, value) -> res.put(tag.getName(), value));
- return res;
- }
- }
-
- /**
- * This method derives a new {@link java.awt.Font} object with certain set of {@link FeatureTag}
- * and correspoinding values
- *
- * @param font basic font
- * @param features set of OpenType's features wrapped inside {@link Features}
- */
- Font deriveFontWithFeatures(Font font, Features features);
-
- Dimension getSubpixelResolution();
-}
\ No newline at end of file
diff --git a/src/jetbrains.api/src/com/jetbrains/FontMetricsAccessor.java b/src/jetbrains.api/src/com/jetbrains/FontMetricsAccessor.java
deleted file mode 100644
index 90d2a3c7a486..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/FontMetricsAccessor.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-import java.awt.font.FontRenderContext;
-import java.awt.image.BufferedImage;
-
-/**
- * Provides convenience methods to access {@link FontMetrics} instances, and obtain character advances from them without
- * rounding. Also provides an (unsafe) way to override character advances in those instances with arbitrary specified
- * values.
- */
-public interface FontMetricsAccessor {
- /**
- * Returns a {@link FontMetrics} instance for the given {@link Font} and {@link FontRenderContext}. This is supposed
- * to be the same instance as returned by the public API methods ({@link Graphics#getFontMetrics()},
- * {@link Graphics#getFontMetrics(Font)} and {@link Component#getFontMetrics(Font)}) in the same context.
- */
- FontMetrics getMetrics(Font font, FontRenderContext context);
-
- /**
- * Returns not rounded value for the character's advance. It's not accessible directly via public
- * {@link FontMetrics} API, one can only extract it from one of the {@code getStringBounds} methods' output.
- */
- float codePointWidth(FontMetrics metrics, int codePoint);
-
- /**
- * Allows to override advance values returned by the specified {@link FontMetrics} instance. It's not generally
- * guaranteed the invocation of this method actually has the desired effect. One can verify whether it's the case
- * using {@link #hasOverride(FontMetrics)} method.
- *
- * A subsequent invocation of this method will override any previous invocations. Passing {@code null} as the
- * {@code overrider} value will remove any override, if it was set up previously.
- *
- * While this method operates on a specific {@link FontMetrics} instance, it's expected that overriding will have
- * effect regardless of the method font metrics are accessed, for all future character advance requests. This is
- * feasible, as JDK implementation generally uses the same cached {@link FontMetrics} instance in identical
- * contexts.
- *
- * The method doesn't provides any concurrency guarantees, i.e. the override isn't guaranteed to be immediately
- * visible for font metrics readers in other threads.
- *
- * WARNING. This method can break the consistency of a UI application, as previously calculated/returned advance
- * values can already be used/cached by some UI components. It's the calling code's responsibility to remediate such
- * consequences (e.g. re-validating all components which use the relevant font might be required).
- */
- void setOverride(FontMetrics metrics, Overrider overrider);
-
- /**
- * Tells whether character advances returned by the specified {@link FontMetrics} instance are overridden using the
- * previous {@link #setOverride(FontMetrics, Overrider)} call.
- */
- boolean hasOverride(FontMetrics metrics);
-
- /**
- * Removes all overrides set previously by {@link #setOverride(FontMetrics, Overrider)} invocations.
- */
- void removeAllOverrides();
-
- /**
- * @see #setOverride(FontMetrics, Overrider)
- */
- interface Overrider {
- /**
- * Returning {@code NaN} means the default (not overridden) value should be used.
- */
- float charWidth(int codePoint);
- }
-
- class __Fallback implements FontMetricsAccessor {
- private final Graphics2D g;
-
- __Fallback() {
- g = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB).createGraphics();
- }
-
- @Override
- public FontMetrics getMetrics(Font font, FontRenderContext context) {
- synchronized (g) {
- g.setTransform(context.getTransform());
- g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, context.getAntiAliasingHint());
- g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, context.getFractionalMetricsHint());
- return g.getFontMetrics(font);
- }
- }
-
- @Override
- public float codePointWidth(FontMetrics metrics, int codePoint) {
- String s = new String(new int[]{codePoint}, 0, 1);
- return (float) metrics.getFont().getStringBounds(s, metrics.getFontRenderContext()).getWidth();
- }
-
- @Override
- public void setOverride(FontMetrics metrics, Overrider overrider) {}
-
- @Override
- public boolean hasOverride(FontMetrics metrics) {
- return false;
- }
-
- @Override
- public void removeAllOverrides() {}
- }
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/FontOpenTypeFeatures.java b/src/jetbrains.api/src/com/jetbrains/FontOpenTypeFeatures.java
deleted file mode 100644
index 01559a4dec81..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/FontOpenTypeFeatures.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2024 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-import java.util.Set;
-
-@Deprecated(forRemoval=true)
-public interface FontOpenTypeFeatures {
- /**
- * This method returns set of OpenType's features converted to String supported by current font
- *
- * @param font basic font
- */
- Set getAvailableFeatures(Font font);
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java b/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java
deleted file mode 100644
index 902b0421f0f2..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/GraphicsUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-import java.awt.geom.Rectangle2D;
-
-public interface GraphicsUtils {
- Graphics2D createConstrainableGraphics(Graphics2D graphics2D,
- ConstrainableGraphics2D constrainable);
-
- public interface ConstrainableGraphics2D {
- Object getDestination();
- void constrain(Rectangle2D region);
- void constrain(int x, int y, int w, int h);
- }
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/JBR.java b/src/jetbrains.api/src/com/jetbrains/JBR.java
deleted file mode 100644
index bbfc953a5b44..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/JBR.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * This class is an entry point into JBR API.
- * JBR API is a collection of services, classes, interfaces, etc.,
- * which require tight interaction with JRE and therefore are implemented inside JBR.
- * JBR API consists of two parts:
- *
- * - Client side - {@code jetbrains.api} module, mostly containing interfaces
- * - JBR side - actual implementation code inside JBR
- *
- * Client and JBR side are linked dynamically at runtime and do not have to be of the same version.
- * In some cases (e.g. running on different JRE or old JBR) system will not be able to find
- * implementation for some services, so you'll need a fallback behavior for that case.
- * Simple usage example:
- * {@code
- * if (JBR.isSomeServiceSupported()) {
- * JBR.getSomeService().doSomething();
- * } else {
- * planB();
- * }
- * }
- * @implNote JBR API is initialized on first access to this class (in static initializer).
- * Actual implementation is linked on demand, when corresponding service is requested by client.
- */
-public class JBR {
-
- private static final ServiceApi api;
- private static final Exception bootstrapException;
- static {
- ServiceApi a = null;
- Exception exception = null;
- try {
- a = (ServiceApi) Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap")
- .getMethod("bootstrap", MethodHandles.Lookup.class)
- .invoke(null, MethodHandles.lookup());
- } catch (InvocationTargetException e) {
- Throwable t = e.getCause();
- if (t instanceof Error error) throw error;
- else throw new Error(t);
- } catch (IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) {
- exception = e;
- }
- api = a;
- bootstrapException = exception;
- }
-
- private JBR() {}
-
- private static T getService(Class interFace, FallbackSupplier fallback) {
- T service = getService(interFace);
- try {
- return service != null ? service : fallback != null ? fallback.get() : null;
- } catch (Throwable ignore) {
- return null;
- }
- }
-
- static T getService(Class interFace) {
- return api == null ? null : api.getService(interFace);
- }
-
- /**
- * @return true when running on JBR which implements JBR API
- */
- public static boolean isAvailable() {
- return api != null;
- }
-
- /**
- * @return JBR API version in form {@code JBR.MAJOR.MINOR.PATCH}
- * @implNote This is an API version, which comes with client application,
- * it has nothing to do with JRE it runs on.
- */
- public static String getApiVersion() {
- return "/*API_VERSION*/";
- }
-
- /**
- * Internal API interface, contains most basic methods for communication between client and JBR.
- */
- private interface ServiceApi {
-
- T getService(Class interFace);
- }
-
- @FunctionalInterface
- private interface FallbackSupplier {
- T get() throws Throwable;
- }
-
- // ========================== Generated metadata ==========================
-
- /**
- * Generated client-side metadata, needed by JBR when linking the implementation.
- */
- private static final class Metadata {
- private static final String[] KNOWN_SERVICES = {/*KNOWN_SERVICES*/};
- private static final String[] KNOWN_PROXIES = {/*KNOWN_PROXIES*/};
- }
-
- // ======================= Generated static methods =======================
-
- /*GENERATED_METHODS*/
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/JBRFileDialog.java b/src/jetbrains.api/src/com/jetbrains/JBRFileDialog.java
deleted file mode 100644
index e64cac60ed29..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/JBRFileDialog.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2000-2024 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-
-/**
- * Extensions to the AWT {@code FileDialog} that allow clients fully use a native file chooser
- * on supported platforms (currently macOS and Windows; the latter requires setting
- * {@code sun.awt.windows.useCommonItemDialog} property to {@code true}).
- */
-public interface JBRFileDialog {
-
- /*CONST com.jetbrains.desktop.JBRFileDialog.*_HINT*/
-
- static JBRFileDialog get(FileDialog dialog) {
- if (JBRFileDialogService.INSTANCE == null) return null;
- else return JBRFileDialogService.INSTANCE.getFileDialog(dialog);
- }
-
- /**
- * Set file dialog hints:
- *
- * - SELECT_FILES_HINT, SELECT_DIRECTORIES_HINT - whether to select files, directories, or both;
- * if neither of the two is set, the behavior is platform-specific
- * - CREATE_DIRECTORIES_HINT - whether to allow creating directories or not (macOS)
- *
- */
- void setHints(int hints);
-
- /**
- * @see #setHints(int)
- */
- int getHints();
-
- /*CONST com.jetbrains.desktop.JBRFileDialog.*_KEY*/
-
- /**
- * Change text of UI elements (Windows).
- * Supported keys:
- *
- * - OPEN_FILE_BUTTON_KEY - "open" button when a file is selected in the list
- * - OPEN_DIRECTORY_BUTTON_KEY - "open" button when a directory is selected in the list
- * - ALL_FILES_COMBO_KEY - "all files" item in the file filter combo box
- *
- */
- void setLocalizationString(String key, String text);
-
- /** @deprecated use {@link #setLocalizationString} */
- @Deprecated(forRemoval = true)
- void setLocalizationStrings(String openButtonText, String selectFolderButtonText);
-
- /**
- * Set file filter - a set of file extensions for files to be visible (Windows)
- * or not greyed out (macOS), and a name for the file filter combo box (Windows).
- */
- void setFileFilterExtensions(String fileFilterDescription, String[] fileFilterExtensions);
-}
-
-interface JBRFileDialogService {
- JBRFileDialogService INSTANCE = JBR.getService(JBRFileDialogService.class);
- JBRFileDialog getFileDialog(FileDialog dialog);
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/Jstack.java b/src/jetbrains.api/src/com/jetbrains/Jstack.java
deleted file mode 100644
index 702e8a808e9f..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/Jstack.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.util.function.Supplier;
-
-public interface Jstack {
- /**
- * Specifies a supplier of additional information to be included into
- * the output of {@code jstack}. The String supplied will be included
- * as-is with no header surrounded only with line breaks.
- *
- * {@code infoSupplier} will be invoked on an unspecified thread that
- * must not be left blocked for a long time.
- *
- * Only one supplier is allowed, so subsequent calls to
- * {@code includeInfoFrom} will overwrite the previously specified supplier.
- *
- * @param infoSupplier a supplier of {@code String} values to be
- * included into jstack's output. If {@code null},
- * then the previously registered supplier is removed
- * (if any) and no extra info will be included.
- */
- void includeInfoFrom(Supplier infoSupplier);
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/ProjectorUtils.java b/src/jetbrains.api/src/com/jetbrains/ProjectorUtils.java
deleted file mode 100644
index 2cddfac79b0b..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/ProjectorUtils.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2022 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-import java.util.function.Supplier;
-
-public interface ProjectorUtils {
- void setLocalGraphicsEnvironmentProvider(Supplier geProvider);
- void overrideGraphicsEnvironment(GraphicsEnvironment overriddenGE);
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/RoundedCornersManager.java b/src/jetbrains.api/src/com/jetbrains/RoundedCornersManager.java
deleted file mode 100644
index 4810631e9a8b..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/RoundedCornersManager.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.Window;
-
-/**
- * This manager allows decorate awt Window with rounded corners.
- * Appearance depends from operating system.
- */
-public interface RoundedCornersManager {
- /**
- * @param params for macOS is Float object with radius or
- * Array with {Float for radius, Integer for border width, java.awt.Color for border color}.
- *
- * @param params for Windows 11 is String with values:
- * "default" - let the system decide whether or not to round window corners,
- * "none" - never round window corners,
- * "full" - round the corners if appropriate,
- * "small" - round the corners if appropriate, with a small radius.
- */
- void setRoundedCorners(Window window, Object params);
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/WindowDecorations.java b/src/jetbrains.api/src/com/jetbrains/WindowDecorations.java
deleted file mode 100644
index 06c523f3082c..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/WindowDecorations.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.*;
-import java.util.Map;
-
-/**
- * Window decorations consist of title bar, window controls and border.
- * @see WindowDecorations.CustomTitleBar
- */
-public interface WindowDecorations {
-
- /**
- * If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the
- * top of the frame with window controls painted over the client area.
- * {@code customTitleBar=null} resets to the default appearance with system-provided title bar.
- * @see CustomTitleBar
- * @see #createCustomTitleBar()
- */
- void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar);
-
- /**
- * If {@code customTitleBar} is not null, system-provided title bar is removed and client area is extended to the
- * top of the dialog with window controls painted over the client area.
- * {@code customTitleBar=null} resets to the default appearance with system-provided title bar.
- * @see CustomTitleBar
- * @see #createCustomTitleBar()
- */
- void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar);
-
- /**
- * You must {@linkplain CustomTitleBar#setHeight(float) set title bar height} before adding it to a window.
- * @see CustomTitleBar
- * @see #setCustomTitleBar(Frame, CustomTitleBar)
- * @see #setCustomTitleBar(Dialog, CustomTitleBar)
- */
- CustomTitleBar createCustomTitleBar();
-
- /**
- * Custom title bar allows merging of window content with native title bar,
- * which is done by treating title bar as part of client area, but with some
- * special behavior like dragging or maximizing on double click.
- * Custom title bar has {@linkplain CustomTitleBar#getHeight() height} and controls.
- * @implNote Behavior is platform-dependent, only macOS and Windows are supported.
- * @see #setCustomTitleBar(Frame, CustomTitleBar)
- */
- interface CustomTitleBar {
-
- /**
- * @return title bar height, measured in pixels from the top of client area, i.e. excluding top frame border.
- */
- float getHeight();
-
- /**
- * @param height title bar height, measured in pixels from the top of client area,
- * i.e. excluding top frame border. Must be > 0.
- */
- void setHeight(float height);
-
- /**
- * @see #putProperty(String, Object)
- */
- Map getProperties();
-
- /**
- * @see #putProperty(String, Object)
- */
- void putProperties(Map m);
-
- /**
- * Windows & macOS properties:
- *
- * - {@code controls.visible} : {@link Boolean} - whether title bar controls
- * (minimize/maximize/close buttons) are visible, default = true.
- *
- * Windows properties:
- *
- * - {@code controls.width} : {@link Number} - width of block of buttons (not individual buttons).
- * Note that dialogs have only one button, while frames usually have 3 of them.
- * - {@code controls.dark} : {@link Boolean} - whether to use dark or light color theme
- * (light or dark icons respectively).
- * - {@code controls..} : {@link Color} - precise control over button colors,
- * where {@code } is one of:
- *
- {@code foreground}
- {@code background}
- * and {@code } is one of:
- *
- * - {@code normal}
- * - {@code hovered}
- * - {@code pressed}
- * - {@code disabled}
- * - {@code inactive}
- *
- *
- */
- void putProperty(String key, Object value);
-
- /**
- * @return space occupied by title bar controls on the left (px)
- */
- float getLeftInset();
- /**
- * @return space occupied by title bar controls on the right (px)
- */
- float getRightInset();
-
- /**
- * By default, any component which has no cursor or mouse event listeners set is considered transparent for
- * native title bar actions. That is, dragging simple JPanel in title bar area will drag the
- * window, but dragging a JButton will not. Adding mouse listener to a component will prevent any native actions
- * inside bounds of that component.
- *
- * This method gives you precise control of whether to allow native title bar actions or not.
- *
- * - {@code client=true} means that mouse is currently over a client area. Native title bar behavior is disabled.
- * - {@code client=false} means that mouse is currently over a non-client area. Native title bar behavior is enabled.
- *
- * Intended usage:
- *
- * - This method must be called in response to all {@linkplain java.awt.event.MouseEvent mouse events}
- * except {@link java.awt.event.MouseEvent#MOUSE_EXITED} and {@link java.awt.event.MouseEvent#MOUSE_WHEEL}.
- * - This method is called per-event, i.e. when component has multiple listeners, you only need to call it once.
- * - If this method hadn't been called, title bar behavior is reverted back to default upon processing the event.
- *
- * Note that hit test value is relevant only for title bar area, e.g. calling
- * {@code forceHitTest(false)} will not make window draggable via non-title bar area.
- *
- * Example:
- * Suppose you have a {@code JPanel} in the title bar area. You want it to respond to right-click for
- * some popup menu, but also retain native drag and double-click behavior.
- *
- * CustomTitleBar titlebar = ...;
- * JPanel panel = ...;
- * MouseAdapter adapter = new MouseAdapter() {
- * private void hit() { titlebar.forceHitTest(false); }
- * public void mouseClicked(MouseEvent e) {
- * hit();
- * if (e.getButton() == MouseEvent.BUTTON3) ...;
- * }
- * public void mousePressed(MouseEvent e) { hit(); }
- * public void mouseReleased(MouseEvent e) { hit(); }
- * public void mouseEntered(MouseEvent e) { hit(); }
- * public void mouseDragged(MouseEvent e) { hit(); }
- * public void mouseMoved(MouseEvent e) { hit(); }
- * };
- * panel.addMouseListener(adapter);
- * panel.addMouseMotionListener(adapter);
- *
- */
- void forceHitTest(boolean client);
-
- Window getContainingWindow();
- }
-}
diff --git a/src/jetbrains.api/src/com/jetbrains/WindowMove.java b/src/jetbrains.api/src/com/jetbrains/WindowMove.java
deleted file mode 100644
index 60d80c80c4db..000000000000
--- a/src/jetbrains.api/src/com/jetbrains/WindowMove.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains;
-
-import java.awt.Window;
-
-public interface WindowMove {
- /**
- * Starts moving the top-level parent window of the given window together with the mouse pointer.
- * The intended use is to facilitate the implementation of window management similar to the way
- * it is done natively on the platform.
- *
- * Preconditions for calling this method:
- *
- * - WM supports _NET_WM_MOVE_RESIZE (this is checked automatically when an implementation
- * of this interface is obtained).
- * - Mouse pointer is within this window's bounds.
- * - The mouse button specified by {@code mouseButton} is pressed.
- *
- *
- * Calling this method will make the window start moving together with the mouse pointer until
- * the specified mouse button is released or Esc is pressed. The conditions for cancelling
- * the move may differ between WMs.
- *
- * @param mouseButton indicates the mouse button that was pressed to start moving the window;
- * must be one of {@code MouseEvent.BUTTON1}, {@code MouseEvent.BUTTON2},
- * or {@code MouseEvent.BUTTON3}.
- */
- void startMovingTogetherWithMouse(Window window, int mouseButton);
-}
diff --git a/src/jetbrains.api/src/module-info.java b/src/jetbrains.api/src/module-info.java
deleted file mode 100644
index e103e1b859b6..000000000000
--- a/src/jetbrains.api/src/module-info.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-module jetbrains.api {
- exports com.jetbrains;
-
- requires static transitive java.desktop;
-}
\ No newline at end of file
diff --git a/src/jetbrains.api/tools/CheckVersion.java b/src/jetbrains.api/tools/CheckVersion.java
deleted file mode 100644
index 6cd7e01b90df..000000000000
--- a/src/jetbrains.api/tools/CheckVersion.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.*;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.*;
-import java.util.function.Function;
-
-public class CheckVersion {
- private static final Map> FILE_TRANSFORMERS = Map.of(
- "com/jetbrains/JBR.java", c -> {
- // Exclude API version from hash calculation
- int versionMethodIndex = c.indexOf("getApiVersion()");
- int versionStartIndex = c.indexOf("\"", versionMethodIndex) + 1;
- int versionEndIndex = c.indexOf("\"", versionStartIndex);
- return c.substring(0, versionStartIndex) + c.substring(versionEndIndex);
- }
- );
-
- private static Path gensrc;
-
- /**
- *
- * - $0 - absolute path to {@code JetBrainsRuntime/src/jetbrains.api} dir
- * - $1 - absolute path to gensrc dir ({@code JetBrainsRuntime/build//jbr-api/gensrc})
- * - $2 - true if hash mismatch is an error
- *
- */
- public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
- Path versionFile = Path.of(args[0]).resolve("version.properties");
- gensrc = Path.of(args[1]);
- boolean error = args[2].equals("true");
-
- Properties props = new Properties();
- props.load(Files.newInputStream(versionFile));
- String hash = SourceHash.calculate();
-
- if (hash.equals(props.getProperty("HASH"))) return;
- System.err.println("================================================================================");
- if (error) {
- System.err.println("Error: jetbrains.api code was changed, hash and API version must be updated in " + versionFile);
- } else {
- System.err.println("Warning: jetbrains.api code was changed, " +
- "update hash and increment API version in " + versionFile + " before committing these changes");
- }
- System.err.println("HASH = " + hash);
- if (!error) System.err.println("DO NOT COMMIT YOUR CHANGES WITH THIS WARNING");
- System.err.println("================================================================================");
- if (error) System.exit(-1);
- }
-
- private static class SourceHash {
-
- private static String calculate() throws NoSuchAlgorithmException, IOException {
- MessageDigest hash = MessageDigest.getInstance("MD5");
- calculate(gensrc, hash);
- byte[] digest = hash.digest();
- StringBuilder result = new StringBuilder();
- for (byte b : digest) {
- result.append(String.format("%X", b));
- }
- return result.toString();
- }
-
- private static void calculate(Path dir, MessageDigest hash) throws IOException {
- for (Entry f : findFiles(dir)) {
- hash.update(f.name.getBytes(StandardCharsets.UTF_8));
- hash.update(f.content.getBytes(StandardCharsets.UTF_8));
- }
- }
-
- private static List findFiles(Path dir) throws IOException {
- List files = new ArrayList<>();
- FileVisitor fileFinder = new SimpleFileVisitor<>() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
- try {
- Path rel = dir.relativize(file);
- StringBuilder name = new StringBuilder();
- for (int i = 0; i < rel.getNameCount(); i++) {
- if (!name.isEmpty()) name.append('/');
- name.append(rel.getName(i));
- }
- String content = Files.readString(file);
- String fileName = name.toString();
- files.add(new Entry(FILE_TRANSFORMERS.getOrDefault(fileName, c -> c).apply(content), fileName));
- return FileVisitResult.CONTINUE;
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- };
- Files.walkFileTree(dir, fileFinder);
- files.sort(Comparator.comparing(Entry::name));
- return files;
- }
-
- private record Entry(String content, String name) {}
- }
-}
diff --git a/src/jetbrains.api/tools/Gensrc.java b/src/jetbrains.api/tools/Gensrc.java
deleted file mode 100644
index 698be1640549..000000000000
--- a/src/jetbrains.api/tools/Gensrc.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.*;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.nio.file.StandardOpenOption.*;
-import static java.util.regex.Pattern.compile;
-
-/**
- * Codegen script for {@code jetbrains.api} module.
- * It produces "main" {@link com.jetbrains.JBR} class from template by
- * inspecting interfaces and implementation code and inserting
- * static utility methods for public services as well as some metadata
- * needed by JBR at runtime.
- */
-public class Gensrc {
-
- private static Path srcroot, src, gensrc;
- private static String apiVersion;
- private static JBRModules modules;
-
- /**
- *
- * - $0 - absolute path to {@code JetBrainsRuntime/src} dir
- * - $1 - absolute path to jbr-api output dir ({@code JetBrainsRuntime/build//jbr-api})
- * - $2 - {@code JBR} part of API version
- *
- */
- public static void main(String[] args) throws IOException {
- srcroot = Path.of(args[0]);
- Path module = srcroot.resolve("jetbrains.api");
- src = module.resolve("src");
- Path output = Path.of(args[1]);
- gensrc = output.resolve("gensrc");
- Files.createDirectories(gensrc);
-
- Properties props = new Properties();
- props.load(Files.newInputStream(module.resolve("version.properties")));
- apiVersion = args[2] + "." + props.getProperty("VERSION");
- Files.writeString(output.resolve("jbr-api.version"), apiVersion,
- CREATE, WRITE, TRUNCATE_EXISTING);
-
- modules = new JBRModules();
- generateFiles();
- }
-
- private static void generateFiles() throws IOException {
- Files.walkFileTree(src, new SimpleFileVisitor<>() {
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
- try {
- Path rel = src.relativize(file);
- Path output = gensrc.resolve(rel);
- Files.createDirectories(output.getParent());
- String content = generateContent(file.getFileName().toString(), Files.readString(file));
- Files.writeString(output, content, CREATE, WRITE, TRUNCATE_EXISTING);
- return FileVisitResult.CONTINUE;
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- });
- }
-
- private static String generateContent(String fileName, String content) throws IOException {
- return switch (fileName) {
- case "JBR.java" -> JBR.generate(content);
- default -> generate(content);
- };
- }
-
- private static String generate(String content) throws IOException {
- Pattern pattern = compile("/\\*CONST ((?:[a-zA-Z0-9]+\\.)+)([a-zA-Z0-9_*]+)\\s*\\*/");
- for (;;) {
- Matcher matcher = pattern.matcher(content);
- if (!matcher.find()) return content;
- String placeholder = matcher.group(0), file = matcher.group(1), name = matcher.group(2);
- file = file.substring(0, file.length() - 1).replace('.', '/') + ".java";
- List statements = new ArrayList<>();
- for (Path module : modules.paths) {
- Path f = module.resolve("share/classes").resolve(file);
- if (Files.exists(f)) {
- Pattern namePattern = compile(name.replaceAll("\\*", "\\\\w+"));
- Pattern statementPattern = compile(
- "((?:(?:MODS) ){2,3})([a-zA-Z0-9]+) (FIELD(?:, FIELD)*);"
- .replaceAll("MODS", "public|protected|private|static|final")
- .replaceAll("FIELD", "\\\\w+ = [\\\\w\"']+ ")
- .replaceAll(" ", "\\\\s+")
- .replaceAll(" ", "\\\\s*")
- );
- Matcher statementMatcher = statementPattern.matcher(Files.readString(f));
- while (statementMatcher.find()) {
- String mods = statementMatcher.group(1);
- if (!mods.contains("static") || !mods.contains("final")) continue;
- for (String s : statementMatcher.group(3).split(",")) {
- s = s.strip();
- String nm = s.substring(0, s.indexOf('=')).strip();
- if (!namePattern.matcher(nm).matches()) continue;
- statements.add("public static final " + statementMatcher.group(2) + " " + s + ";");
- }
- }
- break;
- }
- }
- if (statements.isEmpty()) throw new RuntimeException("Constant not found: " + placeholder);
- content = replaceTemplate(content, placeholder, statements, true);
- }
- }
-
- private static String findRegex(String src, Pattern regex) {
- Matcher matcher = regex.matcher(src);
- if (!matcher.find()) throw new IllegalArgumentException("Regex not found: " + regex.pattern());
- return matcher.group(1);
- }
-
- private static String replaceTemplate(String src, String placeholder, Iterable statements, boolean compact) {
- int placeholderIndex = src.indexOf(placeholder);
- int indent = 0;
- while (placeholderIndex - indent >= 1 && src.charAt(placeholderIndex - indent - 1) == ' ') indent++;
- int nextLineIndex = src.indexOf('\n', placeholderIndex + placeholder.length()) + 1;
- if (nextLineIndex == 0) nextLineIndex = placeholderIndex + placeholder.length();
- String before = src.substring(0, placeholderIndex - indent), after = src.substring(nextLineIndex);
- StringBuilder sb = new StringBuilder(before);
- boolean firstStatement = true;
- for (String s : statements) {
- if (!firstStatement && !compact) sb.append('\n');
- sb.append(s.indent(indent));
- firstStatement = false;
- }
- sb.append(after);
- return sb.toString();
- }
-
- /**
- * Code for generating {@link com.jetbrains.JBR} class.
- */
- private static class JBR {
-
- private static String generate(String content) {
- Service[] interfaces = findPublicServiceInterfaces();
- List statements = new ArrayList<>();
- for (Service i : interfaces) statements.add(generateMethodsForService(i));
- content = replaceTemplate(content, "/*GENERATED_METHODS*/", statements, false);
- content = content.replace("/*KNOWN_SERVICES*/",
- modules.services.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
- content = content.replace("/*KNOWN_PROXIES*/",
- modules.proxies.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")));
- content = content.replace("/*API_VERSION*/", apiVersion);
- return content;
- }
-
- private static Service[] findPublicServiceInterfaces() {
- Pattern javadocPattern = Pattern.compile("/\\*\\*((?:.|\n)*?)\\s*\\*/");
- Pattern deprecatedPattern = Pattern.compile("@Deprecated( *\\(.*?forRemoval *= *true.*?\\))?");
- return modules.services.stream()
- .map(fullName -> {
- if (fullName.indexOf('$') != -1) return null; // Only top level services can be public
- Path path = src.resolve(fullName.replace('.', '/') + ".java");
- if (!Files.exists(path)) return null;
- String name = fullName.substring(fullName.lastIndexOf('.') + 1);
- try {
- String content = Files.readString(path);
- int indexOfDeclaration = content.indexOf("public interface " + name);
- if (indexOfDeclaration == -1) return null;
- Matcher javadocMatcher = javadocPattern.matcher(content.substring(0, indexOfDeclaration));
- String javadoc;
- int javadocEnd;
- if (javadocMatcher.find()) {
- javadoc = javadocMatcher.group(1);
- javadocEnd = javadocMatcher.end();
- } else {
- javadoc = "";
- javadocEnd = 0;
- }
- Matcher deprecatedMatcher = deprecatedPattern.matcher(content.substring(javadocEnd, indexOfDeclaration));
- Status status;
- if (!deprecatedMatcher.find()) status = Status.NORMAL;
- else if (deprecatedMatcher.group(1) == null) status = Status.DEPRECATED;
- else status = Status.FOR_REMOVAL;
- return new Service(name, javadoc, status, content.contains("__Fallback"));
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- })
- .filter(Objects::nonNull).toArray(Service[]::new);
- }
-
- private static String generateMethodsForService(Service service) {
- return """
- private static class $__Holder {
- private static final $ INSTANCE = getService($.class, );
- }
- /**
- * @return true if current runtime has implementation for all methods in {@link $}
- * and its dependencies (can fully implement given service).
- * @see #get$()
- */
- public static boolean is$Supported() {
- return $__Holder.INSTANCE != null;
- }
- /**
- * @return full implementation of {@link $} service if any, or {@code null} otherwise
- */
- public static $ get$() {
- return $__Holder.INSTANCE;
- }
- """
- .replace("", service.hasFallback ? "$.__Fallback::new" : "null")
- .replaceAll("\\$", service.name)
- .replace("", service.javadoc)
- .replaceAll("", service.status.text);
- }
-
- private enum Status {
- NORMAL(""),
- DEPRECATED("\n@Deprecated"),
- FOR_REMOVAL("\n@Deprecated(forRemoval=true)\n@SuppressWarnings(\"removal\")");
-
- private final String text;
- Status(String text) { this.text = text; }
- }
-
- private record Service(String name, String javadoc, Status status, boolean hasFallback) {}
- }
-
- /**
- * Finds and analyzes JBR API implementation modules and collects proxy definitions.
- */
- private static class JBRModules {
-
- private final Path[] paths;
- private final Set proxies = new HashSet<>(), services = new HashSet<>();
-
- private JBRModules() throws IOException {
- String[] moduleNames = findJBRApiModules();
- paths = findPotentialJBRApiContributorModules();
- for (String moduleName : moduleNames) {
- Path module = findJBRApiModuleFile(moduleName, paths);
- findInModule(Files.readString(module));
- }
- }
-
- private void findInModule(String content) {
- Pattern servicePattern = compile("(service|proxy|twoWayProxy)\\s*\\(([^,)]+)");
- Matcher matcher = servicePattern.matcher(content);
- while (matcher.find()) {
- String type = matcher.group(1);
- String interfaceName = extractFromStringLiteral(matcher.group(2));
- if (type.equals("service")) services.add(interfaceName);
- else proxies.add(interfaceName);
- }
- }
-
- private static String extractFromStringLiteral(String value) {
- value = value.strip();
- return value.substring(1, value.length() - 1);
- }
-
- private static Path findJBRApiModuleFile(String module, Path[] potentialPaths) throws FileNotFoundException {
- for (Path p : potentialPaths) {
- Path m = p.resolve("share/classes").resolve(module + ".java");
- if (Files.exists(m)) return m;
- }
- throw new FileNotFoundException("JBR API module file not found: " + module);
- }
-
- private static String[] findJBRApiModules() throws IOException {
- String bootstrap = Files.readString(
- srcroot.resolve("java.base/share/classes/com/jetbrains/bootstrap/JBRApiBootstrap.java"));
- Pattern modulePattern = compile("\"([^\"]+)");
- return Stream.of(findRegex(bootstrap, compile("MODULES *=([^;]+)")).split(","))
- .map(m -> findRegex(m, modulePattern).replace('.', '/')).toArray(String[]::new);
- }
-
- private static Path[] findPotentialJBRApiContributorModules() throws IOException {
- return Files.list(srcroot)
- .filter(p -> Files.exists(p.resolve("share/classes/com/jetbrains"))).toArray(Path[]::new);
- }
- }
-}
diff --git a/src/jetbrains.api/version.properties b/src/jetbrains.api/version.properties
deleted file mode 100644
index d00dcef0cebf..000000000000
--- a/src/jetbrains.api/version.properties
+++ /dev/null
@@ -1,14 +0,0 @@
-# When any changes are made to jetbrains.api module, hash and version value MUST be updated in this file.
-# Version has the following format: MAJOR.MINOR.PATCH
-#
-# How to increment JBR API version?
-# 1. For small changes if no public API was changed (e.g., only javadoc changes) - increment PATCH.
-# 2. When only new API is added, or some existing API was @Deprecated - increment MINOR, reset PATCH to 0.
-# 3. For major backwards incompatible API changes - increment MAJOR, reset MINOR and PATCH to 0.
-
-VERSION = 0.0.18
-
-# Hash is used to track changes to jetbrains.api, so you would not forget to update the version when needed.
-# When you make any changes, "make jbr-api" will fail and ask you to update hash and version number here.
-
-HASH = C3233ED7B0A05816D1B0DB6139AE5F2F
diff --git a/test/jdk/jb/java/api/backend/BootstrapTest.java b/test/jdk/jb/java/api/backend/BootstrapTest.java
new file mode 100644
index 000000000000..c6fb51ce1e9a
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/BootstrapTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2023 JetBrains s.r.o.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @build com.jetbrains.JBR
+ * @run main BootstrapTest new
+ * @run main BootstrapTest old
+ */
+
+import com.jetbrains.JBR;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+public class BootstrapTest {
+
+ public static void main(String[] args) throws Throwable {
+ MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(JBR.class, MethodHandles.lookup());
+ JBR.ServiceApi api;
+ if (!args[0].equals("old")) {
+ Class> bootstrap = Class.forName("com.jetbrains.exported.JBRApiSupport");
+ Objects.requireNonNull(bootstrap, "JBRApi class not accessible");
+ api = (JBR.ServiceApi) (Object) lookup
+ .findStatic(bootstrap, "bootstrap", MethodType.methodType(Object.class, Class.class,
+ Class.class, Class.class, Class.class, Map.class, Function.class))
+ .invokeExact(JBR.ServiceApi.class, (Class>) null, (Class>) null, (Class>) null,
+ Map.of(), (Function>) m -> null);
+ } else {
+ Class> bootstrap = Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap");
+ Objects.requireNonNull(bootstrap, "JBRApiBootstrap class not accessible");
+ api = (JBR.ServiceApi) (Object) lookup
+ .findStatic(bootstrap, "bootstrap", MethodType.methodType(Object.class, MethodHandles.Lookup.class))
+ .invokeExact(lookup);
+ }
+ Objects.requireNonNull(api, "JBR API bootstrap failed");
+ }
+}
diff --git a/test/jdk/jb/java/api/backend/FindDependenciesTest.java b/test/jdk/jb/java/api/backend/FindDependenciesTest.java
deleted file mode 100644
index 7ae2e8211fe8..000000000000
--- a/test/jdk/jb/java/api/backend/FindDependenciesTest.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * @test
- * @modules java.base/com.jetbrains.internal:+open
- * @build com.jetbrains.* com.jetbrains.api.FindDependencies com.jetbrains.jbr.FindDependencies
- * @run main FindDependenciesTest
- */
-
-import com.jetbrains.internal.JBRApi;
-
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static com.jetbrains.Util.*;
-import static com.jetbrains.api.FindDependencies.*;
-import static com.jetbrains.jbr.FindDependencies.*;
-
-public class FindDependenciesTest {
-
- public static void main(String[] args) throws Throwable {
- JBRApi.ModuleRegistry r = init(new String[] {}, names("""
- A AL AR ARL /*ARR is skipped*/ ARRL ARRR
- BV B1 B2 B3 B4 B5 B6 B7 B8 B9
- C1 !C3 C5 C6
- """).toArray(String[]::new));
- // Simple tree with non-proxy type ARR
- validateDependencies(AR.class, cs("AR ARL /*ARR is skipped*/ ARRL ARRR"));
- validateDependencies(A.class, cs("A AL AR ARL /*ARR is skipped*/ ARRL ARRR"));
- validateDependencies(ARRR.class, ARRR.class);
- // Complex graph with many cycles
- for (Class> c : cs("B4 B6 B2 B3 B5")) {
- validateDependencies(c, cs("BV B2 B3 B4 B5 B6 B7 B8 B9"));
- }
- validateDependencies(B1.class, cs("BV B1 B2 B3 B4 B5 B6 B7 B8 B9"));
- validateDependencies(B7.class, B7.class, BV.class);
- validateDependencies(B8.class, B8.class, BV.class);
- validateDependencies(B9.class, B9.class);
- validateDependencies(BV.class, BV.class);
- // Client proxy dependencies
- r.clientProxy(C3.class.getName(), C2.class.getName());
- r.proxy(C5.class.getName(), C4.class.getName());
- validateDependencies(C1.class, C1.class, C3.class, C5.class, C6.class);
- validateDependencies(C5.class, C5.class, C6.class);
- validateDependencies(C6.class, C6.class);
- validateDependencies(C3.class, C3.class, C5.class, C6.class);
- }
-
- private static Class>[] cs(String interfaces) {
- return names(interfaces).map(c -> {
- try {
- return Class.forName(c);
- } catch (ClassNotFoundException e) {
- throw new Error(e);
- }
- }).toArray(Class[]::new);
- }
- private static Stream names(String interfaces) {
- return Stream.of(interfaces.replaceAll("/\\*[^*]*\\*/", "").split("(\s|\n)+")).map(String::strip)
- .map(s -> {
- if (s.startsWith("!")) return "com.jetbrains.jbr.FindDependencies$" + s.substring(1);
- else return "com.jetbrains.api.FindDependencies$" + s;
- });
- }
-
- private static void validateDependencies(Class> src, Class>... expected) throws Throwable {
- Set> actual = getProxyDependencies(src);
- if (actual.size() != expected.length || !actual.containsAll(List.of(expected))) {
- throw new Error("Invalid proxy dependencies for class " + src +
- ". Expected: [" + Stream.of(expected).map(Class::getSimpleName).collect(Collectors.joining(" ")) +
- "]. Actual: [" + actual.stream().map(Class::getSimpleName).collect(Collectors.joining(" ")) + "].");
- }
- }
-
- private static final ReflectedMethod getProxyDependencies =
- getMethod("com.jetbrains.internal.ProxyDependencyManager", "getProxyDependencies", Class.class);
- @SuppressWarnings("unchecked")
- static Set> getProxyDependencies(Class> interFace) throws Throwable {
- return (Set>) getProxyDependencies.invoke(null, interFace);
- }
-}
diff --git a/test/jdk/jb/java/api/backend/MethodMappingTest.java b/test/jdk/jb/java/api/backend/MethodMappingTest.java
index 7fa80b7868e4..2f90d047b0cd 100644
--- a/test/jdk/jb/java/api/backend/MethodMappingTest.java
+++ b/test/jdk/jb/java/api/backend/MethodMappingTest.java
@@ -24,55 +24,39 @@
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
- * @build com.jetbrains.* com.jetbrains.api.MethodMapping com.jetbrains.jbr.MethodMapping
- * @run main MethodMappingTest
+ * @build com.jetbrains.* com.jetbrains.test.api.MethodMapping com.jetbrains.test.jbr.MethodMapping
+ * @run main/othervm -Djetbrains.runtime.api.extendRegistry=true MethodMappingTest
*/
-import com.jetbrains.internal.JBRApi;
+import java.util.Map;
import static com.jetbrains.Util.*;
-import static com.jetbrains.api.MethodMapping.*;
-import static com.jetbrains.jbr.MethodMapping.*;
+import static com.jetbrains.test.api.MethodMapping.*;
+import static com.jetbrains.test.jbr.MethodMapping.*;
public class MethodMappingTest {
public static void main(String[] args) throws Throwable {
- JBRApi.ModuleRegistry r = init();
+ init("MethodMappingTest", Map.of());
// Simple empty interface
- r.proxy(SimpleEmpty.class.getName(), SimpleEmptyImpl.class.getName());
- requireImplemented(SimpleEmpty.class);
+ requireSupported(getProxy(SimpleEmpty.class));
+ requireUnsupported(getProxy(SimpleEmptyImpl.class));
+ requireUnsupported(inverse(getProxy(SimpleEmpty.class)));
+ requireSupported(inverse(getProxy(SimpleEmptyImpl.class)));
// Plain method mapping
- r.proxy(PlainFail.class.getName(), PlainImpl.class.getName());
- r.service(Plain.class.getName(), PlainImpl.class.getName())
- .withStatic("c", "main", MethodMappingTest.class.getName());
- requireNotImplemented(PlainFail.class);
- requireImplemented(Plain.class);
+ requireSupported(getProxy(Plain.class));
+ requireUnsupported(getProxy(PlainFail.class));
// Callback (client proxy)
- r.clientProxy(Callback.class.getName(), ApiCallback.class.getName());
- requireImplemented(Callback.class);
+ requireSupported(getProxy(Callback.class));
// 2-way
- r.twoWayProxy(ApiTwoWay.class.getName(), JBRTwoWay.class.getName());
- requireImplemented(ApiTwoWay.class);
- requireImplemented(JBRTwoWay.class);
+ requireSupported(getProxy(ApiTwoWay.class));
+ requireSupported(getProxy(JBRTwoWay.class));
// Conversion
- r.twoWayProxy(Conversion.class.getName(), ConversionImpl.class.getName());
- r.proxy(ConversionSelf.class.getName(), ConversionSelfImpl.class.getName());
- r.proxy(ConversionFail.class.getName(), ConversionFailImpl.class.getName());
- requireImplemented(Conversion.class);
- requireImplemented(ConversionImpl.class);
- requireImplemented(ConversionSelf.class);
- requireNotImplemented(ConversionFail.class);
- }
-
- private static final ReflectedMethod methodsImplemented = getMethod("com.jetbrains.internal.Proxy", "areAllMethodsImplemented");
- private static void requireImplemented(Class> interFace) throws Throwable {
- if (!(boolean) methodsImplemented.invoke(getProxy(interFace))) {
- throw new Error("All methods must be implemented");
- }
- }
- private static void requireNotImplemented(Class> interFace) throws Throwable {
- if ((boolean) methodsImplemented.invoke(getProxy(interFace))) {
- throw new Error("Not all methods must be implemented");
- }
+ requireSupported(getProxy(Conversion.class));
+ requireSupported(getProxy(ConversionImpl.class));
+ requireSupported(getProxy(ConversionSelf.class));
+ requireUnsupported(getProxy(ConversionFail.class));
+ requireSupported(getProxy(ArrayConversion.class));
+ requireSupported(getProxy(GenericConversion.class));
}
}
diff --git a/test/jdk/jb/java/api/backend/MethodMappingTest.registry b/test/jdk/jb/java/api/backend/MethodMappingTest.registry
new file mode 100644
index 000000000000..26eebbb48fea
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/MethodMappingTest.registry
@@ -0,0 +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
+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
diff --git a/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java
index 5ef678d7e954..13962f7081d4 100644
--- a/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java
+++ b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.java
@@ -24,45 +24,35 @@
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
- * @build com.jetbrains.* com.jetbrains.api.ProxyInfoResolving com.jetbrains.jbr.ProxyInfoResolving
- * @run main ProxyInfoResolvingTest
+ * @build com.jetbrains.* com.jetbrains.test.api.ProxyInfoResolving com.jetbrains.test.jbr.ProxyInfoResolving
+ * @run main/othervm -Djetbrains.runtime.api.extendRegistry=true ProxyInfoResolvingTest
*/
-import com.jetbrains.internal.JBRApi;
-
-import java.util.Objects;
+import java.util.Map;
import static com.jetbrains.Util.*;
-import static com.jetbrains.api.ProxyInfoResolving.*;
-import static com.jetbrains.jbr.ProxyInfoResolving.*;
+import static com.jetbrains.test.api.ProxyInfoResolving.*;
+import static com.jetbrains.test.jbr.ProxyInfoResolving.*;
public class ProxyInfoResolvingTest {
public static void main(String[] args) throws Throwable {
- JBRApi.ModuleRegistry r = init();
- // No mapping defined -> null
- requireNull(getProxy(ProxyInfoResolvingTest.class));
- // Invalid JBR-side target class -> null
- r.proxy(InterfaceWithoutImplementation.class.getName(), "absentImpl");
- requireNull(getProxy(InterfaceWithoutImplementation.class));
- // Invalid JBR-side target static method mapping -> null
- r.service(ServiceWithoutImplementation.class.getName())
- .withStatic("someMethod", "someMethod", "NoClass");
- requireNull(getProxy(ServiceWithoutImplementation.class));
- // Service without target class or static method mapping -> null
- r.service(EmptyService.class.getName());
- requireNull(getProxy(EmptyService.class));
- // Class passed instead of interface for client proxy -> error
- r.clientProxy(ClientProxyClass.class.getName(), ClientProxyClassImpl.class.getName());
- mustFail(() -> getProxy(ClientProxyClass.class), RuntimeException.class);
- // Class passed instead of interface for proxy -> null
- r.proxy(ProxyClass.class.getName(), ProxyClassImpl.class.getName());
- requireNull(getProxy(ProxyClass.class));
+ init("ProxyInfoResolvingTest", Map.of());
+ // No mapping defined -> unsupported
+ requireUnsupported(getProxy(ProxyInfoResolvingTest.class));
+ // Invalid JBR-side target class -> unsupported
+ requireUnsupported(getProxy(InterfaceWithoutImplementation.class));
+ // Invalid JBR-side target static method mapping -> unsupported
+ requireUnsupported(getProxy(ServiceWithoutImplementation.class));
// Valid proxy
- r.proxy(ValidApi.class.getName(), ValidApiImpl.class.getName());
- Objects.requireNonNull(getProxy(ValidApi.class));
- // Multiple implementations
- r.proxy(ValidApi2.class.getName(), "MissingClass", ValidApi2Impl.class.getName());
- Objects.requireNonNull(getProxy(ValidApi2.class));
+ requireSupported(getProxy(ValidApi.class));
+ // Class instead of interface for proxy
+ requireSupported(getProxy(ProxyClass.class));
+ // Class instead of interface for client proxy
+ requireSupported(getProxy(ClientProxyClass.class));
+ // Service without annotation -> unsupported
+ requireUnsupported(getProxy(ServiceWithoutAnnotation.class));
+ // Service with extension method
+ requireSupported(getProxy(ServiceWithExtension.class));
}
}
diff --git a/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.registry b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.registry
new file mode 100644
index 000000000000..2d42d92cfbef
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/ProxyInfoResolvingTest.registry
@@ -0,0 +1,7 @@
+TYPE absentImpl com.jetbrains.test.api.ProxyInfoResolving.InterfaceWithoutImplementation PROVIDES
+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
+STATIC NoClass foo ()V com.jetbrains.test.api.ProxyInfoResolving.ServiceWithExtension foo
diff --git a/test/jdk/jb/java/api/backend/ProxyRegistrationTest.java b/test/jdk/jb/java/api/backend/ProxyRegistrationTest.java
deleted file mode 100644
index 60556339abde..000000000000
--- a/test/jdk/jb/java/api/backend/ProxyRegistrationTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * @test
- * @modules java.base/com.jetbrains.internal:+open
- * @build com.jetbrains.*
- * @run main ProxyRegistrationTest
- */
-
-import com.jetbrains.internal.JBRApi;
-
-import static com.jetbrains.Util.*;
-
-public class ProxyRegistrationTest {
-
- public static void main(String[] args) {
- JBRApi.ModuleRegistry r = init();
- // Only service may not have target type
- r.service("s");
- mustFail(() -> r.proxy("i"), IllegalArgumentException.class);
- mustFail(() -> r.proxy("i", null), NullPointerException.class);
- mustFail(() -> r.clientProxy("i", null), NullPointerException.class);
- mustFail(() -> r.twoWayProxy("i", null), NullPointerException.class);
- // Invalid 2-way mapping
- r.proxy("a", "b");
- mustFail(() -> r.clientProxy("b", "c"), IllegalArgumentException.class);
- mustFail(() -> r.clientProxy("c", "a"), IllegalArgumentException.class);
- }
-}
diff --git a/test/jdk/jb/java/api/backend/RealTest.java b/test/jdk/jb/java/api/backend/RealTest.java
index 80643bb46b03..1654d41f50c7 100644
--- a/test/jdk/jb/java/api/backend/RealTest.java
+++ b/test/jdk/jb/java/api/backend/RealTest.java
@@ -24,29 +24,43 @@
/*
* @test
* @modules java.base/com.jetbrains.internal:+open
- * @build com.jetbrains.* com.jetbrains.api.Real com.jetbrains.jbr.Real
- * @run main RealTest
+ * @build com.jetbrains.* com.jetbrains.test.api.Real com.jetbrains.test.jbr.Real
+ * @run main/othervm -Djetbrains.runtime.api.extendRegistry=true RealTest
*/
+import com.jetbrains.Extensions;
import com.jetbrains.internal.JBRApi;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
import static com.jetbrains.Util.*;
-import static com.jetbrains.api.Real.*;
-import static com.jetbrains.jbr.Real.*;
+import static com.jetbrains.test.api.Real.*;
public class RealTest {
public static void main(String[] args) {
- init()
- .service(Service.class.getName(), ServiceImpl.class.getName())
- .twoWayProxy(Api2Way.class.getName(), JBR2Way.class.getName())
- .twoWayProxy(ApiLazyNumber.class.getName(), JBRLazyNumber.class.getName());
+ 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));
+ // Proxy passthrough
+ Proxy p = Objects.requireNonNull(service.getProxy());
+ Proxy np = Objects.requireNonNull(service.passthrough(p));
+ if (p.getClass() != np.getClass()) {
+ throw new Error("Different classes when passing through the same object");
+ }
+
+ // Client proxy passthrough
+ ClientImpl c = new ClientImpl();
+ ClientImpl nc = Objects.requireNonNull(service.passthrough(c));
+ if (c.getClass() != nc.getClass()) {
+ throw new Error("Different classes when passing through the same object");
+ }
+
// Test JBR-side proxy wrapping & unwrapping
Api2Way stw = Objects.requireNonNull(service.get2Way());
Api2Way nstw = Objects.requireNonNull(service.passthrough(stw));
@@ -65,15 +79,51 @@ public class RealTest {
Objects.requireNonNull(tw.value);
// Passing through null object -> null
- requireNull(service.passthrough(null));
-
- if (!service.isSelf(service)) {
- throw new Error("service.isSelf(service) == false");
- }
+ requireNull(service.passthrough((Api2Way) null));
if (service.sum(() -> 200, () -> 65).get() != 265) {
throw new Error("Lazy numbers conversion error");
}
+
+ // Check extensions
+ if (!JBRApi.isExtensionSupported(Extensions.FOO)) {
+ throw new Error("FOO extension must be supported");
+ }
+ if (JBRApi.isExtensionSupported(Extensions.BAR)) {
+ throw new Error("BAR extension must not be supported");
+ }
+ try {
+ service.getProxy().foo();
+ throw new Error("FOO extension was disabled but call succeeded");
+ } catch (UnsupportedOperationException ignore) {}
+ try {
+ service.getProxy().bar();
+ 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();
+ // 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));
+
+ // Test specialized (implicit) List proxy
+ List list = Objects.requireNonNull(service.testList(null));
+ Api2Way listItem = new TwoWayImpl();
+ list.add(listItem);
+ if (list.size() != 1) {
+ throw new Error("Unexpected List size");
+ }
+ if (list.get(0) != listItem) {
+ throw new Error("Unexpected List item");
+ }
+ service.testList(list);
+ if (!list.isEmpty()) {
+ throw new Error("Unexpected List size");
+ }
+ list = new ArrayList<>();
+ if (list != service.testList(list)) {
+ throw new Error("List passthrough mismatch");
+ }
}
private static class TwoWayImpl implements Api2Way {
diff --git a/test/jdk/jb/java/api/backend/RealTest.registry b/test/jdk/jb/java/api/backend/RealTest.registry
new file mode 100644
index 000000000000..57e136534cd1
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/RealTest.registry
@@ -0,0 +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
diff --git a/test/jdk/jb/java/api/backend/ReflectiveBootstrapTest.java b/test/jdk/jb/java/api/backend/ReflectiveBootstrapTest.java
deleted file mode 100644
index 4c6f7c72e38a..000000000000
--- a/test/jdk/jb/java/api/backend/ReflectiveBootstrapTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * @test
- * @build com.jetbrains.JBR
- * @run main ReflectiveBootstrapTest
- */
-
-import com.jetbrains.JBR;
-
-import java.lang.invoke.MethodHandles;
-import java.util.Objects;
-
-public class ReflectiveBootstrapTest {
-
- public static void main(String[] args) throws Exception {
- JBR.ServiceApi api = (JBR.ServiceApi) Class.forName("com.jetbrains.bootstrap.JBRApiBootstrap")
- .getMethod("bootstrap", MethodHandles.Lookup.class)
- .invoke(null, MethodHandles.privateLookupIn(JBR.class, MethodHandles.lookup()));
- Objects.requireNonNull(api, "JBR API bootstrap failed");
- }
-}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Extension.java b/test/jdk/jb/java/api/backend/com/jetbrains/Extension.java
new file mode 100644
index 000000000000..26fe4cb7e73a
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/Extension.java
@@ -0,0 +1,13 @@
+package com.jetbrains;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface Extension {
+ Extensions value();
+}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Extensions.java b/test/jdk/jb/java/api/backend/com/jetbrains/Extensions.java
new file mode 100644
index 000000000000..a78421aaec95
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/Extensions.java
@@ -0,0 +1,6 @@
+package com.jetbrains;
+
+public enum Extensions {
+ FOO,
+ BAR
+}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java b/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java
index e3eb6e93ea53..47fa97f35ea8 100644
--- a/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/JBR.java
@@ -28,13 +28,5 @@ package com.jetbrains;
*/
public class JBR {
- public interface ServiceApi {
-
- T getService(Class interFace);
- }
-
- static final class Metadata {
- static String[] KNOWN_SERVICES = {};
- static String[] KNOWN_PROXIES = {};
- }
+ public interface ServiceApi {}
}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Provided.java b/test/jdk/jb/java/api/backend/com/jetbrains/Provided.java
new file mode 100644
index 000000000000..61afa82b919d
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/Provided.java
@@ -0,0 +1,12 @@
+package com.jetbrains;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface Provided {
+}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Provides.java b/test/jdk/jb/java/api/backend/com/jetbrains/Provides.java
new file mode 100644
index 000000000000..19646cb4317b
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/Provides.java
@@ -0,0 +1,12 @@
+package com.jetbrains;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface Provides {
+}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Service.java b/test/jdk/jb/java/api/backend/com/jetbrains/Service.java
new file mode 100644
index 000000000000..49871e201b5b
--- /dev/null
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/Service.java
@@ -0,0 +1,12 @@
+package com.jetbrains;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Target(TYPE)
+@Retention(RUNTIME)
+public @interface Service {
+}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/Util.java b/test/jdk/jb/java/api/backend/com/jetbrains/Util.java
index 99d9a68e6781..9bda4115ff66 100644
--- a/test/jdk/jb/java/api/backend/com/jetbrains/Util.java
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/Util.java
@@ -25,81 +25,76 @@ package com.jetbrains;
import com.jetbrains.internal.JBRApi;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.InvocationTargetException;
+import java.io.*;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.Map;
public class Util {
- public static ReflectedMethod getMethod(String className, String method, Class>... parameterTypes) {
- try {
- Method m = Class.forName(className, false, JBRApi.class.getClassLoader())
- .getDeclaredMethod(method, parameterTypes);
- m.setAccessible(true);
- return (o, a) -> {
- try {
- return m.invoke(o, a);
- } catch (IllegalAccessException e) {
- throw new Error(e);
- } catch (InvocationTargetException e) {
- throw e.getCause();
- }
- };
- } catch (NoSuchMethodException | ClassNotFoundException e) {
+ /**
+ * Invoke internal {@link JBRApi#init} bypassing {@link com.jetbrains.exported.JBRApiSupport#bootstrap}.
+ */
+ public static void init(String registryName, Map, 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 -> {
+ Extension e = m.getAnnotation(Extension.class);
+ return e == null ? null : e.value();
+ });
+ } catch (IOException e) {
throw new Error(e);
}
}
- public static JBRApi.ModuleRegistry init() {
- return init(new String[0], new String[0]);
+ 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);
+ }
+ return proxyRepository;
}
- /**
- * Set known services & proxies at runtime and invoke internal
- * {@link JBRApi#init} bypassing {@link com.jetbrains.bootstrap.JBRApiBootstrap#bootstrap}
- * in order not to init normal JBR API modules.
- */
- public static JBRApi.ModuleRegistry init(String[] knownServices, String[] knownProxies) {
- JBR.Metadata.KNOWN_SERVICES = knownServices;
- JBR.Metadata.KNOWN_PROXIES = knownProxies;
- JBRApi.init(MethodHandles.lookup());
- return JBRApi.registerModule(MethodHandles.lookup(), JBR.class.getModule()::addExports);
- }
-
- private static final ReflectedMethod getProxy = getMethod(JBRApi.class.getName(), "getProxy", Class.class);
+ private static Method getProxy;
public static Object getProxy(Class> interFace) throws Throwable {
- return getProxy.invoke(null, interFace);
+ var repo = getProxyRepository();
+ if (getProxy == null) {
+ getProxy = repo.getClass()
+ .getDeclaredMethod("getProxy", Class.class, Class.forName("com.jetbrains.internal.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");
+ inverse.setAccessible(true);
+ }
+ return inverse.invoke(proxy);
+ }
+
+ private static Method generate;
+ public static boolean isSupported(Object proxy) throws Throwable {
+ if (generate == null) {
+ generate = proxy.getClass().getDeclaredMethod("generate");
+ generate.setAccessible(true);
+ }
+ return (boolean) generate.invoke(proxy);
+ }
+
+ public static void requireSupported(Object proxy) throws Throwable {
+ if (!isSupported(proxy)) throw new RuntimeException("Proxy must be supported");
+ }
+
+ public static void requireUnsupported(Object proxy) throws Throwable {
+ if (isSupported(proxy)) throw new RuntimeException("Proxy must be unsupported");
}
public static void requireNull(Object o) {
if (o != null) throw new RuntimeException("Value must be null");
}
- @SafeVarargs
- public static void mustFail(ThrowingRunnable action, Class extends Throwable>... exceptionTypeChain) {
- try {
- action.run();
- } catch(Throwable exception) {
- Throwable e = exception;
- error: {
- for (Class extends Throwable> c : exceptionTypeChain) {
- if (e == null || !c.isInstance(e)) break error;
- e = e.getCause();
- }
- if (e == null) return;
- }
- throw new Error("Unexpected exception", exception);
- }
- throw new Error("Operation must fail, but succeeded");
- }
-
- @FunctionalInterface
- public interface ThrowingRunnable {
- void run() throws Throwable;
- }
-
- @FunctionalInterface
- public interface ReflectedMethod {
- Object invoke(Object obj, Object... args) throws Throwable;
- }
}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/FindDependencies.java b/test/jdk/jb/java/api/backend/com/jetbrains/api/FindDependencies.java
deleted file mode 100644
index 9ddc1e1dfc06..000000000000
--- a/test/jdk/jb/java/api/backend/com/jetbrains/api/FindDependencies.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains.api;
-
-public class FindDependencies {
- public interface A {
- void f(AL l, AR r);
- }
- public interface AL {}
- public interface AR {
- void f(ARL l, ARR r);
- }
- public interface ARL {}
- public interface ARR {
- void f(ARRL l, ARRR r);
- }
- public interface ARRL {}
- public interface ARRR {}
-
- public interface BV {}
- public interface B1 {
- void f(BV bvs, B2 t, BV bve);
- }
- public interface B2 {
- void f(BV bvs, B3 t, BV bve);
- }
- public interface B3 {
- void f(B8 l, BV bvs, B4 t, B2 b, BV bve, B9 r);
- }
- public interface B4 {
- void f(BV bvs, B2 b, B5 t, BV bve);
- }
- public interface B5 {
- void f(BV bvs, B6 s, B3 b, B7 t, BV bve);
- }
- public interface B6 {
- void f(BV bvs, B5 b, BV bve);
- }
- public interface B7 {
- void f(BV v);
- }
- public interface B8 {
- void f(BV v);
- }
- public interface B9 {}
-
- public interface C1 {
- void f(C2 c);
- }
- public interface C2 {}
- public interface C5 {
- void f(C6 c);
- }
- public interface C6 {}
-}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/FindDependencies.java b/test/jdk/jb/java/api/backend/com/jetbrains/jbr/FindDependencies.java
deleted file mode 100644
index 45e7c4012157..000000000000
--- a/test/jdk/jb/java/api/backend/com/jetbrains/jbr/FindDependencies.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2000-2023 JetBrains s.r.o.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package com.jetbrains.jbr;
-
-public class FindDependencies {
- public interface C3 {
- void f(C4 c);
- }
- public interface C4 {}
-}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/MethodMapping.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/MethodMapping.java
similarity index 71%
rename from test/jdk/jb/java/api/backend/com/jetbrains/api/MethodMapping.java
rename to test/jdk/jb/java/api/backend/com/jetbrains/test/api/MethodMapping.java
index d71ff0394fdb..bc4f0eea7596 100644
--- a/test/jdk/jb/java/api/backend/com/jetbrains/api/MethodMapping.java
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/MethodMapping.java
@@ -21,29 +21,56 @@
* questions.
*/
-package com.jetbrains.api;
+package com.jetbrains.test.api;
+
+import com.jetbrains.Provides;
+import com.jetbrains.Provided;
+import com.jetbrains.Service;
public class MethodMapping {
+ @Provided
public interface SimpleEmpty {}
+ @Service
+ @Provided
public interface Plain {
void a();
boolean b();
void c(String... a);
}
+ @Service
+ @Provided
public interface PlainFail extends Plain {}
+ @Provides
public interface ApiCallback {
void hello();
}
+ @Provided
+ @Provides
public interface ApiTwoWay {
void something();
}
+ @Provided
+ @Provides
public interface Conversion {
- SimpleEmpty convert(Plain a, ApiCallback b, ApiTwoWay c);
+ SimpleEmpty convert(SimpleEmpty a, ApiCallback b, ApiTwoWay c);
}
+ @Provided
public interface ConversionSelf extends Conversion {
ConversionSelf convert(Object a, Object b, Object c);
}
+ @Provided
public interface ConversionFail extends Conversion {
void missingMethod();
}
+ @Provided
+ public interface ArrayConversion {
+ Conversion[] convert();
+ }
+ @Provided
+ public interface GenericConversion {
+ ImplicitGeneric convert();
+ }
+ public interface ImplicitGeneric {
+ R apply(P p);
+ }
}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/ProxyInfoResolving.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/ProxyInfoResolving.java
similarity index 75%
rename from test/jdk/jb/java/api/backend/com/jetbrains/api/ProxyInfoResolving.java
rename to test/jdk/jb/java/api/backend/com/jetbrains/test/api/ProxyInfoResolving.java
index e885f1430b55..1e9423e7d1c1 100644
--- a/test/jdk/jb/java/api/backend/com/jetbrains/api/ProxyInfoResolving.java
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/ProxyInfoResolving.java
@@ -21,14 +21,29 @@
* questions.
*/
-package com.jetbrains.api;
+package com.jetbrains.test.api;
+
+import com.jetbrains.*;
public class ProxyInfoResolving {
+ @Provided
public interface InterfaceWithoutImplementation {}
- public interface ServiceWithoutImplementation {}
- public interface EmptyService {}
+ @Service
+ @Provided
+ public interface ServiceWithoutImplementation {
+ void foo();
+ }
+ @Provides
public static class ClientProxyClassImpl {}
+ @Provided
public static class ProxyClass {}
+ @Provided
public interface ValidApi {}
- public interface ValidApi2 {}
+ public interface ServiceWithoutAnnotation {}
+ @Service
+ @Provided
+ public interface ServiceWithExtension {
+ @Extension(Extensions.FOO)
+ void foo();
+ }
}
diff --git a/test/jdk/jb/java/api/backend/com/jetbrains/api/Real.java b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/Real.java
similarity index 63%
rename from test/jdk/jb/java/api/backend/com/jetbrains/api/Real.java
rename to test/jdk/jb/java/api/backend/com/jetbrains/test/api/Real.java
index b65757d763a2..d175173b1a99 100644
--- a/test/jdk/jb/java/api/backend/com/jetbrains/api/Real.java
+++ b/test/jdk/jb/java/api/backend/com/jetbrains/test/api/Real.java
@@ -21,28 +21,58 @@
* questions.
*/
-package com.jetbrains.api;
+package com.jetbrains.test.api;
+import com.jetbrains.Extension;
+import com.jetbrains.Extensions;
+import com.jetbrains.Provides;
+import com.jetbrains.Provided;
+
+import java.util.List;
import java.util.function.Consumer;
+import java.util.function.IntSupplier;
public class Real {
+ @com.jetbrains.Service
+ @Provided
public interface Service {
+ Proxy getProxy();
+ Proxy passthrough(Proxy a);
+ ClientImpl passthrough(ClientImpl a);
Api2Way get2Way();
Api2Way passthrough(Api2Way a);
- boolean isSelf(Service a);
ApiLazyNumber sum(ApiLazyNumber a, ApiLazyNumber b);
/**
- * When generating bridge class, convertible method parameters are changed to Objects,
- * which may cause name collisions
+ * Convertible method parameters are changed to Objects, which may cause name collisions
*/
void testMethodNameConflict(Api2Way a);
void testMethodNameConflict(ApiLazyNumber a);
+ List testList(List list);
}
+ @Provided
+ public interface Proxy extends IntSupplier {
+ @Extension(Extensions.FOO)
+ void foo();
+ @Extension(Extensions.BAR)
+ void bar();
+ }
+
+ @Provides
+ public static class ClientImpl {
+ int getAsInt() {
+ return 562;
+ }
+ }
+
+ @Provided
+ @Provides
@FunctionalInterface
public interface Api2Way extends Consumer