1. Project Clover database Wed Nov 12 2025 05:07:35 UTC
  2. Package guru.mikelue.foxglove.setting

File DataSetting.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

22
104
23
2
579
254
35
0.34
4.52
11.5
1.52

Classes

Class Line # Actions
DataSetting 102 91 0% 31 0
1.0100%
ColumnConfigImpl 539 13 0% 4 0
1.0100%
 

Contributing tests

This file is covered by 117 tests. .

Source view

1    package guru.mikelue.foxglove.setting;
2   
3    import java.sql.JDBCType;
4    import java.util.*;
5    import java.util.function.Consumer;
6    import java.util.function.Supplier;
7   
8    import org.apache.commons.lang3.StringUtils;
9    import org.apache.commons.lang3.Validate;
10    import org.instancio.Instancio;
11    import org.instancio.generator.ValueSpec;
12    import org.slf4j.Logger;
13    import org.slf4j.LoggerFactory;
14   
15    import guru.mikelue.foxglove.ColumnMeta;
16    import guru.mikelue.foxglove.ColumnMeta.Property;
17    import guru.mikelue.foxglove.functional.ColumnMatcher;
18    import guru.mikelue.foxglove.functional.SupplierDecider;
19    import guru.mikelue.foxglove.functional.Suppliers;
20    import guru.mikelue.foxglove.jdbc.JdbcTableFacet;
21    import guru.mikelue.foxglove.jdbc.JdbcDataGenerator;
22   
23    import static java.sql.JDBCType.*;
24   
25    /**
26    * Defines the data generation setting.
27    *
28    * <h2>Overview</h2>
29    *
30    * This object can used by multi-layered mechanism(sorted by priority):
31    *
32    * <ul>
33    * <li>{@link JdbcTableFacet}</li>
34    * <li>{@link guru.mikelue.foxglove.DataGenerator}</li>
35    * <li><strong>globally</strong></li>
36    * </ul>
37    *
38    * <h2>Global setting</h2>
39    *
40    * {@link #defaults()} could be used to configure the global default setting.
41    *
42    * <pre><code class="language-java">
43    * DataSetting.defaults()
44    * .givenType(JDBCType.VARCHAR)
45    * .useSpec(Instancio.gen().string().alphaNumeric().length(16))
46    * </code></pre>
47    *
48    * <hr>
49    * <h2>Use setting locally</h2>
50    *
51    * Some types implement {@link SettingAware#withSetting(DataSettingInfo)},
52    * so you could provide a customized {@link DataSetting} to them.
53    *
54    * Example of {@link guru.mikelue.foxglove.jdbc.JdbcTableFacet.Builder}
55    * <pre><code class="language-java">
56    * var settingOfATable = new DataSetting();
57    * .givenType(JDBCType.VARCHAR)
58    * .useSpec(Instancio.gen().string().alphaNumeric().length(16))
59    *
60    * tableFacetBuilder.withDataSetting(settingOfATable);
61    * </code></pre>
62    *
63    * Example of {@link guru.mikelue.foxglove.DataGenerator}
64    * <pre><code class="language-java">
65    * var settingForGenerator = new DataSetting();
66    * .givenType(JDBCType.VARCHAR)
67    * .useSpec(Instancio.gen().string().alphaNumeric().length(16))
68    *
69    * dataGenerator.withDataSetting(settingForGenerator);
70    * </code></pre>
71    *
72    * <hr>
73    * <h2>Features</h2>
74    *
75    * <h3>Column spec</h3>
76    *
77    * There are ways to define the supplier for matched columns:
78    *
79    * <ul>
80    * <li>Using a {@link Supplier} by {@link ColumnConfig#useSupplier(Supplier)}</li>
81    * <li>Using a {@link Supplier} of {@link ValueSpec} by {@link ColumnConfig#useSpec(Supplier)}</li>
82    * <li>Using a {@link SupplierDecider} by {@link ColumnConfig#decideSupplier(SupplierDecider)}</li>
83    * </ul>
84    *
85    * <h3>Auto-generating by properties</h3>
86    *
87    * <ul>
88    * <li>Use {@link #autoGenerateFor(Property...)} to set properties for auto-generating</li>
89    * <li>Use {@link #notAutoGenerateFor(Property...)} to unset properties for not to auto-generating</li>
90    * </ul>
91    *
92    * <h3>Miscellaneous</h3>
93    *
94    * <ul>
95    * <li>Use {@link #generateNull(boolean)}, {@link #generateNull(int)} to supply possible {@code null} value for nullable columns</li>
96    * <li>Use {@link #largeTextLength(int, int)} to alter length for large text. e.g.: {@link JDBCType#CLOB}</li>
97    * </ul>
98    *
99    * @see JdbcTableFacet.Builder
100    * @see JdbcDataGenerator
101    */
 
