/*******************************************************************************
 * 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;
import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.PlatformUI;

/**
 * This class is a default implementation of <code>IObjectContributorManager</code>.
 * It provides fast merging of contributions with the following semantics:
 * <ul>
 * <li> All of the matching contributors will be invoked per property lookup
 * <li> The search order from a class with the definition<br>
 *			<code>class X extends Y implements A, B</code><br>
 *		is as follows:
 * 		<il>
 *			<li>the target's class: X
 *			<li>X's superclasses in order to <code>Object</code>
 *			<li>a depth-first traversal of the target class's interaces in the order
 *				returned by <code>getInterfaces()</code> (in the example, A and
 *				its superinterfaces then B and its superinterfaces)
 *		</il>
 * </ul>
 *
 * @see IObjectContributor
 */
public abstract class ObjectContributorManager implements IExtensionChangeHandler {

	/**
	 * @since 3.1
	 */
	private static class ContributorRecord {
		/**
		 * @param contributor
		 * @param targetType
		 */
		public ContributorRecord(IObjectContributor contributor, String targetType) {
			this.contributor = contributor;
			this.objectClassName = targetType;
		}

		String objectClassName;
		IObjectContributor contributor;
	}

    /** Table of contributors. */
    protected Map contributors;

    /** Cache of object class contributor search paths; <code>null</code> if none. */
    protected Map objectLookup;

    /** Cache of resource adapter class contributor search paths; <code>null</code> if none. */
    protected Map resourceAdapterLookup;

    /** Cache of adaptable class contributor search paths; <code>null</code> if none. */
    protected Map adaptableLookup;

    protected Set contributorRecordSet;

    /**
     * Constructs a new contributor manager.
     */
    public ObjectContributorManager() {
    	contributors = new Hashtable(5);
        contributorRecordSet = new HashSet(5);
        objectLookup = null;
        resourceAdapterLookup = null;
        adaptableLookup = null;
        String extensionPointId = getExtensionPointFilter();
        if (extensionPointId != null) {
        	IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(
    				PlatformUI.PLUGIN_ID, extensionPointId);
			IExtensionTracker tracker = PlatformUI.getWorkbench()
					.getExtensionTracker();
			tracker.registerHandler(this, ExtensionTracker
					.createExtensionPointFilter(extensionPoint));
		}
    }

    /**
	 * Return the extension point id (local to org.eclipse.ui) that this manager
	 * is associated with. Default implementation returns null, which implies no
	 * relationship with a particular extension.
	 *
	 * @return the extension point id
	 * @since 3.4
	 */
	protected String getExtensionPointFilter() {
		return null;
	}

	/**
     * Adds contributors for the given types to the result list.
     */
    private void addContributorsFor(List types, List result) {
        for (Iterator classes = types.iterator(); classes.hasNext();) {
            Class clazz = (Class) classes.next();
            List contributorList = (List) contributors.get(clazz.getName());
            if (contributorList != null) {
				result.addAll(contributorList);
			}
        }
    }

    /**
     * Returns the class search order starting with <code>extensibleClass</code>.
     * The search order is defined in this class' comment.
     */
    protected final List computeClassOrder(Class extensibleClass) {
        ArrayList result = new ArrayList(4);
        Class clazz = extensibleClass;
        while (clazz != null) {
            result.add(clazz);
            clazz = clazz.getSuperclass();
        }
        return result;
    }

    /**
     * Returns the interface search order for the class hierarchy described
     * by <code>classList</code>.
     * The search order is defined in this class' comment.
     */
    protected final List computeInterfaceOrder(List classList) {
        ArrayList result = new ArrayList(4);
        Map seen = new HashMap(4);
        for (Iterator list = classList.iterator(); list.hasNext();) {
            Class[] interfaces = ((Class) list.next()).getInterfaces();
            internalComputeInterfaceOrder(interfaces, result, seen);
        }
        return result;
    }

    /**
     * Flushes the cache of contributor search paths.  This is generally required
     * whenever a contributor is added or removed.
     * <p>
     * It is likely easier to just toss the whole cache rather than trying to be
     * smart and remove only those entries affected.
     */
    public void flushLookup() {
        objectLookup = null;
        resourceAdapterLookup = null;
        adaptableLookup = null;
    }

