/******************************************************************************
 * 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.context.support;

import java.beans.PropertyEditor;
import java.io.IOException;
import java.io.InputStream;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.gemini.blueprint.context.BundleContextAware;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistrar;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.beans.propertyeditors.ClassArrayEditor;
import org.springframework.beans.propertyeditors.ClassEditor;
import org.springframework.beans.propertyeditors.CustomMapEditor;
import org.springframework.beans.propertyeditors.PropertiesEditor;
import org.springframework.util.Assert;

/**
 * Class that registers {@link PropertyEditor}s, useful inside an OSGi application context.
 * 
 * As this class is used for bootstrapping and is likely to draw classes from various packages that already depend on
 * {@link BundleContextAware}, the configuration has been externalized to avoid package cycles.
 * 
 * @author Costin Leau
 */
class OsgiPropertyEditorRegistrar implements PropertyEditorRegistrar {

	private static final Log log = LogFactory.getLog(OsgiPropertyEditorRegistrar.class);

	private static final String PROPERTIES_FILE =
			"/org/eclipse/gemini/blueprint/context/support/internal/default-property-editors.properties";

	private final Map<Class<?>, Class<? extends PropertyEditor>> editors;
	private final ClassLoader userClassLoader;

	OsgiPropertyEditorRegistrar(ClassLoader userClassLoader) {
		this.userClassLoader = userClassLoader;

		// load properties
		Properties editorsConfig = new Properties();
		InputStream stream = null;
		try {
			stream = getClass().getResourceAsStream(PROPERTIES_FILE);
			editorsConfig.load(stream);
		} catch (IOException ex) {
			throw (RuntimeException) new IllegalStateException(
					"cannot load default propertiy editorsConfig configuration").initCause(ex);
		} finally {
			if (stream != null) {
				try {
					stream.close();
				} catch (IOException ex) {
				}
			}
		}

		if (log.isTraceEnabled())
			log.trace("Loaded property editors configuration " + editorsConfig);
		editors = new LinkedHashMap<Class<?>, Class<? extends PropertyEditor>>(editorsConfig.size());

		createEditors(editorsConfig);
	}

	@SuppressWarnings("unchecked")
	private void createEditors(Properties configuration) {

		boolean trace = log.isTraceEnabled();
		// load properties using this class class loader
		ClassLoader classLoader = getClass().getClassLoader();

		for (Map.Entry<Object, Object> entry : configuration.entrySet()) {
			// key represents type
			Class<?> key;
			// value represents property editor
			Class<?> editorClass;
			try {
				key = classLoader.loadClass((String) entry.getKey());
				editorClass = classLoader.loadClass((String) entry.getValue());
			} catch (ClassNotFoundException ex) {
				throw (RuntimeException) new IllegalArgumentException("Cannot load class").initCause(ex);
			}

			Assert.isAssignable(PropertyEditor.class, editorClass);

			if (trace)
				log.trace("Adding property editor[" + editorClass + "] for type[" + key + "]");
			editors.put(key, (Class<? extends PropertyEditor>) editorClass);
		}
	}

	public void registerCustomEditors(PropertyEditorRegistry registry) {
		for (Map.Entry<Class<?>, Class<? extends PropertyEditor>> entry : editors.entrySet()) {
			Class<?> type = entry.getKey();
			PropertyEditor editorInstance;
			editorInstance = BeanUtils.instantiate(entry.getValue());
			registry.registerCustomEditor(type, editorInstance);
		}

		// register non-externalized types
		registry.registerCustomEditor(Dictionary.class, new CustomMapEditor(Hashtable.class));
		registry.registerCustomEditor(Properties.class, new PropertiesEditor());
		registry.registerCustomEditor(Class.class, new ClassEditor(userClassLoader));
		registry.registerCustomEditor(Class[].class, new ClassArrayEditor(userClassLoader));
	}
}