CharacterRandomField.java

package de.slothsoft.random.types;

import java.util.Objects;
import java.util.Random;

import de.slothsoft.random.RandomField;
import de.slothsoft.random.types.wordgen.StandardWordGeneratorConfig;
import de.slothsoft.random.types.wordgen.WordGeneratorConfig;

/**
 * Generates letters after a defined config stored in {@link WordGeneratorConfig}.
 *
 * @author Stef Schulz
 * @since 2.1.0
 */

public class CharacterRandomField implements RandomField {

	static final Random RND = new Random();

	private WordGeneratorConfig config;

	private char[] configCharacters;
	private double[] configCharactersProbabilities;
	private double nullProbability;
	private double capitalProbability;

	/**
	 * Default constructor.
	 */

	public CharacterRandomField() {
		setConfig(StandardWordGeneratorConfig.EVEN_DISTRIBUTION);
	}

	/**
	 * Generates a brand new word.
	 *
	 * @return a word
	 */

	@Override
	public Character nextValue() {
		if (RND.nextDouble() < this.nullProbability) {
			return null;
		}
		final double letterProbability = RND.nextDouble()
				* this.configCharactersProbabilities[this.configCharactersProbabilities.length - 1];
		for (int i = 0; i < this.configCharacters.length; i++) {
			if (this.configCharactersProbabilities[i + 1] > letterProbability) {
				char result = this.configCharacters[i];
				if (RND.nextDouble() < this.capitalProbability) {
					result = Character.toUpperCase(result);
				} else {
					result = Character.toLowerCase(result);
				}
				return Character.valueOf(result);
			}
		}
		// should not happen
		return Character.valueOf('!');
	}

	private void updateFieldsFromConfig() {
		this.configCharacters = Objects.requireNonNull(this.config.getSupportedCharacters());
		if (this.configCharacters.length == 0) {
			throw new IllegalArgumentException("At least one character must be supported by config: " + this.config);
		}
		this.configCharactersProbabilities = new double[this.configCharacters.length + 1];
		for (int i = 0; i < this.configCharacters.length; i++) {
			this.configCharactersProbabilities[i + 1] = this.configCharactersProbabilities[i]
					+ this.config.getProbability(this.configCharacters[i]);
		}
	}

	/**
	 * Returns the used config.
	 *
	 * @return a config
	 */

	public WordGeneratorConfig getConfig() {
		return this.config;
	}

	/**
	 * Sets the used config.
	 *
	 * @param newConfig a config
	 * @return this instance
	 */

	public CharacterRandomField config(WordGeneratorConfig newConfig) {
		setConfig(newConfig);
		return this;
	}

	/**
	 * Sets the used config.
	 *
	 * @param config a config
	 */

	public void setConfig(WordGeneratorConfig config) {
		this.config = Objects.requireNonNull(config);
		updateFieldsFromConfig();
	}

	/**
	 * Returns the probability for this field returning null. If the value is 0 then no
	 * {@link #nextValue()} is null, if it is 1 then every {@link #nextValue()} is null.
	 *
	 * @return the probability between 0 and 1
	 */

	public double getNullProbability() {
		return this.nullProbability;
	}

	/**
	 * Sets the probability for this field returning null. If the value is 0 then no
	 * {@link #nextValue()} is null, if it is 1 then every {@link #nextValue()} is null.
	 *
	 * @param newNullProbability the probability between 0 and 1
	 * @return this instance
	 */

	public CharacterRandomField nullProbability(double newNullProbability) {
		setNullProbability(newNullProbability);
		return this;
	}

	/**
	 * Sets the probability for this field returning null. If the value is 0 then no
	 * {@link #nextValue()} is null, if it is 1 then every {@link #nextValue()} is null.
	 *
	 * @param nullProbability the probability between 0 and 1
	 */

	public void setNullProbability(double nullProbability) {
		if (nullProbability < 0 || nullProbability > 1) {
			throw new IllegalArgumentException("Null probability must be between 0 and 1!");
		}
		this.nullProbability = nullProbability;
	}

	/**
	 * Returns the probability for this field returning capital. If the value is 0 then no
	 * {@link #nextValue()} is a capital letter, if it is 1 then every
	 * {@link #nextValue()} is a capital letter.
	 *
	 * @return the probability between 0 and 1
	 */

	public double getCapitalProbability() {
		return this.capitalProbability;
	}

	/**
	 * Sets the probability for this field returning capital. If the value is 0 then no
	 * {@link #nextValue()} is a capital letter, if it is 1 then every
	 * {@link #nextValue()} is a capital letter.
	 *
	 * @param newCapitalProbability the probability between 0 and 1
	 * @return this instance
	 */

	public CharacterRandomField capitalProbability(double newCapitalProbability) {
		setCapitalProbability(newCapitalProbability);
		return this;
	}

	/**
	 * Sets the probability for this field returning capital. If the value is 0 then no
	 * {@link #nextValue()} is a capital letter, if it is 1 then every
	 * {@link #nextValue()} is a capital letter.
	 *
	 * @param capitalProbability the probability between 0 and 1
	 */

	public void setCapitalProbability(double capitalProbability) {
		if (capitalProbability < 0 || capitalProbability > 1) {
			throw new IllegalArgumentException("Capital probability must be between 0 and 1!");
		}
		this.capitalProbability = capitalProbability;
	}
}