    /**
     * Cache the real adapter class contributor search path.
     */
    private void cacheResourceAdapterLookup(Class adapterClass, List results) {
        if (resourceAdapterLookup == null) {
			resourceAdapterLookup = new HashMap();
		}
        resourceAdapterLookup.put(adapterClass, results);
    }

    /**
     * Cache the real adapter class contributor search path.
     */
    private void cacheAdaptableLookup(String adapterClass, List results) {
        if (adaptableLookup == null) {
			adaptableLookup = new HashMap();
		}
        adaptableLookup.put(adapterClass, results);
    }

    /**
     * Cache the object class contributor search path.
     */
    private void cacheObjectLookup(Class objectClass, List results) {
        if (objectLookup == null) {
			objectLookup = new HashMap();
		}
        objectLookup.put(objectClass, results);
    }

    /**
     * Get the contributions registered to this manager.
     *
     * @return an unmodifiable <code>Collection</code> containing all registered
     * contributions.  The objects in this <code>Collection</code> will be
     * <code>List</code>s containing the actual contributions.
     * @since 3.0
     */
    public Collection getContributors() {
        return Collections.unmodifiableCollection(contributors.values());
    }

    /**
     * Return the list of contributors for the supplied class.
     */
    protected List addContributorsFor(Class objectClass) {

        List classList = computeClassOrder(objectClass);
        List result = new ArrayList();
        addContributorsFor(classList, result);
        classList = computeInterfaceOrder(classList); // interfaces
        addContributorsFor(classList, result);
        return result;
    }

    /**
     * Returns true if contributors exist in the manager for
     * this object and any of it's super classes, interfaces, or
     * adapters.
     *
     * @param object the object to test
     * @return whether the object has contributors
     */
    public boolean hasContributorsFor(Object object) {

        List contributors = getContributors(object);
        return contributors.size() > 0;
    }

    /**
     * Add interface Class objects to the result list based
     * on the class hierarchy. Interfaces will be searched
     * based on their position in the result list.
     */
    private void internalComputeInterfaceOrder(Class[] interfaces, List result,
            Map seen) {
        List newInterfaces = new ArrayList(seen.size());
        for (Class currentInterface : interfaces) {
            if (seen.get(currentInterface) == null) {
                result.add(currentInterface);
                seen.put(currentInterface, currentInterface);
                newInterfaces.add(currentInterface);
            }
        }
        for (Iterator newList = newInterfaces.iterator(); newList.hasNext();) {
			internalComputeInterfaceOrder(((Class) newList.next())
                    .getInterfaces(), result, seen);
		}
    }

    /**
	 * Return whether the given contributor is applicable to all elements in the
	 * selection.
	 *
	 * @param selection
	 *            the selection
	 * @param contributor
	 *            the contributor
	 * @return whether it is applicable
	 */
    public boolean isApplicableTo(IStructuredSelection selection,
            IObjectContributor contributor) {
        Iterator elements = selection.iterator();
        while (elements.hasNext()) {
            if (contributor.isApplicableTo(elements.next()) == false) {
				return false;
			}
        }
        return true;
    }

    /**
	 * Return whether the given contributor is applicable to all elements in the
	 * list.
	 *
	 * @param list
	 *            the selection
	 * @param contributor
	 *            the contributor
	 * @return whether it is applicable
	 */

    public boolean isApplicableTo(List list, IObjectContributor contributor) {
        Iterator elements = list.iterator();
        while (elements.hasNext()) {
            if (contributor.isApplicableTo(elements.next()) == false) {
				return false;
			}
        }
        return true;
    }

    /**
     * Register a contributor.
     *
     * @param contributor the contributor
     * @param targetType the target type
     */
    public void registerContributor(IObjectContributor contributor,
            String targetType) {
        List contributorList = (List) contributors.get(targetType);
        if (contributorList == null) {
            contributorList = new ArrayList(5);
            contributors.put(targetType, contributorList);
        }
        contributorList.add(contributor);
        flushLookup();

        IConfigurationElement element = Adapters.adapt(contributor, IConfigurationElement.class);

        //hook the object listener
        if (element != null) {
			ContributorRecord contributorRecord = new ContributorRecord(
					contributor, targetType);
			contributorRecordSet.add(contributorRecord);
			PlatformUI.getWorkbench().getExtensionTracker().registerObject(
					element.getDeclaringExtension(), contributorRecord,
					IExtensionTracker.REF_WEAK);
        }
    }

