public static Calendar toCalendar(String source, DateFormat format) {
	Calendar date = Calendar.getInstance();
	try {
		date.setTimeInMillis(format.parse(source).getTime());
	} catch (ParseException e) {
		throw new IllegalArgumentException(e);
	}
	return date;
} public class CalendarInterval implements Serializable {
	
	private static final long serialVersionUID = 2223542302515335884L;
	
	private int calendarField;
	private int value;

	public CalendarInterval(int interval, int calendarField) {
		this.calendarField = calendarField;
		this.value = interval;
	}

	public int getCalendarField() {
		return this.calendarField;
	}

	public int getValue() {
		return this.value;
	}
} public class CalendarSequence implements Sequence<Calendar> {

	private Calendar baseCalendar;
	private CalendarInterval interval;
	private int multiplier;

	public CalendarSequence(Calendar baseCalendar, CalendarInterval interval) {
		this.baseCalendar = (Calendar) baseCalendar.clone();
		this.interval = interval;
	}

	@Override
	public Calendar nextValue() {
		Calendar result = (Calendar) this.baseCalendar.clone();
		result.add(this.interval.getCalendarField(), this.interval.getValue() * this.multiplier);
		
		this.multiplier++;
		return result;
	}
} public class Interval {

	private int value;

	public Interval(int value) {
		this.value = value;
	}
	
	public CalendarInterval month() {
		return new CalendarInterval(this.value, Calendar.MONTH);
	}
	
	public CalendarInterval day() {
		return new CalendarInterval(this.value, Calendar.DAY_OF_MONTH);
	}
	
	public CalendarInterval year() {
		return new CalendarInterval(this.value, Calendar.YEAR);
	}
} public class Range {

	private Number start;
	
	private Number end;

	public Range(Number start, Number end) {
		if (start.doubleValue() >= end.doubleValue()) {
			throw new IllegalArgumentException(String.format("incorrect range: [%d, %d]", start, end));
		}
		
		this.start = start;
		this.end = end;
	}

	public Number getStart() {
		return start;
	}

	public Number getEnd() {
		return end;
	}
	
}
 public interface AssociationFunction extends AtomicFunction, RelationFunction, Chainable {

	Function targetAttribute(String targetAttribute);
	
} public interface Chainable {

	AssociationFunction of(Class<?> clazz, String label);
	
	AssociationFunction of(Class<?> clazz, String... labels);

	Function of(Class<? extends Enum<?>> clazz);
} public interface DateFunction extends AtomicFunction {

	AtomicFunction asString(String pattern);
	
} public class FixtureFunction implements AtomicFunction, RelationFunction {

	private Class<?> clazz;

	private List<String> labels;
	
	private Integer quantity;

	public FixtureFunction(Class<?> clazz, String label) {
		this.clazz = clazz;
		this.labels = Arrays.asList(label);
	}

	public FixtureFunction(Class<?> clazz, String label, Integer quantity) {
		this(clazz, Arrays.asList(label), quantity);
	}
	
	public FixtureFunction(Class<?> clazz, List<String> labels, Integer quantity) {
		this.clazz = clazz;
		this.labels = labels;
		this.quantity = quantity;
	}

	@Override
	public <T> T generateValue() {
		return generate(Fixture.from(clazz));
	}
	
	@Override
	public <T> T generateValue(Processor processor) {
		return generate(Fixture.from(clazz).uses(processor));
	}
	
	@Override
	public <T> T generateValue(Object owner) {
		return generate(new ObjectFactory(Fixture.of(clazz), owner));
	}

	@Override
	public <T> T generateValue(Object owner, Processor processor) {
	    return generate(Fixture.from(clazz).uses(processor));
	}

	@SuppressWarnings("unchecked")
	private <T> T generate(ObjectFactory objectFactory) {
		if(quantity != null) {
			return gimmeWithQuantity(objectFactory);
		} else {
			return (T) objectFactory.gimme(getLabel());
		}
	}

	@SuppressWarnings("unchecked")
	private <T> T gimmeWithQuantity(ObjectFactory objectFactory) {
		if(labels.size() == 1) {
			return (T) objectFactory.gimme(quantity, getLabel());
		} else {
			return (T) objectFactory.gimme(quantity, labels);
		}
	}
	
	private String getLabel() {
		return labels.get(0);
	}
}
 public class ChronicFunction implements DateFunction {

    private String dateText;
    
    private Options options;
    
    public ChronicFunction(String text) {
        this.dateText = text;
        this.options = new Options();
    }
    
    public ChronicFunction(String text, Options options) {
        this.dateText = text;
        this.options = options;
    }
    
    
    @SuppressWarnings("unchecked")
    @Override
    public <T> T generateValue() {
        Span span = Chronic.parse(dateText, options);
        return (T) (span != null ? span.getBeginCalendar() : null);
    }

	@Override
	public AtomicFunction asString(String pattern) {
		return new DateAsStringFunction(this, pattern);
	}

}
 public class CnpjFunction implements AtomicFunction {

	private boolean formatted;
	
	public CnpjFunction() { }
	
	public CnpjFunction(boolean formatted) {
		this.formatted = formatted;
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public <T> T generateValue() {
		RandomFunction random = new RandomFunction(Integer.class, new Range(1, 9));
		Integer a  = random.generateValue();
		Integer b  = random.generateValue();
		Integer c  = random.generateValue();
		Integer d  = random.generateValue();
		Integer e  = random.generateValue();
		Integer f  = random.generateValue();
		Integer g  = random.generateValue();
		Integer h  = random.generateValue();
		Integer i  = 0;
		Integer j = 0;
		Integer l = 0;
		Integer m = 1;
		
		int n = m*2+l*3+j*4+i*5+h*6+g*7+f*8+e*9+d*2+c*3+b*4+a*5;
		n = n % 11 < 2 ? 0 : 11 - (n % 11);

		int o = n*2+m*3+l*4+j*5+i*6+h*7+g*8+f*9+e*2+d*3+c*4+b*5+a*6;
		o = o % 11 < 2 ? 0 : 11 - (o % 11);
		
		return (T) String.format(formatted? "%d%d.%d%d%d.%d%d%d/%d%d%d%d-%d%d" : "%d%d%d%d%d%d%d%d%d%d%d%d%d%d", a, b, c, d, e, f, g, h, i, j, l, m, n, o);
	}
} public class IdentityFunction implements AtomicFunction {
    
    private Object value;
    
    public IdentityFunction(Object value) {
        this.value = value;
    }
    
    @Override
    @SuppressWarnings("unchecked")
	public <T> T generateValue() {
		return (T) this.value;
	}
} public class NameFunction implements AtomicFunction {

	private Gender gender;
	
	private NameType type;
	
	public NameFunction() { }
	
	public NameFunction(Gender gender) {
		this.gender = gender;
	}

	public NameFunction(NameType type) {
		this.type = type;
	}

	public NameFunction(NameType type, Gender gender) {
		this(type);
		this.gender = gender;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T generateValue() {
		Object value = null;
		
		if (this.type != null && type == NameType.FIRST) {
			value = this.gender != null ? RandomGen.firstName(this.gender) : RandomGen.firstName();
			
		} else if (this.type != null && type == NameType.LAST) {
			value = RandomGen.lastName();
			
		} else if (this.gender != null) {
			value = RandomGen.name(this.gender);
			
		} else {
			value = RandomGen.name();
		}
		
		return (T) value;
	}

	public enum NameType {
		FIRST, LAST
	}
	
} public class RandomFunction implements AtomicFunction {

    private Class<?> type;
    private Object[] dataset;
    private AtomicFunction[] functions;
    private Range range;
    private MathContext mathContext = MathContext.DECIMAL32;

    public RandomFunction(Class<?> type) {
        this.type = type;
    }

    public RandomFunction(Object[] dataset) {
        this.dataset = dataset;
    }

    public RandomFunction(AtomicFunction[] functions) {
        this.functions = functions;
    }

    public RandomFunction(Class<?> type, Object[] dataset) {
        this.type = type;
        this.dataset = dataset;
    }

    public RandomFunction(Class<?> type, Range range) {
        this.type = type;
        this.range = range;
    }

    public RandomFunction(Class<? extends BigDecimal> type, MathContext mathContext) {
        this.type = type;
        this.mathContext = mathContext;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T generateValue() {
        Object result = null;
        Random random = new Random();

        if (this.dataset != null && this.dataset.length > 0) {
            result = this.dataset[random.nextInt(this.dataset.length)];

        } else if (this.functions != null && this.functions.length > 0) {
            result = this.functions[random.nextInt(this.functions.length)].generateValue();

        } else if (this.type.isEnum()) {
            result = this.type.getEnumConstants()[random.nextInt(this.type.getEnumConstants().length)];

        } else if (this.type.isAssignableFrom(Byte.class) || this.type.equals(byte.class)) {
            result = this.range == null ? (byte) random.nextInt(Byte.MAX_VALUE + 1) : (byte) getRandomLong(this.range);

        } else if (this.type.isAssignableFrom(Short.class) || this.type.equals(short.class)) {
            result = this.range == null ? (short) random.nextInt(Short.MAX_VALUE + 1) : (short) getRandomLong(this.range);

        } else if (this.type.isAssignableFrom(Integer.class) || this.type.equals(int.class)) {
            result = this.range == null ? random.nextInt() : (int) getRandomLong(this.range);

        } else if (this.type.isAssignableFrom(Long.class) || this.type.equals(long.class)) {
            result = this.range == null ? random.nextLong() : getRandomLong(this.range);

        } else if (this.type.isAssignableFrom(Float.class) || this.type.equals(float.class)) {
            result = this.range == null ? random.nextFloat() : (float) getRandomDouble(this.range);

        } else if (this.type.isAssignableFrom(Double.class) || this.type.equals(double.class)) {
            result = this.range == null ? random.nextDouble() : getRandomDouble(this.range);

        } else if (this.type.isAssignableFrom(Boolean.class) || this.type.equals(boolean.class)) {
            result = random.nextBoolean();

        } else if (this.type.isAssignableFrom(BigDecimal.class)) {
            if (this.range == null) {
                result = new BigDecimal(random.nextDouble(), this.mathContext);

            } else {

                result = getRandomBigDecimalByRange();

            }

        } else if (this.type.isAssignableFrom(BigInteger.class)) {
            if (this.range == null) {
                result = new BigInteger(64, random);

            } else {

                result = getRandomBigDecimalByRange().toBigInteger();

            }
        }

        return (T) result;
    }

    private long getRandomLong(Range range) {
        return Math.round(getRandomDouble(range));
    }
    
    private double getRandomDouble(Range range) {
        return range.getStart().doubleValue() + (Math.random() * (range.getEnd().doubleValue() - range.getStart().doubleValue()));
    }
    
    private BigDecimal getRandomBigDecimal(BigDecimal start, BigDecimal end) {
        int scale = start.scale() > end.scale() ? start.scale() : end.scale();
        return start.add(new BigDecimal(Math.random()).multiply(end.subtract(start))).setScale(scale, RoundingMode.HALF_EVEN);
    }

    private BigDecimal getRandomBigDecimalByRange() {
        final BigDecimal start = new BigDecimal(this.range.getStart().toString());
        final BigDecimal end = new BigDecimal(this.range.getEnd().toString());
        return getRandomBigDecimal(start, end);
    }
}
 public class SequenceFunction implements AtomicFunction {

	private Sequence<?> sequence;
	
	public SequenceFunction(Sequence<?> sequence) {
		this.sequence = sequence;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T generateValue() {
		return (T) sequence.nextValue();
	}
} public class UniqueRandomFunction implements AtomicFunction{

	private Object[] dataset;
	
	private int nextValueIndex = 0;
	
	public UniqueRandomFunction(int minValue, int maxValue) {
		if(minValue >= maxValue) {
			throw new IllegalArgumentException("maxValue cannot be greater than minValue.");
		}
		this.dataset = this.initIntegerDataset(minValue, maxValue);
		this.shuffleDataset();
	}
	
	public UniqueRandomFunction(Object[] dataset) {
		if(dataset.length == 0) {
			throw new IllegalArgumentException("provided dataset has no elements.");
		}
		this.dataset = dataset;
		this.shuffleDataset();
	}
	
	public UniqueRandomFunction(Class<? extends Enum<?>> clazz) {
		if(clazz.getEnumConstants().length == 0) {
			throw new IllegalArgumentException("Enum has no values.");
		}
		this.dataset = clazz.getEnumConstants();
		this.shuffleDataset();
	}
	
	private Object[] initIntegerDataset(int minValue, int maxValue) {
		Integer[] dataset = new Integer[maxValue - minValue + 1];
		int currValue = minValue;
		for(int i = 0; i < dataset.length; i++) {
			dataset[i] = currValue;
			currValue ++;
		}
		
		return dataset;
	}
	
	private void shuffleDataset() {
		Random random = new Random();
		for(int shufflePosition = 0, iterator = dataset.length - 1; iterator > 0; iterator--) {
			shufflePosition = random.nextInt(iterator);
			Object temp = dataset[iterator];
			dataset[iterator] = dataset[shufflePosition];
			dataset[shufflePosition] = temp;
		}
	}
	
	@Override
	@SuppressWarnings("unchecked")
	public <T> T generateValue() {
		
		if(this.nextValueIndex >= this.dataset.length) {
			this.nextValueIndex = 0; 
		}
		
		Object nextValue = this.dataset[this.nextValueIndex];
		this.nextValueIndex ++;
		return (T)nextValue;
		
	}
} public class ReflectionUtils {

    public static final String CGLIB_CLASS_SEPARATOR = "$$";
    private static final String NO_SUCH_ATTRIBUTE_MESSAGE = "%s-> No such attribute: %s[%s]";
    
    @SuppressWarnings("unchecked")
    public static <T> T cast(Object source){
        try {
            return source == null ? null : (T) source;
        } catch (Exception e) {
            throw new IllegalArgumentException("Cannot convert to type");
        }
    }
    
    @SuppressWarnings("unchecked")
    public static <T> T invokeGetter(Object bean, String attribute) {
        return (T) ReflectionUtils.invokeGetter(bean, attribute, true);
    }
    
    @SuppressWarnings("unchecked")
    public static <T> T invokeGetter(Object bean, String attribute, boolean fail) {
        try {
            return (T) getPropertyUtilsBean().getProperty(bean, attribute);
        }catch (Exception e) {
            if (fail) {
                throw new IllegalArgumentException("Error invoking get method for " + attribute);   
            } else {
                return null;
            }
        }
    }
    
    public static Object invokeRecursiveGetter(Object bean, String objectsPath) {
        Object lastValue = null;
        Object lastBean = bean;
        for (String propertyItem : objectsPath.split("\\.")) {
            lastValue = ReflectionUtils.invokeGetter(lastBean, propertyItem);
            lastBean = lastValue;
            if (lastValue == null) {
                break;
            }
        }
        return lastValue;
    }
    
    public static <T> void invokeSetter(Object bean, String attribute, Object value, boolean fail){
        try {
            new Mirror().on(bean).set().field(attribute).withValue(value);
        } catch (Exception ex){
            if(fail) {
                throw new IllegalArgumentException(String.format(NO_SUCH_ATTRIBUTE_MESSAGE, bean.getClass().getName(), attribute, value.getClass().getName()));
            }
        }   
    }
    
    public static <T> void invokeSetter(Object bean, String attribute, Object value){
        ReflectionUtils.invokeSetter(bean, attribute, value, true);
    }       
    
    public static void invokeRecursiveSetter(Object bean, String attribute, Object value) {
	    ReflectionUtils.invokeSetter(prepareInvokeRecursiveSetter(bean, attribute, value), attribute.substring(attribute.lastIndexOf(".") + 1), value, true);
	}
    
    public static Class<?> invokeRecursiveType(Class<?> clazz, String attribute) {
    	return invokeRecursiveField(clazz, attribute).getType();
    }

    public static Field invokeRecursiveField(Class<?> clazz, String attribute) {
        Field field = null;
        Class<?> targetBeanClass = getTargetClass(clazz);
        
        for (String propertyItem : attribute.split("\\.")) {
            field = new Mirror().on(targetBeanClass).reflect().field(propertyItem);
            if (field == null) throw new IllegalArgumentException(String.format("%s-> Field %s doesn't exists", clazz.getName(), attribute));
            targetBeanClass = field.getType();
        }

        return field;
    }

    @SuppressWarnings("unchecked")
    public static <T> T newInstance(Class<?> clazz) {
        return (T) newInstance(clazz, Collections.emptyList());
    }
    
    public static <T> T newInstance(Class<T> target, List<? extends Object> parameters) {
		if (parameters.size() > 0) {
			return new Mirror().on(target).invoke().constructor().withArgs(parameters.toArray());			
		} else {
			return new Mirror().on(target).invoke().constructor().withoutArgs();
		}
	}
    
    public static <T> List<String> filterConstructorParameters(Class<T> target, Collection<String> names) {
		List<String> result = Collections.emptyList();
		Paranamer paranamer = new AdaptiveParanamer();
		
		for (Constructor<T> constructor : new Mirror().on(target).reflectAll().constructors()) {
		    List<String> constructorParameterNames = lookupParameterNames(paranamer, constructor);
		    
			if (result.size() < constructorParameterNames.size()) {
				if (names.containsAll(constructorParameterNames) 
						&& constructorParameterTypesMatch(target, constructorParameterNames, Arrays.asList(constructor.getParameterTypes())))
					result = constructorParameterNames;
			}
		}
		return result;
    }

    private static <T> List<String> lookupParameterNames(Paranamer paranamer, Constructor<T> ctor) {
        String[] paramNames = paranamer.lookupParameterNames(ctor, false);

        // as of jdk8, javac adds the "this$n" in the debug info.
        if (isInnerClass(ctor.getDeclaringClass()) && paramNames[0].startsWith("this$")) {
            paramNames = Arrays.copyOfRange(paramNames, 1, paramNames.length);
        }

        return Arrays.asList(paramNames);
    }
    
    private static boolean constructorParameterTypesMatch(Class<?> target, List<String> parameterNames, List<Class<?>> parameterTypes) {
    	for (int idx = 0; idx < parameterNames.size(); idx++) {
    		String parameterName = parameterNames.get(idx);
    		Class<?> parameterType = parameterTypes.get(idx);
    		if (isInnerClass(target) && parameterType.equals(target.getEnclosingClass())) continue;
			if (!parameterType.equals(invokeRecursiveType(target, parameterName))) return false;
    	}
    	return true;
    }
    
    public static Class<?> getTargetClass(Class<?> clazz) {
        if (isCglibProxy(clazz) || Proxy.isProxyClass(clazz)) {
            clazz = clazz.getSuperclass();
        }
        return clazz;
    }
    
    public static boolean isCglibProxy(Class<?> clazz) {
        return (clazz != null && clazz.getName().indexOf(CGLIB_CLASS_SEPARATOR) != -1);
    }
    
    public static PropertyUtilsBean getPropertyUtilsBean() {
    	return BeanUtilsBean.getInstance().getPropertyUtils();
    }
 
    private static Object prepareInvokeRecursiveSetter(Object bean, String attribute, Object value) {
        Object targetBean = bean;
        Object lastBean = null;
        
        int lastAttributeIdx = attribute.lastIndexOf(".");
        
        String path  = null;
        if (lastAttributeIdx > 0) {
            path = StringUtils.defaultIfEmpty(attribute.substring(0, lastAttributeIdx), null);
        }
        
        if (path != null) {
            for (String propertyItem : path.split("\\.")) {
                lastBean = targetBean;
                targetBean = ReflectionUtils.invokeGetter(targetBean, propertyItem);
                if(targetBean == null) {
                	Class<?> type = invokeRecursiveType(lastBean.getClass(), propertyItem);
                    try {
                        List<Object> args = new ArrayList<Object>();
                        if (isInnerClass(type)) {
                            args.add(lastBean);
                        }
                        targetBean = newInstance(type, args);
                        ReflectionUtils.invokeSetter(lastBean, propertyItem, targetBean, true);                     
                    } catch (Exception e) {
                        throw new IllegalArgumentException(String.format(NO_SUCH_ATTRIBUTE_MESSAGE, lastBean.getClass().getName(), propertyItem, type.getName()));
                    }
                }
            }
        }
        
        return targetBean;
    }
    
    public static boolean isInnerClass(Class<?> clazz) {
    	return clazz.getEnclosingClass() != null && !Modifier.isStatic(clazz.getModifiers());
    }
    
    @SuppressWarnings("unchecked")
	public static <T,U> Collection<U> map(Collection<T> collection, String propertyName) {
    	Collection<U> map = null; 
	    try {
	         map = collection.getClass().newInstance();    
        } catch (Exception e) {
            map = new ArrayList<U>();
        }
	    
    	for (T item : collection) {
    		map.add((U) ReflectionUtils.invokeGetter(item, propertyName, true));
		};
		return map;
    }

	public static <T> boolean hasDefaultConstructor(final Class<T> clazz) {
		return !new Mirror().on(clazz).reflectAll().constructors().matching(new Matcher<Constructor<T>>() {
			@Override
			public boolean accepts(Constructor<T> constructor) {
				if (ReflectionUtils.isInnerClass(clazz)) {
					return constructor.getParameterTypes().length == 1 && constructor.getParameterTypes()[0].equals(clazz.getEnclosingClass());
				}
				return Arrays.asList(constructor.getParameterTypes()).isEmpty();
			}
		}).isEmpty();
	}
}

import static br.com.six2six.fixturefactory.function.impl.DateTimeFunction.DateType.AFTER;
import static br.com.six2six.fixturefactory.function.impl.DateTimeFunction.DateType.BEFORE;
import static br.com.six2six.fixturefactory.function.impl.NameFunction.NameType.FIRST;
import static br.com.six2six.fixturefactory.function.impl.NameFunction.NameType.LAST;
import static br.com.six2six.fixturefactory.util.DateTimeUtils.toCalendar;
import java.math.BigDecimal;
import java.math.MathContext;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import br.com.six2six.bfgex.Gender;
import br.com.six2six.fixturefactory.base.CalendarInterval;
import br.com.six2six.fixturefactory.base.CalendarSequence;
import br.com.six2six.fixturefactory.base.Interval;
import br.com.six2six.fixturefactory.base.Range;
import br.com.six2six.fixturefactory.base.Sequence;
import br.com.six2six.fixturefactory.function.AssociationFunction;
import br.com.six2six.fixturefactory.function.Chainable;
import br.com.six2six.fixturefactory.function.DateFunction;
import br.com.six2six.fixturefactory.function.FixtureFunction;
import br.com.six2six.fixturefactory.function.Function;
import br.com.six2six.fixturefactory.function.impl.AssociationFunctionImpl;
import br.com.six2six.fixturefactory.function.impl.ChronicFunction;
import br.com.six2six.fixturefactory.function.impl.CnpjFunction;
import br.com.six2six.fixturefactory.function.impl.DateTimeFunction;
import br.com.six2six.fixturefactory.function.impl.IdentityFunction;
import br.com.six2six.fixturefactory.function.impl.NameFunction;
import br.com.six2six.fixturefactory.function.impl.NumberSequence;
import br.com.six2six.fixturefactory.function.impl.RandomFunction;
import br.com.six2six.fixturefactory.function.impl.RegexFunction;
import br.com.six2six.fixturefactory.function.impl.SequenceFunction;
import br.com.six2six.fixturefactory.function.impl.UniqueRandomFunction;
import br.com.six2six.fixturefactory.util.ReflectionUtils;
import com.mdimension.jchronic.Options;

package br.com.six2six.fixturefactory;





public class Rule {

	public Rule() {}

	public Rule(Rule baseRule, Rule extendedRule) {
		properties = new LinkedHashSet<Property>(baseRule.getProperties());
		for(Property property : extendedRule.getProperties()) {
			properties.remove(property);
			properties.add(property);
		}
	}
	
    private Set<Property> properties = new LinkedHashSet<Property>();

    public void add(String property, Object value) {
        this.properties.add(new Property(property, value));
    }

    public void add(String property, Function function) {
        if (function == null) {
            function = new IdentityFunction(null);
        }
    	this.properties.add(new Property(property, function));
    }
    
    /**
     * @deprecated use {@link one(clazz, label)} instead.
     */
    @Deprecated
	public Function fixture(Class<?> clazz, String label) {
    	return new FixtureFunction(clazz, label);
    }

    /**
     * @deprecated use {@link has(quantity).of(clazz, label)} instead.
     */
    @Deprecated
	public Function fixture(Class<?> clazz, Integer quantity, String label) {
    	return new FixtureFunction(clazz, label, quantity);
    }
	
	public Chainable has(int quantity) {
		return new AssociationFunctionImpl(quantity);
	}
	
	public AssociationFunction one(Class<?> clazz, String label) {
		return new AssociationFunctionImpl(clazz, label);
	}
	
	public Function random(Class<?> clazz, Object... dataset) {
		return new RandomFunction(clazz, dataset);
	}

	public Function random(Class<? extends BigDecimal> clazz, MathContext mc) {
	    return new RandomFunction(clazz, mc);
	}

	public Function random(Object... dataset) {
		return new RandomFunction(dataset);
	}
	
	public Function random(Class<?> clazz, Range range) {
		return new RandomFunction(clazz, range);
	}
	
	public Function uniqueRandom(int minValue, int maxValue) {
		return new UniqueRandomFunction(minValue, maxValue);
	}
	
	public Function uniqueRandom(Object... dataset) {
		return new UniqueRandomFunction(dataset);
	}
	
	public Function uniqueRandom(Class<? extends Enum<?>> clazz) {
		return new UniqueRandomFunction(clazz);
	}

	public Function name() {
		return new NameFunction();
	}

	public Function name(Gender gender) {
		return new NameFunction(gender);
	}

	public Function firstName() {
		return new NameFunction(FIRST);
	}

	public Function firstName(Gender gender) {
		return new NameFunction(FIRST, gender);
	}
	
	public Function lastName() {
		return new NameFunction(LAST);
	}

	public Function beforeDate(String source, SimpleDateFormat format) {
		return new DateTimeFunction(toCalendar(source, format), BEFORE);
	}
	
	public Function afterDate(String source, SimpleDateFormat format) {
		return new DateTimeFunction(toCalendar(source, format), AFTER);
	}
	
	public Function randomDate(String startDate, String endDate, DateFormat format) {
		return new DateTimeFunction(toCalendar(startDate, format), toCalendar(endDate, format));
	}

	public Function regex(String regex) {
		return new RegexFunction(regex);
	}
	
	public Range range(Number start, Number end) {
		return new Range(start, end);
	}

	public Function sequence(Sequence<?> sequence) {
		return new SequenceFunction(sequence);
	}

	public Function sequence(Number startWith, int incrementBy) {
		return new SequenceFunction(new NumberSequence(startWith, incrementBy));
	}
	
	public Function sequence(Class<? extends Number> clazz) {
		Number number = ReflectionUtils.newInstance(clazz, Arrays.asList("1"));
		return new SequenceFunction(new NumberSequence(number, 1));
	}
	
	public Function sequenceDate(String base, CalendarInterval interval) {
		return this.sequenceDate(base, new SimpleDateFormat("yyyy-MM-dd"), interval);
	}
	
	public Function sequenceDate(String base, DateFormat simpleDateFormat, CalendarInterval interval) {

		return new SequenceFunction(new CalendarSequence(toCalendar(base, simpleDateFormat), interval));
