/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.blueprint.container.support.internal.config;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.blueprint.config.internal.ParsingUtils;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.core.Ordered;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * Simple processor for sorting out cycles between beans. Inspects the construction relationship between beans to
 * provide hints to the container. Specifically, it forces the creation of any beans referred inside the construction
 * through the 'depends-on' attribute on the inspected bean.
 * 
 * @author Costin Leau
 */
public class CycleOrderingProcessor implements BeanFactoryPostProcessor, Ordered {

	public static final String SYNTHETIC_DEPENDS_ON =
			"org.eclipse.gemini.blueprint.blueprint.container.support.internal.config.dependson";

	/** logger */
	private static final Log log = LogFactory.getLog(CycleOrderingProcessor.class);

	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

		boolean trace = log.isTraceEnabled();

		String[] names = beanFactory.getBeanDefinitionNames();
		for (String name : names) {
			BeanDefinition definition = beanFactory.getBeanDefinition(name);
			if (definition.hasAttribute(ParsingUtils.BLUEPRINT_MARKER_NAME)) {
				ConstructorArgumentValues cArgs = definition.getConstructorArgumentValues();
				if (trace)
					log.trace("Inspecting cycles for (blueprint) bean " + name);

				tag(cArgs.getGenericArgumentValues(), name, definition);
				tag(cArgs.getIndexedArgumentValues().values(), name, definition);
			}
		}
	}

	private void tag(Collection<ValueHolder> values, String name, BeanDefinition definition) {
		boolean trace = log.isTraceEnabled();

		for (ValueHolder value : values) {
			Object val = value.getValue();
			if (val instanceof BeanMetadataElement) {
				if (val instanceof RuntimeBeanReference) {
					String beanName = ((RuntimeBeanReference) val).getBeanName();

					if (trace) {
						log.trace("Adding (cycle breaking) depends-on on " + name + " to " + beanName);
					}

					addSyntheticDependsOn(definition, beanName);
				}
			}
		}
	}

	private void addSyntheticDependsOn(BeanDefinition definition, String beanName) {
		if (StringUtils.hasText(beanName)) {
			String[] dependsOn = definition.getDependsOn();
			if (dependsOn != null && dependsOn.length > 0) {
				for (String dependOn : dependsOn) {
					if (beanName.equals(dependOn)) {
						return;
					}
				}
			}

			// add depends on
			dependsOn = (String[]) ObjectUtils.addObjectToArray(dependsOn, beanName);
			definition.setDependsOn(dependsOn);
			Collection<String> markers = (Collection<String>) definition.getAttribute(SYNTHETIC_DEPENDS_ON);
			if (markers == null) {
				markers = new ArrayList<String>(2);
				definition.setAttribute(SYNTHETIC_DEPENDS_ON, markers);
			}
			markers.add(beanName);
		}
	}

	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}
}