    /**
     * Unregister all contributors.
     */
    public void unregisterAllContributors() {
        contributors = new Hashtable(5);
        flushLookup();
    }

    /**
     * Unregister a contributor from the target type.
     *
     * @param contributor the contributor
     * @param targetType the target type
     */
    public void unregisterContributor(IObjectContributor contributor,
            String targetType) {
        List contributorList = (List) contributors.get(targetType);
        if (contributorList == null) {
			return;
		}
        contributorList.remove(contributor);
        if (contributorList.isEmpty()) {
			contributors.remove(targetType);
		}
        flushLookup();
    }


    /**
     * Unregister all contributors for the target type.
     *
     * @param targetType the target type
     */
    public void unregisterContributors(String targetType) {
        contributors.remove(targetType);
        flushLookup();
    }

    protected List getContributors(Object object) {
    	// Determine is the object is a resource
    	Object resource  = LegacyResourceSupport.getAdaptedContributorResource(object);

    	// Fetch the unique adapters
    	List adapters = new ArrayList(Arrays.asList(Platform.getAdapterManager().computeAdapterTypes(object.getClass())));
    	removeCommonAdapters(adapters, Arrays.asList(new Class[] {object.getClass()}));

    	List contributors = new ArrayList();

        // Calculate the contributors for this object class
        addAll(contributors, getObjectContributors(object.getClass()));
        // Calculate the contributors for resource classes
        if(resource != null) {
			addAll(contributors, getResourceContributors(resource.getClass()));
		}
        // Calculate the contributors for each adapter type
    	if(adapters != null) {
    		for (Iterator it = adapters.iterator(); it.hasNext();) {
				String adapter = (String) it.next();
				addAll(contributors, getAdaptableContributors(adapter));
			}
    	}

        // Remove duplicates.  Note: this -must- maintain the element order to preserve menu order.
        contributors = removeDups(contributors);

    	return contributors.isEmpty() ? Collections.EMPTY_LIST : new ArrayList(contributors);
    }

    /**
     * Returns the contributions for the given class. This considers
     * contributors on any super classes and interfaces.
     *
     * @param objectClass the class to search for contributions.
     * @return the contributions for the given class. This considers
     * contributors on any super classes and interfaces.
     *
     * @since 3.1
     */
    protected List getObjectContributors(Class objectClass) {
		List objectList = null;
		// Lookup the results in the cache first.
		if (objectLookup != null) {
			objectList = (List) objectLookup.get(objectClass);
		}
		if (objectList == null) {
			objectList = addContributorsFor(objectClass);
			if (objectList.isEmpty()) {
				objectList = Collections.EMPTY_LIST;
			}
			else {
				objectList = Collections.unmodifiableList(objectList);
			}
			cacheObjectLookup(objectClass, objectList);
		}
		return objectList;
	}

    /**
     * Returns the contributions for the given <code>IResource</code>class.
     * This considers contributors on any super classes and interfaces. This
     * will only return contributions that are adaptable.
     *
     * @param resourceClass the class to search for contributions.
     * @return the contributions for the given class. This considers
     * adaptable contributors on any super classes and interfaces.
     *
     * @since 3.1
     */
	protected List getResourceContributors(Class resourceClass) {
		List resourceList = null;
		if (resourceAdapterLookup != null) {
			resourceList = (List) resourceAdapterLookup.get(resourceClass);
		}
		if (resourceList == null) {
			resourceList = addContributorsFor(resourceClass);
			if (resourceList.isEmpty()) {
				resourceList = Collections.EMPTY_LIST;
			} else {
				resourceList = Collections.unmodifiableList(filterOnlyAdaptableContributors(resourceList));
			}
			cacheResourceAdapterLookup(resourceClass, resourceList);
		}
		return resourceList;
	}

