/********************************************************************
 Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.fw.util.builder;

// Java classes

// EDS Classes
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.util.ReflectionException;
import gov.va.med.fw.util.Reflector;

/**
 * An abstract builder class that implements a Builder interface to encapsulate
 * how an object is built from a generic input data. This class is designed as
 * an abstract class to force a derived class to provide its own implementation
 * of constructing an object from a specific data type in a processBuild method.
 * 
 * @author Vu Le
 * @version 1.0
 * @since 1.0
 */
public abstract class AbstractBuilder extends AbstractComponent implements Builder {

	private static final long serialVersionUID = -1370346746640850300L;

	/**
	 * An instance of a builder class
	 */
	protected AbstractBuilder() {
		super();
	}

	/**
	 * Constructs an object from a generic input data. This method uses an input
	 * object's class name to look up a formatter class that knows how to format
	 * an input object's data into a valid form. This method gets a formatter
	 * class from a formatter factory class then pass a formatter and an input
	 * object to a following method: <code>
	 * <pre>
	 *   public Object build( Object input, Formatter formatter )
	 * </pre>
    * </code>
	 * 
	 * @param input
	 *            data from which an object is constructed
	 * @throws BuilderException
	 *             Thrown if failed to construct an object
	 * @return Object a constructed object
	 */
	public Object build(Object input) throws BuilderException {

		if (input == null) {
			throw new BuilderException("Invalid null input object");
		}

		if (input instanceof Object[]) {
			try {
				return this.invokeBuild((Object[]) input);
			} catch (NoSuchMethodException e) {
				if (logger.isDebugEnabled()) {
					logger.debug("Could not invoke method from unpackaged object array types");
				}
			} catch (InvocationTargetException e) {
				Throwable cause = e.getCause();
				throw new BuilderException(cause.getMessage(), cause);
			} catch (Exception e) {
				throw new BuilderException("Failed to build a concrete object", e);
			}
		}

		try {
			return this.invokeBuild(new Object[] { input });
		} catch (InvocationTargetException e) {
			Throwable cause = e.getCause();
			throw new BuilderException(cause.getMessage(), cause);
		} catch (Exception e) {
			throw new BuilderException("Failed to build a concrete object", e);
		}
	}

	/**
	 * Constructs and returns an object by invoking a build method using a
	 * generic input object array. This method prevents recursive calling of
	 * build method when a single Object is passed in for build method.
	 * 
	 * @param inputs
	 *            data from which an object is constructed
	 * @throws InvocationTargetException
	 *             Thrown if failed to invoke a method
	 * @throws NoSuchMethodException
	 *             Thrown if failed to find a method
	 * @return Object a constructed object
	 */
	public Object invokeBuild(Object[] inputs) throws InvocationTargetException,
			NoSuchMethodException, ReflectionException {
		Method method = Reflector.findMethod(this, "build", inputs);
		Class[] paramTypes = method.getParameterTypes();

		if ((paramTypes != null) && (paramTypes.length == 1)
				&& (Object.class.equals(paramTypes[0]))) {
			throw new NoSuchMethodException();
		} else {
			return Reflector.invoke(this, "build", inputs);
		}
	}
}