RandomFactory.java

package de.slothsoft.random;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * The main class for creating random data inside a defined POJO.
 *
 * @author Stef Schulz
 * @since 1.0.0
 * @param <T> the type to be created
 */

public class RandomFactory<T> implements RandomField {

	/**
	 * Represents a supplier of results.
	 *
	 * @param <U> the type of results supplied by this supplier
	 */

	@FunctionalInterface
	public interface Supplier<U> {

		/**
		 * Gets a result.
		 *
		 * @return a result
		 * @throws RandomException for any reason
		 */

		U get() throws RandomException;
	}

	/**
	 * Creates a {@link RandomFactory} with just one class and then tries to guess which
	 * fields to fill. Might not be the best option for some cases.
	 *
	 * @param pojoClass the class of the POJO to be created; the class must have a default
	 *            constructor and be public
	 * @param <U> the class that should be generated
	 * @return a {@link RandomFactory} with guessed mapping
	 */

	public static <U> RandomFactory<U> forClass(Class<U> pojoClass) {
		return new RandomFactory<>(() -> {
			try {
				return pojoClass.getConstructor().newInstance();
			} catch (final Exception e) {
				throw new RandomException("Could not create instance of class " + pojoClass
						+ ". Class must have default constructor and be public visible!", e);
			}
		}, guessMapping(pojoClass));
	}

	/**
	 * Guesses the mapping from the actual name of the getter and setter.
	 *
	 * @param pojoClass the class
	 * @return a mapping
	 */

	static Map<String, RandomField> guessMapping(Class<?> pojoClass) {
		final Map<String, Class<?>> fields = PropertyUtil.getProperties(pojoClass);
		final Map<String, RandomField> result = new LinkedHashMap<>();

		for (final Entry<String, Class<?>> fieldEntry : fields.entrySet()) {
			final RandomField randomField = RandomFieldSupplier.createRandomFieldByField(fieldEntry.getKey(),
					fieldEntry.getValue());
			if (randomField != null) {
				result.put(fieldEntry.getKey(), randomField);
			}
		}
		return result;
	}

	private final Supplier<T> pojoSupplier;
	private final Map<String, RandomField> fieldMapping;
	private final Class<?> pojoClass;

	/**
	 * A constructor that takes one {@link Supplier}. The mapping of properties to the
	 * factories filling them is guessed.
	 *
	 * @param pojoSupplier the supplier for the POJO
	 */

	public RandomFactory(Supplier<T> pojoSupplier) {
		this(pojoSupplier, guessMapping(pojoSupplier.get().getClass()));
	}

	/**
	 * A constructor that takes one class and the mapping of properties to the factories
	 * filling them.
	 *
	 * @param pojoSupplier the supplier for the POJO
	 * @param fieldMapping the initial mapping for the fields
	 */

	public RandomFactory(Supplier<T> pojoSupplier, Map<String, RandomField> fieldMapping) {
		this.pojoSupplier = pojoSupplier;
		this.fieldMapping = fieldMapping;
		this.pojoClass = pojoSupplier.get().getClass();
	}

	@Override
	public T nextValue() {
		return createSingle();
	}

	/**
	 * Creates a single instance of the class this factory is for.
	 *
	 * @return a single POJO instance
	 * @throws RandomException - if something went wrong
	 */

	public T createSingle() throws RandomException {
		final T result = this.pojoSupplier.get();
		fillFields(result);
		return result;
	}

	void fillFields(T value) throws RandomException {
		final Map<String, Object> context = new HashMap<>();

		for (final Entry<String, RandomField> fieldEntry : this.fieldMapping.entrySet()) {
			final RandomField randomField = fieldEntry.getValue();
			randomField.init(context);
			final Object randomValue = randomField.nextValue();
			PropertyUtil.setProperty(value, fieldEntry.getKey(), randomValue);
			context.put(fieldEntry.getKey(), randomValue);
		}
	}

	/**
	 * Creates some instances of the POJO this factory is for.
	 *
	 * @param count number of instances to be created
	 * @return some POJO instances
	 * @throws RandomException - if something went wrong
	 */

	public List<T> create(int count) {
		final List<T> result = new ArrayList<>();
		for (int i = 0; i < count; i++) {
			result.add(createSingle());
		}
		return result;
	}

	/**
	 * Returns the {@link RandomField} used to fill the property.
	 *
	 * @param property the property
	 * @return the random field to fill this property; never null
	 * @throws RandomException if no {@link RandomField} was found
	 */

	public RandomField getRandomField(String property) {
		final RandomField result = findRandomField(property);
		if (result == null) {
			throw new RandomException("Could not find RandomField for property " + property);
		}
		return result;
	}

	/**
	 * Returns the {@link RandomField} used to fill the property.
	 *
	 * @param property the property
	 * @return the random field to fill this property; can be null
	 */

	public RandomField findRandomField(String property) {
		return this.fieldMapping.get(property);
	}

	/**
	 * Adds a {@link RandomField} to fill some property.
	 *
	 * @param property the property
	 * @param randomField the random field to fill this property; can be null
	 */

	public void addRandomField(String property, RandomField randomField) {
		this.fieldMapping.put(property, randomField);
	}

	/**
	 * Removes a {@link RandomField}.
	 *
	 * @param property the property
	 */

	public void removeRandomField(String property) {
		this.fieldMapping.remove(property);
	}

	Class<?> getPojoClass() {
		return this.pojoClass;
	}
}