    /**
     * Returns the contributions for the given type name.
     *
     * @param adapterType the class to search for contributions.
     * @return the contributions for the given class. This considers
     * contributors to this specific type.
     *
     * @since 3.1
     */
	protected List getAdaptableContributors(String adapterType) {
		List adaptableList = null;
		// Lookup the results in the cache first, there are two caches
		// one that stores non-adapter contributions and the other
		// contains adapter contributions.
		if (adaptableLookup != null) {
			adaptableList = (List) adaptableLookup.get(adapterType);
		}
		if (adaptableList == null) {
			// ignore resource adapters because these must be adapted via the
			// IContributorResourceAdapter.
			if (LegacyResourceSupport.isResourceType(adapterType) || LegacyResourceSupport.isResourceMappingType(adapterType)) {
				adaptableList = Collections.EMPTY_LIST;
			}
			else {
				adaptableList = (List) contributors.get(adapterType);
				if (adaptableList == null || adaptableList.isEmpty()) {
					adaptableList = Collections.EMPTY_LIST;
				} else {
					adaptableList = Collections.unmodifiableList(filterOnlyAdaptableContributors(adaptableList));
				}
			}
			cacheAdaptableLookup(adapterType, adaptableList);
		}
		return adaptableList;
	}

	/**
	 * Prunes from the list of adapters type names that are in the class
	 * search order of every class in <code>results</code>.
	 * @param adapters
	 * @param results
	 * @since 3.1
	 */
	protected void removeCommonAdapters(List adapters, List results) {
    	for (Iterator it = results.iterator(); it.hasNext();) {
			Class clazz = ((Class) it.next());
			List commonTypes = computeCombinedOrder(clazz);
			for (Iterator it2 = commonTypes.iterator(); it2.hasNext();) {
				Class type = (Class) it2.next();
				adapters.remove(type.getName());
			}
		}
    }

	/**
     * Returns the class search order starting with <code>extensibleClass</code>.
     * The search order is defined in this class' comment.
     */
    protected List computeCombinedOrder(Class inputClass) {
        List result = new ArrayList(4);
        Class clazz = inputClass;
        while (clazz != null) {
            // add the class
            result.add(clazz);
            // add all the interfaces it implements
            Class[] interfaces = clazz.getInterfaces();
            for (Class currentInterface : interfaces) {
                result.add(currentInterface);
            }
            // get the superclass
            clazz = clazz.getSuperclass();
        }
        return result;
    }

	private List filterOnlyAdaptableContributors(List contributors) {
		List adaptableContributors = null;
		for (Iterator it = contributors.iterator(); it.hasNext();) {
			IObjectContributor c = (IObjectContributor) it.next();
			if(c.canAdapt()) {
				if(adaptableContributors == null) {
					adaptableContributors = new ArrayList();
				}
				adaptableContributors.add(c);
			}
		}
		return adaptableContributors == null ? Collections.EMPTY_LIST : adaptableContributors;
	}

    @Override
	public void removeExtension(IExtension source, Object[] objects) {
        for (Object object : objects) {
            if (object instanceof ContributorRecord) {
                ContributorRecord contributorRecord = (ContributorRecord) object;
                unregisterContributor((contributorRecord).contributor, (contributorRecord).objectClassName);
                contributorRecordSet.remove(contributorRecord);
            }
        }
    }

    /**
     * Remove listeners and dispose of this manager.
     *
     * @since 3.1
     */
    public void dispose() {
    	if(getExtensionPointFilter() != null) {
			PlatformUI.getWorkbench().getExtensionTracker().unregisterHandler(this);
		}
    }

