/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.ui.internal.util.BundleUtility;
import org.osgi.framework.Bundle;

/**
 * Provides access to resource-specific classes, needed to provide
 * backwards compatibility for resource-specific functions which
 * could not be moved up from the generic workbench layer to the
 * IDE layer.
 */
public final class LegacyResourceSupport {

	private static String[] resourceClassNames = {
        "org.eclipse.core.resources.IResource", //$NON-NLS-1$
        "org.eclipse.core.resources.IContainer", //$NON-NLS-1$
        "org.eclipse.core.resources.IFolder", //$NON-NLS-1$
        "org.eclipse.core.resources.IProject", //$NON-NLS-1$
        "org.eclipse.core.resources.IFile", //$NON-NLS-1$
	};

    /**
     * Cached value of
     * <code>Class.forName("org.eclipse.core.resources.IResource")</code>;
     * <code>null</code> if not initialized or not present.
     * @since 3.0
     */
    private static Class iresourceClass = null;

    /**
     * Cached value of
     * <code>Class.forName("org.eclipse.core.resources.IFile")</code>;
     * <code>null</code> if not initialized or not present.
     * @since 3.1
     */
    private static Class ifileClass;

    /**
     * Cached value of
     * <code>Class.forName("org.eclipse.ui.IContributorResourceAdapter")</code>;
     * <code>null</code> if not initialized or not present.
     * @since 3.0
     */
    private static Class icontributorResourceAdapterClass = null;

    /**
     * Cached value of </code> org.eclipse.ui.IContributorResourceAdapter.getAdaptedResource(IAdaptable) </code>
     * <code>null</code> if not initialized or not present.
     *
     * @since 3.3
     */
    private static Method getAdaptedResourceMethod = null;

    /**
     * Cached value of </code> org.eclipse.ui.IContributorResourceAdapter2.getAdaptedResourceMapping(IAdaptable) </code>
     * <code>null</code> if not initialized or not present.
     *
     * @since 3.3
     */
    private static Method getAdaptedResourceMappingMethod = null;

    /**
     * Cached value of
     * <code>Class.forName("org.eclipse.ui.ide.IContributorResourceAdapter2")</code>;
     * <code>null</code> if not initialized or not present.
     * @since 3.1
     */
    private static Class icontributorResourceAdapter2Class = null;

    /**
     * Cached value of
     * <code>Class.forName("org.eclipse.ui.internal.ide.DefaultContributorResourceAdapter")</code>;
     * <code>null</code> if not initialized or not present.
     * @since 3.0
     */
    private static Class defaultContributorResourceAdapterClass = null;

    /**
     * Cached value for reflective result of <code>DefaultContributorRessourceAdapter.getDefault()</code>.
     * <code>null</code> if not initialized or not present.
     *
     * @since 3.3
     */
    private static Object defaultContributorResourceAdapter = null;

    /**
     * Cached value of
     * <code>Class.forName("org.eclipse.core.resources.mapping.ResourceMappingr")</code>;
     * <code>null</code> if not initialized or not present.
     * @since 3.0
     */
    private static Class resourceMappingClass = null;

    /**
     * Indicates whether the IDE plug-in (which supplies the
     * resource contribution adapters) is even around.
     */
    private static boolean resourceAdapterPossible = true;


    /**
     * Returns <code>IFile.class</code> or <code>null</code> if the
     * class is not available.
     * <p>
     * This method exists to avoid explicit references from the generic
     * workbench to the resources plug-in.
     * </p>
     *
     * @return <code>IFile.class</code> or <code>null</code> if class
     * not available
     * @since 3.1
     */
    public static Class getFileClass() {
        if (ifileClass != null) {
            // tried before and succeeded
            return ifileClass;
        }
        Class c = loadClass("org.eclipse.core.resources", "org.eclipse.core.resources.IFile"); //$NON-NLS-1$ //$NON-NLS-2$
        if (c != null) {
            // The class was found so record it
            ifileClass = c;
        }
        return c;
    }

    /**
     * Returns <code>IResource.class</code> or <code>null</code> if the
     * class is not available.
     * <p>
     * This method exists to avoid explicit references from the generic
     * workbench to the resources plug-in.
     * </p>
     *
     * @return <code>IResource.class</code> or <code>null</code> if class
     * not available
     * @since 3.0
     */
    public static Class getResourceClass() {
        if (iresourceClass != null) {
            // tried before and succeeded
            return iresourceClass;
        }
        Class c = loadClass("org.eclipse.core.resources", "org.eclipse.core.resources.IResource"); //$NON-NLS-1$ //$NON-NLS-2$
        if (c != null) {
            // The class was found so record it
            iresourceClass = c;
        }
        return c;
    }

