├── .gitignore
├── CITATION.cff
├── LICENCE.md
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── mx
│ └── kenzie
│ └── mimic
│ ├── ClassDefiner.java
│ ├── InternalAccess.java
│ ├── MethodExecutor.java
│ ├── MethodWriter.java
│ ├── Mimic.java
│ ├── MimicBuilder.java
│ └── MimicGenerator.java
└── test
└── java
└── mx
└── kenzie
└── mimic
└── test
├── MimicTest.java
└── PrivateMimicTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project exclude paths
2 | /target/
3 | .idea/
4 | .DS_Store
5 | *.iml
6 |
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | message: "If you use this software, please cite it as below."
3 | authors:
4 | - family-names: "Scott"
5 | given-names: "Mackenzie"
6 | title: "Mimic"
7 | version: 1.0.0
8 | date-released: 2021-12-25
9 | url: "https://github.com/Moderocky/Mimic"
10 |
--------------------------------------------------------------------------------
/LICENCE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Kenzie
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 | Mimic
2 | =====
3 |
4 | ### Opus #10
5 |
6 | A replacement candidate for Java's Proxy API. Allows the creation of 'mimicked' classes and interfaces with the ability to replace their method behaviour.
7 |
8 | ## Maven Information
9 | ```xml
10 |
11 | kenzie
12 | Kenzie's Repository
13 | https://repo.kenzie.mx/releases
14 |
15 | ```
16 |
17 | ```xml
18 |
19 | mx.kenzie
20 | mimic
21 | 1.1.0
22 | compile
23 |
24 | ```
25 |
26 | ## Introduction
27 |
28 | This small library was designed to replace the `java.lang.reflect.Proxy` system in the JDK's reflection API. Like most of the API, proxies are filled with needless, slow boilerplate and use old-fashioned practices with faster, modern alternatives.
29 |
30 | These internal APIs are very difficult to replace with third-party implementations: JDK classes benefit from being able to avoid module security, instruction verification and having access to powerful system methods that are completely barred to other packages in newer Java versions.
31 | By using my *Weapons of Disorder*, Mimic is able to access some of these internal benefits allowing it to sufficiently replace proxies.
32 |
33 | Mimic allows the superficial creation of any (non-final) type, with user-provided method behaviour. Unlike Java proxies, Mimic is not limited only to interfaces and allows superclasses to be used as well. No reflection is used during method-calling.
34 |
35 | This is effected by writing a class at runtime which adapts all the available methods to call a user-provided invoker system very similar to Java's proxy handler but without using a reflection object to increase speed and reliability.
36 |
37 | Mimic also comes with some tools for building more advanced proxy behaviour, such as forwarding specific methods directly to objects (or object-suppliers), mapping methods to individual executors or simply not implementing a method at all.
38 | While the resulting behaviour is achievable with the JDK's Proxy, it will be significantly slower as Mimic bakes the access route directly into the compiled code for maximum efficiency.
39 |
40 | ## Examples
41 |
42 | Creating a very basic mimic, identical in function to Java's proxies:
43 | ```java
44 | interface Blob {
45 | String say(String word);
46 | }
47 |
48 | final Blob blob = Mimic.create((proxy, method, arguments) -> arguments[0], Blob.class);
49 | assert blob.say("hello").equals("hello");
50 | ```
51 |
52 | Mimicking a default class:
53 | ```java
54 | class Alice {
55 | public Instant timeNow() {
56 | return Instant.now();
57 | }
58 | }
59 |
60 | final Alice alice = Mimic.create((proxy, method, arguments) -> Instant.EPOCH, Alice.class);
61 | assert alice.timeNow().equals(Instant.EPOCH);
62 | ```
63 |
64 | Mimicking multiple types:
65 | ```java
66 | class Alice {
67 | }
68 |
69 | interface Bob {
70 | }
71 |
72 | final Alice alice = Mimic.create(executor, Alice.class, Bob.class);
73 | assert alice instanceof Bob;
74 | ```
75 |
76 | Forwarding calls to an object:
77 | ```java
78 | interface Thing {
79 | int bean(int i);
80 |
81 | String say(String word);
82 | }
83 |
84 | class Box {
85 | public int bean(int i) {
86 | return 0;
87 | }
88 |
89 | public String say(String word) {
90 | return "hello";
91 | }
92 | }
93 |
94 | final Thing thing = Mimic.create(Thing.class).forward(new Box()).build();
95 | assert !(thing instanceof Box); // not a Box
96 | assert thing.bean(3) == 0; // forwarded call to the new Box()
97 | ```
98 |
99 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | mx.kenzie
8 | mimic
9 | 1.2.0
10 | Mimic
11 |
12 |
13 | 22
14 | 22
15 | UTF-8
16 |
17 |
18 |
19 |
20 | kenzie
21 | https://repo.kenzie.mx/releases
22 |
23 |
24 |
25 |
26 |
27 | kenzie
28 | https://repo.kenzie.mx/releases
29 |
30 |
31 |
32 |
33 |
34 | org.jetbrains
35 | annotations
36 | 22.0.0
37 | provided
38 |
39 |
40 | junit
41 | junit
42 | 4.13.2
43 | test
44 |
45 |
46 | org.valross
47 | constantine
48 | 1.0.0
49 |
50 |
51 | org.valross
52 | foundation-assembler
53 | 3.0.0
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/main/java/mx/kenzie/mimic/ClassDefiner.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic;
2 |
3 | @FunctionalInterface
4 | public interface ClassDefiner {
5 |
6 | Type define(ClassLoader loader, String name, byte[] bytecode);
7 |
8 | default String getPackage() {
9 | return null;
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/mx/kenzie/mimic/InternalAccess.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic;
2 |
3 | import org.valross.foundation.detail.Member;
4 | import sun.misc.Unsafe;
5 |
6 | import java.lang.invoke.MethodHandles;
7 | import java.lang.reflect.*;
8 | import java.security.PrivilegedExceptionAction;
9 | import java.security.ProtectionDomain;
10 |
11 | @SuppressWarnings("removal")
12 | public class InternalAccess implements ClassDefiner {
13 |
14 | static final String LOCATION = "com.sun.proxy";
15 | static Object javaLangAccess;
16 | static Unsafe unsafe;
17 | static Method defineClass;
18 | static Method addExports0;
19 | static long offset;
20 |
21 | static {
22 | try {
23 | final Class> secrets = Class.forName("jdk.internal.access.SharedSecrets", false,
24 | ClassLoader.getSystemClassLoader());
25 | unsafe = java.security.AccessController.doPrivileged((PrivilegedExceptionAction) () -> {
26 | final Field field = Unsafe.class.getDeclaredField("theUnsafe");
27 | field.setAccessible(true);
28 | return (Unsafe) field.get(null);
29 | });
30 | final Field field = Class.class.getDeclaredField("module");
31 | offset = unsafe.objectFieldOffset(field);
32 | unsafe.putObject(InternalAccess.class, offset, Object.class.getModule());
33 | final Method setAccessible0 = AccessibleObject.class.getDeclaredMethod("setAccessible0", boolean.class);
34 | setAccessible0.setAccessible(true);
35 | final Method implAddExportsOrOpens = Module.class.getDeclaredMethod("implAddExportsOrOpens", String.class
36 | , Module.class, boolean.class, boolean.class);
37 | setAccessible0.invoke(implAddExportsOrOpens, true);
38 | addExports0 = Module.class.getDeclaredMethod("addExports0", Module.class, String.class, Module.class);
39 | setAccessible0.invoke(addExports0, true);
40 | final Method getJavaLangAccess = secrets.getDeclaredMethod("getJavaLangAccess");
41 | setAccessible0.invoke(getJavaLangAccess, true);
42 | javaLangAccess = getJavaLangAccess.invoke(null);
43 | defineClass = javaLangAccess.getClass()
44 | .getMethod("defineClass", ClassLoader.class, String.class, byte[].class, ProtectionDomain.class,
45 | String.class);
46 | setAccessible0.invoke(defineClass, true);
47 | } catch (Throwable ex) {
48 | throw new RuntimeException(ex);
49 | }
50 | }
51 |
52 | InternalAccess() {
53 | }
54 |
55 | static void export(final Module module, final String namespace) {
56 | try {
57 | addExports0.invoke(null, module, namespace, Member.class.getModule());
58 | } catch (InvocationTargetException | IllegalAccessException e) {
59 | e.printStackTrace();
60 | module.addExports(namespace, Member.class.getModule());
61 | }
62 | }
63 |
64 | @SuppressWarnings("unchecked")
65 | static Type allocateInstance(Class> type) {
66 | try {
67 | return (Type) unsafe.allocateInstance(type);
68 | } catch (InstantiationException e) {
69 | throw new RuntimeException(e);
70 | }
71 | }
72 |
73 | static void put(Object target, long offset, Object value) {
74 | unsafe.putObject(target, offset, value);
75 | }
76 |
77 | static Module getStrictModule(Class> top, Class>... interfaces) {
78 | if (!top.getModule().isExported(top.getPackageName())) return top.getModule();
79 | for (final Class> place : interfaces) {
80 | if (!place.getModule().isExported(place.getPackageName())) return place.getModule();
81 | }
82 | return Member.class.getModule();
83 | }
84 |
85 | static String getStrictPackageName(Class> top, Class>... interfaces) {
86 | String namespace = LOCATION + ".mimic";
87 | if (top != null && !Modifier.isPublic(top.getModifiers())) namespace = top.getPackageName();
88 | for (final Class> place : interfaces) {
89 | if (!Modifier.isPublic(place.getModifiers())) namespace = place.getPackageName();
90 | }
91 | return namespace;
92 | }
93 |
94 | static long offset(Field field) {
95 | return unsafe.objectFieldOffset(field);
96 | }
97 |
98 | static void moveModule(Class> from, Class> to) {
99 | unsafe.putObject(from, offset, to.getModule());
100 | }
101 |
102 | public static Object getJavaLangAccess() {
103 | return javaLangAccess;
104 | }
105 |
106 | public static Unsafe getUnsafe() {
107 | return unsafe;
108 | }
109 |
110 | public static ClassDefiner createDefiner(Class> target) {
111 | try {
112 | final MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(target, MethodHandles.lookup());
113 | class Definer implements ClassDefiner {
114 |
115 | @Override
116 | public Type define(ClassLoader loader, String name, byte[] bytecode) {
117 | try {
118 | return (Type) lookup.defineHiddenClass(bytecode, true,
119 | MethodHandles.Lookup.ClassOption.NESTMATE)
120 | .lookupClass();
121 | } catch (IllegalAccessException e) {
122 | unsafe.throwException(e);
123 | return null;
124 | }
125 | }
126 |
127 | @Override
128 | public String getPackage() {
129 | return target.getPackageName();
130 | }
131 |
132 | }
133 | // moveModule(Definer.class, target);
134 | return new Definer();
135 | } catch (IllegalAccessException e) {
136 | unsafe.throwException(e);
137 | return null;
138 | }
139 | }
140 |
141 | static Class> loadClass(ClassLoader loader, String name, byte[] bytes) {
142 | try {
143 | return (Class>) defineClass.invoke(javaLangAccess, loader, name, bytes, null, "__Mimic__");
144 | } catch (InvocationTargetException | IllegalAccessException e) {
145 | e.printStackTrace();
146 | return null;
147 | }
148 | }
149 |
150 | @Override
151 | @SuppressWarnings({"unchecked"})
152 | public Type define(ClassLoader loader, String name, byte[] bytecode) {
153 | return (Type) loadClass(loader, name, bytecode);
154 | }
155 |
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/mx/kenzie/mimic/MethodExecutor.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic;
2 |
3 | import org.valross.foundation.detail.Signature;
4 |
5 | @FunctionalInterface
6 | @SuppressWarnings("unused")
7 | public interface MethodExecutor {
8 |
9 | Object invoke(Object proxy, Signature method, Object... arguments) throws Throwable;
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/mx/kenzie/mimic/MethodWriter.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic;
2 |
3 | import org.valross.foundation.assembler.code.Instruction;
4 | import org.valross.foundation.assembler.tool.Access;
5 | import org.valross.foundation.assembler.tool.ClassFileBuilder;
6 | import org.valross.foundation.assembler.tool.CodeBuilder;
7 | import org.valross.foundation.assembler.tool.MethodBuilder;
8 | import org.valross.foundation.detail.Member;
9 | import org.valross.foundation.detail.Signature;
10 | import org.valross.foundation.detail.Type;
11 |
12 | import java.lang.reflect.Method;
13 | import java.util.function.Supplier;
14 |
15 | import static org.valross.foundation.assembler.code.OpCode.*;
16 |
17 | class AcquireForwardingWriter extends ForwardingWriter {
18 |
19 | AcquireForwardingWriter(Class> type, int index) {
20 | super(type, index);
21 | }
22 |
23 | @Override
24 | protected void acquire(CodeBuilder visitor, Method method, Type internal) {
25 | visitor.write(ALOAD_0);
26 | visitor.write(GETFIELD.field(internal, "supplier_" + index, Supplier.class));
27 | visitor.write(INVOKEINTERFACE.method(true, Supplier.class, Object.class, "get"));
28 | visitor.write(CHECKCAST.type(target));
29 | }
30 |
31 | }
32 |
33 | class ForwardingWriter extends MethodWriter {
34 |
35 | protected Class> target;
36 | protected int index;
37 |
38 | ForwardingWriter(Class> target, int index) {
39 | this.target = target;
40 | this.index = index;
41 | }
42 |
43 | ForwardingWriter() {
44 | this.target = null;
45 | }
46 |
47 | @Override
48 | protected void write(MimicGenerator generator, Method original, ClassFileBuilder writer, Type internal,
49 | int index) {
50 | final Method method = this.findSimilar(original, target);
51 | final MethodBuilder visitor = writer.method().addModifiers(Access.PUBLIC, Access.FINAL, Access.SYNTHETIC)
52 | .named(method.getName()).signature(new Signature(method));
53 | CodeBuilder code = visitor.code();
54 | this.acquire(code, method, internal);
55 | int argumentIndex = 0;
56 | for (Class> parameter : method.getParameterTypes()) {
57 | code.write(ALOAD.var(parameter, ++argumentIndex));
58 | argumentIndex += wideIndexOffset(parameter);
59 | }
60 | final Class> owner = method.getDeclaringClass();
61 | final Member member = new Member(method);
62 | if (owner.isInterface())
63 | code.write(INVOKEINTERFACE.interfaceMethod(member));
64 | else code.write(INVOKEVIRTUAL.method(member));
65 | if (method.getReturnType() == void.class) {
66 | code.write(RETURN);
67 | } else {
68 | code.write((byte) (171 + instructionOffset(method.getReturnType())));
69 | }
70 | }
71 |
72 | protected void acquire(CodeBuilder visitor, Method method, Type internal) {
73 | visitor.write(ALOAD_0, GETFIELD.field(internal, "target_" + index, target));
74 | }
75 |
76 | }
77 |
78 | class SpecificExecutorWriter extends MethodWriter {
79 |
80 | protected final int index;
81 |
82 | SpecificExecutorWriter(int index) {
83 | this.index = index;
84 | }
85 |
86 | @Override
87 | protected void write(MimicGenerator generator, Method method, ClassFileBuilder writer, Type internal,
88 | final int index) {
89 | final MethodBuilder visitor = writer.method().addModifiers(Access.PUBLIC, Access.FINAL, Access.SYNTHETIC)
90 | .named(method.getName()).signature(new Signature(method));
91 | final CodeBuilder code = visitor.code();
92 | code.write(ALOAD_0, GETFIELD.field(internal, "executor_" + this.index, MethodExecutor.class));
93 | code.write(ALOAD_0, ALOAD_0, GETFIELD.field(internal, "methods", Signature[].class));
94 | code.write(BIPUSH.value(index), AALOAD, BIPUSH.value(method.getParameterCount()));
95 | code.write(ANEWARRAY.type(Object.class));
96 | int argumentIndex = 0;
97 | int storeIndex = -1;
98 | for (final Class> parameter : method.getParameterTypes()) {
99 | code.write(DUP, BIPUSH.value(++storeIndex));
100 | code.write(ALOAD.var(parameter, ++argumentIndex));
101 | this.box(code, parameter);
102 | code.write(AASTORE);
103 | argumentIndex += wideIndexOffset(parameter);
104 | }
105 | code.write(INVOKEINTERFACE.method(true, MethodExecutor.class, Object.class, "invoke", Object.class,
106 | Signature.class, Object[].class));
107 | if (method.getReturnType() == void.class) {
108 | code.write(POP, RETURN);
109 | } else {
110 | code.write(CHECKCAST.type(this.getWrapperType(method.getReturnType())));
111 | this.unbox(code, method.getReturnType());
112 | code.write((byte) (171 + instructionOffset(method.getReturnType())));
113 | }
114 | }
115 |
116 | }
117 |
118 | public class MethodWriter {
119 |
120 | protected void write(MimicGenerator generator, Method method, ClassFileBuilder writer, Type internal,
121 | final int index) {
122 | final MethodBuilder visitor = writer.method()
123 | .addModifiers(Access.PUBLIC, Access.FINAL, Access.SYNTHETIC)
124 | .named(method.getName()).signature(new Signature(method));
125 | final CodeBuilder code = visitor.code();
126 | code.write(ALOAD_0).write(GETFIELD.field(internal, "executor", MethodExecutor.class)).write(ALOAD_0);
127 | code.write(ALOAD_0, GETFIELD.field(internal, "methods", Signature[].class));
128 | code.write(BIPUSH.value(index), AALOAD, BIPUSH.value(method.getParameterCount()));
129 | code.write(ANEWARRAY.type(Object.class));
130 | int argumentIndex = 0;
131 | int storeIndex = -1;
132 | for (final Class> parameter : method.getParameterTypes()) {
133 | code.write(DUP, BIPUSH.value(++storeIndex));
134 | code.write(ALOAD.var(parameter, ++argumentIndex));
135 | this.box(code, parameter);
136 | code.write(AASTORE);
137 | argumentIndex += wideIndexOffset(parameter);
138 | }
139 | code.write(INVOKEINTERFACE.method(true, MethodExecutor.class, Object.class, "invoke", Object.class,
140 | Signature.class, Object[].class));
141 | if (method.getReturnType() == void.class) {
142 | code.write(POP, RETURN);
143 | } else {
144 | code.write(CHECKCAST.type(this.getWrapperType(method.getReturnType())));
145 | this.unbox(code, method.getReturnType());
146 | code.write((byte) (171 + instructionOffset(method.getReturnType())));
147 | }
148 | }
149 |
150 | protected int instructionOffset(Class> type) {
151 | if (type == int.class) return 1;
152 | if (type == boolean.class) return 1;
153 | if (type == byte.class) return 1;
154 | if (type == short.class) return 1;
155 | if (type == long.class) return 2;
156 | if (type == float.class) return 3;
157 | if (type == double.class) return 4;
158 | if (type == void.class) return 6;
159 | return 5;
160 | }
161 |
162 | protected void box(CodeBuilder visitor, Class> value) {
163 | if (value == byte.class)
164 | visitor.write(INVOKESTATIC.method(false, Byte.class, Byte.class, "valueOf", byte.class));
165 | if (value == short.class)
166 | visitor.write(INVOKESTATIC.method(false, Short.class, Short.class, "valueOf", short.class));
167 | if (value == int.class)
168 | visitor.write(INVOKESTATIC.method(false, Integer.class, Integer.class, "valueOf", int.class));
169 | if (value == long.class)
170 | visitor.write(INVOKESTATIC.method(false, Long.class, Long.class, "valueOf", long.class));
171 | if (value == float.class)
172 | visitor.write(INVOKESTATIC.method(false, Float.class, Float.class, "valueOf", float.class));
173 | if (value == double.class)
174 | visitor.write(INVOKESTATIC.method(false, Double.class, Double.class, "valueOf", double.class));
175 | if (value == boolean.class)
176 | visitor.write(INVOKESTATIC.method(false, Boolean.class, Boolean.class, "valueOf", boolean.class));
177 | if (value == char.class)
178 | visitor.write(INVOKESTATIC.method(false, Character.class, Character.class, "valueOf", char.class));
179 | if (value == void.class)
180 | visitor.write(ACONST_NULL);
181 | }
182 |
183 | protected int wideIndexOffset(Class> thing) {
184 | if (thing == long.class || thing == double.class) return 1;
185 | return 0;
186 | }
187 |
188 | protected Class> getWrapperType(Class> primitive) {
189 | if (primitive == byte.class) return Byte.class;
190 | if (primitive == short.class) return Short.class;
191 | if (primitive == int.class) return Integer.class;
192 | if (primitive == long.class) return Long.class;
193 | if (primitive == float.class) return Float.class;
194 | if (primitive == double.class) return Double.class;
195 | if (primitive == boolean.class) return Boolean.class;
196 | if (primitive == void.class) return Void.class;
197 | return primitive;
198 | }
199 |
200 | protected void unbox(CodeBuilder visitor, Class> parameter) {
201 | if (parameter == byte.class)
202 | visitor.write(INVOKEVIRTUAL.method(false, Number.class, byte.class, "byteValue"));
203 | if (parameter == short.class)
204 | visitor.write(INVOKEVIRTUAL.method(false, Number.class, short.class, "shortValue"));
205 | if (parameter == int.class)
206 | visitor.write(INVOKEVIRTUAL.method(false, Number.class, int.class, "intValue"));
207 | if (parameter == long.class)
208 | visitor.write(INVOKEVIRTUAL.method(false, Number.class, long.class, "longValue"));
209 | if (parameter == float.class)
210 | visitor.write(INVOKEVIRTUAL.method(false, Number.class, float.class, "floatValue"));
211 | if (parameter == double.class)
212 | visitor.write(INVOKEVIRTUAL.method(false, Number.class, double.class, "doubleValue"));
213 | if (parameter == boolean.class)
214 | visitor.write(INVOKEVIRTUAL.method(false, Boolean.class, boolean.class, "booleanValue"));
215 | if (parameter == char.class)
216 | visitor.write(INVOKEVIRTUAL.method(false, Character.class, char.class, "charValue"));
217 | }
218 |
219 | protected int wideIndexOffset(Class>[] params, Class> ret) {
220 | int i = 0;
221 | for (Class> param : params) {
222 | i += wideIndexOffset(param);
223 | }
224 | return Math.max(i, wideIndexOffset(ret));
225 | }
226 |
227 | protected void doTypeConversion(CodeBuilder visitor, Class> from, Class> to) {
228 | if (from == to) return;
229 | if (from == void.class || to == void.class) return;
230 | if (from.isPrimitive() && to.isPrimitive()) {
231 | final Instruction opcode;
232 | if (from == float.class) {
233 | if (to == double.class) opcode = F2D;
234 | else if (to == long.class) opcode = F2L;
235 | else opcode = F2I;
236 | } else if (from == double.class) {
237 | if (to == float.class) opcode = D2F;
238 | else if (to == long.class) opcode = D2L;
239 | else opcode = D2I;
240 | } else if (from == long.class) {
241 | if (to == float.class) opcode = L2F;
242 | else if (to == double.class) opcode = L2D;
243 | else opcode = L2I;
244 | } else {
245 | if (to == float.class) opcode = I2F;
246 | else if (to == double.class) opcode = I2D;
247 | else if (to == byte.class) opcode = I2B;
248 | else if (to == short.class) opcode = I2S;
249 | else if (to == char.class) opcode = I2C;
250 | else opcode = I2L;
251 | }
252 | visitor.write(opcode);
253 | } else if (from.isPrimitive() ^ to.isPrimitive()) {
254 | throw new IllegalArgumentException("Type wrapping is currently unsupported due to side-effects: '" + from.getSimpleName() + "' -> '" + to.getSimpleName() + "'");
255 | } else visitor.write(CHECKCAST.type(to));
256 | }
257 |
258 | protected Method findSimilar(Method method, Class> type) {
259 | try {
260 | return type.getMethod(method.getName(), method.getParameterTypes());
261 | } catch (NoSuchMethodException e) {
262 | return method;
263 | }
264 | }
265 |
266 | protected Method findSimilar(Signature erasure, Class> type) {
267 | try {
268 | return type.getMethod(erasure.name(), Type.classArray(erasure.parameters()));
269 | } catch (NoSuchMethodException e) {
270 | return null;
271 | }
272 | }
273 |
274 | }
275 |
--------------------------------------------------------------------------------
/src/main/java/mx/kenzie/mimic/Mimic.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic;
2 |
3 | import java.security.PrivilegedAction;
4 | import java.util.function.Supplier;
5 |
6 | public interface Mimic {
7 |
8 | static Template create(Supplier target, Class type) {
9 | return create(type).forward(target, type).build();
10 | }
11 |
12 | static MimicBuilder create(Class template, Class>... interfaces) {
13 | return new SimpleMimicBuilder<>(template, interfaces);
14 | }
15 |
16 | @SuppressWarnings("removal")
17 | static Template create(MethodExecutor executor, Class template, Class>... interfaces) {
18 | final int index = MimicGenerator.count();
19 | final String name = InternalAccess.getStrictPackageName(template, interfaces);
20 | final Module module = InternalAccess.getStrictModule(template, interfaces);
21 | final String path = name.replace('.', '/') + "/Mimic_" + index;
22 | final PrivilegedAction action = module::getClassLoader;
23 | final ClassLoader loader = java.security.AccessController.doPrivileged(action);
24 | return new MimicGenerator(path, template, interfaces).create(loader, executor);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/mx/kenzie/mimic/MimicBuilder.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic;
2 |
3 | import org.valross.foundation.detail.Signature;
4 |
5 | import java.lang.reflect.Method;
6 | import java.lang.reflect.Modifier;
7 | import java.security.PrivilegedAction;
8 | import java.util.ArrayList;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.function.Supplier;
13 |
14 | public interface MimicBuilder {
15 |
16 | MimicBuilder definer(ClassDefiner definer);
17 |
18 | MimicBuilder forward(Object object);
19 |
20 | MimicBuilder forward(Supplier supplier, Class type);
21 |
22 | default MimicBuilder override(MethodExecutor executor, Method... methods) {
23 | final Signature[] erasures = new Signature[methods.length];
24 | for (int i = 0; i < methods.length; i++) {
25 | erasures[i] = new Signature(methods[i]);
26 | }
27 | return this.override(executor, erasures);
28 | }
29 |
30 | MimicBuilder override(MethodExecutor executor, Signature... methods);
31 |
32 | MimicBuilder executor(MethodExecutor executor);
33 |
34 | Template build();
35 |
36 | }
37 |
38 | class SimpleMimicBuilder implements MimicBuilder {
39 |
40 | protected static final MethodExecutor DEFAULT = (object, erasure, args) -> {
41 | throw new RuntimeException("This method has no defined executor.");
42 | };
43 | protected final Map overrides;
44 | protected final Map fields;
45 | protected Class top;
46 | protected Class>[] interfaces;
47 | protected MethodExecutor executor = DEFAULT;
48 | protected int index;
49 | protected ClassDefiner definer;
50 |
51 | public SimpleMimicBuilder(Class top, Class>... interfaces) {
52 | this.top = top;
53 | this.interfaces = interfaces;
54 | this.overrides = new HashMap<>();
55 | this.fields = new HashMap<>();
56 | }
57 |
58 | @Override
59 | public MimicBuilder definer(ClassDefiner definer) {
60 | this.definer = definer;
61 | return this;
62 | }
63 |
64 | @Override
65 | public MimicBuilder forward(Object object) {
66 | final int index = ++this.index;
67 | final MethodWriter writer = new ForwardingWriter(object.getClass(), index);
68 | for (final Signature erasure : this.scrapeMethods(object.getClass())) {
69 | this.overrides.put(erasure, writer);
70 | }
71 | this.fields.put(new Signature("target_" + index, object.getClass()), object);
72 | return this;
73 | }
74 |
75 | @Override
76 | public MimicBuilder forward(Supplier supplier, Class type) {
77 | final int index = ++this.index;
78 | final MethodWriter writer = new AcquireForwardingWriter(type, index);
79 | for (final Signature erasure : this.scrapeMethods(type)) {
80 | this.overrides.put(erasure, writer);
81 | }
82 | this.fields.put(new Signature("supplier_" + index, Supplier.class), supplier);
83 | return this;
84 | }
85 |
86 | @Override
87 | public MimicBuilder override(MethodExecutor executor, Signature... methods) {
88 | final int index = ++this.index;
89 | final MethodWriter writer = new SpecificExecutorWriter(index);
90 | for (final Signature erasure : methods) {
91 | this.overrides.put(erasure.asMethodErasure().getSignature(), writer);
92 | }
93 | this.fields.put(new Signature("executor_" + index, MethodExecutor.class), executor);
94 | return this;
95 | }
96 |
97 | @Override
98 | public MimicBuilder executor(MethodExecutor executor) {
99 | this.executor = executor;
100 | return this;
101 | }
102 |
103 | @Override
104 | @SuppressWarnings("removal")
105 | public Template build() {
106 | final int index = MimicGenerator.count();
107 | final String name;
108 | if (definer != null && definer.getPackage() != null) name = definer.getPackage();
109 | else name = InternalAccess.getStrictPackageName(top, interfaces);
110 | final Module module = InternalAccess.getStrictModule(top, interfaces);
111 | final String path = name.replace('.', '/') + "/Mimic_" + index;
112 | final PrivilegedAction action = module::getClassLoader;
113 | final ClassLoader loader = java.security.AccessController.doPrivileged(action);
114 | final MimicGenerator generator = new MimicGenerator(path, top, interfaces);
115 | if (definer != null) generator.definer = definer;
116 | generator.overrides.putAll(this.overrides);
117 | generator.fields.putAll(this.fields);
118 | return generator.create(loader, executor);
119 | }
120 |
121 | protected List scrapeMethods(Class> template) {
122 | final List methods = new ArrayList<>();
123 | for (final Method method : template.getMethods()) {
124 | if (Modifier.isStatic(method.getModifiers())) continue;
125 | if (Modifier.isFinal(method.getModifiers())) continue;
126 | if (Modifier.isPrivate(method.getModifiers())) continue;
127 | final Signature erasure = new Signature(method);
128 | if (overrides.containsKey(erasure)) continue;
129 | methods.add(erasure);
130 | }
131 | return methods;
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/mx/kenzie/mimic/MimicGenerator.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic;
2 |
3 | import org.valross.foundation.assembler.tool.Access;
4 | import org.valross.foundation.assembler.tool.ClassFileBuilder;
5 | import org.valross.foundation.detail.Signature;
6 | import org.valross.foundation.detail.Type;
7 |
8 | import java.lang.reflect.Field;
9 | import java.lang.reflect.Method;
10 | import java.lang.reflect.Modifier;
11 | import java.util.*;
12 |
13 | import static org.valross.foundation.detail.Version.JAVA_17;
14 | import static org.valross.foundation.detail.Version.RELEASE;
15 |
16 | public class MimicGenerator {
17 |
18 | private static volatile int counter;
19 | protected final ClassFileBuilder writer;
20 | protected final Type internal;
21 | protected final Class> top;
22 | protected final Class>[] interfaces;
23 | protected final List finished;
24 | protected final MethodWriter handler;
25 | protected final Map overrides = new HashMap<>();
26 | protected final Map fields = new HashMap<>();
27 | protected ClassDefiner definer = new InternalAccess();
28 | protected int index;
29 |
30 | protected MimicGenerator(String location, Class> top, Class>... interfaces) {
31 | this(new MethodWriter(), location, top, interfaces);
32 | }
33 |
34 | protected MimicGenerator(MethodWriter handler, String location, Class> top, Class>... interfaces) {
35 | this.handler = handler;
36 | this.writer = new ClassFileBuilder(JAVA_17, RELEASE);
37 | this.internal = Type.fromInternalName(location);
38 | this.top = top;
39 | this.interfaces = interfaces;
40 | this.finished = new ArrayList<>();
41 | }
42 |
43 | static synchronized int count() {
44 | return counter++;
45 | }
46 |
47 | @SuppressWarnings("unchecked")
48 | public Template create(ClassLoader loader, MethodExecutor executor) {
49 | final boolean complex = !top.isInterface() || !overrides.isEmpty() || !fields.isEmpty();
50 | final byte[] bytecode = writeCode();
51 | final Class> type = definer.define(loader, internal.getTypeName(), bytecode);
52 | assert type != null;
53 | final Object object = this.allocateInstance(type);
54 | if (complex) {
55 | for (Map.Entry entry : fields.entrySet())
56 | this.setField(object, entry.getKey().name(), entry.getValue());
57 | this.setField(object, "executor", executor);
58 | this.setField(object, "methods", finished.toArray(new Signature[0]));
59 | } else { // trivial
60 | this.putValue(object, 12, executor);
61 | this.putValue(object, 16, finished.toArray(new Signature[0]));
62 | }
63 | return (Template) object;
64 | }
65 |
66 | protected byte[] writeCode() {
67 | this.writer.setType(internal);
68 | this.writer.setSuperType(top != null && !top.isInterface() ? top : Object.class);
69 | this.writer.setInterfaces(this.getInterfaces());
70 | this.writer.setModifiers(Access.PUBLIC, Access.FINAL, Access.SYNTHETIC);
71 | this.writer.field().setModifiers(Access.PROTECTED, Access.TRANSIENT)
72 | .signature(new Signature("executor", MethodExecutor.class));
73 | this.writer.field().setModifiers(Access.PROTECTED, Access.TRANSIENT)
74 | .signature(new Signature("methods", Signature[].class));
75 | for (final Signature erasure : fields.keySet()) {
76 | this.writer.field().named(erasure.name()).signature(erasure);
77 | }
78 | if (top != null && top != Object.class) this.scrapeMethods(top);
79 | for (final Class> template : interfaces) this.scrapeMethods(template);
80 | return writer.bytecode();
81 | }
82 |
83 | protected Object allocateInstance(Class> type) {
84 | return InternalAccess.allocateInstance(type);
85 | }
86 |
87 | private void setField(Object owner, String name, Object value) {
88 | final Field field = this.getField(owner.getClass(), name);
89 | if (field == null) return;
90 | final long offset = this.offset(this.getField(owner.getClass(), name));
91 | this.putValue(owner, offset, value);
92 | }
93 |
94 | protected void putValue(final Object object, final long offset, final Object value) {
95 | InternalAccess.put(object, offset, value);
96 | }
97 |
98 | protected Type[] getInterfaces() {
99 | final Set types = new HashSet<>();
100 | if (top.isInterface()) types.add(Type.of(top));
101 | for (final Class> type : interfaces) types.add(Type.of(type));
102 | return types.toArray(new Type[0]);
103 | }
104 |
105 | protected void scrapeMethods(Class> template) {
106 | for (final Method method : template.getMethods()) {
107 | if (Modifier.isStatic(method.getModifiers())) continue;
108 | if (Modifier.isFinal(method.getModifiers())) continue;
109 | if (Modifier.isPrivate(method.getModifiers())) continue;
110 | final Signature erasure = new Signature(method);
111 | if (finished.contains(erasure)) continue;
112 | this.finished.add(erasure);
113 | this.writeCaller(method);
114 | this.index++;
115 | }
116 | }
117 |
118 | private Field getField(Class> owner, String name) {
119 | try {
120 | return owner.getDeclaredField(name);
121 | } catch (NoSuchFieldException | NullPointerException ex) {
122 | return null;
123 | }
124 | }
125 |
126 | protected long offset(final Field field) {
127 | return InternalAccess.offset(field);
128 | }
129 |
130 | protected void writeCaller(Method method) {
131 | final Signature erasure = new Signature(method);
132 | final MethodWriter writer;
133 | if (overrides.containsKey(erasure)) writer = this.overrides.get(erasure);
134 | else writer = this.handler;
135 | writer.write(this, method, this.writer, internal, index);
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/test/java/mx/kenzie/mimic/test/MimicTest.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic.test;
2 |
3 | import mx.kenzie.mimic.Mimic;
4 | import org.junit.Test;
5 | import org.valross.foundation.detail.Signature;
6 |
7 | public class MimicTest {
8 |
9 | @Test
10 | public void basic() {
11 |
12 | interface Blob {
13 |
14 | int bean(int i);
15 |
16 | String say(String word);
17 |
18 | }
19 |
20 | class Box {
21 |
22 | public String doSomething(String word) {
23 | return "goodbye";
24 | }
25 |
26 | public String doSomething(int i) {
27 | return "goodbye";
28 | }
29 |
30 | }
31 |
32 | final Blob blob = Mimic.create((_, _, arguments) -> arguments[0], Blob.class);
33 | assert blob.bean(2) == 2;
34 | assert blob.say("hello").equals("hello");
35 | assert !(blob instanceof Box);
36 |
37 | final Box box = Mimic.create((_, _, arguments) -> arguments[0] + "", Box.class, Blob.class);
38 | assert box.doSomething("goodbye").equals("goodbye");
39 | assert box.doSomething(6).equals("6");
40 | assert box instanceof Blob;
41 |
42 | }
43 |
44 | @Test
45 | public void forward() {
46 |
47 | interface Blob {
48 |
49 | int bean(int i);
50 |
51 | String say(String word);
52 |
53 | }
54 |
55 | class Thing implements Blob {
56 |
57 | @Override
58 | public int bean(int i) {
59 | return 0;
60 | }
61 |
62 | @Override
63 | public String say(String word) {
64 | return "hello";
65 | }
66 |
67 | }
68 |
69 | {
70 | final Blob blob = Mimic.create(Blob.class).forward(Thing::new, Thing.class).build();
71 | assert blob.bean(2) == 0;
72 | assert blob.say("box").equals("hello");
73 | assert !(blob instanceof Thing);
74 | }
75 |
76 | {
77 | final Blob blob = Mimic.create(Blob.class).forward(new Thing()).build();
78 | assert blob.bean(2) == 0;
79 | assert blob.say("box").equals("hello");
80 | assert !(blob instanceof Thing);
81 | }
82 |
83 | }
84 |
85 | @Test
86 | public void override() {
87 |
88 | interface Blob {
89 |
90 | int a();
91 |
92 | int b();
93 |
94 | int c();
95 |
96 | }
97 |
98 | class Thing1 {
99 |
100 | public int a() {
101 | return 6;
102 | }
103 |
104 | public int b() {
105 | return 5;
106 | }
107 |
108 | }
109 |
110 | class Thing2 {
111 |
112 | public int a() {
113 | return 3;
114 | }
115 |
116 | public int c() {
117 | return 2;
118 | }
119 |
120 | }
121 |
122 | {
123 | final Blob blob = Mimic.create(Blob.class)
124 | .forward(Thing1::new, Thing1.class)
125 | .forward(Thing2::new, Thing2.class)
126 | .build();
127 | assert blob.a() == 6;
128 | assert blob.b() == 5;
129 | assert blob.c() == 2;
130 | assert !(blob instanceof Thing1);
131 | assert !(blob instanceof Thing2);
132 | }
133 |
134 | {
135 | final Blob blob = Mimic.create(Blob.class)
136 | .override((proxy, method, arguments) -> 17, new Signature(int.class, "b"))
137 | .forward(new Thing1())
138 | .forward(Thing2::new, Thing2.class)
139 | .build();
140 | assert blob.a() == 6;
141 | assert blob.b() == 17;
142 | assert blob.c() == 2;
143 | assert !(blob instanceof Thing1);
144 | assert !(blob instanceof Thing2);
145 | }
146 |
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/src/test/java/mx/kenzie/mimic/test/PrivateMimicTest.java:
--------------------------------------------------------------------------------
1 | package mx.kenzie.mimic.test;
2 |
3 | import mx.kenzie.mimic.InternalAccess;
4 | import mx.kenzie.mimic.Mimic;
5 | import org.junit.BeforeClass;
6 | import org.junit.Test;
7 |
8 | public class PrivateMimicTest {
9 |
10 | static Object javaLangAccess;
11 |
12 | @BeforeClass
13 | public static void find() {
14 | javaLangAccess = InternalAccess.getJavaLangAccess();
15 | }
16 |
17 | @Test
18 | public void test() {
19 |
20 | interface JLS {
21 |
22 | Object classData(Class> c);
23 |
24 | }
25 |
26 | final JLS jls = Mimic.create(JLS.class).forward(javaLangAccess).build();
27 | IllegalAccessError error = null;
28 | try {
29 | jls.classData(PrivateMimicTest.class);
30 | } catch (IllegalAccessError ex) {
31 | error = ex;
32 | }
33 | assert error != null;
34 |
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------