MixinUtils { @SuppressWarnings("unchecked") public static <T> T mixin(T target, Class<?>... interfaces) { if (target == null) return null; final Class<?> targetClass = target.getClass(); if (Arrays.stream(interfaces).allMatch(i -> i.isAssignableFrom(targetClass))) { return target; } Class[] classes; int addedIndex; if (Proxy.isProxyClass(targetClass)) { MixinProxyHandler<T> handler = ((MixinProxyHandler<T>) Proxy.getInvocationHandler(target)); target = handler.getOriginal(); Class[] originalInterfaces = handler.getProxyInterfaces(); classes = new Class[originalInterfaces.length + interfaces.length]; System.arraycopy(originalInterfaces, 0, classes, 0, originalInterfaces.length); addedIndex = originalInterfaces.length; } else { Class[] targetInterfaces = getAllInterfaces(targetClass); classes = new Class[targetInterfaces.length + interfaces.length]; System.arraycopy(targetInterfaces, 0, classes, 0, targetInterfaces.length); addedIndex = targetInterfaces.length; } Arrays.asList(interfaces).forEach(i -> Arrays.stream(i.getMethods()) .filter(Method::isDefault) .forEach(MixinUtils::getMethodHandle)); System.arraycopy(interfaces, 0, classes, addedIndex, interfaces.length); ClassLoader cl = Thread.currentThread().getContextClassLoader(); return (T) Proxy.newProxyInstance(cl, classes, new MixinProxyHandler<>(target, classes)); }  @SuppressWarnings("unchecked") static T mixin(T target, Class<?>... interfaces);  }
@Test public void mixin() { Money m1 = new MoneyImpl(5); m1 = MixinUtils.mixin(m1, ComparableMoney.class); assertThat(ComparableMoney.class.cast(m1).isBigger(new MoneyImpl(3))) .isTrue(); }
@Test public void argumentIsNotInterface() { assertThatThrownBy(() -> { ImplOnly impl = new ImplOnly(); impl = MixinUtils.mixin(impl, ComparableMoney.class); ComparableMoney.class.cast(impl); }).isExactlyInstanceOf(ClassCastException.class); }
@Test public void proxyImplClass() { assertThatThrownBy(() -> { ImplOnly impl = new ImplOnly(); MixinUtils.mixin(impl, MoneyImpl.class); }).isExactlyInstanceOf(IllegalArgumentException.class); }
@Test public void multipleCall() { Money m1 = new MoneyImpl(5); m1 = MixinUtils.mixin(m1, ComparableMoney.class); Money m2 = MixinUtils.mixin(m1, ComparableMoney.class); assertThat(m2).isEqualTo(m1); }