    /**
     * Returns <code>ResourceMapping.class</code> or <code>null</code> if the
     * class is not available.
     * <p>
     * This method exists to avoid explicit references from the generic
     * workbench to the resources plug-in.
     * </p>
     *
     * @return <code>ResourceMapping.class</code> or <code>null</code> if class
     * not available
     * @since 3.1
     */
    public static Class getResourceMappingClass() {
        if (resourceMappingClass != null) {
            // tried before and succeeded
            return resourceMappingClass;
        }
        Class c = loadClass("org.eclipse.core.resources", "org.eclipse.core.resources.mapping.ResourceMapping"); //$NON-NLS-1$ //$NON-NLS-2$
        if (c != null) {
            // The class was found so record it
            resourceMappingClass = c;
        }
        return c;
    }

    /**
     * Returns <code>IContributorResourceAdapter.class</code> or
     * <code>null</code> if the class is not available.
     * <p>
     * This method exists to avoid explicit references from the generic
     * workbench to the IDE plug-in.
     * </p>
     *
     * @return <code>IContributorResourceAdapter.class</code> or
     * <code>null</code> if class not available
     * @since 3.0
     */
    public static Class getIContributorResourceAdapterClass() {
        if (icontributorResourceAdapterClass != null) {
            // tried before and succeeded
            return icontributorResourceAdapterClass;
        }
        Class c = loadClass("org.eclipse.ui.ide", "org.eclipse.ui.IContributorResourceAdapter"); //$NON-NLS-1$ //$NON-NLS-2$
        if (c != null) {
            // The class was found so record it
            icontributorResourceAdapterClass = c;
        }
        return c;
    }

    /**
     * Returns <code>IContributorResourceAdapter2.class</code> or
     * <code>null</code> if the class is not available.
     * <p>
     * This method exists to avoid explicit references from the generic
     * workbench to the IDE plug-in.
     * </p>
     *
     * @return <code>IContributorResourceAdapter.class</code> or
     * <code>null</code> if class not available
     * @since 3.1
     */
    public static Class getIContributorResourceAdapter2Class() {
        if (icontributorResourceAdapter2Class != null) {
            // tried before and succeeded
            return icontributorResourceAdapter2Class;
        }
        Class c = loadClass("org.eclipse.ui.ide", "org.eclipse.ui.ide.IContributorResourceAdapter2"); //$NON-NLS-1$ //$NON-NLS-2$
        if (c != null) {
            // The class was found so record it
            icontributorResourceAdapter2Class = c;
        }
        return c;
    }

    private static Class loadClass(String bundleName, String className) {
        if (!resourceAdapterPossible) {
            // tried before and failed
            return null;
        }
        Bundle bundle = Platform.getBundle(bundleName);
        if (bundle == null) {
            // Required plug-in is not around
            // assume that it will never be around
            resourceAdapterPossible = false;
            return null;
        }
        // Required plug-in is around
        // it's not our job to activate the plug-in
        if (!BundleUtility.isActivated(bundle)) {
            // assume it might come alive later
            resourceAdapterPossible = true;
            return null;
        }
        try {
            return bundle.loadClass(className);
        } catch (ClassNotFoundException e) {
            // unable to load the class - sounds pretty serious
            // treat as if the plug-in were unavailable
            resourceAdapterPossible = false;
            return null;
        }
    }

    /**
     * Returns <code>DefaultContributorResourceAdapter.class</code> or
     * <code>null</code> if the class is not available.
     * <p>
     * This method exists to avoid explicit references from the generic
     * workbench to the IDE plug-in.
     * </p>
     *
     * @return <code>DefaultContributorResourceAdapter.class</code> or
     * <code>null</code> if class not available
     * @since 3.0
     */
    public static Class getDefaultContributorResourceAdapterClass() {
        if (defaultContributorResourceAdapterClass != null) {
            // tried before and succeeded
            return defaultContributorResourceAdapterClass;
        }
        Class c = loadClass("org.eclipse.ui.ide", "org.eclipse.ui.internal.ide.DefaultContributorResourceAdapter"); //$NON-NLS-1$ //$NON-NLS-2$
        if (c != null) {
            // The class was found so record it
            defaultContributorResourceAdapterClass = c;
        }
        return c;
    }

