├── .github
└── workflows
│ └── maven.yml
├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── encodings.xml
├── google-java-format.xml
├── hidden_proxy.iml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── libraries
│ ├── Maven__org_apiguardian_apiguardian_api_1_1_0.xml
│ ├── Maven__org_junit_jupiter_junit_jupiter_api_5_7_0.xml
│ ├── Maven__org_junit_jupiter_junit_jupiter_engine_5_7_0.xml
│ ├── Maven__org_junit_platform_junit_platform_commons_1_7_0.xml
│ ├── Maven__org_junit_platform_junit_platform_engine_1_7_0.xml
│ ├── Maven__org_opentest4j_opentest4j_1_2_0.xml
│ └── Maven__org_ow2_asm_asm_9_0.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── LICENSE
├── README.md
├── com.github.forax.proxy
├── com.github.forax.proxy.iml
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ ├── com
│ │ └── github
│ │ │ └── forax
│ │ │ └── proxy
│ │ │ └── Proxy.java
│ │ └── module-info.java
│ └── test
│ └── java
│ └── com
│ └── github
│ └── forax
│ └── proxy
│ └── ProxyTest.java
└── pom.xml
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches: [ master ]
4 | pull_request:
5 | branches: [ master ]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | java: [ 16, 17-ea ]
13 | name: Java ${{ matrix.java }} sample
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: setup
17 | uses: actions/setup-java@v1
18 | with:
19 | java-version: ${{ matrix.java }}
20 | - name: install
21 | run: |
22 | mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
23 | - name: build
24 | run: |
25 | mvn -B package
26 |
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
25 | # Maven target directory
26 | /com.github.forax.hiddenproxy/target/
27 | /target/
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/google-java-format.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/hidden_proxy.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_7_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_7_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_7_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_7_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/libraries/Maven__org_ow2_asm_asm_9_0.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Rémi Forax
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hidden_proxy
2 | A simple API that shows how,to use Hidden class to create proxies.
3 |
4 | This is a replacement of `java.lang.reflect.Proxy` API of Java using a more modern design leading to must faster proxies implementation. See the javadoc of `Proxy` for more information.
5 |
6 | This code is using the Hidden Class API [JEP 371](https://openjdk.java.net/jeps/371) that was introduced in java 15,
7 | so it requires Java 16 to run.
8 |
9 | ## Example
10 |
11 | Let say we have an interface `HelloProxy` with an abstract method `hello` and a static method `implementation` somewhere else
12 | ```java
13 | interface HelloProxy {
14 | String hello(String text);
15 | }
16 | static class Impl {
17 | static String impl(int repeated, String text) {
18 | return text.repeat(repeated);
19 | }
20 | }
21 | ```
22 |
23 | The method `Proxy.defineProxy(lookup, interfaces, overriden, field, linker)` let you dynamically create a class
24 | that implements the interface `HelloProxy` with a field (here an int).
25 | The return value of `defineProxy` is a lookup you can use to find the constructor and
26 | invoke it invoke with the value of the field.
27 | Then the first time an abstract method of the interface is called, here when calling `proxy.hello("proxy")`,
28 | the linker is called to ask how the abstract method should be implemented.
29 | Here the linker will use the method `impl` and discard (using `dropArguments`) the first argument (the proxy)
30 | before calling the method `impl`.
31 | So when calling the method `hello`, the method `impl` will be called with the field stored inside the proxy
32 | as first argument followed by the arguments of the method `hello``.
33 | ```java
34 | Lookup lookup = MethodHandles.lookup();
35 | MethodHandle impl = lookup.findStatic(Impl.class, "impl",
36 | methodType(String.class, int.class, String.class));
37 | Proxy.Linker linker = methodInfo -> switch(methodInfo.getName()) {
38 | case "hello" -> MethodHandles.dropArguments(impl, 0, HelloProxy.class);
39 | default -> fail("unknown method " + methodInfo);
40 | };
41 | Lookup proxyLookup = Proxy.defineProxy(lookup,
42 | new Class>[] { HelloProxy.class }, // proxy interfaces
43 | __ -> false, // don't override toString, equals and hashCode
44 | int.class, // proxy field
45 | linker);
46 | MethodHandle constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(),
47 | methodType(void.class, int.class));
48 | HelloProxy proxy = (HelloProxy) constructor.invoke(2);
49 | assertEquals("proxyproxy", proxy.hello("proxy"));
50 | ```
51 |
52 | If you want more examples, you can take a look to the test class `ProxyTest`.
53 |
54 |
--------------------------------------------------------------------------------
/com.github.forax.proxy/com.github.forax.proxy.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/com.github.forax.proxy/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 | com.github.forax.proxy
9 | com.github.forax.proxy
10 | 1.0-SNAPSHOT
11 |
12 |
13 | UTF-8
14 |
15 |
16 |
17 |
18 | org.ow2.asm
19 | asm
20 | 9.0
21 | compile
22 |
23 |
24 | org.junit.jupiter
25 | junit-jupiter-api
26 | 5.7.0
27 | test
28 |
29 |
30 | org.junit.jupiter
31 | junit-jupiter-engine
32 | 5.7.0
33 | test
34 |
35 |
36 |
37 |
38 |
39 |
40 | org.apache.maven.plugins
41 | maven-compiler-plugin
42 | 3.8.1
43 |
44 | 16
45 |
46 | --enable-preview
47 |
48 |
49 |
50 |
51 |
52 | org.apache.maven.plugins
53 | maven-surefire-plugin
54 | 3.0.0-M5
55 |
56 | --enable-preview
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/com.github.forax.proxy/src/main/java/com/github/forax/proxy/Proxy.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.proxy;
2 |
3 | import org.objectweb.asm.ClassWriter;
4 | import org.objectweb.asm.Handle;
5 | import org.objectweb.asm.Type;
6 |
7 | import java.lang.constant.MethodTypeDesc;
8 | import java.lang.invoke.CallSite;
9 | import java.lang.invoke.ConstantCallSite;
10 | import java.lang.invoke.MethodHandle;
11 | import java.lang.invoke.MethodHandleInfo;
12 | import java.lang.invoke.MethodHandles;
13 | import java.lang.invoke.MethodType;
14 | import java.lang.invoke.WrongMethodTypeException;
15 | import java.lang.reflect.Method;
16 | import java.lang.reflect.Modifier;
17 | import java.util.Arrays;
18 | import java.util.LinkedHashMap;
19 | import java.util.List;
20 | import java.util.Objects;
21 | import java.util.function.Predicate;
22 | import java.lang.invoke.MethodHandles.Lookup;
23 | import java.lang.invoke.MethodHandles.Lookup.ClassOption;
24 | import java.util.stream.Stream;
25 |
26 | import static java.lang.constant.ConstantDescs.*;
27 | import static org.objectweb.asm.Opcodes.*;
28 |
29 | /**
30 | * A class that dynamically generates a proxy class that implement several interfaces and
31 | * delegate to a {@link Proxy.Linker} the actual implementation of each method of the proxy.
32 | */
33 | public class Proxy {
34 | /**
35 | * A linker that resolves the calls to the proxy by providing a method handle for a method of the proxy.
36 | */
37 | @FunctionalInterface
38 | public interface Linker {
39 | /**
40 | * Returns a method handle that will be installed inside the proxy for a declared method of the proxy.
41 | * @param methodInfo the method of the proxy that should be implemented
42 | * @return a method handle that will be installed inside the proxy for a declared method of the proxy.
43 | */
44 | MethodHandle resolve(MethodHandleInfo methodInfo) throws Throwable;
45 | }
46 |
47 | private Proxy() {
48 | throw new AssertionError();
49 | }
50 |
51 | /**
52 | * Defines a proxy class and returns a lookup on that class.
53 | *
54 | * @param lookup the lookup used to define the proxy class.
55 | * @param interfaces the interfaces implemented by the proxy class.
56 | * @param shouldOverride a predicate indicating if methods of java.lang.Object or default method
57 | * should be overridden or not
58 | * @param delegateClass the class of the delegate field inside the proxy or void.class if there is no field.
59 | * @param linker the linker that will resolve the calls to the proxy methods.
60 | * @return a new proxy class that implements the interfaces.
61 | * @throws IllegalAccessException if this Lookup does not have full privilege access
62 | * @throws SecurityException if a security manager is present and it refuses access
63 | * @throws NullPointerException if any parameter is null
64 | */
65 | public static Lookup defineProxy(Lookup lookup, Class>[] interfaces, Predicate shouldOverride, Class> delegateClass, Linker linker) throws IllegalAccessException {
66 | Objects.requireNonNull(lookup);
67 | Objects.requireNonNull(interfaces);
68 | Objects.requireNonNull(shouldOverride);
69 | Objects.requireNonNull(delegateClass);
70 | Objects.requireNonNull(linker);
71 | var proxyName = lookup.lookupClass().getPackageName().replace('.', '/') + "/ProxyImpl";
72 | var bytecode = generateBytecode(proxyName, interfaces, shouldOverride, delegateClass);
73 | return lookup.defineHiddenClassWithClassData(bytecode, linker, true, ClassOption.NESTMATE, ClassOption.STRONG);
74 | }
75 |
76 | private static byte[] generateBytecode(String proxyName, Class>[] interfaces, Predicate shouldOverride, Class> delegateClass) {
77 | var interfaceList = List.of(interfaces);
78 | record Key(String name, String descriptor) { }
79 | var map = new LinkedHashMap();
80 | Stream.concat(Stream.of(Object.class), interfaceList.stream())
81 | .flatMap(type -> Arrays.stream(type.getMethods()))
82 | .filter(m -> (m.getModifiers() & (Modifier.FINAL | Modifier.STATIC)) == 0)
83 | .filter(m -> (m.getDeclaringClass() != Object.class && !m.isDefault()) || shouldOverride.test(m))
84 | .forEach(m -> map.putIfAbsent(new Key(m.getName(), MethodType.methodType(m.getReturnType(), m.getParameterTypes()).descriptorString()), m));
85 | var writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
86 | var interfaceNames = interfaceList.stream().map(itf -> itf.getName().replace('.', '/')).toArray(String[]::new);
87 | writer.visit(V16, ACC_PUBLIC | ACC_SUPER, proxyName, null, "java/lang/Object", interfaceNames);
88 | if (delegateClass != void.class) {
89 | var fv = writer.visitField(ACC_PRIVATE | ACC_FINAL, "delegate", delegateClass.descriptorString(), null, null);
90 | fv.visitEnd();
91 | }
92 |
93 | var init = writer.visitMethod(ACC_PUBLIC, "", delegateClass == void.class? "()V": "(" + delegateClass.descriptorString() + ")V", null, null);
94 | init.visitCode();
95 | init.visitVarInsn(ALOAD, 0);
96 | init.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
97 | if (delegateClass != void.class) {
98 | var delegateType = Type.getType(delegateClass);
99 | init.visitVarInsn(ALOAD, 0);
100 | init.visitVarInsn(delegateType.getOpcode(ILOAD), 1);
101 | init.visitFieldInsn(PUTFIELD, proxyName, "delegate", delegateClass.descriptorString());
102 | }
103 | init.visitInsn(RETURN);
104 | init.visitMaxs(-1, -1);
105 | init.visitEnd();
106 |
107 | map.forEach((key, method) -> {
108 | var descriptor = key.descriptor;
109 | var mv = writer.visitMethod(ACC_PUBLIC, key.name, descriptor, null, null);
110 | mv.visitCode();
111 | mv.visitVarInsn(ALOAD, 0);
112 | if (delegateClass != void.class) {
113 | mv.visitVarInsn(ALOAD, 0);
114 | mv.visitFieldInsn(GETFIELD, proxyName, "delegate", delegateClass.descriptorString());
115 | }
116 | var parameterSlot = 1;
117 | var parameterTypes = Type.getArgumentTypes(descriptor);
118 | for (var parameterType : parameterTypes) {
119 | mv.visitVarInsn(parameterType.getOpcode(ILOAD), parameterSlot);
120 | parameterSlot += parameterType.getSize();
121 | }
122 | var isInterface = method.getDeclaringClass().isInterface(); // it can be java/lang/Object
123 | var methodHandle = new Handle(isInterface? H_INVOKEINTERFACE: H_INVOKEVIRTUAL, method.getDeclaringClass().getName().replace('.', '/'), method.getName(), descriptor, isInterface);
124 | var proxyType = Arrays.stream(interfaces).findFirst().map(Class::descriptorString).orElse("Ljava/lang/Object;");
125 | var indyDesc = '(' + proxyType + (delegateClass == void.class? "": delegateClass.descriptorString()) + descriptor.substring(1);
126 | mv.visitInvokeDynamicInsn(method.getName(), indyDesc, BSM, methodHandle);
127 | mv.visitInsn(Type.getReturnType(descriptor).getOpcode(IRETURN));
128 | mv.visitMaxs(-1, -1);
129 | mv.visitEnd();
130 | });
131 | writer.visitEnd();
132 | return writer.toByteArray();
133 | }
134 |
135 | private static final Handle BSM = new Handle(H_INVOKESTATIC,
136 | Proxy.class.getName().replace('.', '/'),
137 | "proxyMetaFactory",
138 | MethodTypeDesc.of(CD_CallSite, CD_MethodHandles_Lookup, CD_String, CD_MethodType, CD_MethodHandle).descriptorString(),
139 | false);
140 |
141 | public static CallSite proxyMetaFactory(Lookup lookup, String name, MethodType methodType, MethodHandle mh) throws Throwable {
142 | Objects.requireNonNull(lookup);
143 | Objects.requireNonNull(name);
144 | Objects.requireNonNull(methodType);
145 | Objects.requireNonNull(mh);
146 | var info = lookup.revealDirect(mh);
147 | var linker = MethodHandles.classData(lookup, "_", Linker.class);
148 | var target = linker.resolve(info);
149 | if (target == null) {
150 | Objects.requireNonNull(target, "linker " + linker.getClass().getSimpleName() + " returned function is null for proxy method " + info);
151 | }
152 | try {
153 | target = target.asType(methodType);
154 | } catch(WrongMethodTypeException e) {
155 | String detail = "";
156 | if (target.type().parameterCount() != methodType.parameterCount()) {
157 | detail = ", maybe it's because the first parameter is not a type compatible with the proxy type ?";
158 | }
159 | throw new LinkageError("error for linker " + linker.getClass().getSimpleName() + " while trying to link proxy method " + info + detail, e);
160 | }
161 | return new ConstantCallSite(target);
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/com.github.forax.proxy/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module com.github.forax.proxy {
2 | requires org.objectweb.asm;
3 |
4 | exports com.github.forax.proxy;
5 | }
--------------------------------------------------------------------------------
/com.github.forax.proxy/src/test/java/com/github/forax/proxy/ProxyTest.java:
--------------------------------------------------------------------------------
1 | package com.github.forax.proxy;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.lang.invoke.MethodHandle;
6 | import java.lang.invoke.MethodHandles;
7 | import java.lang.invoke.MethodHandles.Lookup;
8 | import java.lang.reflect.Method;
9 | import java.util.Arrays;
10 | import java.util.function.DoubleSupplier;
11 | import java.util.function.IntBinaryOperator;
12 | import java.util.function.IntSupplier;
13 |
14 | import static java.lang.invoke.MethodHandles.constant;
15 | import static java.lang.invoke.MethodHandles.dropArguments;
16 | import static java.lang.invoke.MethodHandles.empty;
17 | import static java.lang.invoke.MethodHandles.identity;
18 | import static java.lang.invoke.MethodType.methodType;
19 | import static org.junit.jupiter.api.Assertions.assertEquals;
20 | import static org.junit.jupiter.api.Assertions.assertFalse;
21 | import static org.junit.jupiter.api.Assertions.assertNull;
22 | import static org.junit.jupiter.api.Assertions.assertTrue;
23 | import static org.junit.jupiter.api.Assertions.fail;
24 |
25 | public class ProxyTest {
26 | @Test
27 | public void callSum() throws Throwable {
28 | var lookup = MethodHandles.lookup();
29 | var target = lookup.findStatic(Integer.class, "sum", methodType(int.class, int.class, int.class));
30 |
31 | var proxyLookup = Proxy.defineProxy(lookup,
32 | new Class>[] { IntBinaryOperator.class },
33 | method -> false, // Object methods (toString, equals, hashCode and default method are not overridden
34 | void.class, // no field
35 | methodInfo -> dropArguments(target,0, Object.class) // drop the proxy which is the first argument
36 | );
37 |
38 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class));
39 | var proxy = (IntBinaryOperator) constructor.invoke();
40 | assertEquals(5, proxy.applyAsInt(2, 3));
41 | }
42 |
43 | @Test
44 | public void transparentProxy() throws Throwable {
45 | interface Foo {
46 | double m(int i);
47 | String s(Object o);
48 | }
49 | record FooImpl(int x) implements Foo {
50 | @Override
51 | public double m(int i) {
52 | return x + i;
53 | }
54 | @Override
55 | public String s(Object o) {
56 | return o.toString();
57 | }
58 | }
59 |
60 | var lookup = MethodHandles.lookup();
61 | var proxyLookup = Proxy.defineProxy(lookup,
62 | new Class>[] { Foo.class },
63 | method -> method.getDeclaringClass() == Object.class, // override Object methods (toString, equals, hashCode)
64 | Foo.class, // delegate field
65 | methodInfo -> {
66 | var method = methodInfo.reflectAs(Method.class, lookup);
67 | var target = lookup.unreflect(method);
68 | return dropArguments(target,0, Object.class); // drop the proxy which is the first argument
69 | }
70 | );
71 |
72 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class, Foo.class));
73 | var impl = new FooImpl(4);
74 | var proxy = (Foo) constructor.invoke(impl);
75 | assertEquals(8.0, proxy.m(4));
76 | assertEquals("bar", proxy.s("bar"));
77 | assertEquals(impl.toString(), proxy.toString());
78 | }
79 |
80 | private static boolean same(Object o1, Object o2) {
81 | return o1 == o2;
82 | }
83 |
84 | @Test
85 | public void defineDelegatingProxyPrimitive() throws Throwable {
86 | var lookup = MethodHandles.lookup();
87 | var same = lookup.findStatic(ProxyTest.class, "same", methodType(boolean.class, Object.class, Object.class));
88 | var linker = (Proxy.Linker)(methodInfo) -> switch(methodInfo.getName()) {
89 | case "getAsInt", "hashCode" -> dropArguments(MethodHandles.identity(int.class), 0, Object.class);
90 | case "equals" -> dropArguments(same, 1, int.class);
91 | case "toString" -> dropArguments(constant(String.class, "proxy"), 0, Object.class, int.class);
92 | default -> fail("unknown method " + methodInfo);
93 | };
94 |
95 | var proxyLookup = Proxy.defineProxy(lookup, new Class>[] { IntSupplier.class }, __ -> true, int.class, linker);
96 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class, int.class));
97 |
98 | var proxy = (IntSupplier)constructor.invoke(42);
99 | assertEquals(42, proxy.getAsInt());
100 | assertTrue(proxy.equals(proxy));
101 | assertFalse(proxy.equals("foo"));
102 | assertFalse(proxy.equals(null));
103 | assertEquals(42, proxy.hashCode());
104 | assertEquals("proxy", proxy.toString());
105 | }
106 |
107 | @Test
108 | public void defineProxyEmptyObjectMethods() throws Throwable {
109 | var lookup = MethodHandles.lookup();
110 | var proxyLookup = Proxy.defineProxy(lookup, new Class>[] { Runnable.class }, __ -> true, void.class, methodInfo -> {
111 | switch (methodInfo.getName()) {
112 | case "equals", "hashCode", "toString" -> { /* OK */ }
113 | default -> fail();
114 | }
115 |
116 | // return an empty implementation
117 | return empty(methodInfo.getMethodType().insertParameterTypes(0, Object.class));
118 | });
119 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class));
120 | var proxy = constructor.invoke();
121 | assertFalse(proxy.equals("not used"));
122 | assertEquals(0, proxy.hashCode());
123 | assertNull(proxy.toString());
124 | }
125 |
126 | @Test
127 | public void hello() throws Throwable {
128 | interface HelloProxy {
129 | String hello(String text);
130 | }
131 | class Impl {
132 | static String impl(int repeated, String text) {
133 | return text.repeat(repeated);
134 | }
135 | }
136 |
137 | Lookup lookup = MethodHandles.lookup();
138 | MethodHandle impl = lookup.findStatic(Impl.class, "impl", methodType(String.class, int.class, String.class));
139 | Proxy.Linker linker = methodInfo -> switch(methodInfo.getName()) {
140 | case "hello" -> MethodHandles.dropArguments(impl, 0, HelloProxy.class);
141 | default -> fail("unknown method " + methodInfo);
142 | };
143 | Lookup proxyLookup = Proxy.defineProxy(lookup, new Class>[] { HelloProxy.class }, __ -> false, int.class, linker);
144 | MethodHandle constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class, int.class));
145 | HelloProxy proxy = (HelloProxy) constructor.invoke(2);
146 | assertEquals("proxyproxy", proxy.hello("proxy"));
147 | }
148 |
149 | @Test
150 | public void exactProxyType() throws Throwable {
151 | interface Foo {
152 | int bar(int value);
153 | }
154 |
155 | var lookup = MethodHandles.lookup();
156 | var linker = (Proxy.Linker) methodInfo -> dropArguments(identity(int.class), 0, Foo.class);
157 | var proxyLookup = Proxy.defineProxy(lookup, new Class>[] { Foo.class }, __ -> false, void.class, linker);
158 | var constructor = proxyLookup.findConstructor(proxyLookup.lookupClass(), methodType(void.class))
159 | .asType(methodType(Foo.class));
160 | var proxy = (Foo) constructor.invokeExact();
161 | assertEquals(42, proxy.bar(42));
162 | }
163 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 | 4.0.0
6 | com.github.forax.hiddenproxy
7 | hidden_proxy
8 | pom
9 | 1.0-SNAPSHOT
10 | parent
11 | http://maven.apache.org
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 | com.github.forax.proxy
19 |
20 |
21 |
--------------------------------------------------------------------------------