1. Project Clover database Fri Jul 17 2026 06:10:26 UTC
  2. Package guru.mikelue.foxglove.setting

File DataSetting.java

 

Coverage histogram

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

Code metrics

24
110
25
2
607
271
38
0.35
4.4
12.5
1.52

Classes

Class Line # Actions
DataSetting 103 97 0% 34 2
0.985915598.6%
ColumnConfigImpl 567 13 0% 4 0
1.0100%
 

Contributing tests

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