View Javadoc
1   /*
2    * SPDX-License-Identifier: Apache-2.0
3    * See LICENSE file for details.
4    *
5    * Copyright 2012-2026 hazendaz
6    *
7    * Portions of initial baseline code (getter/setter test) by Rob Dawson (CodeBox)
8    */
9   package com.codebox.instance;
10  
11  import com.codebox.bean.ValueBuilder;
12  import com.codebox.enums.LoadData;
13  import com.codebox.enums.LoadType;
14  import com.codebox.util.LombokBuilderUtil;
15  
16  import java.lang.reflect.Constructor;
17  import java.lang.reflect.InvocationTargetException;
18  import java.lang.reflect.Method;
19  import java.util.Arrays;
20  
21  import org.junit.jupiter.api.Assertions;
22  
23  /**
24   * The Class Instance.
25   *
26   * @param <T>
27   *            the element type
28   */
29  public class ClassInstance<T> {
30  
31      /**
32       * New instance. This will get the first available constructor to run the test on. This allows for instances where
33       * there is intentionally not a no-arg constructor.
34       *
35       * @param clazz
36       *            the class
37       *
38       * @return the t
39       */
40      @SuppressWarnings("unchecked")
41      public final T newInstance(final Class<T> clazz) {
42          // If class is null, return null
43          if (clazz == null) {
44              return null;
45          }
46  
47          // Check if class has Lombok @Builder pattern (static builder() method)
48          Method builderMethod = LombokBuilderUtil.getLombokBuilderMethod(clazz);
49          if (builderMethod != null) {
50              try {
51                  return this.createInstanceWithBuilder(clazz, builderMethod);
52              } catch (final IllegalAccessException | IllegalArgumentException | InvocationTargetException
53                      | NoSuchMethodException | SecurityException e) {
54                  Assertions.fail(String.format(
55                          "An exception was thrown while creating instance using Lombok @Builder for class '%s': '%s'",
56                          clazz.getName(), e.toString()));
57              }
58          }
59  
60          // Try no-arg constructor first
61          for (final Constructor<?> constructor : clazz.getConstructors()) {
62              // Skip deprecated constructors
63              if (constructor.isAnnotationPresent(Deprecated.class)) {
64                  continue;
65              }
66  
67              // Find available no-arg constructor and return it
68              if (constructor.getParameterCount() == 0) {
69                  try {
70                      return (T) constructor.newInstance();
71                  } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
72                      Assertions.fail(String.format(
73                              "An exception was thrown while testing class no-arg constructor (new instance) '%s': '%s'",
74                              constructor.getName(), e.toString()));
75                  }
76              }
77          }
78  
79          // Try any constructor
80          for (final Constructor<?> constructor : clazz.getConstructors()) {
81  
82              // Skip deprecated constructors
83              if (constructor.isAnnotationPresent(Deprecated.class)) {
84                  continue;
85              }
86  
87              final Class<?>[] types = constructor.getParameterTypes();
88  
89              final Object[] values = new Object[constructor.getParameterTypes().length];
90  
91              // Load Data
92              for (int i = 0; i < values.length; i++) {
93                  values[i] = this.buildValue(types[i], LoadType.STANDARD_DATA);
94              }
95  
96              try {
97                  return (T) constructor.newInstance(values);
98              } catch (final InstantiationException | IllegalAccessException | InvocationTargetException e) {
99                  Assertions.fail(String.format(
100                         "An exception was thrown while testing the class (new instance) '%s' with '%s': '%s'",
101                         constructor.getName(), Arrays.toString(values), e.toString()));
102             }
103         }
104         return null;
105     }
106 
107     /**
108      * Create an instance using Lombok's builder pattern.
109      *
110      * @param clazz
111      *            the class to instantiate
112      * @param builderMethod
113      *            the builder method
114      * @return the instance created via builder
115      * @throws IllegalAccessException
116      *             the illegal access exception
117      * @throws IllegalArgumentException
118      *             the illegal argument exception
119      * @throws InvocationTargetException
120      *             the invocation target exception
121      * @throws NoSuchMethodException
122      *             the no such method exception
123      * @throws SecurityException
124      *             the security exception
125      */
126     @SuppressWarnings("unchecked")
127     private T createInstanceWithBuilder(final Class<T> clazz, final Method builderMethod) throws IllegalAccessException,
128             IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
129         // Invoke the builder() method to get a builder instance
130         final T builder = (T) builderMethod.invoke(null);
131         // Find the build() method on the builder
132         Method buildMethod = builder.getClass().getMethod("build");
133         // Invoke build() to create the instance
134         return (T) buildMethod.invoke(builder);
135     }
136 
137     /**
138      * Builds the value.
139      *
140      * @param <R>
141      *            the generic type
142      * @param returnType
143      *            the return type
144      * @param loadType
145      *            the load type
146      *
147      * @return the object
148      */
149     public <R> Object buildValue(final Class<R> returnType, final LoadType loadType) {
150         final ValueBuilder valueBuilder = new ValueBuilder();
151         valueBuilder.setLoadData(LoadData.ON);
152         return valueBuilder.buildValue(returnType, loadType);
153     }
154 
155 }