102    public class DataSetting implements DataSettingInfo {
103    private final static Optional<Supplier<?>> NULL_SUPPLIER_OPT = Optional.of(() -> null);
104   
105    /**
106    * Gives the data setting can be changed for applying any {@link SettingAware} globally.
107    *
108    * @return The default data setting.
109    *
110    * @see <a href="https://foxglove.mikelue.guru/docs/default-generators/">Default Generators</a>
111    */
 
112  123 toggle public final static DataSetting defaults()
113    {
114  123 return DefaultSetting.instance();
115    }
116   
117    private Logger logger = LoggerFactory.getLogger(DataSetting.class);
118   
119    private final Map<JDBCType, SupplierDecider<?>> jdbcTypeConfigMap = new HashMap<>(32);
120    private final Map<String, SupplierDecider<?>> typeNameConfigMap = new HashMap<>(4);
121    private final Map<ColumnMatcher, SupplierDecider<?>> matcherConfigMap = new HashMap<>(4);
122   
123    private int minLengthOfLargeText = DefaultSetting.LARGE_TEXT_MIN_LENGTH;
124    private int maxLengthOfLargeText = DefaultSetting.LARGE_TEXT_MAX_LENGTH;
125   
126    private int defaultNumberOfRows = DefaultSetting.DEFAULT_NUMBER_OF_ROWS;
127    private Set<ColumnMeta.Property> autoGeneratingByProperties = EnumSet.copyOf(
128    DefaultSetting.DEFAULT_COLUMN_PROPERTIES_FOR_AUTO_GENERATING
129    );
130   
131    private int diceSides = DefaultSetting.DEFAULT_DICE_SIDES;
132    private boolean generateNull = DefaultSetting.DEFAULT_GENERATE_NULL;
133   
134    private Set<JDBCType> notSupportedJdbcTypes = EnumSet.noneOf(JDBCType.class);
135   
136    private ColumnMatcher exclusion = c -> false;
137   
138    /**
139    * Constructs an empty data setting.
140    *
141    * <p>following settings are copied from {@link #defaults()}:
142    *
143    * <ul>
144    * <li>Default number of rows</li>
145    * <li>Auto-generating properties</li>
146    * <li>Null generating setting</li>
147    * <li>Large text length setting</li>
148    * </ul>
149    */
 
150  25 toggle public DataSetting()
151    {
152  25 var defaultSetting = defaults();
153   
154    /*
155    * The global setting may be not initialized yet
156    */
157  25 if (defaultSetting != null) {
158  24 setDefaultNumberOfRows(defaultSetting.getDefaultNumberOfRows());
159  24 autoGeneratingByProperties = EnumSet.copyOf(defaultSetting.autoGeneratingByProperties);
160   
161  24 diceSides = defaultSetting.diceSides;
162  24 generateNull = defaultSetting.generateNull;
163   
164  24 minLengthOfLargeText = defaultSetting.minLengthOfLargeText;
165  24 maxLengthOfLargeText = defaultSetting.maxLengthOfLargeText;
166    }
167    // :~)
168    }
169   
170    /**
171    * Starts to configure {@link Supplier} for columns matched by given {@link JDBCType}.
172    *
173    * @param <T> The type of values supplied
174    * @param jdbcType The JDBC type to match columns
175    *
176    * @return The next step to configure value generator for matched {@link JDBCType}
177    */
 
178  55 toggle public <T> ColumnConfig<T, DataSetting> givenType(JDBCType jdbcType)
179    {
180  55 Validate.notNull(jdbcType, "JDBC type must not be null");
181   
182  55 var newColumnConfig = new ColumnConfigImpl<T>(
183    this,
184    decider -> jdbcTypeConfigMap.put(jdbcType, decider)
185    );
186   
187  55 return newColumnConfig;
188    }
189   
190    /**
191    * Starts to configure {@link Supplier} for columns matched by given type name.
192    *
193    * @param <T> The type of values supplied
194    * @param typeName The type name to match columns
195    *
196    * @return The next step to configure value generator for matched type name
197    */
 
198  13 toggle public <T> ColumnConfig<T, DataSetting> givenType(String typeName)
199    {
200  13 final String safeTypeName = StringUtils.trimToNull(typeName);
201  13 Validate.notBlank(safeTypeName, "Type name must not be blank");
202   
203  13 var newColumnConfig = new ColumnConfigImpl<T>(
204    this,
205    decider -> typeNameConfigMap.put(safeTypeName.toUpperCase(), decider)
206    );
207   
208  13 return newColumnConfig;
209    }
210   
211    /**
212    * Starts to configure {@link Supplier} for columns matched by given {@link ColumnMatcher}.
213    *
214    * <p>
215    * <em>The priority of multiple matchers <span style="color: Crimson;">is not determined</span></em>
216    *
217    * <strong>You have to provide a valid {@link Supplier} in your decider for matched column.</strong>
218    *
219    * @param <T> The type of values supplied
220    * @param matcher The matcher to match columns
221    *
222    * @return The next step to configure value generator for matched columns
223    */
 
224  14 toggle public <T> ColumnConfig<T, DataSetting> columnMatcher(ColumnMatcher matcher)
225    {
226  14 Validate.notNull(matcher, "Column matcher must not be null");
227   
228  14 var newColumnConfig = new ColumnConfigImpl<T>(
229    this,
230    decider -> matcherConfigMap.put(matcher, decider)
231    );
232   
233  14 return newColumnConfig;
234    }
235   
236    /**
237    * {@inheritDoc}
238    */
 
239  28 toggle @Override
240    public int getDefaultNumberOfRows()
241    {
242  28 return this.defaultNumberOfRows;
243    }
244   
245    /**
246    * Sets the default number of rows for generated data.
247    *
248    * <p> This number of rows is used when:
249    *
250    * <ul>
251    * <li>No specific number of rows is defined by {@link guru.mikelue.foxglove.TableFacet}</li>
252    * </ul>
253    *
254    * @param numberOfRows of rows The default number of rows for generated data
255    *
256    * @return The data setting itself
257    *
258    * @see #getDefaultNumberOfRows()
259    */
 
260  28 toggle public DataSetting setDefaultNumberOfRows(int numberOfRows)
261    {
262  28 Validate.isTrue(numberOfRows > 0, "Default number of rows must be greater than zero");
263   
264  28 this.defaultNumberOfRows = numberOfRows;
265  28 return this;
266    }
267   
268    /**
269    * Sets whether or not to generate value automatically by the given properties of a column.
270    *
271    * <p>
272    * Other properties not given will be set to not generate value automatically
273    * for first time call this method.
274    *
275    * @param properties The properties of columns
276    *
277    * @return The data setting itself
278    *
279    * @see notAutoGenerateFor(Property...)
280    */
 
281  1 toggle public DataSetting autoGenerateFor(ColumnMeta.Property... properties)
282    {
283  1 Validate.notEmpty(properties, "At least one property is required to set auto-generating");
284   
285  1 for (var property: properties) {
286  1 autoGeneratingByProperties.add(property);
287    }
288   
289  1 return this;
290    }
291   
292    /**
293    * Sets to not to generate value automatically by the given properties of a column.
294    *
295    * Other properties not given will be set to generate value automatically
296    * for first time call this method.
297    *
298    * @param properties The properties of columns
299    *
300    * @return The data setting itself
301    *
302    * @see #autoGenerateFor(Property...)
303    */
 
304  2 toggle public DataSetting notAutoGenerateFor(ColumnMeta.Property... properties)
305    {
306  2 Validate.notEmpty(properties, "At least one property is required to set auto-generating");
307   
308  2 for (var property: properties) {
309  2 autoGeneratingByProperties.remove(property);
310    }
311   
312  2 return this;
313    }
314   
315    /**
316    * Sets to generate possible {@code null} for nullable columns.
317    *
318    * <p>
319    *
320    * Default is not to generate {@code null}.
321    *
322    * @param enabled Whether or not to generate possible {@code null}
323    *
324    * @return The data setting itself
325    *
326    * @see #generateNull(int)
327    */
 
328  1 toggle public DataSetting generateNull(boolean enabled)
329    {
330  1 this.generateNull = enabled;
331  1 return this;
332    }
333   
334    /**
335    * Enables null value generating and sets the dice sides to generate possible {@code null}
336    * for nullable columns, by ratio of <em>{@code 1/diceSides}</em>.
337    *
338    * <p>
339    *
340    * Default sides of dice is {@value DefaultSetting#DEFAULT_DICE_SIDES}.
341    *
342    * @param diceSides The sides of dice to generate possible {@code null}
343    *
344    * @return The data setting itself
345    *
346    * @see #generateNull(boolean)
347    */
 
348  3 toggle public DataSetting generateNull(int diceSides)
349    {
350  3 Validate.isTrue(diceSides >= 2, "Sides of dice must be greater than or equal to 2");
351   
352  3 this.diceSides = diceSides;
353  3 this.generateNull = true;
354   
355  3 return this;
356    }
357   
358    /**
359    * Excludes columns matched by given {@link ColumnMatcher} from auto-generating.
360    *
361    * This exclusion has highest priority than auto-generating by other settings.
362    *
363    * @param matcher The matcher to match columns
364    *
365    * @return The data setting itself
366    *
367    * @see #givenType(JDBCType)
368    * @see #columnMatcher(ColumnMatcher)
369    */
 
370  13 toggle public DataSetting excludeWhen(ColumnMatcher matcher)
371    {
372  13 this.exclusion = matcher;
373  13 return this;
374    }
375   
376    /**
377    * Sets the length for types of {@code CLOB}, {@code LONGVARCHAR}, etc.
378    *
379    * @param length The fixed length of large text
380    *
381    * @return The data setting itself
382    *
383    * @see #largeTextLength(int, int)
384    */
 
385  2 toggle public DataSetting largeTextLength(int length)
386    {
387  2 return largeTextLength(length, length);
388    }
389   
390    /**
391    * Sets the length range for types of {@code CLOB}, {@code LONGVARCHAR}, etc.
392    *
393    * @param minLength The minimum length of large text
394    * @param maxLength The maximum length of large text
395    *
396    * @return The data setting itself
397    *
398    * @see #largeTextLength(int)
399    */
 
400  2 toggle public DataSetting largeTextLength(int minLength, int maxLength)
401    {
402  2 Validate.isTrue(minLength >= 0, "Minimum length of large text must not be negative");
403  2 Validate.isTrue(maxLength >= minLength,
404    "Maximum length of large text[%d] must be greater than or equal to minimum length[%d]",
405    maxLength, minLength
406    );
407   
408  2 minLengthOfLargeText = minLength;
409  2 maxLengthOfLargeText = maxLength;
410   
411  2 var newTextSpec = Instancio.gen().string().alphaNumeric()
412    .length(minLengthOfLargeText, maxLengthOfLargeText);
413   
414  2 this.<String>givenType(LONGVARCHAR).useSupplier(newTextSpec);
415  2 this.<String>givenType(CLOB).useSupplier(newTextSpec);
416  2 this.<String>givenType(LONGNVARCHAR).useSupplier(newTextSpec);
417  2 this.<String>givenType(NCLOB).useSupplier(newTextSpec);
418   
419  2 return this;
420    }
421   
422    /**
423    * {@inheritDoc}
424    */
 
425  740 toggle @Override
426    @SuppressWarnings("unchecked")
427    public <T> Optional<Supplier<T>> resolveSupplier(ColumnMeta columnMeta)
428    {
429  740 Validate.notNull(columnMeta, "Column metadata must not be null");
430   
431    /*
432    * By column matcher
433    */
434  740 for (var entry: matcherConfigMap.entrySet()) {
435  18 if (entry.getKey().test(columnMeta)) {
436  4 SupplierDecider<T> supplierDecider = (SupplierDecider<T>)entry.getValue();
437  4 var matchedSupplier = supplierDecider.apply(columnMeta);
438  4 Validate.notNull(matchedSupplier,
439    "Value supplier resolved by column matcher[%s] must not be null",
440    entry.getKey()
441    );
442   
443  4 logger.debug("Found supplier for column(matcher): {}", columnMeta);
444   
445  4 return buildSupplier(columnMeta, matchedSupplier);
446    }
447    }
448    // :~)
449   
450    /*
451    * By type name
452    */
453  736 var typeName = columnMeta.typeName().toUpperCase();
454  736 if (typeNameConfigMap.containsKey(typeName)) {
455  11 SupplierDecider<T> supplierDecider = (SupplierDecider<T>)typeNameConfigMap.get(typeName);
456  11 logger.debug("Found supplier for column(type name): {}", columnMeta);
457   
458  11 return buildSupplier(columnMeta, supplierDecider.apply(columnMeta));
459    }
460    // :~)
461   
462    /*
463    * By JDBC type
464    */
465  725 var jdbcType = columnMeta.jdbcType();
466  725 if (jdbcTypeConfigMap.containsKey(jdbcType)) {
467  709 SupplierDecider<T> supplierDecider = (SupplierDecider<T>)jdbcTypeConfigMap.get(jdbcType);
468  709 logger.debug("Found supplier for column(JDBCType): {}", columnMeta);
469   
470  709 return buildSupplier(columnMeta, supplierDecider.apply(columnMeta));
471    }
472    // :~)
473   
474  16 if (notSupportedJdbcTypes.contains(columnMeta.jdbcType())) {
475  1 logger.debug("No supplier for column (not supported JDBCType): {}", columnMeta);
476  1 return (Optional<Supplier<T>>)(Optional<?>)NULL_SUPPLIER_OPT;
477    }
478   
479  15 return Optional.empty();
480    }
481   
482    /**
483    * {@inheritDoc}
484    */
 
485  916 toggle @Override
486    public boolean isAutoGenerating(ColumnMeta column)
487    {
488  916 if (exclusion.test(column)) {
489  5 return false;
490    }
491   
492  911 for (var matcher: matcherConfigMap.keySet()) {
493  18 if (matcher.test(column)) {
494  2 return true;
495    }
496    }
497   
498  909 if (!GeneratingUtils.checkAutoGenerating(column, autoGeneratingByProperties)) {
499  125 return false;
500    }
501   
502  784 if (typeNameConfigMap.containsKey(column.typeName())) {
503  9 return true;
504    }
505   
506  775 if (jdbcTypeConfigMap.containsKey(column.jdbcType())) {
507  627 return true;
508    }
509   
510  148 return !notSupportedJdbcTypes.contains(column.jdbcType());
511    }
512   
 
513  1 toggle DataSetting notSupportedJdbcTypes(Set<JDBCType> jdbcTypes)
514    {
515  1 notSupportedJdbcTypes = jdbcTypes;
516  1 return this;
517    }
518   
519    /**
520    * Builds a supplier which may generate {@code null} value based on current setting.
521    */
 
522  724 toggle private <T> Optional<Supplier<T>> buildSupplier(ColumnMeta column, Supplier<T> baseSupplier)
523    {
524  724 if (generateNull && isNullableColumn(column)) {
525  3 logger.debug("[GENERATE NULL][1/{}] For column: {}", diceSides, column);
526  3 return Optional.of(Suppliers.rollingSupplier(baseSupplier, diceSides));
527    } else {
528  721 return Optional.of(baseSupplier);
529    }
530    }
531   
 
532  3 toggle private static boolean isNullableColumn(ColumnMeta columnMeta)
533    {
534  3 return columnMeta.properties().contains(ColumnMeta.Property.NULLABLE);
535    }
536    }
537   
538    @SuppressWarnings("unchecked")
 
539    class ColumnConfigImpl<T> implements ColumnConfig<T, DataSetting> {
540    SupplierDecider<?> supplierDecider;
541   
542    private final DataSetting dataSetting;
543    private final Consumer<SupplierDecider<T>> finalStageSetter;
544   
 
545  82 toggle ColumnConfigImpl(DataSetting setting, Consumer<SupplierDecider<T>> finalStageSetter)
546    {
547  82 this.dataSetting = setting;
548  82 this.finalStageSetter = finalStageSetter;
549    }
550   
 
551  1 toggle @Override
552    public DataSetting useSpec(Supplier<ValueSpec<? extends T>> valueSpecSupplier)
553    {
554  1 var baseSpecSupplier = (Supplier<ValueSpec<T>>)(Object)valueSpecSupplier;
555  1 SupplierDecider<T> decider = columnMeta -> baseSpecSupplier.get();
556  1 finalStageSetter.accept(decider);
557   
558  1 return dataSetting;
559    }
560   
 
561  66 toggle @Override
562    public DataSetting useSupplier(Supplier<? extends T> valueSupplier)
563    {
564  66 var baseSupplier = (Supplier<T>)valueSupplier;
565  66 SupplierDecider<T> decider = columnMeta -> baseSupplier;
566  66 finalStageSetter.accept(decider);
567   
568  66 return dataSetting;
569    }
570   
 
571  15 toggle @Override
572    public DataSetting decideSupplier(SupplierDecider<? extends T> supplierDecider)
573    {
574  15 var baseDecider = (SupplierDecider<T>)supplierDecider;
575  15 finalStageSetter.accept(columnMeta -> baseDecider.apply(columnMeta));
576   
577  15 return dataSetting;
578    }
579    }