PodamUtils.java

/**
 *
 */
package uk.co.jemos.podam.api;

import uk.co.jemos.podam.common.PodamConstants;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.ThreadLocalRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;

/**
 * PODAM Utilities class.
 *
 * @author mtedone
 *
 * @since 1.0.0
 *
 */
public abstract class PodamUtils {

	// ---------------------->> Constants

	/** An array of valid String characters */
	public static final char[] NICE_ASCII_CHARACTERS = new char[] { 'a', 'b',
			'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
			'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
			'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
			'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1',
			'2', '3', '4', '5', '6', '7', '8', '9', '_' };

	/** The application logger. */
	private static final Logger LOG = LoggerFactory.getLogger(PodamUtils.class);

	/**
	 * It returns a {@link Field} matching the attribute name or null if a field
	 * was not found.
	 *
	 * @param pojoClass
	 *            The class supposed to contain the field
	 * @param attributeName
	 *            The field name
	 *
	 * @return a {@link Field} matching the attribute name or null if a field
	 *         was not found.
	 */
	public static Field getField(Class<?> pojoClass, String attributeName) {

		Class<?> clazz = pojoClass;
		while (clazz != null) {
			try {
				return clazz.getDeclaredField(attributeName);
			} catch (NoSuchFieldException e) {
				clazz = clazz.getSuperclass();
			}
		}

		LOG.warn("A field could not be found for attribute '{}[{}]'",
				pojoClass, attributeName);
		return null;
	}

	/**
	 * It returns an value for a {@link Field} matching the attribute
	 * name or null if a field was not found.
	 *
	 * @param <T>
	 *            The type of field to be returned
	 * @param pojo
	 *            The class supposed to contain the field
	 * @param attributeName
	 *            The field name
	 *
	 * @return an instance of {@link Field} matching the attribute name or
	 *         null if a field was not found.
	 */
	public static <T> T getFieldValue(Object pojo, String attributeName) {
		T retValue = null;

		try {
			Field field = PodamUtils.getField(pojo.getClass(), attributeName);

			if (field != null) {

				// It allows to invoke Field.get on private fields
				field.setAccessible(true);

				@SuppressWarnings("unchecked")
				T t = (T) field.get(pojo);
				retValue = t;
			} else {

				LOG.info("The field {}[{}] didn't exist.", pojo.getClass(), attributeName);
			}

		} catch (Exception e) {

			LOG.warn("We couldn't get default value for {}[{}]",
					pojo.getClass(), attributeName, e);
		}

		return retValue;
	}

	/**
	 * It returns an value from getter for a {@link Field} matching the attribute
	 * name or null if a field was not found.
	 *
	 * @param <T>
	 *            The type of field to be returned
	 * @param pojo
	 *            The class supposed to contain the field
	 * @param attributeName
	 *            The field name
	 *
	 * @return an instance of {@link Field} matching the attribute name or
	 *         null if a field was not found.
	 */
	public static <T> T getFieldValueWithGetter(Object pojo, String attributeName) {
		T retValue = null;

		try {
			Method method = pojo.getClass().getMethod("get" +
					+ Character.toUpperCase(attributeName.charAt(0))
					+ attributeName.substring(1), PodamConstants.NO_CLASSES);

			if (method != null) {

				// It allows to invoke Field.get on private fields
				method.setAccessible(true);

				@SuppressWarnings("unchecked")
				T t = (T) method.invoke(pojo, PodamConstants.NO_ARGS);
				retValue = t;
			} else {

				LOG.info("The field {}[{}] didn't exist.", pojo.getClass(), attributeName);
			}

		} catch (Exception e) {

			LOG.warn("We couldn't get default value for {}[{}]",
					pojo.getClass(), attributeName, e);
		}

		return retValue;
	}

	/**
	 * Searches among set of a class'es methods and selects the one defined in
	 * the most specific descend of the hierarchy tree
	 *
	 * @param methods a set of methods to choose from
	 * @return the selected method
	 */
	public static Method selectLatestMethod(Set<Method> methods) {
		/* We want to find a method defined the latest */
		Method selected = null;
		for (Method method : methods) {
			if (selected == null || selected.getDeclaringClass().isAssignableFrom(method.getDeclaringClass())) {
				selected = method;
			}
		}
		return selected;
	}