    /**
     * Returns the list of contributors that are interested in the
     * given list of model elements.
     * @param elements a list of model elements (<code>Object</code>)
     * @return the list of interested contributors (<code>IObjectContributor</code>)
     */
    protected List getContributors(List elements) {
        // Calculate the common class, interfaces, and adapters registered
        // via the IAdapterManager.
        List commonAdapters = new ArrayList();
        List commonClasses = getCommonClasses(elements, commonAdapters);

        // Get the resource class. It will be null if any of the
        // elements are resources themselves or do not adapt to
        // IResource.
        Class resourceClass = getCommonResourceClass(elements);
        Class resourceMappingClass = getResourceMappingClass(elements);

        // Get the contributors.

        List contributors = new ArrayList();

        // Add the resource contributions to avoid duplication
        if (resourceClass != null) {
            addAll(contributors, getResourceContributors(resourceClass));
        }
        if (commonClasses != null && !commonClasses.isEmpty()) {
            for (int i = 0; i < commonClasses.size(); i++) {
                List results = getObjectContributors((Class) commonClasses
                        .get(i));
                addAll(contributors, results);
            }
        }
        // Add the resource mappings explicitly to avoid possible duplication
        if (resourceMappingClass == null) {
            // Still show the menus if the object is not adaptable but the adapter manager
            // has an entry for it
            resourceMappingClass = LegacyResourceSupport
                    .getResourceMappingClass();
            if (resourceMappingClass != null
                    && commonAdapters.contains(resourceMappingClass.getName())) {
            	addAll(contributors, getResourceContributors(resourceMappingClass));
            }
        } else {
            contributors.addAll(getResourceContributors(resourceMappingClass));
        }
        if (!commonAdapters.isEmpty()) {
            for (Iterator it = commonAdapters.iterator(); it.hasNext();) {
                String adapter = (String) it.next();
                addAll(contributors, getAdaptableContributors(adapter));
            }
        }

        // Remove duplicates.  Note: this -must- maintain the element order to preserve menu order.
        contributors = removeDups(contributors);

        return contributors.isEmpty() ? Collections.EMPTY_LIST : new ArrayList(contributors);
    }

    /**
	 * Adds all items in toAdd to the given collection.  Optimized to avoid creating an iterator.
	 * This assumes that toAdd is efficient to index (i.e. it's an ArrayList or some other RandomAccessList),
	 * which is the case for all uses in this class.
	 */
	private static void addAll(Collection collection, List toAdd) {
		for (int i = 0, size = toAdd.size(); i < size; ++i) {
			collection.add(toAdd.get(i));
		}
	}

    /**
     * Removes duplicates from the given list, preserving order.
     */
    private static List removeDups(List list) {
    	if (list.size() <= 1) {
    		return list;
    	}
    	HashSet set = new HashSet(list);
    	if (set.size() == list.size()) {
    		return list;
    	}
    	ArrayList result = new ArrayList(set.size());
    	for (Iterator i = list.iterator(); i.hasNext();) {
    		Object o = i.next();
    		if (set.remove(o)) {
    			result.add(o);
    		}
		}
    	return result;
    }

