/*
 * Copyright 2004-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.webflow.engine.builder;

import org.springframework.util.Assert;
import org.springframework.webflow.engine.Flow;

/**
 * A director for assembling flows, delegating to a {@link FlowBuilder} to construct a flow. This class encapsulates the
 * algorithm for using a FlowBuilder to assemble a Flow properly. It acts as the director in the classic GoF builder
 * pattern.
 * <p>
 * Flow assemblers may be used in a standalone, programmatic fashion as follows:
 * 
 * <pre>
 *     FlowBuilder builder = ...;
 *     FlowBuilder context = ...;
 *     Flow flow = new FlowAssembler(builder, builderContext).assembleFlow();
 * </pre>
 * 
 * @see org.springframework.webflow.engine.builder.FlowBuilder
 * 
 * @author Keith Donald
 * @author Erwin Vervaet
 */
public class FlowAssembler {

	/**
	 * The flow builder strategy used to construct the flow from its component parts.
	 */
	private FlowBuilder flowBuilder;

	/**
	 * Context needed to initialize the builder so it can perform a build operation.
	 */
	private FlowBuilderContext flowBuilderContext;

	/**
	 * Create a new flow assembler that will direct Flow assembly using the specified builder strategy.
	 * @param flowBuilder the builder the factory will use to build flows
	 * @param flowBuilderContext context to influence the build process
	 */
	public FlowAssembler(FlowBuilder flowBuilder, FlowBuilderContext flowBuilderContext) {
		Assert.notNull(flowBuilder, "A flow builder is required for flow assembly");
		Assert.notNull(flowBuilderContext, "A flow builder context is required for flow assembly");
		this.flowBuilder = flowBuilder;
		this.flowBuilderContext = flowBuilderContext;
	}

	/**
	 * Returns the flow builder strategy used to construct the flow from its component parts.
	 */
	public FlowBuilder getFlowBuilder() {
		return flowBuilder;
	}

	/**
	 * Returns the flow builder context.
	 * @return flow builder context
	 */
	public FlowBuilderContext getFlowBuilderContext() {
		return flowBuilderContext;
	}

	/**
	 * Assembles the flow, directing the construction process by delegating to the configured FlowBuilder. Every call to
	 * this method will assemble the Flow instance.
	 * <p>
	 * This will drive the flow construction process as described in the {@link FlowBuilder} JavaDoc, starting with
	 * builder initialization using {@link FlowBuilder#init(FlowBuilderContext)} and finishing by cleaning up the
	 * builder with a call to {@link FlowBuilder#dispose()}.
	 * @return the constructed flow
	 * @throws FlowBuilderException when flow assembly fails
	 */
	public Flow assembleFlow() throws FlowBuilderException {
		try {
			flowBuilder.init(flowBuilderContext);
			directAssembly();
			return flowBuilder.getFlow();
		} finally {
			flowBuilder.dispose();
		}
	}

	/**
	 * Build all parts of the flow by directing flow assembly by the flow builder.
	 * @throws FlowBuilderException when flow assembly fails
	 */
	protected void directAssembly() throws FlowBuilderException {
		flowBuilder.buildVariables();
		flowBuilder.buildInputMapper();
		flowBuilder.buildStartActions();
		flowBuilder.buildStates();
		flowBuilder.buildGlobalTransitions();
		flowBuilder.buildEndActions();
		flowBuilder.buildOutputMapper();
		flowBuilder.buildExceptionHandlers();
	}
}