	/**
	 * Given the attribute and setter it combines annotations from them
	 * or an empty collection if no custom annotations were found
	 *
	 * @param attribute
	 *            The class attribute
	 * @param methods
	 *            List of setters and getter to check annotations
	 * @return all annotations for the attribute
	 */
	public static List<Annotation> getAttributeAnnotations(final Field attribute,
			final Method... methods) {

		List<Annotation> retValue = new ArrayList<Annotation>();

		if (null != attribute) {
			for (Annotation annotation : attribute.getAnnotations()) {
				retValue.add(annotation);
			}
		}
		for (Method method : methods) {
			Annotation[][] paramAnnotations = method.getParameterAnnotations();
			if (paramAnnotations.length > 0) {
				for (Annotation annotation : paramAnnotations[0]) {
					retValue.add(annotation);
				}
			} else {
				for (Annotation annotation : method.getAnnotations()) {
					retValue.add(annotation);
				}
			}
		}

		return retValue;
	}

	/**
	 * Generates random character from set valid for identifiers in Java language
	 *
	 * @return random character suitable for identifier
	 */
	public static Character getNiceCharacter() {

		int randomCharIdx = getIntegerInRange(0, NICE_ASCII_CHARACTERS.length - 1);
		return NICE_ASCII_CHARACTERS[randomCharIdx];
	}

	/**
	 * Generates random string from set valid for identifiers in Java language
	 *
	 * @param length
	 *            The length of the strings to generate
	 * @return random string suitable for identifier
	 */
	public static String getNiceString(int length) {

        StringBuilder sb = new StringBuilder(length);
		while (sb.length() < length) {
            sb.append(PodamUtils.getNiceCharacter());
        }
		return sb.toString();
	}

	/**
	 * It returns a long/Long value between min and max value (included).
	 * 
	 * @param minValue
	 *            The minimum value for the returned value
	 * @param maxValue
	 *            The maximum value for the returned value
	 * @return A long/Long value between min and max value (included).
	 */
	public static long getLongInRange(long minValue, long maxValue) {
		return (long) (getDoubleInRange(minValue - 0.5, maxValue + 0.5 - (1 / Long.MAX_VALUE)) + 0.5);
	}

	/**
	 * It returns a random int/Integer value between min and max value (included).
	 * 
	 * @param minValue
	 *            The minimum value for the returned value
	 * @param maxValue
	 *            The maximum value for the returned value
	 * @return An int/Integer value between min and max value (included).
	 */
	public static int getIntegerInRange(int minValue, int maxValue) {
		return (int) getLongInRange(minValue, maxValue);
	}

	/**
	 * It returns a double/Double value between min and max value (included).
	 * 
	 * @param minValue
	 *            The minimum value for the returned value
	 * @param maxValue
	 *            The maximum value for the returned value
	 * @return A double/Double value between min and max value (included)
	 */
	public static double getDoubleInRange(double minValue, double maxValue) {

		// This can happen. It's a way to specify a precise value
		if (minValue == maxValue) {
			return minValue;
		}
		double retValue;
		double margin = (maxValue - minValue + 0.1);
		Random random = ThreadLocalRandom.current();
		do {
			retValue = minValue + random.nextDouble() * margin;
		} while (retValue > maxValue);
		return retValue;
	}

	/**
	 * Finds boxed type for a primitive type
	 * 
	 * @param primitiveType
	 *            Primitive type to find boxed type for
	 * @return A boxed type or the same type, if original type was not primitive
	 */
	public static Class<?> primitiveToBoxedType(Class<?> primitiveType) {

		if (int.class.equals(primitiveType)) {
			return Integer.class;
		} else if (double.class.equals(primitiveType)) {
			return Double.class;
		} else if (long.class.equals(primitiveType)) {
			return Long.class;
		} else if (byte.class.equals(primitiveType)) {
			return Byte.class;
		} else if (float.class.equals(primitiveType)) {
			return Float.class;
		} else if (char.class.equals(primitiveType)) {
			return Character.class;
		} else if (short.class.equals(primitiveType)) {
			return Short.class;
		} else if (boolean.class.equals(primitiveType)) {
			return Boolean.class;
		} else {
			return primitiveType;
		}
	}
}