	/**
     * Returns the common denominator class, interfaces, and adapters
     * for the given collection of objects.
     */
    private List getCommonClasses(List objects, List commonAdapters) {
        if (objects == null || objects.isEmpty()) {
			return null;
		}

        // Optimization: if n==1 (or if all objects are of the same class), then the common class is the object's class,
        // and the common adapters are the adapters cached for that class in the adapter manager
        // See bug 177592 for more details.
        if (allSameClass(objects)) {

        	Class clazz = objects.get(0).getClass();
        	commonAdapters.addAll(Arrays.asList(Platform.getAdapterManager().computeAdapterTypes(clazz)));
        	List result = new ArrayList(1);
        	result.add(clazz);
        	return result;
        }

        // Compute all the super classes, interfaces, and adapters
        // for the first element.
        List classes = computeClassOrder(objects.get(0).getClass());
        List adapters = computeAdapterOrder(classes);
        List interfaces = computeInterfaceOrder(classes);

        // Cache of all types found in the selection - this is needed
        // to compute common adapters.
        List lastCommonTypes = new ArrayList();

        boolean classesEmpty = classes.isEmpty();
        boolean interfacesEmpty = interfaces.isEmpty();

        // Traverse the selection if there is more than one element selected.
        for (int i = 1; i < objects.size(); i++) {
            // Compute all the super classes for the current element
            List otherClasses = computeClassOrder(objects.get(i).getClass());
            if (!classesEmpty) {
                classesEmpty = extractCommonClasses(classes, otherClasses);
            }

            // Compute all the interfaces for the current element
            // and all of its super classes.
            List otherInterfaces = computeInterfaceOrder(otherClasses);
            if (!interfacesEmpty) {
                interfacesEmpty = extractCommonClasses(interfaces,
                        otherInterfaces);
            }

            // Compute all the adapters provided for the calculated
            // classes and interfaces for this element.
            List classesAndInterfaces = new ArrayList(otherClasses);
            if (otherInterfaces != null) {
				classesAndInterfaces.addAll(otherInterfaces);
			}
            List otherAdapters = computeAdapterOrder(classesAndInterfaces);

            // Compute common adapters
            // Note here that an adapter can match a class or interface, that is
            // that an element in the selection may not adapt to a type but instead
            // be of that type.
            // If the selected classes doesn't have adapters, keep
            // adapters that match the given classes types (classes and interfaces).
            if (otherAdapters.isEmpty() && !adapters.isEmpty()) {
                removeNonCommonAdapters(adapters, classesAndInterfaces);
            } else {
                if (adapters.isEmpty()) {
                    removeNonCommonAdapters(otherAdapters, lastCommonTypes);
                    if (!otherAdapters.isEmpty()) {
						adapters.addAll(otherAdapters);
					}
                } else {
                    // Remove any adapters of the first element that
                    // are not in the current element's adapter list.
                    for (Iterator it = adapters.iterator(); it.hasNext();) {
                        String adapter = (String) it.next();
                        if (!otherAdapters.contains(adapter)) {
                            it.remove();
                        }
                    }
                }
            }

            // Remember the common search order up to now, this is
            // used to match adapters against common classes or interfaces.
            lastCommonTypes.clear();
            lastCommonTypes.addAll(classes);
            lastCommonTypes.addAll(interfaces);

            if (interfacesEmpty && classesEmpty && adapters.isEmpty()) {
                // As soon as we detect nothing in common, just exit.
                return null;
            }
        }

        // Once the common classes, interfaces, and adapters are
        // calculated, let's prune the lists to remove duplicates.
        ArrayList results = new ArrayList(4);
        ArrayList superClasses = new ArrayList(4);
        if (!classesEmpty) {
            for (int j = 0; j < classes.size(); j++) {
                if (classes.get(j) != null) {
                    superClasses.add(classes.get(j));
                }
            }
            // Just keep the first super class
            if (!superClasses.isEmpty()) {
                results.add(superClasses.get(0));
            }
        }

        if (!interfacesEmpty) {
            removeCommonInterfaces(superClasses, interfaces, results);
        }

        // Remove adapters already included as common classes
        if (!adapters.isEmpty()) {
            removeCommonAdapters(adapters, results);
            commonAdapters.addAll(adapters);
        }
        return results;
    }

    /**
     * Returns <code>true</code> if all objects in the given list are of the same class,
     * <code>false</code> otherwise.
	 */
	private boolean allSameClass(List objects) {
		int size = objects.size();
		if (size <= 1) return true;
		Class clazz = objects.get(0).getClass();
		for (int i = 1; i < size; ++i) {
			if (!objects.get(i).getClass().equals(clazz)) {
				return false;
			}
		}
		return true;
	}

	private boolean extractCommonClasses(List classes, List otherClasses) {
        boolean classesEmpty = true;
        if (otherClasses.isEmpty()) {
            // When no super classes, then it is obvious there
            // are no common super classes with the first element
            // so clear its list.
            classes.clear();
        } else {
            // Remove any super classes of the first element that
            // are not in the current element's super classes list.
            for (int j = 0; j < classes.size(); j++) {
                if (classes.get(j) != null) {
                    classesEmpty = false; // TODO: should this only be set if item not nulled out?
                    if (!otherClasses.contains(classes.get(j))) {
                        classes.set(j, null);
                    }
                }
            }
        }
        return classesEmpty;
    }

    private void removeNonCommonAdapters(List adapters, List classes) {
        for (int i = 0; i < classes.size(); i++) {
            Object o = classes.get(i);
            if (o != null) {
                Class clazz = (Class) o;
                String name = clazz.getName();
                if (adapters.contains(name)) {
					return;
				}
            }
        }
        adapters.clear();
    }

    private void removeCommonInterfaces(List superClasses, List types,
            List results) {
        List dropInterfaces = null;
        if (!superClasses.isEmpty()) {
            dropInterfaces = computeInterfaceOrder(superClasses);
        }
        for (int j = 0; j < types.size(); j++) {
            if (types.get(j) != null) {
                if (dropInterfaces != null
                        && !dropInterfaces.contains(types.get(j))) {
                    results.add(types.get(j));
                }
            }
        }
    }

    private List computeAdapterOrder(List classList) {
        Set result = new HashSet(4);
        IAdapterManager adapterMgr = Platform.getAdapterManager();
        for (Iterator list = classList.iterator(); list.hasNext();) {
            Class clazz = ((Class) list.next());
			for (String adapter : adapterMgr.computeAdapterTypes(clazz)) {
                if (!result.contains(adapter)) {
                    result.add(adapter);
                }
            }
        }
        return new ArrayList(result);
    }

    /**
     * Returns the common denominator resource class for the given
     * collection of objects.
     * Do not return a resource class if the objects are resources
     * themselves so as to prevent double registration of actions.
     */
    private Class getCommonResourceClass(List objects) {
        if (objects == null || objects.isEmpty()) {
            return null;
        }
        Class resourceClass = LegacyResourceSupport.getResourceClass();
        if (resourceClass == null) {
            // resources plug-in not loaded - no resources. period.
            return null;
        }

        List testList = new ArrayList(objects.size());

        for (int i = 0; i < objects.size(); i++) {
            Object object = objects.get(i);

            if (object instanceof IAdaptable) {
                if (resourceClass.isInstance(object)) {
                    continue;
                }

                Object resource = LegacyResourceSupport
                        .getAdaptedContributorResource(object);

                if (resource == null) {
                    //Not a resource and does not adapt. No common resource class
                    return null;
                }
                testList.add(resource);
            } else {
                return null;
            }
        }

        return getCommonClass(testList);
    }

    /**
     * Return the ResourceMapping class if the elements all adapt to it.
     */
    private Class getResourceMappingClass(List objects) {
        if (objects == null || objects.isEmpty()) {
            return null;
        }
        Class resourceMappingClass = LegacyResourceSupport
                .getResourceMappingClass();
        if (resourceMappingClass == null) {
            // resources plug-in not loaded - no resources. period.
            return null;
        }

        for (int i = 0; i < objects.size(); i++) {
            Object object = objects.get(i);

            if (object instanceof IAdaptable) {
                if (resourceMappingClass.isInstance(object)) {
                    continue;
                }

                Object resourceMapping = LegacyResourceSupport
                        .getAdaptedContributorResourceMapping(object);

                if (resourceMapping == null) {
                    //Not a resource and does not adapt. No common resource class
                    return null;
                }
            } else {
                return null;
            }
        }
        // If we get here then all objects adapt to ResourceMapping
        return resourceMappingClass;
    }

    /**
     * Returns the common denominator class for the given
     * collection of objects.
     */
    private Class getCommonClass(List objects) {
        if (objects == null || objects.isEmpty()) {
			return null;
		}
        Class commonClass = objects.get(0).getClass();
        // try easy
        if (objects.size() == 1) {
			return commonClass;
        // try harder
		}

        for (int i = 1; i < objects.size(); i++) {
            Object object = objects.get(i);
            Class newClass = object.getClass();
            // try the short cut
            if (newClass.equals(commonClass)) {
				continue;
			}
            // compute common class
            commonClass = getCommonClass(commonClass, newClass);
            // give up
            if (commonClass == null) {
				return null;
			}
        }
        return commonClass;
    }

    /**
     * Returns the common denominator class for
     * two input classes.
     */
    private Class getCommonClass(Class class1, Class class2) {
        List list1 = computeCombinedOrder(class1);
        List list2 = computeCombinedOrder(class2);
        for (int i = 0; i < list1.size(); i++) {
            for (int j = 0; j < list2.size(); j++) {
                Class candidate1 = (Class) list1.get(i);
                Class candidate2 = (Class) list2.get(j);
                if (candidate1.equals(candidate2)) {
					return candidate1;
				}
            }
        }
        // no common class
        return null;
    }
}
