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