    private static Object getDefaultContributorResourceAdapter() {
        if (defaultContributorResourceAdapter != null) {
            return defaultContributorResourceAdapter;
        }

		// reflective equivalent of
		//    resourceAdapter = DefaultContributorResourceAdapter.getDefault();

			Class c = LegacyResourceSupport.getDefaultContributorResourceAdapterClass();
			if (c != null) {
				try {
					Method m  = c.getDeclaredMethod("getDefault", new Class[0]);//$NON-NLS-1$
					defaultContributorResourceAdapter = m.invoke(null, new Object[0]);
					return defaultContributorResourceAdapter;
				} catch (SecurityException e) {
					// shouldn't happen - but play it safe
				} catch (NoSuchMethodException e) {
					// shouldn't happen - but play it safe
				} catch (IllegalArgumentException e) {
					// shouldn't happen - but play it safe
				} catch (IllegalAccessException e) {
					// shouldn't happen - but play it safe
				} catch (InvocationTargetException e) {
					// shouldn't happen - but play it safe
				}


			}

		return null;

    }

    /**
     * Returns <code>true</code> if the provided type name is an
     * <code>IResource</code>, and <code>false</code> otherwise.
     * @param objectClassName
     * @return <code>true</code> if the provided type name is an
     * <code>IResource</code>, and <code>false</code> otherwise.
     *
     * @since 3.1
     */
    public static boolean isResourceType(String objectClassName) {
        for (String resourceClassName : resourceClassNames) {
            if (resourceClassName.equals(objectClassName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns <code>true</code> if the provided type name is an
     * <code>"org.eclipse.core.resources.mapping.ResourceMapping"</code>, and <code>false</code> otherwise.
     * @param objectClassName
     * @return <code>true</code> if the provided type name is an
     * <code>"org.eclipse.core.resources.mapping.ResourceMapping"</code>, and <code>false</code> otherwise.
     *
     * @since 3.1
     */
    public static boolean isResourceMappingType(String objectClassName) {
        return objectClassName.equals("org.eclipse.core.resources.mapping.ResourceMapping"); //$NON-NLS-1$
    }

    /**
     * Returns the class search order starting with <code>extensibleClass</code>.
     * The search order is defined in this class' comment.
     *
     * @since 3.1
     */
    private static boolean isInstanceOf(Class clazz, String type) {
		if (clazz.getName().equals(type)) {
			return true;
		}
		Class superClass= clazz.getSuperclass();
		if (superClass != null && isInstanceOf(superClass, type)) {
			return true;
		}
		Class[] interfaces= clazz.getInterfaces();
		for (Class currentInterface : interfaces) {
			if (isInstanceOf(currentInterface, type)) {
				return true;
			}
		}
		return false;
	}

    /**
     * Returns the adapted resource using the <code>IContributorResourceAdapter</code>
     * registered for the given object. If the Resources plug-in is not loaded
     * the object can not be adapted.
     *
     * @param object the object to adapt to <code>IResource</code>.
     * @return returns the adapted resource using the <code>IContributorResourceAdapter</code>
     * or <code>null</code> if the Resources plug-in is not loaded.
     *
     * @since 3.1
     */
    public static Object getAdaptedContributorResource(Object object) {
		Class resourceClass = LegacyResourceSupport.getResourceClass();
		if (resourceClass == null) {
			return null;
		}
		if (resourceClass.isInstance(object)) {
			return null;
		}
		if (object instanceof IAdaptable) {
			IAdaptable adaptable = (IAdaptable) object;
			Class contributorResourceAdapterClass = LegacyResourceSupport.getIContributorResourceAdapterClass();
			if (contributorResourceAdapterClass == null) {
				return adaptable.getAdapter(resourceClass);
			}
			Object resourceAdapter = adaptable.getAdapter(contributorResourceAdapterClass);
			if (resourceAdapter == null) {
			    resourceAdapter = LegacyResourceSupport.getDefaultContributorResourceAdapter();
			    if (resourceAdapter == null) {
					return null;
				}
			}
			// reflective equivalent of
			//    result = ((IContributorResourceAdapter) resourceAdapter).getAdaptedResource(adaptable);

				Method m = getContributorResourceAdapterGetAdaptedResourceMethod();
				if (m != null) {
					try {
						return m.invoke(resourceAdapter, new Object[]{adaptable});
					} catch (IllegalArgumentException e) {
						// shouldn't happen - but play it safe
					} catch (IllegalAccessException e) {
						// shouldn't happen - but play it safe
					} catch (InvocationTargetException e) {
						// shouldn't happen - but play it safe
					}
				}

		}
		return null;
	}

    private static Method getContributorResourceAdapterGetAdaptedResourceMethod() {
        if (getAdaptedResourceMethod != null) {
            return getAdaptedResourceMethod;
        }

        Class c = getIContributorResourceAdapterClass();
        if (c != null) {
            try {
				getAdaptedResourceMethod = c.getDeclaredMethod("getAdaptedResource", new Class[]{IAdaptable.class}); //$NON-NLS-1$
				return getAdaptedResourceMethod;
			} catch (SecurityException e) {
				// shouldn't happen - but play it safe
			} catch (NoSuchMethodException e) {
				// shouldn't happen - but play it safe
			}

        }

    	return null;
	}


    private static Method getContributorResourceAdapter2GetAdaptedResourceMappingMethod() {
        if (getAdaptedResourceMappingMethod != null) {
            return getAdaptedResourceMappingMethod;
        }

        Class c = getIContributorResourceAdapter2Class();
        if (c != null) {
            try {
				getAdaptedResourceMappingMethod = c.getDeclaredMethod("getAdaptedResourceMapping", new Class[]{IAdaptable.class}); //$NON-NLS-1$
				return getAdaptedResourceMappingMethod;
			} catch (SecurityException e) {
				// do nothing - play it safe
			} catch (NoSuchMethodException e) {
				// do nothing - play it safe
			}

        }

    	return null;
	}

	/**
     * Returns the adapted resource mapping using the <code>IContributorResourceAdapter2</code>
     * registered for the given object. If the Resources plug-in is not loaded
     * the object can not be adapted.
     *
     * @param object the object to adapt to <code>ResourceMapping</code>.
     * @return returns the adapted resource using the <code>IContributorResourceAdapter2</code>
     * or <code>null</code> if the Resources plug-in is not loaded.
     *
     * @since 3.1
     */
    public static Object getAdaptedContributorResourceMapping(Object object) {
        Class resourceMappingClass = LegacyResourceSupport.getResourceMappingClass();
        if (resourceMappingClass == null) {
            return null;
        }
        if (resourceMappingClass.isInstance(object)) {
            return null;
        }
        if (object instanceof IAdaptable) {
            IAdaptable adaptable = (IAdaptable) object;
            Class contributorResourceAdapterClass = LegacyResourceSupport.getIContributorResourceAdapterClass();
            if (contributorResourceAdapterClass == null) {
                return adaptable.getAdapter(resourceMappingClass);
            }
            Class contributorResourceAdapter2Class = LegacyResourceSupport.getIContributorResourceAdapter2Class();
            if (contributorResourceAdapter2Class == null) {
                return adaptable.getAdapter(resourceMappingClass);
            }
            Object resourceAdapter = adaptable.getAdapter(contributorResourceAdapterClass);
            Object resourceMappingAdapter;
			if (resourceAdapter != null && contributorResourceAdapter2Class.isInstance(resourceAdapter)) {
            	// The registered adapter also handles resource mappings
            	resourceMappingAdapter = resourceAdapter;
            } else {
            	// Either there is no registered adapter or it doesn't handle resource mappings.
            	// In this case, we will use the default contribution adapter
                resourceMappingAdapter = getDefaultContributorResourceAdapter();
                if (resourceMappingAdapter == null) {
                    return null;
                }
            }


	            // reflective equivalent of
	            //    result = ((IContributorResourceAdapter2) resourceAdapter).getAdaptedResource(adaptable);

	         Method m = getContributorResourceAdapter2GetAdaptedResourceMappingMethod();
	         if (m != null) {

				try {
					Object result  = m.invoke(resourceMappingAdapter, new Object[]{adaptable});
					if (result != null) {
		           		return result;
		           	}
				} catch (IllegalArgumentException e) {
					 // shouldn't happen - but play it safe
				} catch (IllegalAccessException e) {
					 // shouldn't happen - but play it safe
				} catch (InvocationTargetException e) {
					 // shouldn't happen - but play it safe
				}

	         }


            // If we get here, that means that the object in question doesn't adapt to resource mapping
            // and it's contributed adapter doesn't do the adaptation either.
            // Before we fail, we will attempt to adapt the object to IResource and then to ResourceMapping
            Object r = getAdaptedContributorResource(object);
            if (r != null) {
            	return Platform.getAdapterManager().getAdapter(r, resourceMappingClass);
            }

            // we've exhausted every avenue so just return null
            return null;
        }
        // Fallback to querying the adapter manager directly when the object isn't an IAdaptable
        return Platform.getAdapterManager().getAdapter(object, resourceMappingClass);
    }

    /**
     * Adapts a selection to the given objectClass considering the Legacy resource
     * support. Non resource objectClasses are adapted using the <code>IAdapterManager</code>
     * and this may load the plug-in that contributes the adapter factory.
     * <p>
     * The returned selection will only contain elements successfully adapted.
     * </p>
     * @param selection the selection to adapt
     * @param objectClass the class name to adapt the selection to
     * @return an adapted selection
     *
     * @since 3.1
     */
    public static IStructuredSelection adaptSelection(IStructuredSelection selection, String objectClass) {
		List newSelection = new ArrayList(10);
		for (Iterator it = selection.iterator(); it.hasNext();) {
			Object element = it.next();
			Object adaptedElement = getAdapter(element, objectClass);
			if (adaptedElement != null) {
				newSelection.add(adaptedElement);
			}
		}
		return new StructuredSelection(newSelection);
	}

    /**
     * Adapts an object to a specified objectClass considering the Legacy resource
     * support. Non resource objectClasses are adapted using the <code>IAdapterManager</code>
     * and this may load the plug-in that contributes the adapter factory.
     * <p>
     * The returned selection will be of the same size as the original, and elements that could
     * not be adapted are added to the returned selection as is.
     * </p>
     * @param element the element to adapt
     * @param objectClass the class name to adapt the selection to
     * @return an adapted element or <code>null</code> if the
     * element could not be adapted.
     *
     * @since 3.1
     */
    public static Object getAdapter(Object element, String objectClass) {
		Object adaptedElement = null;
		if (isInstanceOf(element.getClass(), objectClass)) {
			adaptedElement = element;
		} else {
			// Handle IResource
			if (LegacyResourceSupport.isResourceType(objectClass)) {
				adaptedElement = getAdaptedResource(element);
            } else if (LegacyResourceSupport.isResourceMappingType(objectClass)) {
                adaptedElement = getAdaptedResourceMapping(element);
                if (adaptedElement == null) {
                    // The object doesn't adapt directly so check if it adapts transitively
                    Object resource = getAdaptedResource(element);
                    if (resource != null) {
                        adaptedElement =( (IAdaptable)resource).getAdapter(LegacyResourceSupport.getResourceMappingClass());
                    }
                }
			} else {
				// Handle all other types by using the adapter factory.
				adaptedElement = Platform.getAdapterManager().loadAdapter(element, objectClass);
			}
		}
		return adaptedElement;
	}

	/**
     * Adapt the given element to an <code>IResource</code> using the following
     * search order:
     * <ol>
     * <li> using the IContributorResourceAdapter registered for the given element, or
     * <li> directly asking the element if it adapts.
     * </ol>
     *
     * @param element the element to adapt
     * @return an <code>IResource</code> instance if the element could be adapted or <code>null</code>
     * otherwise.
     * @since 3.1
     */
    public static Object getAdaptedResource(Object element) {
		Class resourceClass = LegacyResourceSupport.getResourceClass();
		Object adaptedValue = null;
		if (resourceClass != null) {
			if (resourceClass.isInstance(element)) {
				adaptedValue = element;
			} else {
				adaptedValue = LegacyResourceSupport.getAdaptedContributorResource(element);
			}
		}
		return adaptedValue;
	}

    /**
     * Adapt the given element to an <code>ResourceMapping</code> using the following
     * search order:
     * <ol>
     * <li> using the IContributorResourceAdapter2 registered for the given element, or
     * <li> directly asking the element if it adapts.
     * </ol>
     *
     * @param element the element to adapt
     * @return an <code>ResourceMapping</code> instance if the element could be adapted or <code>null</code>
     * otherwise.
     * @since 3.1
     */
    public static Object getAdaptedResourceMapping(Object element) {
        Class resourceMappingClass = LegacyResourceSupport.getResourceMappingClass();
        Object adaptedValue = null;
        if (resourceMappingClass != null) {
            if (resourceMappingClass.isInstance(element)) {
                adaptedValue = element;
            } else {
                adaptedValue = LegacyResourceSupport.getAdaptedContributorResourceMapping(element);
            }
        }
        return adaptedValue;
    }

    /**
     * Prevents construction
     */
    private LegacyResourceSupport() {
        // do nothing
    }

}
