Index: src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/AbstractLazyLoader.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/AbstractLazyLoader.java b/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/AbstractLazyLoader.java --- a/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/AbstractLazyLoader.java (revision c1069b74c36ee39fda56163df450d557971636fa) +++ b/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/AbstractLazyLoader.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -19,18 +19,18 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.function.Function; +import org.apache.jackrabbit.ocm.manager.objectconverter.proxy.ProxyBuddy.InvocationHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.sf.cglib.proxy.InvocationHandler; - /** * Parent Class of the OCM Lazy Loaders * * @author Stephane LANDELLE */ -public abstract class AbstractLazyLoader implements InvocationHandler, Serializable { +public abstract class AbstractLazyLoader implements InvocationHandler, Serializable { /** * The logger @@ -82,12 +82,8 @@ /** * Invoke proxy methods : delegate to proxified instance except for OcmProxy * methods that are intercepted (because not concretely implemented) - * - * @see net.sf.cglib.proxy.InvocationHandler#invoke(java.lang.Object, - * java.lang.reflect.Method, java.lang.Object[]) */ - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - + public Object invoke(T proxy, Function pipe, Method method, Object... args) throws Throwable { if (!(proxy instanceof OcmProxy)) { throw new IllegalArgumentException("proxy should implement OcmProxy"); } @@ -118,4 +114,5 @@ } return returnValue == getTarget() ? proxy : returnValue; } + } Index: src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/ProxyManagerImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/ProxyManagerImpl.java b/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/ProxyManagerImpl.java --- a/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/ProxyManagerImpl.java (revision c1069b74c36ee39fda56163df450d557971636fa) +++ b/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/impl/ProxyManagerImpl.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -17,21 +17,22 @@ package org.apache.jackrabbit.ocm.manager.objectconverter.impl; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; -import org.apache.commons.lang.ArrayUtils; import org.apache.jackrabbit.ocm.manager.beanconverter.BeanConverter; import org.apache.jackrabbit.ocm.manager.collectionconverter.CollectionConverter; import org.apache.jackrabbit.ocm.manager.objectconverter.ProxyManager; +import org.apache.jackrabbit.ocm.manager.objectconverter.proxy.ProxyBuddy; import org.apache.jackrabbit.ocm.mapper.model.BeanDescriptor; import org.apache.jackrabbit.ocm.mapper.model.ClassDescriptor; import org.apache.jackrabbit.ocm.mapper.model.CollectionDescriptor; -import net.sf.cglib.proxy.Callback; -import net.sf.cglib.proxy.Enhancer; - public class ProxyManagerImpl implements ProxyManager { /** @@ -49,8 +50,18 @@ throw new org.apache.jackrabbit.ocm.exception.RepositoryException("Impossible to check,if the object exits on " + path, e); } - Callback loader = new BeanLazyLoader(beanConverter, session, parentNode, beanDescriptor, beanClassDescriptor, beanClass, parent); - return Enhancer.create(beanClass, getInterfaces(beanClass), loader); + var loader = new BeanLazyLoader(beanConverter, session, parentNode, beanDescriptor, beanClassDescriptor, beanClass, parent); + try { + return new ProxyBuddy<>(beanClass, loader) + .withInterfaces(getInterfaces(beanClass)) + .createProxy(); + } catch (IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e) { + throw new IllegalStateException(e); + } + } + + private static class Base { + public Base() {} } /** @@ -67,20 +78,29 @@ return null; } - Callback loader = new CollectionLazyLoader(collectionConverter, session, parentNode, collectionDescriptor, collectionFieldClass); - return Enhancer.create(collectionFieldClass, getInterfaces(collectionFieldClass), loader); + var loader = new CollectionLazyLoader(collectionConverter, session, parentNode, collectionDescriptor, collectionFieldClass); + try { + Class baseClass = collectionFieldClass.isInterface() + ? Base.class // Package access restrictions prevent us from using Object.class, so we use this dummy base class instead + : collectionFieldClass; + return new ProxyBuddy<>(baseClass, loader) + .withInterfaces(getInterfaces(collectionFieldClass)) + .createProxy(); + } catch (IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e) { + throw new IllegalStateException(e); + } } - - private Class[] getInterfaces(Class collectionFieldClass) { - - Class[] interfaces = null; + + private List> getInterfaces(Class collectionFieldClass) { + List> interfaces = new ArrayList<>(); if (collectionFieldClass.isInterface()) { // if collectionFieldClass is an interface, simply use it - interfaces = new Class[] { collectionFieldClass }; + interfaces.add(collectionFieldClass); } else { // else, use all interfaces - interfaces = collectionFieldClass.getInterfaces(); + interfaces.addAll(List.of(collectionFieldClass.getInterfaces())); } - return (Class[]) ArrayUtils.add(interfaces, OcmProxy.class); + interfaces.add(OcmProxy.class); + return interfaces; } } Index: src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyBuddy.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyBuddy.java b/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyBuddy.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/main/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyBuddy.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,321 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodHandles.privateLookupIn; +import static net.bytebuddy.description.modifier.Visibility.PRIVATE; +import static net.bytebuddy.description.modifier.Visibility.PUBLIC; +import static net.bytebuddy.implementation.MethodCall.invoke; +import static net.bytebuddy.implementation.MethodDelegation.withDefaultConfiguration; +import static net.bytebuddy.implementation.bind.annotation.Pipe.Binder.install; +import static net.bytebuddy.matcher.ElementMatchers.isPublic; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.Pipe; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.This; + +/** + * {@code ProxyBuddy} is a simple factory for creating dynamic proxies for arbitrary + * Java classes and interfaces with ByteBuddy. + *
+ * var proxy = new ProxyBuddy<>(MyClass.class, (pipe, method, arguments) ->
+ *     switch (method.getName()) {
+ *         case "method1" -> 1;
+ *         case "method2" -> 2;
+ *         case "method3" -> 3;
+ *         default -> 0;
+ *     })
+ *     .withInterface(Interface1.class)
+ *     .withInterface(Interface2.class)
+ *     .withInterface(Interface3.class)
+ *     .createProxy();
+ * 
+ * + * Adapted from ProxyBuddy + */ +public class ProxyBuddy { + private final Class superClass; + private final Cons> interfaces; + private final Constructor constructor; + private final Object[] arguments; + private final InvocationHandler invocationHandler; + + private ProxyBuddy( + Class superClass, + Cons> interfaces, + Constructor constructor, + Object[] arguments, + InvocationHandler invocationHandler) { + this.superClass = superClass; + this.interfaces = interfaces; + this.constructor = constructor; + this.arguments = arguments; + this.invocationHandler = invocationHandler; + } + + /** + * Implementations of {@code InvocationHandler} are responsible for dispatching calls + * to the proxy instance. + * + * @param + */ + public interface InvocationHandler { + + /** + * The {@code invoke} method is called for each call to a method of a proxy. + * @param proxy the proxy instance the method is invoked on + * @param pipe a function for forwarding a proxy call to a real instance of the same type {@code T}. + * @param method the method that was called on the proxy + * @param arguments the arguments that were passed to the proxy method + * @return result of the method call + * @throws Exception + */ + Object invoke(T proxy, Function pipe, Method method, Object... arguments) throws Throwable; + } + + /** + * Create a proxy of type {@code T} for the given {@code superClass}. The returned instance + * is a subclass of {@code superClass}. + * @param superClass class to proxy + * @param invocationHandler handler receiving all calls to the returned proxy + */ + public ProxyBuddy(Class superClass, InvocationHandler invocationHandler) { + this(superClass, cons(ProxyBuddyProxy.class, null), null, new Object[]{}, invocationHandler); + } + + /** + * Create a proxy implementing the given interface. + * @param interfaze interface to implement + * @return a new {@code ProxyBuddy} instance + */ + public ProxyBuddy withInterface(Class interfaze) { + return new ProxyBuddy<>(superClass, cons(interfaze, interfaces), constructor, arguments, invocationHandler); + } + + public ProxyBuddy withInterfaces(List> interfaces) { + return new ProxyBuddy<>(superClass, Cons.cons(interfaces), constructor, arguments, invocationHandler); + } + + interface Witness { + Object get(); + } + + /** + * Create a proxy with correct implementations for {@code equals} and {@code hashCode}. + * With this implementation a proxy will never equal an instance of the proxied class. + * @param witness a object for witnessing the equality between a proxy and proxied instance. + * The only requirement for the witness is to implement {@code equals} and + * {@code hashCode} consistently with the proxied class. + * @return a new {@code ProxyBuddy} instance + */ + public ProxyBuddy withProxyNeverEqualsTarget(Object witness) { + return new ProxyBuddy<>(superClass, interfaces, constructor, arguments, (proxy, pipe, method, arguments) -> { + if ("equals".equals(method.getName())) { + if (isProxy(arguments[0])) { + return arguments[0].equals((Witness) () -> witness); + } else { + if (arguments[0] instanceof Witness) { + return witness.equals(((Witness) arguments[0]).get()); + } else { + return false; + } + } + } else if ("hashCode".equals(method.getName())) { + return 31 * witness.hashCode(); + } else { + return invocationHandler.invoke(proxy, pipe, method, arguments); + } + }); + } + + /** + * Create a proxy with correct implementations for {@code equals} and {@code hashCode}. + * With this implementation a may equal an instance of the proxied class. + * @param witness a object for witnessing the equality between a proxy and proxied instance. + * The only requirement for the witness is to implement {@code equals} and + * {@code hashCode} consistently with the proxied class. + * @return a new {@code ProxyBuddy} instance + */ + public ProxyBuddy withProxyCanEqualTarget(Object witness) { + return new ProxyBuddy<>(superClass, interfaces, constructor, arguments, (proxy, pipe, method, arguments) -> { + if ("equals".equals(method.getName())) { + return witness.equals(arguments[0]); + } else if ("hashCode".equals(method.getName())) { + return witness.hashCode(); + } else { + return invocationHandler.invoke(proxy,pipe, method, arguments); + } + }); + } + + /** + * Create a proxy that uses the supplied constructor and arguments for creating a super class + * instance. + * @param constructor constructor for creating the super class instance + * @param arguments arguments passed to the constructor for creating the super class instance + * @return + */ + public ProxyBuddy withConstructor(Constructor constructor, Object... arguments) { + return new ProxyBuddy<>(superClass, interfaces, constructor, arguments, invocationHandler); + } + + /** + * Create a proxy for a class of type {@code T} + * @return a new proxy instance of type {@code T} + * @throws IllegalAccessException + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws InstantiationException + */ + public T createProxy() + throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { + return new ByteBuddy() + .subclass(superClass) + .implement(toList(interfaces)) + .method(isPublic()) + .intercept(withDefaultConfiguration() + .withBinders(install(Function.class)) + .toField("INVOKER")) + .defineField("INVOKER", Invoker.class, PRIVATE) + .defineConstructor(PUBLIC) + .withParameters(Invoker.class) + .intercept((constructor == null + ? invoke(superClass.getConstructor()) + : invoke(constructor).with(arguments)) + .andThen(FieldAccessor.ofField("INVOKER").setsArgumentAt(0))) + .make() + .load(superClass.getClassLoader(), ClassLoadingStrategy.UsingLookup.of(privateLookupIn(superClass, lookup()))) + .getLoaded() + .getConstructor(Invoker.class) + .newInstance(new Invoker<>(invocationHandler)); + } + + /** + * Helper method to determine whether an instance is a proxy created through {@code ProxyBuddy}. + * @param object an instance to check + * @return {@code true} if and only if {@code object} is a proxy created by {@code ProxyBuddy}. + */ + public static boolean isProxy(Object object) { + return object instanceof ProxyBuddyProxy; + } + + /** + * Marker interface for {@code ProxyBuddy} proxies. + */ + public interface ProxyBuddyProxy {} + + /** + * Internal helper class for delegating method calls from a proxy instance to + * the {@link InvocationHandler}. + * @param + */ + public static final class Invoker { + + private final InvocationHandler invocationHandler; + + private Invoker(InvocationHandler invocationHandler) { + this.invocationHandler = invocationHandler; + } + + @RuntimeType + public Object delegate(@This T proxy, @Pipe Function pipe, @Origin Method method, @AllArguments Object[] args) + throws Throwable { + return invocationHandler.invoke(proxy, pipe, method, args); + } + } + + private static final class Cons { + private final T value; + private final Cons values; + + private Cons(T value, Cons values) { + this.value = value; + this.values = values; + } + + private static Cons cons(List ts) { + if (ts.isEmpty()) { + return null; + } else { + return new Cons<>(ts.get(0), cons(ts.subList(1, ts.size()))); + } + } + + public T value() { + return value; + } + + public Cons values() { + return values; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + var that = (Cons) obj; + return Objects.equals(this.value, that.value) && + Objects.equals(this.values, that.values); + } + + @Override + public int hashCode() { + return Objects.hash(value, values); + } + + @Override + public String toString() { + return "Cons[" + + "value=" + value + ", " + + "values=" + values + ']'; + } + + } + + private static Cons cons(T value, Cons values) { + return new Cons<>(value, values); + } + + private static List toList(Cons values) { + var result = new ArrayList(); + + while (values != null) { + result.add(values.value); + values = values.values; + } + return result; + } + +} Index: src/main/java/org/apache/jackrabbit/ocm/mapper/model/MappingDescriptor.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/main/java/org/apache/jackrabbit/ocm/mapper/model/MappingDescriptor.java b/src/main/java/org/apache/jackrabbit/ocm/mapper/model/MappingDescriptor.java --- a/src/main/java/org/apache/jackrabbit/ocm/mapper/model/MappingDescriptor.java (revision c1069b74c36ee39fda56163df450d557971636fa) +++ b/src/main/java/org/apache/jackrabbit/ocm/mapper/model/MappingDescriptor.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -91,7 +91,14 @@ * @return the class descriptor found or null */ public ClassDescriptor getClassDescriptorByName(String className) { - return (ClassDescriptor) classDescriptorsByClassName.get(className); + return (ClassDescriptor) classDescriptorsByClassName.get(unmangleClassname(className)); + } + + private static String unmangleClassname(String className) { + int i = className.indexOf("$ByteBuddy$"); + return i >= 0 + ? className.substring(0, i) + : className; } public ClassDescriptor getClassDescriptorByNodeType(String nodeType) Index: src/main/java/org/apache/jackrabbit/ocm/reflection/ReflectionUtils.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/main/java/org/apache/jackrabbit/ocm/reflection/ReflectionUtils.java b/src/main/java/org/apache/jackrabbit/ocm/reflection/ReflectionUtils.java --- a/src/main/java/org/apache/jackrabbit/ocm/reflection/ReflectionUtils.java (revision c1069b74c36ee39fda56163df450d557971636fa) +++ b/src/main/java/org/apache/jackrabbit/ocm/reflection/ReflectionUtils.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -31,8 +31,7 @@ import org.apache.commons.beanutils2.ConstructorUtils; import org.apache.commons.beanutils2.PropertyUtils; import org.apache.jackrabbit.ocm.exception.JcrMappingException; - -import net.sf.cglib.proxy.Enhancer; +import org.apache.jackrabbit.ocm.manager.objectconverter.proxy.ProxyBuddy; /** @@ -189,9 +188,8 @@ } } - public static boolean isProxy(Class beanClass) - { - return Enhancer.isEnhanced(beanClass); + public static boolean isProxy(Class beanClass) { + return ProxyBuddy.isProxy(beanClass); } public static Class getBeanClass(Object bean) @@ -199,7 +197,6 @@ Class beanClass = bean.getClass(); if (isProxy(beanClass)) { - //CGLIB specific return beanClass.getSuperclass(); } return beanClass; Index: src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ImplementInterfacesTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ImplementInterfacesTest.java b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ImplementInterfacesTest.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ImplementInterfacesTest.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class ImplementInterfacesTest { + + public static class C { } + + interface I1 { + int m1(); + } + + interface I2 { + int m2(); + } + + interface I3 { + int m3(); + } + + @Test + public void implementInterfaces() throws Exception { + var proxy = new ProxyBuddy<>(C.class, (thisProxy, pipe, method, arguments) -> { + switch (method.getName()) { + case "m1": return 1; + case "m2": return 2; + case "m3": return 3; + default: return 0; + }}) + .withInterface(I1.class) + .withInterface(I2.class) + .withInterface(I3.class) + .createProxy(); + + assertTrue(proxy instanceof I1); + assertEquals(1, ((I1)proxy).m1()); + + assertTrue(proxy instanceof I2); + assertEquals(2, ((I2)proxy).m2()); + + assertTrue(proxy instanceof I3); + assertEquals(3, ((I3)proxy).m3()); + } + +} Index: src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/InvalidProxyTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/InvalidProxyTest.java b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/InvalidProxyTest.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/InvalidProxyTest.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Test; + +public class InvalidProxyTest { + + @Test(expected = IllegalAccessException.class) + public void proxyNonOpenPackage() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + var proxyBuddy = new ProxyBuddy<>(Object.class, + (proxy, pipe, method, arguments) -> null); + + proxyBuddy.createProxy(); + } + + public static final class T { } + + @Test(expected = IllegalArgumentException.class) + public void proxyFinalClass() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + var proxyBuddy = new ProxyBuddy<>(T.class, (proxy, pipe, method, arguments) -> null); + + proxyBuddy.createProxy(); + } + + @Test(expected = IllegalArgumentException.class) + public void proxyArray() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + var proxyBuddy = new ProxyBuddy<>(T[].class, (proxy, pipe, method, arguments) -> null); + + proxyBuddy.createProxy(); + } + + @Test(expected = IllegalArgumentException.class) + public void proxyPrimitive() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + var proxyBuddy = new ProxyBuddy<>(int.class, (proxy, pipe, method, arguments) -> null); + + proxyBuddy.createProxy(); + } + +} Index: src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/NonArgProxyTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/NonArgProxyTest.java b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/NonArgProxyTest.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/NonArgProxyTest.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + +import static org.apache.jackrabbit.ocm.manager.objectconverter.proxy.ProxyBuddy.isProxy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Test; + +public class NonArgProxyTest { + private static class TestException extends Exception{} + + public static class Target { + boolean voidCalled; + + public int add(int a, int b) { + return a + b; + } + + public String noArgMethod() { + return "noArg"; + } + + public void voidMethod() { + voidCalled = true; + } + + public void exception() throws TestException { + throw new TestException(); + } + + @Override + public int hashCode() { + return 42; + } + + @Override + public String toString() { + return "four two"; + } + } + + @Test + public void testReflectingProxyCalls() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + Target target = new Target(); + Target proxy = new ProxyBuddy<>(Target.class, (thisProxy, pipe, method, arguments) -> method.invoke(target, arguments)) + .createProxy(); + + assertTrue(isProxy(proxy)); + assertFalse(isProxy(target)); + + assertEquals(3, proxy.add(1, 2)); + assertEquals("noArg", proxy.noArgMethod()); + + proxy.voidMethod(); + assertTrue(target.voidCalled); + + Exception ex = null; + try { + proxy.exception(); + } catch (Exception e) { + ex = e; + } + assertNotNull(ex); + + assertEquals(InvocationTargetException.class, ex.getClass()); + assertEquals(TestException.class, ex.getCause().getClass()); + + assertEquals(42, proxy.hashCode()); + assertEquals("four two", proxy.toString()); + assertEquals("four two", proxy + ""); + } + + @Test + public void testDelegatingProxyCalls() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + Target target = new Target(); + Target proxy = new ProxyBuddy<>(Target.class, (thisProxy, pipe, method, arguments) -> pipe.apply(target)) + .createProxy(); + + assertTrue(isProxy(proxy)); + assertFalse(isProxy(target)); + + assertEquals(3, proxy.add(1, 2)); + assertEquals("noArg", proxy.noArgMethod()); + + proxy.voidMethod(); + assertTrue(target.voidCalled); + + Exception ex = null; + try { + proxy.exception(); + } catch (Exception e) { + ex = e; + } + assertNotNull(ex); + + assertEquals(42, proxy.hashCode()); + assertEquals("four two", proxy.toString()); + assertEquals("four two", proxy + ""); + } + +} Index: src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyCanEqualTargetTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyCanEqualTargetTest.java b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyCanEqualTargetTest.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyCanEqualTargetTest.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Test; + +public class ProxyCanEqualTargetTest { + + @SuppressWarnings("ClassCanBeRecord") + public static class Target { + private final int value; + + public Target(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Target) { + return value == ((Target) obj).getValue(); + } else { + return false; + } + } + + @Override + public int hashCode() { + return getValue(); + } + } + + private static Target createProxy(Target target) + throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { + return new ProxyBuddy<>(Target.class, (proxy, pipe, method, arguments) -> pipe.apply(target)) + .withConstructor(Target.class.getConstructor(int.class), 0) + .withProxyCanEqualTarget(target) + .createProxy(); + } + + @SuppressWarnings({"EqualsWithItself", "SimplifiableAssertion"}) + @Test + public void testEquality() + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { + Target target1 = new Target(1); + Target target2 = new Target(1); + + Target proxy1 = createProxy(target1); + Target proxy2 = createProxy(target2); + + assertTrue(target1.equals(proxy1)); + assertTrue(target1.equals(proxy2)); + + assertTrue(target2.equals(proxy1)); + assertTrue(target2.equals(proxy2)); + + assertTrue(proxy1.equals(target1)); + assertTrue(proxy1.equals(target2)); + + assertTrue(proxy1.equals(proxy1)); + assertTrue(proxy1.equals(proxy2)); + + assertTrue(proxy2.equals(target1)); + assertTrue(proxy2.equals(target2)); + + assertTrue(proxy2.equals(proxy1)); + assertTrue(proxy2.equals(proxy2)); + + assertEquals(target1.hashCode(), proxy1.hashCode()); + assertEquals(target1.hashCode(), proxy2.hashCode()); + + assertEquals(target2.hashCode(), proxy1.hashCode()); + assertEquals(target2.hashCode(), proxy2.hashCode()); + + assertEquals(proxy1.hashCode(), target1.hashCode()); + assertEquals(proxy1.hashCode(), target2.hashCode()); + + assertEquals(proxy1.hashCode(), proxy1.hashCode()); + assertEquals(proxy1.hashCode(), proxy2.hashCode()); + + assertEquals(proxy2.hashCode(), target1.hashCode()); + assertEquals(proxy2.hashCode(), target2.hashCode()); + + assertEquals(proxy2.hashCode(), proxy1.hashCode()); + assertEquals(proxy2.hashCode(), proxy2.hashCode()); + } + + @SuppressWarnings({"SimplifiableAssertion", "EqualsWithItself"}) + @Test + public void testInequality() + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { + Target target1 = new Target(1); + Target target2 = new Target(2); + + Target proxy1 = createProxy(target1); + Target proxy2 = createProxy(target2); + + assertTrue(target1.equals(proxy1)); + assertFalse(target1.equals(proxy2)); + + assertFalse(target2.equals(proxy1)); + assertTrue(target2.equals(proxy2)); + + assertTrue(proxy1.equals(target1)); + assertFalse(proxy1.equals(target2)); + + assertTrue(proxy1.equals(proxy1)); + assertFalse(proxy1.equals(proxy2)); + + assertFalse(proxy2.equals(target1)); + assertTrue(proxy2.equals(target2)); + + assertFalse(proxy2.equals(proxy1)); + assertTrue(proxy2.equals(proxy2)); + + assertEquals(target1.hashCode(), proxy1.hashCode()); + assertNotEquals(target1.hashCode(), proxy2.hashCode()); + + assertNotEquals(target2.hashCode(), proxy1.hashCode()); + assertEquals(target2.hashCode(), proxy2.hashCode()); + + assertEquals(proxy1.hashCode(), target1.hashCode()); + assertNotEquals(proxy1.hashCode(), target2.hashCode()); + + assertEquals(proxy1.hashCode(), proxy1.hashCode()); + assertNotEquals(proxy1.hashCode(), proxy2.hashCode()); + + assertNotEquals(proxy2.hashCode(), target1.hashCode()); + assertEquals(proxy2.hashCode(), target2.hashCode()); + + assertNotEquals(proxy2.hashCode(), proxy1.hashCode()); + assertEquals(proxy2.hashCode(), proxy2.hashCode()); + } + +} Index: src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyLocalTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyLocalTest.java b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyLocalTest.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyLocalTest.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ProxyLocalTest { + + @SuppressWarnings("JavaReflectionMemberAccess") + @Test + public void proxyLocalClass() throws Exception { + class Local { + public Local() {} + public int get() { return 42; } + } + + var localProxy = new ProxyBuddy<>(Local.class, + (proxy, pipe, method, arguments) -> pipe.apply(new Local())) + .withConstructor(Local.class.getConstructor(ProxyLocalTest.class), new Object[]{this}) + .createProxy(); + + assertEquals(42, localProxy.get()); + } + +} Index: src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyNeverEqualsTargetTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyNeverEqualsTargetTest.java b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyNeverEqualsTargetTest.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/ProxyNeverEqualsTargetTest.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Test; + +public class ProxyNeverEqualsTargetTest { + + @SuppressWarnings("ClassCanBeRecord") + public static class Target { + private final int value; + + public Target(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object.getClass() == Target.class) { + return value == ((Target) object).getValue(); + } else { + return false; + } + } + + @Override + public int hashCode() { + return value; + } + } + + private static Target createProxy(Target target) + throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { + return new ProxyBuddy<>(Target.class, (proxy, pipe, method, arguments) -> pipe.apply(target)) + .withConstructor(Target.class.getConstructor(int.class), 0) + .withProxyNeverEqualsTarget(target) + .createProxy(); + } + + @SuppressWarnings({"SimplifiableAssertion", "EqualsWithItself"}) + @Test + public void testEquality() + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { + Target target1 = new Target(1); + Target target2 = new Target(1); + + Target proxy1 = createProxy(target1); + Target proxy2 = createProxy(target2); + + assertFalse(target1.equals(proxy1)); + assertFalse(target1.equals(proxy2)); + + assertFalse(target2.equals(proxy1)); + assertFalse(target2.equals(proxy2)); + + assertFalse(proxy1.equals(target1)); + assertFalse(proxy1.equals(target2)); + + assertTrue(proxy1.equals(proxy1)); + assertTrue(proxy1.equals(proxy2)); + + assertFalse(proxy2.equals(target1)); + assertFalse(proxy2.equals(target2)); + + assertTrue(proxy2.equals(proxy1)); + assertTrue(proxy2.equals(proxy2)); + + assertNotEquals(target1.hashCode(), proxy1.hashCode()); + assertNotEquals(target1.hashCode(), proxy2.hashCode()); + + assertNotEquals(target2.hashCode(), proxy1.hashCode()); + assertNotEquals(target2.hashCode(), proxy2.hashCode()); + + assertNotEquals(proxy1.hashCode(), target1.hashCode()); + assertNotEquals(proxy1.hashCode(), target2.hashCode()); + + assertEquals(proxy1.hashCode(), proxy1.hashCode()); + assertEquals(proxy1.hashCode(), proxy2.hashCode()); + + assertNotEquals(proxy2.hashCode(), target1.hashCode()); + assertNotEquals(proxy2.hashCode(), target2.hashCode()); + + assertEquals(proxy2.hashCode(), proxy1.hashCode()); + assertEquals(proxy2.hashCode(), proxy2.hashCode()); + } + + @SuppressWarnings({"SimplifiableAssertion", "EqualsWithItself"}) + @Test + public void testInequality() + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { + Target target1 = new Target(1); + Target target2 = new Target(2); + + Target proxy1 = createProxy(target1); + Target proxy2 = createProxy(target2); + + assertFalse(target1.equals(proxy1)); + assertFalse(target1.equals(proxy2)); + + assertFalse(target2.equals(proxy1)); + assertFalse(target2.equals(proxy2)); + + assertFalse(proxy1.equals(target1)); + assertFalse(proxy1.equals(target2)); + + assertTrue(proxy1.equals(proxy1)); + assertFalse(proxy1.equals(proxy2)); + + assertFalse(proxy2.equals(target1)); + assertFalse(proxy2.equals(target2)); + + assertFalse(proxy2.equals(proxy1)); + assertTrue(proxy2.equals(proxy2)); + + assertNotEquals(target1.hashCode(), proxy1.hashCode()); + assertNotEquals(target1.hashCode(), proxy2.hashCode()); + + assertNotEquals(target2.hashCode(), proxy1.hashCode()); + assertNotEquals(target2.hashCode(), proxy2.hashCode()); + + assertNotEquals(proxy1.hashCode(), target1.hashCode()); + assertNotEquals(proxy1.hashCode(), target2.hashCode()); + + assertEquals(proxy1.hashCode(), proxy1.hashCode()); + assertNotEquals(proxy1.hashCode(), proxy2.hashCode()); + + assertNotEquals(proxy2.hashCode(), target1.hashCode()); + assertNotEquals(proxy2.hashCode(), target2.hashCode()); + + assertNotEquals(proxy2.hashCode(), proxy1.hashCode()); + assertEquals(proxy2.hashCode(), proxy2.hashCode()); + } + +} Index: src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/WithArgProxyTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/WithArgProxyTest.java b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/WithArgProxyTest.java new file mode 100644 --- /dev/null (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) +++ b/src/test/java/org/apache/jackrabbit/ocm/manager/objectconverter/proxy/WithArgProxyTest.java (revision ac417d1c058fe23449b16b8784fa6a7a449e0b4e) @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.ocm.manager.objectconverter.proxy; + +import static org.apache.jackrabbit.ocm.manager.objectconverter.proxy.ProxyBuddy.isProxy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.InvocationTargetException; + +import org.junit.Test; + +public class WithArgProxyTest { + + public static class Target { + + private final int a; + + public Target(int a) { + this.a = a; + } + public int add(int b) { + return a + b; + } + + public String noArgMethod() { + return "noArg"; + } + } + + @Test + public void testReflectingProxyCalls() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + Target target = new Target(1); + Target proxy = new ProxyBuddy<>(Target.class, (thisProxy, pipe, method, arguments) -> method.invoke(target, arguments)) + .withConstructor(Target.class.getConstructor(int.class), 3) + .createProxy(); + + assertTrue(isProxy(proxy)); + assertFalse(isProxy(target)); + + assertEquals(3, proxy.add(2)); + assertEquals("noArg", proxy.noArgMethod()); + } + + @Test + public void testDelegatingProxyCalls() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException { + Target target = new Target(1); + Target proxy = new ProxyBuddy<>(Target.class, (thisProxy, pipe, method, arguments) -> pipe.apply(target)) + .withConstructor(Target.class.getConstructor(int.class), 3) + .createProxy(); + + assertTrue(isProxy(proxy)); + assertFalse(isProxy(target)); + + assertEquals(3, proxy.add(2)); + assertEquals("noArg", proxy.noArgMethod()); + } + +}