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.bean;
10  
11  import com.codebox.enums.LoadData;
12  import com.codebox.enums.LoadType;
13  import com.codebox.instance.ClassInstance;
14  import com.codebox.instance.ConstructorInstance;
15  
16  import java.lang.reflect.Array;
17  import java.lang.reflect.Constructor;
18  import java.lang.reflect.Field;
19  import java.math.BigDecimal;
20  import java.time.Instant;
21  import java.time.LocalDate;
22  import java.time.LocalDateTime;
23  import java.time.LocalTime;
24  import java.time.OffsetDateTime;
25  import java.time.ZoneId;
26  import java.time.ZoneOffset;
27  import java.time.ZonedDateTime;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.TreeSet;
35  import java.util.UUID;
36  import java.util.concurrent.ConcurrentHashMap;
37  import java.util.concurrent.ConcurrentMap;
38  
39  import lombok.Data;
40  
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * Value Builder Builds values from given type.
46   */
47  @Data
48  public class ValueBuilder {
49  
50      /** The Constant Logger. */
51      private static final Logger logger = LoggerFactory.getLogger(ValueBuilder.class);
52  
53      /** The load data. */
54      private LoadData loadData;
55  
56      /**
57       * Builds the value.
58       *
59       * @param <T>
60       *            the generic type
61       * @param clazz
62       *            the clazz
63       * @param loadType
64       *            the load type
65       *
66       * @return the object
67       */
68      public <T> Object buildValue(final Class<T> clazz, final LoadType loadType) {
69          // Next check for a no-arg constructor
70          final Constructor<?>[] ctrs = clazz.getConstructors();
71          for (final Constructor<?> ctr : ctrs) {
72              if (ctr.getParameterTypes().length == 0 && clazz != String.class) {
73                  if (this.loadData == LoadData.ON && !containsSelf(clazz)) {
74                      // Load Underlying Data to deeply test when it doesn't contain self
75                      final JavaBeanTesterWorker<T, Object> beanTesterWorker = new JavaBeanTesterWorker<>(clazz);
76                      beanTesterWorker.setLoadData(this.loadData);
77                      beanTesterWorker.getterSetterTests(new ClassInstance<T>().newInstance(clazz));
78                      return null;
79                  }
80                  // The class has a no-arg constructor, so just call it (e.g. String, Date, etc.)
81                  return ConstructorInstance.newInstance(ctr);
82              }
83          }
84  
85          // Specific rules for common classes
86          Object returnObject = null;
87          switch (loadType) {
88              case ALTERNATE_DATA:
89                  returnObject = ValueBuilder.setAlternateValues(clazz);
90                  break;
91              case NULL_DATA:
92                  returnObject = ValueBuilder.setNullValues(clazz);
93                  break;
94              case STANDARD_DATA:
95              default:
96                  returnObject = ValueBuilder.setStandardValues(clazz);
97                  break;
98          }
99  
100         if (returnObject != null || loadType == LoadType.NULL_DATA) {
101             return returnObject;
102         }
103 
104         if (clazz.isAssignableFrom(BigDecimal.class)) {
105             return BigDecimal.ONE;
106         }
107 
108         if (clazz.isAssignableFrom(UUID.class)) {
109             return UUID.fromString("00000000-0000-0000-0000-000123456789");
110         }
111 
112         if (clazz.isAssignableFrom(Instant.class)) {
113             return Instant.ofEpochSecond(1L);
114         }
115 
116         if (clazz.isAssignableFrom(List.class)) {
117             return new ArrayList<>();
118         }
119 
120         if (clazz.isAssignableFrom(Map.class)) {
121             return new HashMap<>();
122         }
123 
124         if (clazz.isAssignableFrom(ConcurrentMap.class)) {
125             return new ConcurrentHashMap<>();
126         }
127 
128         if (clazz.isAssignableFrom(Set.class)) {
129             return new TreeSet<>();
130         }
131 
132         if (clazz.isAssignableFrom(LocalDate.class)) {
133             return LocalDate.now();
134         }
135 
136         if (clazz.isAssignableFrom(LocalDateTime.class)) {
137             return LocalDateTime.of(2000, 10, 1, 0, 0);
138         }
139 
140         if (clazz.isAssignableFrom(LocalTime.class)) {
141             return LocalTime.of(0, 0);
142         }
143 
144         if (clazz.isAssignableFrom(OffsetDateTime.class)) {
145             return OffsetDateTime.of(2000, 10, 1, 0, 0, 0, 0, ZoneOffset.MIN);
146         }
147 
148         if (clazz.isAssignableFrom(ZonedDateTime.class)) {
149             return ZonedDateTime.of(LocalDateTime.of(2020, 11, 16, 10, 26, 00, 01), ZoneId.of("UTC"));
150         }
151 
152         if (clazz == Logger.class) {
153             return LoggerFactory.getLogger(clazz);
154         }
155 
156         if (clazz.isEnum()) {
157             return clazz.getEnumConstants()[0];
158         }
159 
160         // XXX Add additional rules here
161 
162         // XXX Don't fail this...until alternative solution is determined
163         ValueBuilder.logger.warn(
164                 "Unable to build value for class '{}', please raise ticket with JavaBeanTester for desired support.",
165                 clazz.getName());
166         return null;
167     }
168 
169     /**
170      * Set using alternate test values.
171      *
172      * @param <T>
173      *            the type parameter associated with the class under test.
174      * @param clazz
175      *            the class under test.
176      *
177      * @return Object the Object to use for test.
178      */
179     private static <T> Object setAlternateValues(final Class<T> clazz) {
180         return ValueBuilder.setValues(clazz, "ALT_VALUE", 1, Boolean.FALSE, Integer.valueOf(2), Long.valueOf(2),
181                 Double.valueOf(2.0), Float.valueOf(2.0F), Character.valueOf('N'), Byte.valueOf((byte) 2));
182     }
183 
184     /**
185      * Set using null test values.
186      *
187      * @param <T>
188      *            the type parameter associated with the class under test.
189      * @param clazz
190      *            the class under test.
191      *
192      * @return Object the Object to use for test.
193      */
194     private static <T> Object setNullValues(final Class<T> clazz) {
195         return ValueBuilder.setValues(clazz, null, 0, null, null, null, null, null, null, null);
196     }
197 
198     /**
199      * Set using standard test values.
200      *
201      * @param <T>
202      *            the type parameter associated with the class under test.
203      * @param clazz
204      *            the class under test.
205      *
206      * @return Object the Object to use for test.
207      */
208     private static <T> Object setStandardValues(final Class<T> clazz) {
209         return ValueBuilder.setValues(clazz, "TEST_VALUE", 1, Boolean.TRUE, Integer.valueOf(1), Long.valueOf(1),
210                 Double.valueOf(1.0), Float.valueOf(1.0F), Character.valueOf('Y'), Byte.valueOf((byte) 1));
211     }
212 
213     /**
214      * Set Values for object.
215      *
216      * @param <T>
217      *            the type parameter associated with the class under test.
218      * @param clazz
219      *            the class instance under test.
220      * @param string
221      *            value of string object.
222      * @param arrayLength
223      *            amount of array objects to create.
224      * @param booleanValue
225      *            value of boolean object.
226      * @param integerValue
227      *            value of integer object.
228      * @param longValue
229      *            value of long object.
230      * @param doubleValue
231      *            value of double object.
232      * @param floatValue
233      *            value of float object.
234      * @param characterValue
235      *            value of character object.
236      * @param byteValue
237      *            value of character object.
238      *
239      * @return Object value determined by input class. If not found, returns null.
240      */
241     private static <T> Object setValues(final Class<T> clazz, final String string, final int arrayLength,
242             final Boolean booleanValue, final Integer integerValue, final Long longValue, final Double doubleValue,
243             final Float floatValue, final Character characterValue, final Byte byteValue) {
244         if (clazz == String.class) {
245             return string;
246         }
247         if (clazz.isArray()) {
248             return Array.newInstance(clazz.getComponentType(), arrayLength);
249         }
250         if (clazz == boolean.class || clazz == Boolean.class) {
251             return ValueBuilder.initializeBoolean(clazz, booleanValue);
252         }
253         if (clazz == int.class || clazz == Integer.class) {
254             return ValueBuilder.initializeInteger(clazz, integerValue);
255         }
256         if (clazz == long.class || clazz == Long.class) {
257             return ValueBuilder.initializeLong(clazz, longValue);
258         }
259         if (clazz == double.class || clazz == Double.class) {
260             return ValueBuilder.initializeDouble(clazz, doubleValue);
261         }
262         if (clazz == float.class || clazz == Float.class) {
263             return ValueBuilder.initializeFloat(clazz, floatValue);
264         }
265         if (clazz == char.class || clazz == Character.class) {
266             return ValueBuilder.initializeCharacter(clazz, characterValue);
267         }
268         if (clazz == byte.class || clazz == Byte.class) {
269             return ValueBuilder.initializeByte(clazz, byteValue);
270         }
271         return null;
272     }
273 
274     /**
275      * Initialize boolean.
276      *
277      * @param <T>
278      *            the generic type
279      * @param clazz
280      *            the clazz
281      * @param booleanValue
282      *            the boolean value
283      *
284      * @return the object
285      */
286     private static <T> Object initializeBoolean(final Class<T> clazz, final Boolean booleanValue) {
287         if (clazz == boolean.class && booleanValue == null) {
288             return Boolean.FALSE;
289         }
290         return booleanValue;
291     }
292 
293     /**
294      * Initialize integer.
295      *
296      * @param <T>
297      *            the generic type
298      * @param clazz
299      *            the clazz
300      * @param integerValue
301      *            the integer value
302      *
303      * @return the object
304      */
305     private static <T> Object initializeInteger(final Class<T> clazz, final Integer integerValue) {
306         if (clazz == int.class && integerValue == null) {
307             return Integer.valueOf(-1);
308         }
309         return integerValue;
310     }
311 
312     /**
313      * Initialize long.
314      *
315      * @param <T>
316      *            the generic type
317      * @param clazz
318      *            the clazz
319      * @param longValue
320      *            the long value
321      *
322      * @return the object
323      */
324     private static <T> Object initializeLong(final Class<T> clazz, final Long longValue) {
325         if (clazz == long.class && longValue == null) {
326             return Long.valueOf(-1);
327         }
328         return longValue;
329     }
330 
331     /**
332      * Initialize double.
333      *
334      * @param <T>
335      *            the generic type
336      * @param clazz
337      *            the clazz
338      * @param doubleValue
339      *            the double value
340      *
341      * @return the object
342      */
343     private static <T> Object initializeDouble(final Class<T> clazz, final Double doubleValue) {
344         if (clazz == double.class && doubleValue == null) {
345             return Double.valueOf(-1.0);
346         }
347         return doubleValue;
348     }
349 
350     /**
351      * Initialize float.
352      *
353      * @param <T>
354      *            the generic type
355      * @param clazz
356      *            the clazz
357      * @param floatValue
358      *            the float value
359      *
360      * @return the object
361      */
362     private static <T> Object initializeFloat(final Class<T> clazz, final Float floatValue) {
363         if (clazz == float.class && floatValue == null) {
364             return Float.valueOf(-1.0F);
365         }
366         return floatValue;
367     }
368 
369     /**
370      * Initialize character.
371      *
372      * @param <T>
373      *            the generic type
374      * @param clazz
375      *            the clazz
376      * @param characterValue
377      *            the character value
378      *
379      * @return the object
380      */
381     private static <T> Object initializeCharacter(final Class<T> clazz, final Character characterValue) {
382         if (clazz == char.class && characterValue == null) {
383             return Character.valueOf('\u0000');
384         }
385         return characterValue;
386     }
387 
388     /**
389      * Initialize byte.
390      *
391      * @param <T>
392      *            the generic type
393      * @param clazz
394      *            the clazz
395      * @param byteValue
396      *            the byte value
397      *
398      * @return the object
399      */
400     private static <T> Object initializeByte(final Class<T> clazz, final Byte byteValue) {
401         if (clazz == byte.class && byteValue == null) {
402             return Byte.valueOf((byte) -1);
403         }
404         return byteValue;
405     }
406 
407     /**
408      * Contains self.
409      *
410      * @param <T>
411      *            the generic type
412      * @param clazz
413      *            the clazz
414      * @return true, if successful
415      */
416     private <T> boolean containsSelf(final Class<T> clazz) {
417         final List<Field> fields = Arrays.asList(clazz.getDeclaredFields());
418         for (Field field : fields) {
419             if (field.getType().equals(clazz)) {
420                 return true;
421             }
422         }
423         return false;
424     }
425 
426 }