All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.plexus.component.ComponentIndex Maven / Gradle / Ivy

There is a newer version: 2.1.1
Show newest version
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
{
    /**
     * 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, Multimap>> index =
        new LinkedHashMap, Multimap>>();

    /**
     * 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 values = findAll( type, classLoader ).get( roleHint );
        if ( values.isEmpty() )
        {
            return null;
        }
        return values.iterator().next();
    }

    public synchronized Collection getAll( )
    {
        ArrayList values = new ArrayList();

        for ( SortedMap, Multimap> roleIndex : index.values() )
        {
            for ( Multimap 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 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 getAll( Class type, ClassLoader classLoader )
    {
        if ( type == null )
        {
            throw new NullPointerException( "type is null" );
        }

        return new ArrayList( 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 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 getAllAsMap( Class type, ClassLoader classLoader )
    {
        if ( type == null )
        {
            throw new NullPointerException( "type is null" );
        }

        Map descriptors = new TreeMap();
        for ( Entry entry : findAll( type, classLoader ).entries() )
        {
            if ( !descriptors.containsKey( entry.getKey() ) )
            {
                descriptors.put( entry.getKey(), entry.getValue() );
            }
        }
        return descriptors;
    }

    private synchronized Multimap findAll( Class type, ClassLoader classLoader )
    {
         if ( classLoader == null )
        {
            classLoader = type.getClassLoader();
        }

        // Determine class loaders to search
        LinkedHashSet classLoaders = new LinkedHashSet();
        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 roleHintIndex = Multimaps.newHashMultimap();
        for ( ClassLoader cl : classLoaders )
        {
            SortedMap, Multimap> roleIndex = index.get( cl );
            if ( roleIndex != null )
            {
                Multimap 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, Multimap> roleIndex = index.get( classLoader );
        if ( roleIndex == null )
        {
            roleIndex = new TreeMap, Multimap>( ClassComparator.INSTANCE );
            index.put( classLoader, roleIndex );
        }

        for ( Class clazz : getAllTypes( type ) )
        {
            Multimap roleHintIndex = roleIndex.get( clazz );
            if ( roleHintIndex == null )
            {
                roleHintIndex = new ArrayListMultimap();
                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, Multimap> roleIndex : index.values() )
        {
            for ( Multimap roleHintIndex : roleIndex.values() )
            {
                for ( Iterator 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 removeAll( ClassLoader classLoader )
    {
        if ( classLoader == null )
        {
            throw new NullPointerException( "classLoader is null" );
        }

        ArrayList values = new ArrayList();

        SortedMap, Multimap> roleIndex = index.remove( classLoader );
        for ( Multimap roleHintIndex : roleIndex.values() )
        {
            values.addAll(roleHintIndex.values());
        }
        return values;
    }

    /**
     * Removes all values from this index.
     */
    public synchronized Collection clear()
    {
        Collection all = getAll();
        index.clear();
        return all;
    }

    private Set> 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.>singleton( type );
        }

        // All found types
        Set> allTypes = new LinkedHashSet>();

        // 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> typesToProcess = new LinkedList>();
        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.>asList( clazz.getInterfaces() ) );
            }
        }
        return allTypes;
    }

    private static final class ClassComparator implements Comparator>, Serializable
    {
        private static final ClassComparator INSTANCE = new ClassComparator();

        public int compare( Class class1, Class class2 )
        {
            return class1.getName().compareTo( class2.getName() );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy