View Javadoc
1   /*
2    * JavaBean Tester (https://github.com/hazendaz/javabean-tester)
3    *
4    * Copyright 2012-2025 Hazendaz.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of The Apache Software License,
8    * Version 2.0 which accompanies this distribution, and is available at
9    * http://www.apache.org/licenses/LICENSE-2.0.txt
10   *
11   * Contributors:
12   *     CodeBox (Rob Dawson).
13   *     Hazendaz (Jeremy Landis).
14   */
15  package com.codebox.bean;
16  
17  import java.lang.reflect.Modifier;
18  
19  import net.bytebuddy.ByteBuddy;
20  import net.bytebuddy.NamingStrategy;
21  import net.bytebuddy.description.NamedElement;
22  import net.bytebuddy.description.modifier.Visibility;
23  import net.bytebuddy.description.type.TypeDescription;
24  import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
25  import net.bytebuddy.implementation.HashCodeMethod;
26  import net.bytebuddy.implementation.MethodDelegation;
27  import net.bytebuddy.implementation.ToStringMethod;
28  import net.bytebuddy.implementation.bind.annotation.Argument;
29  import net.bytebuddy.implementation.bind.annotation.This;
30  import net.bytebuddy.matcher.ElementMatchers;
31  
32  /**
33   * This helper class can be used to unit test the get/set/equals/canEqual/toString/hashCode methods of JavaBean-style
34   * Value Objects.
35   */
36  public enum JavaBeanTester {
37  
38      // Private Usage
39      ;
40  
41      /**
42       * Configure JavaBeanTester using Fluent API.
43       *
44       * @param <T>
45       *            the generic type
46       * @param clazz
47       *            the clazz
48       *
49       * @return A builder implementing the fluent API to configure JavaBeanTester
50       */
51      public static <T> JavaBeanTesterBuilder<T, ?> builder(final Class<T> clazz) {
52          // If class is final, use Object.class for comparison needs
53          if (Modifier.isFinal(clazz.getModifiers())) {
54              return new JavaBeanTesterBuilder<>(clazz, Object.class);
55          }
56  
57          // Build extension from class using byte buddy
58          Class<? extends T> loaded = new ByteBuddy().with(new NamingStrategy.AbstractBase() {
59              @Override
60              protected String name(TypeDescription superClass) {
61                  return clazz.getPackageName() + ".ByteBuddyExt" + superClass.getSimpleName();
62              }
63          }).subclass(clazz).method(ElementMatchers.isEquals())
64                  .intercept(MethodDelegation.to(InstanceOfEqualsInterceptor.class)).method(ElementMatchers.isHashCode())
65                  .intercept(HashCodeMethod.usingSuperClassOffset()).method(ElementMatchers.isToString())
66                  .intercept(ToStringMethod.prefixedBySimpleClassName()).method(ElementMatchers.named("canEqual"))
67                  .intercept(MethodDelegation.to(CanEqualInterceptor.class))
68                  .defineField("javabeanExtension", String.class, Visibility.PACKAGE_PRIVATE).make()
69                  .load(clazz.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER).getLoaded();
70  
71          // Builder with proper extension class
72          return builder(clazz, loaded);
73      }
74  
75      /**
76       * Configure JavaBeanTester using Fluent API.
77       *
78       * @param <T>
79       *            the generic type
80       * @param <E>
81       *            the element type
82       * @param clazz
83       *            the clazz
84       * @param extension
85       *            the extension
86       *
87       * @return A builder implementing the fluent API to configure JavaBeanTester
88       */
89      public static <T, E> JavaBeanTesterBuilder<T, E> builder(final Class<T> clazz, final Class<E> extension) {
90          return new JavaBeanTesterBuilder<>(clazz, extension);
91      }
92  
93      /**
94       * The Class CanEqualInterceptor.
95       */
96      public final static class CanEqualInterceptor {
97  
98          /**
99           * Prevents instantiation a new can equal interceptor.
100          */
101         CanEqualInterceptor() {
102             // Private constructor to prevent instantiation
103         }
104 
105         /**
106          * Can Equal Interceptor.
107          *
108          * @param object
109          *            The object to check can equals
110          * @return boolean of can equals
111          */
112         public static boolean canEqual(final Object object) {
113             return object instanceof NamedElement.WithRuntimeName;
114         }
115 
116     }
117 
118     /**
119      * The Class InstanceOfEqualsInterceptor.
120      */
121     public final static class InstanceOfEqualsInterceptor {
122 
123         /**
124          * Prevents instantiation a new instance of equals interceptor.
125          */
126         InstanceOfEqualsInterceptor() {
127             // Private constructor to prevent instantiation
128         }
129 
130         /**
131          * Equals.
132          *
133          * @param self
134          *            the self
135          * @param other
136          *            the other
137          * @return true, if successful
138          */
139         public static boolean equals(@This Object self, @Argument(0) Object other) {
140             if (self == other) {
141                 return true;
142             }
143             if ((other == null) || (!self.getClass().isInstance(other) && !other.getClass().isInstance(self))) {
144                 return false;
145             }
146             return self.hashCode() == other.hashCode();
147         }
148     }
149 
150 }