| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| * David Green - fix factories with non-standard class loading (bug 200068) |
| * Filip Hrbek - fix thread safety problem described in bug 305863 |
| * Sergey Prigogin (Google) - use parameterized types (bug 442021) |
| *******************************************************************************/ |
| package org.eclipse.core.internal.runtime; |
| |
| import java.util.*; |
| import org.eclipse.core.runtime.*; |
| |
| /** |
| * This class is the standard implementation of <code>IAdapterManager</code>. It provides |
| * fast lookup of property values with the following semantics: |
| * <ul> |
| * <li>At most one factory will be invoked per property lookup |
| * <li>If multiple installed factories provide the same adapter, only the first found in |
| * the search order will be invoked. |
| * <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 breadth-first traversal of each class's interfaces in the |
| * order returned by <code>getInterfaces</code> (in the example, X's |
| * superinterfaces then Y's superinterfaces) </li> |
| * </ul> |
| * |
| * @see IAdapterFactory |
| * @see IAdapterManager |
| */ |
| public final class AdapterManager implements IAdapterManager { |
| /** |
| * Cache of adapters for a given adaptable class. Maps String -> Map |
| * (adaptable class name -> (adapter class name -> factory instance)) |
| * Thread safety note: The outer map is synchronized using a synchronized |
| * map wrapper class. The inner map is not synchronized, but it is immutable |
| * so synchronization is not necessary. |
| */ |
| private Map<String, Map<String, IAdapterFactory>> adapterLookup; |
| |
| /** |
| * Cache of classes for a given type name. Avoids too many loadClass calls. |
| * (factory -> (type name -> Class)). |
| * Thread safety note: Since this structure is a nested hash map, and both |
| * the inner and outer maps are mutable, access to this entire structure is |
| * controlled by the classLookupLock field. Note the field can still be |
| * nulled concurrently without holding the lock. |
| */ |
| private Map<IAdapterFactory, Map<String, Class<?>>> classLookup; |
| |
| /** |
| * The lock object controlling access to the classLookup data structure. |
| */ |
| private final Object classLookupLock = new Object(); |
| |
| /** |
| * Cache of class lookup order (Class -> Class[]). This avoids having to compute often, and |
| * provides clients with quick lookup for instanceOf checks based on type name. |
| * Thread safety note: The map is synchronized using a synchronized |
| * map wrapper class. The arrays within the map are immutable. |
| */ |
| private Map<Class<?>, Class<?>[]> classSearchOrderLookup; |
| |
| /** |
| * Map of factories, keyed by <code>String</code>, fully qualified class name of |
| * the adaptable class that the factory provides adapters for. Value is a <code>List</code> |
| * of <code>IAdapterFactory</code>. |
| */ |
| private final HashMap<String, List<IAdapterFactory>> factories; |
| |
| private final ArrayList<IAdapterManagerProvider> lazyFactoryProviders; |
| |
| private static final AdapterManager singleton = new AdapterManager(); |
| |
| public static AdapterManager getDefault() { |
| return singleton; |
| } |
| |
| /** |
| * Private constructor to block instance creation. |
| */ |
| private AdapterManager() { |
| factories = new HashMap<>(5); |
| lazyFactoryProviders = new ArrayList<>(1); |
| } |
| |
| /** |
| * Given a type name, add all of the factories that respond to those types into |
| * the given table. Each entry will be keyed by the adapter class name (supplied in |
| * IAdapterFactory.getAdapterList). |
| */ |
| private void addFactoriesFor(String typeName, Map<String, IAdapterFactory> table) { |
| List<IAdapterFactory> factoryList = getFactories().get(typeName); |
| if (factoryList == null) |
| return; |
| for (int i = 0, imax = factoryList.size(); i < imax; i++) { |
| IAdapterFactory factory = factoryList.get(i); |
| if (factory instanceof IAdapterFactoryExt) { |
| String[] adapters = ((IAdapterFactoryExt) factory).getAdapterNames(); |
| for (int j = 0; j < adapters.length; j++) { |
| if (table.get(adapters[j]) == null) |
| table.put(adapters[j], factory); |
| } |
| } else { |
| Class<?>[] adapters = factory.getAdapterList(); |
| for (int j = 0; j < adapters.length; j++) { |
| String adapterName = adapters[j].getName(); |
| if (table.get(adapterName) == null) |
| table.put(adapterName, factory); |
| } |
| } |
| } |
| } |
| |
| private void cacheClassLookup(IAdapterFactory factory, Class<?> clazz) { |
| synchronized (classLookupLock) { |
| //cache reference to lookup to protect against concurrent flush |
| Map<IAdapterFactory, Map<String, Class<?>>> lookup = classLookup; |
| if (lookup == null) |
| classLookup = lookup = new HashMap<>(4); |
| Map<String, Class<?>> classes = lookup.get(factory); |
| if (classes == null) { |
| classes = new HashMap<>(4); |
| lookup.put(factory, classes); |
| } |
| classes.put(clazz.getName(), clazz); |
| } |
| } |
| |
| private Class<?> cachedClassForName(IAdapterFactory factory, String typeName) { |
| synchronized (classLookupLock) { |
| Class<?> clazz = null; |
| //cache reference to lookup to protect against concurrent flush |
| Map<IAdapterFactory, Map<String, Class<?>>> lookup = classLookup; |
| if (lookup != null) { |
| Map<String, Class<?>> classes = lookup.get(factory); |
| if (classes != null) { |
| clazz = classes.get(typeName); |
| } |
| } |
| return clazz; |
| } |
| } |
| |
| /** |
| * Returns the class with the given fully qualified name, or null |
| * if that class does not exist or belongs to a plug-in that has not |
| * yet been loaded. |
| */ |
| private Class<?> classForName(IAdapterFactory factory, String typeName) { |
| Class<?> clazz = cachedClassForName(factory, typeName); |
| if (clazz == null) { |
| if (factory instanceof IAdapterFactoryExt) |
| factory = ((IAdapterFactoryExt) factory).loadFactory(false); |
| if (factory != null) { |
| try { |
| clazz = factory.getClass().getClassLoader().loadClass(typeName); |
| } catch (ClassNotFoundException e) { |
| // it is possible that the default bundle classloader is unaware of this class |
| // but the adaptor factory can load it in some other way. See bug 200068. |
| if (typeName == null) |
| return null; |
| Class<?>[] adapterList = factory.getAdapterList(); |
| clazz = null; |
| for (int i = 0; i < adapterList.length; i++) { |
| if (typeName.equals(adapterList[i].getName())) { |
| clazz = adapterList[i]; |
| break; |
| } |
| } |
| if (clazz == null) |
| return null; // class not yet loaded |
| } |
| cacheClassLookup(factory, clazz); |
| } |
| } |
| return clazz; |
| } |
| |
| @Override |
| public String[] computeAdapterTypes(Class<? extends Object> adaptable) { |
| Set<String> types = getFactories(adaptable).keySet(); |
| return types.toArray(new String[types.size()]); |
| } |
| |
| /** |
| * Computes the adapters that the provided class can adapt to, along |
| * with the factory object that can perform that transformation. Returns |
| * a table of adapter class name to factory object. |
| */ |
| private Map<String, IAdapterFactory> getFactories(Class<? extends Object> adaptable) { |
| //cache reference to lookup to protect against concurrent flush |
| Map<String, Map<String, IAdapterFactory>> lookup = adapterLookup; |
| if (lookup == null) |
| adapterLookup = lookup = Collections.synchronizedMap(new HashMap<String, Map<String, IAdapterFactory>>(30)); |
| Map<String, IAdapterFactory> table = lookup.get(adaptable.getName()); |
| if (table == null) { |
| // calculate adapters for the class |
| table = new HashMap<>(4); |
| Class<?>[] classes = computeClassOrder(adaptable); |
| for (int i = 0; i < classes.length; i++) |
| addFactoriesFor(classes[i].getName(), table); |
| // cache the table |
| lookup.put(adaptable.getName(), table); |
| } |
| return table; |
| } |
| |
| /** |
| * Returns the super-type search order starting with <code>adaptable</code>. |
| * The search order is defined in this class' comment. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> Class<? super T>[] computeClassOrder(Class<T> adaptable) { |
| Class<?>[] classes = null; |
| //cache reference to lookup to protect against concurrent flush |
| Map<Class<?>, Class<?>[]> lookup = classSearchOrderLookup; |
| if (lookup == null) |
| classSearchOrderLookup = lookup = Collections.synchronizedMap(new HashMap<Class<?>, Class<?>[]>()); |
| else |
| classes = lookup.get(adaptable); |
| // compute class order only if it hasn't been cached before |
| if (classes == null) { |
| classes = doComputeClassOrder(adaptable); |
| lookup.put(adaptable, classes); |
| } |
| return (Class<? super T>[]) classes; |
| } |
| |
| /** |
| * Computes the super-type search order starting with <code>adaptable</code>. |
| * The search order is defined in this class' comment. |
| */ |
| private Class<?>[] doComputeClassOrder(Class<?> adaptable) { |
| List<Class<?>> classes = new ArrayList<>(); |
| Class<?> clazz = adaptable; |
| Set<Class<?>> seen = new HashSet<>(4); |
| //first traverse class hierarchy |
| while (clazz != null) { |
| classes.add(clazz); |
| clazz = clazz.getSuperclass(); |
| } |
| //now traverse interface hierarchy for each class |
| Class<?>[] classHierarchy = classes.toArray(new Class[classes.size()]); |
| for (int i = 0; i < classHierarchy.length; i++) |
| computeInterfaceOrder(classHierarchy[i].getInterfaces(), classes, seen); |
| return classes.toArray(new Class[classes.size()]); |
| } |
| |
| private void computeInterfaceOrder(Class<?>[] interfaces, Collection<Class<?>> classes, Set<Class<?>> seen) { |
| List<Class<?>> newInterfaces = new ArrayList<>(interfaces.length); |
| for (int i = 0; i < interfaces.length; i++) { |
| Class<?> interfac = interfaces[i]; |
| if (seen.add(interfac)) { |
| //note we cannot recurse here without changing the resulting interface order |
| classes.add(interfac); |
| newInterfaces.add(interfac); |
| } |
| } |
| for (Class<?> clazz : newInterfaces) |
| computeInterfaceOrder(clazz.getInterfaces(), classes, seen); |
| } |
| |
| /** |
| * Flushes the cache of adapter search paths. This is generally required whenever an |
| * adapter 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. |
| * </p> |
| */ |
| public synchronized void flushLookup() { |
| adapterLookup = null; |
| classLookup = null; |
| classSearchOrderLookup = null; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> T getAdapter(Object adaptable, Class<T> adapterType) { |
| Assert.isNotNull(adaptable); |
| Assert.isNotNull(adapterType); |
| IAdapterFactory factory = getFactories(adaptable.getClass()).get(adapterType.getName()); |
| T result = null; |
| if (factory != null) |
| result = factory.getAdapter(adaptable, adapterType); |
| if (result == null && adapterType.isInstance(adaptable)) |
| return (T) adaptable; |
| return result; |
| } |
| |
| @Override |
| public Object getAdapter(Object adaptable, String adapterType) { |
| Assert.isNotNull(adaptable); |
| Assert.isNotNull(adapterType); |
| return getAdapter(adaptable, adapterType, false); |
| } |
| |
| /** |
| * Returns an adapter of the given type for the provided adapter. |
| * @param adaptable the object to adapt |
| * @param adapterType the type to adapt the object to |
| * @param force <code>true</code> if the plug-in providing the |
| * factory should be activated if necessary. <code>false</code> |
| * if no plugin activations are desired. |
| */ |
| private Object getAdapter(Object adaptable, String adapterType, boolean force) { |
| IAdapterFactory factory = getFactories(adaptable.getClass()).get(adapterType); |
| if (force && factory instanceof IAdapterFactoryExt) |
| factory = ((IAdapterFactoryExt) factory).loadFactory(true); |
| Object result = null; |
| if (factory != null) { |
| Class<?> clazz = classForName(factory, adapterType); |
| if (clazz != null) |
| result = factory.getAdapter(adaptable, clazz); |
| } |
| if (result == null && adaptable.getClass().getName().equals(adapterType)) |
| return adaptable; |
| return result; |
| } |
| |
| @Override |
| public boolean hasAdapter(Object adaptable, String adapterTypeName) { |
| return getFactories(adaptable.getClass()).get(adapterTypeName) != null; |
| } |
| |
| @Override |
| public int queryAdapter(Object adaptable, String adapterTypeName) { |
| IAdapterFactory factory = getFactories(adaptable.getClass()).get(adapterTypeName); |
| if (factory == null) |
| return NONE; |
| if (factory instanceof IAdapterFactoryExt) { |
| factory = ((IAdapterFactoryExt) factory).loadFactory(false); // don't force loading |
| if (factory == null) |
| return NOT_LOADED; |
| } |
| return LOADED; |
| } |
| |
| @Override |
| public Object loadAdapter(Object adaptable, String adapterTypeName) { |
| return getAdapter(adaptable, adapterTypeName, true); |
| } |
| |
| /* |
| * @see IAdapterManager#registerAdapters |
| */ |
| @Override |
| public synchronized void registerAdapters(IAdapterFactory factory, Class<?> adaptable) { |
| registerFactory(factory, adaptable.getName()); |
| flushLookup(); |
| } |
| |
| /* |
| * @see IAdapterManager#registerAdapters |
| */ |
| public void registerFactory(IAdapterFactory factory, String adaptableType) { |
| List<IAdapterFactory> list = factories.get(adaptableType); |
| if (list == null) { |
| list = new ArrayList<>(5); |
| factories.put(adaptableType, list); |
| } |
| list.add(factory); |
| } |
| |
| /* |
| * @see IAdapterManager#unregisterAdapters |
| */ |
| @Override |
| public synchronized void unregisterAdapters(IAdapterFactory factory) { |
| for (List<IAdapterFactory> list : factories.values()) |
| list.remove(factory); |
| flushLookup(); |
| } |
| |
| /* |
| * @see IAdapterManager#unregisterAdapters |
| */ |
| @Override |
| public synchronized void unregisterAdapters(IAdapterFactory factory, Class<?> adaptable) { |
| List<IAdapterFactory> factoryList = factories.get(adaptable.getName()); |
| if (factoryList == null) |
| return; |
| factoryList.remove(factory); |
| flushLookup(); |
| } |
| |
| /* |
| * Shuts down the adapter manager by removing all factories |
| * and removing the registry change listener. Should only be |
| * invoked during platform shutdown. |
| */ |
| public synchronized void unregisterAllAdapters() { |
| factories.clear(); |
| flushLookup(); |
| } |
| |
| public void registerLazyFactoryProvider(IAdapterManagerProvider factoryProvider) { |
| synchronized (lazyFactoryProviders) { |
| lazyFactoryProviders.add(factoryProvider); |
| } |
| } |
| |
| public boolean unregisterLazyFactoryProvider(IAdapterManagerProvider factoryProvider) { |
| synchronized (lazyFactoryProviders) { |
| return lazyFactoryProviders.remove(factoryProvider); |
| } |
| } |
| |
| public HashMap<String, List<IAdapterFactory>> getFactories() { |
| synchronized (lazyFactoryProviders) { |
| while (lazyFactoryProviders.size() > 0) { |
| IAdapterManagerProvider provider = lazyFactoryProviders.remove(0); |
| if (provider.addFactories(this)) |
| flushLookup(); |
| } |
| } |
| return factories; |
| } |
| } |