package org.codehaus.plexus.component;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import static org.codehaus.plexus.PlexusConstants.PLEXUS_DEFAULT_HINT;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.Collections;
public class ComponentIndex<V>
{
/**
* Should values be indexed by all types or simply the supplied type?
*/
private final boolean indexByAllTypes;
/**
* This is the actual index.
* ClassLoader -> Class -> RoleHint -> Values
*/
private final Map<ClassLoader, SortedMap<Class<?>, Multimap<String, V>>> index =
new LinkedHashMap<ClassLoader, SortedMap<Class<?>, Multimap<String, V>>>();
/**
* Creates a component index that indexes by all super types and interfaces of supplied type.
*/
public ComponentIndex()
{
this( false );
}
/**
* Creates a component index.
* @param indexByAllTypes if true, values are indexed by all super types and interfaces of supplied type; otherwise
* values are only indexed by supplied type
*/
public ComponentIndex( boolean indexByAllTypes )
{
this.indexByAllTypes = indexByAllTypes;
}
/**
* Are values are indexed by all super types and interfaces of supplied type?
* @return true, values are indexed by all super types and interfaces of supplied type; otherwise
* false and values are only indexed by supplied type
*/
public boolean isIndexByAllTypes()
{
return indexByAllTypes;
}
/**
* Gets the value associated with the specified type and roleHint.
*
* Values are searched for in classloader order starting from the thread context class loader or type class loader
* if thread context class loader is not set.
*
* @param type the type (or super type if enabled) associated with the value; not null
* @param roleHint the roleHint associated with the value, or null for the default roleHint
* @return the value associated with the type and roleHint, or null
*/
public synchronized V get( Class<?> type, String roleHint )
{
return get( type, roleHint, Thread.currentThread().getContextClassLoader() );
}
/**
* Gets the value associated with the specified type and roleHint.
*
* Values are searched for in classloader order starting from the specified class loader, or thread context class
* loader or type class loader if specified class loader is null.
*
* @param type the type (or super type if enabled) associated with the value; not null
* @param roleHint the roleHint associated with the value, or null for the default roleHint
* @param classLoader the class loader to search from
* @return the value associated with the type and roleHint, or null
*/
public synchronized V get( Class<?> type, String roleHint, ClassLoader classLoader )
{
if ( type == null )
{
throw new NullPointerException( "type is null" );
}
if ( roleHint == null )
{
roleHint = PLEXUS_DEFAULT_HINT;
}
Collection<V> values = findAll( type, classLoader ).get( roleHint );
if ( values.isEmpty() )
{
return null;
}
return values.iterator().next();
}
public synchronized Collection<V> getAll( )
{
ArrayList<V> values = new ArrayList<V>();
for ( SortedMap<Class<?>, Multimap<String, V>> roleIndex : index.values() )
{
for ( Multimap<String, V> roleHintIndex : roleIndex.values() )
{
values.addAll(roleHintIndex.values());
}
}
return values;
}
/**
* Gets all values associated with the specified type.
*
* Values are searched for in classloader order starting from the thread context class loader or type class loader
* if thread context class loader is not set.
*
* The values are sorted in class loader search order then by registration order.
*
* @param type the type (or super type if enabled) associated with the value; not null
* @return all values associated with the type; never null
*/
public synchronized List<V> getAll( Class<?> type )
{
return getAll( type, Thread.currentThread().getContextClassLoader() );
}
/**
* Gets all values associated with the specified type.
*
* Values are searched for in classloader order starting from the specified class loader, or thread context class
* loader or type class loader if specified class loader is null.
*
* The values are sorted in class loader search order then by registration order.
*
* @param type the type (or super type if enabled) associated with the value; not null
* @param classLoader the class loader to search from
* @return all values associated with the type; never null
*/
public synchronized List<V> getAll( Class<?> type, ClassLoader classLoader )
{
if ( type == null )
{
throw new NullPointerException( "type is null" );
}
return new ArrayList<V>( findAll( type, classLoader ).values() );
}
/**
* Gets a map of all values associated with the specified type indexed by roleHint.
*
* Values are searched for in classloader order starting from the thread context class loader or type class loader
* if thread context class loader is not set.
*
* @param type the type (or super type if enabled) associated with the value; not null
* @return all of the value associated with the type; never null
*/
public synchronized Map<String, V> getAllAsMap( Class<?> type )
{
return getAllAsMap( type, Thread.currentThread().getContextClassLoader() );
}
/**
* Gets a map of all values associated with the specified type indexed by roleHint.
*
* Values are searched for in classloader order starting from the specified class loader, or thread context class
* loader or type class loader if specified class loader is null.
*
* @param type the type (or super type if enabled) associated with the value; not null
* @param classLoader the class loader to search from
* @return all of the value associated with the type; never null
*/
public synchronized Map<String, V> getAllAsMap( Class<?> type, ClassLoader classLoader )
{
if ( type == null )
{
throw new NullPointerException( "type is null" );
}
Map<String, V> descriptors = new TreeMap<String, V>();
for ( Entry<String, V> entry : findAll( type, classLoader ).entries() )
{
if ( !descriptors.containsKey( entry.getKey() ) )
{
descriptors.put( entry.getKey(), entry.getValue() );
}
}
return descriptors;
}
private synchronized Multimap<String, V> findAll( Class<?> type, ClassLoader classLoader )
{
if ( classLoader == null )
{
classLoader = type.getClassLoader();
}
// Determine class loaders to search
LinkedHashSet<ClassLoader> classLoaders = new LinkedHashSet<ClassLoader>();
for ( ClassLoader cl = classLoader; cl != null; cl = cl.getParent() )
{
if ( cl instanceof ClassRealm )
{
ClassRealm realm = (ClassRealm) cl;
while ( realm != null )
{
classLoaders.add( realm );
realm = realm.getParentRealm();
}
}
else
{
// todo lots of plexus code depends on a global search when there is a class loader associated with
// the thread but the cl is not a class realm
// classLoaders.add( cl );
}
}
// todo remove this when plexus code is updated to manage thread context class loader correctly
if ( classLoaders.isEmpty() )
{
classLoaders.addAll( index.keySet() );
}
// Get all valid component descriptors
Multimap<String, V> roleHintIndex = Multimaps.newHashMultimap();
for ( ClassLoader cl : classLoaders )
{
SortedMap<Class<?>, Multimap<String, V>> roleIndex = index.get( cl );
if ( roleIndex != null )
{
Multimap<String, V> values = roleIndex.get( type );
if ( values != null )
{
roleHintIndex.putAll( values );
}
}
}
return Multimaps.unmodifiableMultimap( roleHintIndex );
}
/**
* Associate a value with the specified class loader, type and roleHint. The value is also associated with all
* superclasses and interfaces of the specified type unless index by all types is disabled.
*/
public synchronized void add( ClassLoader classLoader, Class<?> type, String roleHint, V value )
{
if ( classLoader == null )
{
throw new NullPointerException( "classLoader is null" );
}
if ( type == null )
{
throw new NullPointerException( "type is null" );
}
if ( roleHint == null )
{
roleHint = PLEXUS_DEFAULT_HINT;
}
if ( value == null )
{
throw new NullPointerException( "value is null" );
}
SortedMap<Class<?>, Multimap<String, V>> roleIndex = index.get( classLoader );
if ( roleIndex == null )
{
roleIndex = new TreeMap<Class<?>, Multimap<String, V>>( ClassComparator.INSTANCE );
index.put( classLoader, roleIndex );
}
for ( Class<?> clazz : getAllTypes( type ) )
{
Multimap<String, V> roleHintIndex = roleIndex.get( clazz );
if ( roleHintIndex == null )
{
roleHintIndex = new ArrayListMultimap<String, V>();
roleIndex.put( clazz, roleHintIndex );
}
roleHintIndex.put( roleHint, value );
}
}
/**
* Removes the specified value from the index. This is operation requires a linear search of the whole index, and
* is therefor very expensive.
* @param value the value to remove
*/
public synchronized void remove( V value )
{
if ( value == null )
{
throw new NullPointerException( "value is null" );
}
for ( SortedMap<Class<?>, Multimap<String, V>> roleIndex : index.values() )
{
for ( Multimap<String, V> roleHintIndex : roleIndex.values() )
{
for ( Iterator<V> iterator = roleHintIndex.values().iterator(); iterator.hasNext(); )
{
V v = iterator.next();
if ( value.equals( v ) )
{
iterator.remove();
}
}
}
}
}
/**
* Removes all values associated with the specified class loader. This operation is very fast.
*/
public synchronized List<V> removeAll( ClassLoader classLoader )
{
if ( classLoader == null )
{
throw new NullPointerException( "classLoader is null" );
}
ArrayList<V> values = new ArrayList<V>();
SortedMap<Class<?>, Multimap<String, V>> roleIndex = index.remove( classLoader );
for ( Multimap<String, V> roleHintIndex : roleIndex.values() )
{
values.addAll(roleHintIndex.values());
}
return values;
}
/**
* Removes all values from this index.
*/
public synchronized Collection<V> clear()
{
Collection<V> all = getAll();
index.clear();
return all;
}
private Set<Class<?>> getAllTypes( Class<?> type )
{
if ( type.isArray() )
{
throw new IllegalArgumentException( "type is an array: type=" + type );
}
// if we are not indexing by all types, simply return a set containing the source type
if ( !indexByAllTypes )
{
return Collections.<Class<?>>singleton( type );
}
// All found types
Set<Class<?>> allTypes = new LinkedHashSet<Class<?>>();
// Types that must still be processed... may contain entries that
// have already been added to allTypes, so check all types before
// actuall processing to avoid infinite loops
LinkedList<Class<?>> typesToProcess = new LinkedList<Class<?>>();
typesToProcess.add( type );
while ( !typesToProcess.isEmpty() )
{
Class<?> clazz = typesToProcess.removeFirst();
// have we already processed this type
if ( !allTypes.contains( clazz ) )
{
allTypes.add( clazz );
// schedule superclass for processing
Class<?> superclass = clazz.getSuperclass();
if ( superclass != null )
{
typesToProcess.addFirst( superclass );
}
// schedule all interfaces for processing
typesToProcess.addAll( 0, Arrays.<Class<?>>asList( clazz.getInterfaces() ) );
}
}
return allTypes;
}
private static final class ClassComparator implements Comparator<Class<?>>, Serializable
{
private static final ClassComparator INSTANCE = new ClassComparator();
public int compare( Class<?> class1, Class<?> class2 )
{
return class1.getName().compareTo( class2.getName() );
}
}
}