View Javadoc
1   /*
2    * (c) Copyright 2006-2020 by rapiddweller GmbH & Volker Bergmann. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, is permitted under the terms of the
6    * GNU General Public License.
7    *
8    * For redistributing this software or a derivative work under a license other
9    * than the GPL-compatible Free Software License as defined by the Free
10   * Software Foundation or approved by OSI, you must first obtain a commercial
11   * license to this software product from rapiddweller GmbH & Volker Bergmann.
12   *
13   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14   * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS,
15   * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE
17   * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24   * POSSIBILITY OF SUCH DAMAGE.
25   */
26  
27  package com.rapiddweller.benerator.factory;
28  
29  import com.rapiddweller.benerator.Generator;
30  import com.rapiddweller.benerator.GeneratorProvider;
31  import com.rapiddweller.benerator.NonNullGenerator;
32  import com.rapiddweller.benerator.distribution.Distribution;
33  import com.rapiddweller.benerator.distribution.SequenceManager;
34  import com.rapiddweller.benerator.primitive.EquivalenceStringGenerator;
35  import com.rapiddweller.benerator.primitive.number.NumberQuantizer;
36  import com.rapiddweller.benerator.sample.OneShotGenerator;
37  import com.rapiddweller.benerator.sample.SequenceGenerator;
38  import com.rapiddweller.benerator.wrapper.AlternativeGenerator;
39  import com.rapiddweller.benerator.wrapper.CompositeStringGenerator;
40  import com.rapiddweller.benerator.wrapper.GeneratorChain;
41  import com.rapiddweller.benerator.wrapper.UniqueMultiSourceArrayGenerator;
42  import com.rapiddweller.benerator.wrapper.WrapperFactory;
43  import com.rapiddweller.common.ArrayUtil;
44  import com.rapiddweller.common.Assert;
45  import com.rapiddweller.common.CollectionUtil;
46  import com.rapiddweller.common.ComparableComparator;
47  import com.rapiddweller.common.Converter;
48  import com.rapiddweller.common.NumberUtil;
49  import com.rapiddweller.common.OrderedSet;
50  import com.rapiddweller.common.Period;
51  import com.rapiddweller.common.converter.AnyConverter;
52  import com.rapiddweller.common.converter.ConverterManager;
53  import com.rapiddweller.common.converter.NumberToNumberConverter;
54  import com.rapiddweller.common.math.Interval;
55  import com.rapiddweller.model.data.Uniqueness;
56  import com.rapiddweller.script.DatabeneScriptParser;
57  import com.rapiddweller.script.WeightedSample;
58  import com.rapiddweller.script.math.ArithmeticEngine;
59  
60  import java.util.Calendar;
61  import java.util.Collection;
62  import java.util.Date;
63  import java.util.GregorianCalendar;
64  import java.util.List;
65  import java.util.Locale;
66  import java.util.Set;
67  import java.util.TreeSet;
68  
69  /**
70   * {@link GeneratorFactory} implementation which creates minimal data sets for
71   * <a href="http://en.wikipedia.org/wiki/Equivalence_partitioning">Equivalence Partitioning</a>
72   * and <a href="http://en.wikipedia.org/wiki/Boundary_value_analysis">Boundary-value analysis</a> Tests.<br/>
73   * <br/>
74   * Created: 04.07.2011 09:39:38
75   *
76   * @author Volker Bergmann
77   * @since 0.7.0
78   */
79  public class EquivalenceGeneratorFactory extends GeneratorFactory {
80  
81    /**
82     * Instantiates a new Equivalence generator factory.
83     */
84    public EquivalenceGeneratorFactory() {
85      super(new MeanDefaultsProvider());
86    }
87  
88    @Override
89    public <T> Generator<T> createAlternativeGenerator(
90        Class<T> targetType, Generator<T>[] sources, Uniqueness uniqueness) {
91      return new GeneratorChain<>(targetType, true, sources);
92    }
93  
94    @Override
95    public <T> Generator<T[]> createCompositeArrayGenerator(
96        Class<T> componentType, Generator<T>[] sources, Uniqueness uniqueness) {
97      return new UniqueMultiSourceArrayGenerator<>(componentType, sources);
98    }
99  
100   @Override
101   public <T> Generator<T> createSampleGenerator(Collection<T> values, Class<T> generatedType, boolean unique) {
102     return new SequenceGenerator<>(generatedType, values);
103   }
104 
105   @Override
106   @SuppressWarnings({"unchecked", "rawtypes"})
107   public <T> Generator<T> createFromWeightedLiteralList(String valueSpec, Class<T> targetType,
108                                                         Distribution distribution, boolean unique) {
109     List<WeightedSample<?>> samples = CollectionUtil.toList(DatabeneScriptParser.parseWeightedLiteralList(valueSpec));
110     List<T> values = FactoryUtil.extractValues((List) samples);
111     Converter<?, T> typeConverter = new AnyConverter<>(targetType);
112     Collection<T> convertedValues = ConverterManager.convertAll((List) values, typeConverter);
113     return createSampleGenerator(convertedValues, targetType, true);
114   }
115 
116   @Override
117   public <T> Generator<T> createWeightedSampleGenerator(Collection<WeightedSample<T>> samples, Class<T> targetType) {
118     List<T> values = FactoryUtil.extractValues(samples);
119     return createSampleGenerator(values, targetType, true);
120   }
121 
122 
123   @Override
124   public Generator<Date> createDateGenerator(Date min, Date max, long granularity, Distribution distribution) {
125     if (min == null) {
126       min = defaultsProvider.defaultMinDate();
127     }
128     if (max == null) {
129       max = defaultsProvider.defaultMaxDate();
130     }
131     TreeSet<Date> values = new TreeSet<>();
132     values.add(min);
133     values.add(midDate(min, max, granularity));
134     values.add(max);
135     return new SequenceGenerator<>(Date.class, values);
136   }
137 
138   /**
139    * Mid date date.
140    *
141    * @param min         the min
142    * @param max         the max
143    * @param granularity the granularity
144    * @return the date
145    */
146   Date midDate(Date min, Date max, long granularity) {
147     int segmentNo = (int) ((max.getTime() - min.getTime()) / granularity / 2);
148     long millisOffset = segmentNo * granularity;
149     Calendar medianDay = new GregorianCalendar();
150     medianDay.setTime(min);
151     long daysOffset = millisOffset / Period.DAY.getMillis();
152     long subDayOffset = millisOffset - daysOffset * Period.DAY.getMillis();
153     medianDay.add(Calendar.DAY_OF_YEAR, (int) daysOffset);
154     medianDay.add(Calendar.MILLISECOND, (int) subDayOffset);
155     return medianDay.getTime();
156   }
157 
158   @SuppressWarnings("unchecked")
159   @Override
160   public <T extends Number> NonNullGenerator<T> createNumberGenerator(
161       Class<T> numberType, T min, Boolean minInclusive, T max, Boolean maxInclusive,
162       T granularity, Distribution distribution, Uniqueness uniqueness) {
163     Assert.notNull(numberType, "numberType");
164     boolean quantization = true;
165     if (distribution != null) {
166       return super.createNumberGenerator(numberType, min, minInclusive, max, maxInclusive,
167           granularity, distribution, uniqueness);
168     }
169     if (min == null) {
170       quantization = false;
171       min = (NumberUtil.isLimited(numberType) ? NumberUtil.minValue(numberType) : defaultsProvider.defaultMin(numberType));
172     }
173     if (max == null) {
174       max = (NumberUtil.isLimited(numberType) ? NumberUtil.maxValue(numberType) : defaultsProvider.defaultMax(numberType));
175     }
176     if (granularity == null) {
177       quantization = false;
178       granularity = defaultsProvider.defaultGranularity(numberType);
179     }
180     if (((Comparable<T>) min).compareTo(max) == 0) { // if min==max then return min once
181       return WrapperFactory.asNonNullGenerator(new OneShotGenerator<>(min));
182     }
183     if (minInclusive == null) {
184       minInclusive = true;
185     }
186     if (maxInclusive == null) {
187       maxInclusive = true;
188     }
189 
190     NumberToNumberConverter<Number, T> converter = new NumberToNumberConverter<>(Number.class, numberType);
191     ArithmeticEngine engine = ArithmeticEngine.defaultInstance();
192     ValueSet<T> values = new ValueSet<>(min, minInclusive, max, maxInclusive, (quantization ? granularity : null), numberType);
193 
194     // values to be tested for any range, duplicated are sieved out by ValueSet
195     values.addIfViable(min);
196     values.addIfViable((Number) engine.add(min, granularity));
197     values.addIfViable((Number) engine.subtract(max, granularity));
198     values.addIfViable(max);
199 
200     // Check the environment of zero
201     T zeroExact = converter.convert(0);
202     T zeroApprox = converter.convert(NumberQuantizer.quantize(zeroExact, min, (quantization ? granularity : null), numberType));
203     int minVsZero = ((Comparable<T>) min).compareTo(zeroApprox);
204     int maxVsZero = ((Comparable<T>) max).compareTo(zeroApprox);
205 
206     if (minVsZero <= 0 && maxVsZero >= 0) {
207       // 0 is contained in the number range, so add values around it
208       if (((Comparable<T>) zeroApprox).compareTo(zeroExact) == 0) {
209         // 0 is contained in the value set (min + N * granularity),
210         // so add -2*granularity, -granularity, 0, granularity, 2*granularity
211         Number minusGranularity = (Number) engine.subtract(zeroExact, granularity);
212         values.addIfViable((Number) engine.multiply(minusGranularity, 2));
213         values.addIfViable(minusGranularity);
214         values.addIfViable(zeroExact);
215         values.addIfViable(granularity);
216         values.addIfViable((Number) engine.multiply(granularity, 2));
217       } else {
218         values.addIfViable(zeroApprox);
219         if (((Comparable<T>) zeroApprox).compareTo(zeroExact) > 0) {
220           // the zero approximation is larger than zero
221           values.addIfViable((Number) engine.subtract(zeroApprox, granularity));
222         } else {
223           // the zero approximation is less than zero
224           values.addIfViable((Number) engine.add(zeroApprox, granularity));
225         }
226 
227       }
228     }
229     if (minVsZero >= 0 || maxVsZero <= 0) {
230       // 0 is not contained in the range (or it is a border value), so add a value in the middle of the range
231       values.addIfViable((Number) engine.divide(engine.add(min, max), 2));
232     }
233     return WrapperFactory.asNonNullGenerator(new SequenceGenerator<>(numberType, values.getAll()));
234   }
235 
236   @Override
237   public NonNullGenerator<String> createStringGenerator(Set<Character> chars,
238                                                         Integer minLength, Integer maxLength, int lengthGranularity, Distribution lengthDistribution,
239                                                         Uniqueness uniqueness) {
240     Generator<Character> charGenerator = createCharacterGenerator(chars);
241     if (maxLength == null) {
242       maxLength = defaultsProvider.defaultMaxLength();
243     }
244     Set<Integer> counts = defaultCounts(minLength, maxLength, lengthGranularity);
245     NonNullGenerator<Integer> lengthGenerator = WrapperFactory.asNonNullGenerator(
246         new SequenceGenerator<>(Integer.class, counts));
247     return new EquivalenceStringGenerator<>(charGenerator, lengthGenerator);
248   }
249 
250   @SuppressWarnings("unchecked")
251   @Override
252   public NonNullGenerator<String> createCompositeStringGenerator(
253       GeneratorProvider<?> partGeneratorProvider, int minParts, int maxParts, Uniqueness uniqueness) {
254     AlternativeGenerator<String> result = new AlternativeGenerator<>(String.class);
255     Set<Integer> partCounts = defaultCounts(minParts, maxParts, 1);
256     for (int partCount : partCounts) {
257       Generator<String>[] sources = new Generator[partCount];
258       for (int i = 0; i < partCount; i++) {
259         sources[i] = WrapperFactory.asStringGenerator(partGeneratorProvider.create());
260       }
261       result.addSource(new CompositeStringGenerator(true, sources));
262     }
263     return WrapperFactory.asNonNullGenerator(result);
264   }
265 
266   @Override
267   public Generator<Character> createCharacterGenerator(String pattern, Locale locale, boolean unique) {
268     Character[] chars = CollectionUtil.toArray(defaultSubSet(FactoryUtil.fullLocaleCharSet(pattern, locale)), Character.class);
269     return new SequenceGenerator<>(Character.class, chars);
270   }
271 
272   @Override
273   public NonNullGenerator<Character> createCharacterGenerator(Set<Character> characters) {
274     return WrapperFactory.asNonNullGenerator(
275         new SequenceGenerator<>(Character.class, defaultSubSet(characters)));
276   }
277 
278   /**
279    * Default counts set.
280    *
281    * @param minParts          the min parts
282    * @param maxParts          the max parts
283    * @param lengthGranularity the length granularity
284    * @return the set
285    */
286   protected Set<Integer> defaultCounts(int minParts, int maxParts, int lengthGranularity) {
287     Set<Integer> lengths = new TreeSet<>();
288     lengths.add(minParts);
289     lengths.add(((minParts + maxParts) / 2 - minParts) / lengthGranularity * lengthGranularity + minParts);
290     lengths.add(maxParts);
291     if (maxParts > minParts) {
292       lengths.add(minParts + 1);
293       lengths.add(maxParts - 1);
294     }
295     return lengths;
296   }
297 
298   @Override
299   public <T> Generator<T> createSingleValueGenerator(T value, boolean unique) {
300     return new OneShotGenerator<>(value);
301   }
302 
303   @Override
304   public <T> Generator<T> createNullGenerator(Class<T> generatedType) {
305     return new OneShotGenerator<>(null, generatedType);
306   }
307 
308   @Override
309   public Set<Character> defaultSubSet(Set<Character> characters) {
310     Set<Character> uppers = new TreeSet<>();
311     Set<Character> lowers = new TreeSet<>();
312     Set<Character> digits = new TreeSet<>();
313     Set<Character> spaces = new TreeSet<>();
314     Set<Character> others = new TreeSet<>();
315     for (char c : characters) {
316       if (Character.isUpperCase(c)) {
317         uppers.add(c);
318       } else if (Character.isLowerCase(c)) {
319         lowers.add(c);
320       } else if (Character.isDigit(c)) {
321         digits.add(c);
322       } else if (Character.isWhitespace(c)) {
323         spaces.add(c);
324       } else {
325         others.add(c);
326       }
327     }
328     Set<Character> result = new OrderedSet<>();
329     addSelection(uppers, result);
330     addSelection(lowers, result);
331     addSelection(digits, result);
332     result.addAll(spaces);
333     result.addAll(others);
334     return result;
335   }
336 
337   /**
338    * Add selection.
339    *
340    * @param ofChars the of chars
341    * @param toChars the to chars
342    */
343   protected void addSelection(Set<Character> ofChars, Set<Character> toChars) {
344     if (ofChars.size() == 0) {
345       return;
346     }
347     Character[] array = CollectionUtil.toArray(ofChars);
348     toChars.add(array[0]);
349     if (array.length >= 3) {
350       toChars.add(array[array.length / 2]);
351     }
352     if (array.length >= 2) {
353       toChars.add(ArrayUtil.lastElementOf(array));
354     }
355   }
356 
357   // defaults --------------------------------------------------------------------------------------------------------
358 
359   @Override
360   public Generator<?> applyNullSettings(Generator<?> source, Boolean nullable, Double nullQuota) {
361     if (nullable == null || nullable || (nullQuota != null && nullQuota > 0)) {
362       return WrapperFactory.prependNull(source);
363     } else {
364       return source;
365     }
366   }
367 
368   @Override
369   protected Distribution defaultLengthDistribution(Uniqueness uniqueness, boolean required) {
370     return (required ? SequenceManager.STEP_SEQUENCE : null);
371   }
372 
373   @SuppressWarnings("SwitchStatementWithTooFewBranches")
374   @Override
375   public Distribution defaultDistribution(Uniqueness uniqueness) {
376     switch (uniqueness) {
377       case NONE:
378         return SequenceManager.RANDOM_SEQUENCE;
379       default:
380         return SequenceManager.STEP_SEQUENCE;
381     }
382   }
383 
384   @Override
385   protected double defaultTrueQuota() {
386     return 0.5;
387   }
388 
389   /**
390    * The type Value set.
391    *
392    * @param <T> the type parameter
393    */
394   static class ValueSet<T extends Number> {
395 
396     /**
397      * The Number range.
398      */
399     final Interval<T> numberRange;
400     /**
401      * The Granularity.
402      */
403     final T granularity;
404     /**
405      * The Number type.
406      */
407     final Class<T> numberType;
408     private final TreeSet<T> set;
409 
410     /**
411      * Instantiates a new Value set.
412      *
413      * @param min          the min
414      * @param minInclusive the min inclusive
415      * @param max          the max
416      * @param maxInclusive the max inclusive
417      * @param granularity  the granularity
418      * @param numberType   the number type
419      */
420     @SuppressWarnings({"rawtypes", "unchecked"})
421     public ValueSet(T min, boolean minInclusive, T max, boolean maxInclusive, T granularity, Class<T> numberType) {
422       this.set = new TreeSet<>();
423       this.numberRange = new Interval<T>(min, minInclusive, max, maxInclusive, new ComparableComparator());
424       this.granularity = granularity;
425       this.numberType = numberType;
426     }
427 
428     /**
429      * Gets all.
430      *
431      * @return the all
432      */
433     public Collection<T> getAll() {
434       return set;
435     }
436 
437     /**
438      * Add if viable.
439      *
440      * @param value the value
441      */
442     public void addIfViable(Number value) {
443       T numberToAdd = NumberToNumberConverter.convert(value, numberType);
444       if (numberRange.contains(numberToAdd)) {
445         if (granularity != null) {
446           numberToAdd = NumberQuantizer.quantize(value, numberRange.getMin(), granularity, numberType);
447         }
448         set.add(numberToAdd);
449       }
450     }
451   }
452 
453   @Override
454   protected boolean defaultUnique() {
455     return true;
456   }
457 
458 }