├── .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