/**
 * File:    EditPartAdapterFactory.java
 * Created: 19.01.2006
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Markus Bauer, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *    Marc Blachon, Bull SAS, Grenoble, France
 *      - making EditPartAdapterFactory dependent on the bundle calling the EditPartFactory
 *******************************************************************************/

package org.eclipse.jwt.we.parts;

import org.eclipse.core.runtime.Platform;
import org.eclipse.draw2d.IFigure;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.AdapterFactoryImpl;
import org.eclipse.emf.common.ui.EclipseUIPlugin;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.jwt.we.Plugin;
import org.eclipse.jwt.we.misc.logging.Logger;
import org.eclipse.jwt.we.parts.internal.EditPartAdapterSupport;
import org.osgi.framework.Bundle;

/**
 * A factory that creates EditPartsAdapters.
 * 
 * This factory can be asked to create an EditPartAdapterSupport for it model
 * element. It creates the EditPart for the corresponding model.
 * 
 * The adapt() method will return an adapter that is <emp>not</emp> an EditPart,
 * because of the separation of the adapter and the EditPart. But it returns an
 * {@link EditPartAdapter} which know the {@link EditPartAdapterSupport}.
 * 
 * 
 * TODO Evaluate a merge with EMF Edit ItemProviders
 * 
 * @version $Id: EditPartAdapterFactory.java,v 1.11 2009/11/26 12:41:48 chsaad
 *          Exp $
 * @author Markus Bauer (markusbauer@users.sourceforge.net), Programming
 *         distributed Systems Lab, University of Augsburg, Germany,
 *         www.ds-lab.org
 * @since 0.6.0
 */
public class EditPartAdapterFactory extends AdapterFactoryImpl
{

	/**
	 * A Logger.
	 */
	private static final Logger logger = Logger.getLogger(EditPartAdapterFactory.class);

	public static final String MODEL_VIEW_ROOT_PACKAGE = "org.eclipse.jwt.we.model";
	public static final String MODEL_JWT_ROOT_PACKAGE = "org.eclipse.jwt.meta.model"; //$NON-NLS-1$

	// Plugin.class.getPackage()
	// .getName()
	// + ".model_ext";

	/**
	 * Package that contains the editparts.
	 */
	private String editPartPackage;

	/**
	 * Package that contains the model.
	 */
	private String modelPackage;

	/**
	 * Bundle
	 */
	private Bundle bundle;

	/**
	 * Postfix for editPart names.
	 */
	private static final String EDITPART_POSTFIX = "EditPart";//$NON-NLS-1$


	/**
	 * Create a new Factory for EditParts.
	 */
	public EditPartAdapterFactory(final String editPartPackage,
			final String modelPackage, final EclipseUIPlugin plugin)
	{
		this.editPartPackage = editPartPackage;
		this.modelPackage = modelPackage;
		this.bundle = (plugin == null) ? Platform.getBundle(Plugin.getDefault()
				.getBundle().getSymbolicName()) : Platform.getBundle(plugin.getBundle()
				.getSymbolicName());
	}


	/**
	 * Returns whether this factory supports adapters for the given type.
	 * 
	 * If the factory supports a specific type the adapt() method will return an
	 * object of this type as a result.
	 * 
	 * This factory support the type {@link EditPartAdapter} and {@link IFigure}
	 * .
	 * 
	 * @param type
	 *            the key indicating the type of adapter in question.
	 * @return whether this factory supports adapters for the given type.
	 * @see Adapter#isAdapterForType
	 */
	@Override
	public boolean isFactoryForType(Object type)
	{
		return type == EditPartAdapter.class.getInterfaces() || type == IFigure.class;
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.eclipse.emf.common.notify.impl.AdapterFactoryImpl#adapt(java.lang
	 * .Object, java.lang.Object)
	 */
	@Override
	@SuppressWarnings("nls")
	public Object adapt(Object target, Object type)
	{
		logger.enter("Create a \"" + type + "\" for a \"" + target.getClass().getName() //$NON-NLS-1$
				+ "\"."); //$NON-NLS-1$

		Object adapter = null;
		if (type == IFigure.class)
		{
			if (target instanceof EClass)
				adapter = findAdapterWithSubclasses((EClass) target, type);
			else if (target instanceof Class)
				adapter = findAdapterWithSubclasses((Class) target, type);
		}

		if (adapter == null)
		{
			adapter = super.adapt(target, type);
		}

		return adapter;
	}


	/**
	 * Creates an adapter of the given type.
	 * 
	 * This method is called by adapt() if a new adapter needs to be created.
	 * 
	 * @param target
	 *            the notifier to adapt.
	 * @param type
	 *            the key indicating the type of adapter required.
	 * @return a new adapter.
	 */
	@Override
	protected Adapter createAdapter(Notifier target, Object type)
	{
		if (type == EditPartAdapter.class && target instanceof EObject)
			return (Adapter) findAdapterWithSubclasses(((EObject) target).eClass(), type);

		// nothing found, return default
		return super.createAdapter(target, type);
	}


	/**
	 * Tries to create an adapter for the given class.
	 * 
	 * @param targetClass
	 *            The class to create the adapter for.
	 * @param type
	 *            the key indicating the type of adapter required.
	 * @return An Adapter for the class or <code>null</code> if no adapter could
	 *         be instaciated.
	 */
	private Object findAdapterWithSubclasses(EClass targetClass, Object type)
	{
		// try to find the EditPart.
		// If not found try to find an EditPart for a superclass.
		Object adapter = findAdapter(targetClass.getInstanceClass(), type);
		if (adapter != null)
		{
			return adapter;
		}

		EList superTypes = targetClass.getEAllSuperTypes();
		// highest supertype is first, so start with end of list.
		for (int i = superTypes.size() - 1; i >= 0; i--)
		{
			EClass testType = (EClass) superTypes.get(i);
			adapter = findAdapter(testType.getInstanceClass(), type);
			if (adapter != null)
			{
				return adapter;
			}
		}

		return null;
	}


	/**
	 * Tries to create an adapter for the given class.
	 * 
	 * @param targetClass
	 *            The class to create the adapter for.
	 * @param type
	 *            the key indicating the type of adapter required.
	 * @return An Adapter for the class or <code>null</code> if no adapter could
	 *         be instaciated.
	 */
	private Object findAdapterWithSubclasses(Class targetClass, Object type)
	{
		// try to find the EditPart.
		// If not found try to find an EditPart for a superclass.
		Object adapter = findAdapter(targetClass, type);
		if (adapter != null)
		{
			return adapter;
		}

		adapter = findAdapterWithSubclasses(targetClass.getSuperclass(), type);
		if (adapter != null)
		{
			return adapter;
		}

		for (Class testType : targetClass.getInterfaces())
		{
			adapter = findAdapterWithSubclasses(testType, type);
			if (adapter != null)
			{
				return adapter;
			}
		}

		return null;
	}


	/**
	 * Tries to create an adapter for the given class.
	 * 
	 * @param targetClass
	 *            The class to create the adapter for.
	 * @param type
	 *            the key indicating the type of adapter required.
	 * @return An Adapter for the class or <code>null</code> if no adapter could
	 *         be instaciated.
	 */
	private Object findAdapter(Class targetClass, Object type)
	{
		String target = targetClass.getName();
		Object editPart = null;

		// redirect package paths from jwt-meta to jwt-we
		if (target.startsWith(MODEL_JWT_ROOT_PACKAGE))
			target = target.replace(MODEL_JWT_ROOT_PACKAGE, modelPackage);

		// only if this factory knows the model package path
		if (!target.startsWith(modelPackage))
			return null;

		// compose the path to the editpart
		target = editPartPackage + target.substring(modelPackage.length())
				+ EDITPART_POSTFIX;

		try
		{
			// load edit part
			editPart = this.bundle.loadClass(target).newInstance();
			logger.instanceCreation(target);
		}
		catch (ClassNotFoundException e)
		{
			logger.debug(e);
			return null;
		}
		catch (InstantiationException e)
		{
			logger.debug(e);
			return null;
		}
		catch (IllegalAccessException e)
		{
			logger.debug(e);
			return null;
		}

		if (type == EditPartAdapter.class)
		{
			if (editPart instanceof EditPartAdapterSupport)
			{
				Adapter adapter = ((EditPartAdapterSupport) editPart).getAdapter();
				if (adapter.isAdapterForType(targetClass))
				{
					return adapter;
				}
			}
		}
		else if (type == IFigure.class)
		{
			// return the figure
			if (editPart instanceof GraphicalEditPart)
				return ((GraphicalEditPart) editPart).getFigure();
		}

		return null;
	}
}
