blob: ce7063016f9c38be1bdddd4bddef90235437dcb2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010-present Sonatype, Inc.
* 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:
* Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
*******************************************************************************/
package org.eclipse.sisu.wire;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Qualifier;
import org.eclipse.sisu.BeanEntry;
import org.eclipse.sisu.Dynamic;
import org.eclipse.sisu.Hidden;
import org.eclipse.sisu.inject.BeanLocator;
import org.eclipse.sisu.inject.Sources;
import org.eclipse.sisu.inject.TypeArguments;
import com.google.inject.Binder;
import com.google.inject.ImplementedBy;
import com.google.inject.Key;
import com.google.inject.ProvidedBy;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import com.google.inject.spi.InjectionPoint;
/**
* Adds {@link BeanLocator}-backed bindings for unresolved bean dependencies.
*/
@SuppressWarnings( { "unchecked", "rawtypes" } )
public final class LocatorWiring
implements Wiring
{
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
private static final Hidden HIDDEN_WIRING = Sources.hide( LocatorWiring.class.getName() );
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private final BeanProviders beanProviders;
private final Binder binder;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
public LocatorWiring( final Binder binder )
{
beanProviders = new BeanProviders( binder );
this.binder = binder.withSource( HIDDEN_WIRING );
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
public boolean wire( final Key<?> key )
{
final Class<?> clazz = key.getTypeLiteral().getRawType();
if ( Map.class == clazz )
{
bindMapImport( key );
}
else if ( List.class == clazz || Collection.class == clazz || Iterable.class == clazz )
{
bindListImport( key );
}
else if ( Set.class == clazz )
{
bindSetImport( key );
}
else
{
bindBeanImport( key );
}
return true;
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
/**
* Adds an imported {@link Map} binding; uses the generic type arguments to determine the search details.
*
* @param key The dependency key
*/
private void bindMapImport( final Key key )
{
final TypeLiteral<?>[] args = TypeArguments.get( key.getTypeLiteral() );
if ( 2 == args.length && null == key.getAnnotation() )
{
final Class qualifierType = args[0].getRawType();
if ( String.class == qualifierType )
{
binder.bind( key ).toProvider( beanProviders.stringMapOf( args[1] ) );
}
else if ( qualifierType.isAnnotation() )
{
binder.bind( key ).toProvider( beanProviders.mapOf( Key.get( args[1], qualifierType ) ) );
}
else if ( Annotation.class == qualifierType )
{
binder.bind( key ).toProvider( beanProviders.mapOf( Key.get( args[1] ) ) );
}
}
}
/**
* Adds an imported {@link List} binding; uses the generic type arguments to determine the search details.
*
* @param key The dependency key
*/
@SuppressWarnings( "deprecation" )
private void bindListImport( final Key key )
{
final TypeLiteral<?>[] args = TypeArguments.get( key.getTypeLiteral() );
if ( 1 == args.length && null == key.getAnnotation() )
{
final TypeLiteral<?> elementType = args[0];
if ( BeanEntry.class == elementType.getRawType()
|| org.sonatype.inject.BeanEntry.class == elementType.getRawType() )
{
final Provider beanEntriesProvider = getBeanEntriesProvider( elementType );
if ( null != beanEntriesProvider )
{
binder.bind( key ).toProvider( beanEntriesProvider );
}
}
else
{
binder.bind( key ).toProvider( beanProviders.listOf( Key.get( elementType ) ) );
}
}
}
/**
* Returns the appropriate {@link BeanEntry} provider for the given entry type.
*
* @param entryType The entry type
* @return Provider of bean entries
*/
@SuppressWarnings( "deprecation" )
private Provider getBeanEntriesProvider( final TypeLiteral entryType )
{
final TypeLiteral<?>[] args = TypeArguments.get( entryType );
if ( 2 == args.length )
{
final Class qualifierType = args[0].getRawType();
final Key key = qualifierType.isAnnotation() ? Key.get( args[1], qualifierType ) : Key.get( args[1] );
final Provider beanEntries = beanProviders.beanEntriesOf( key );
return BeanEntry.class == entryType.getRawType() ? beanEntries
: org.eclipse.sisu.inject.Legacy.adapt( beanEntries );
}
return null;
}
/**
* Adds an imported {@link Set} binding; uses the generic type arguments to determine the search details.
*
* @param key The dependency key
*/
private void bindSetImport( final Key key )
{
final TypeLiteral<?>[] args = TypeArguments.get( key.getTypeLiteral() );
if ( 1 == args.length && null == key.getAnnotation() )
{
binder.bind( key ).toProvider( beanProviders.setOf( Key.get( args[0] ) ) );
}
}
/**
* Adds an imported bean binding; uses the type and {@link Qualifier} annotation to determine the search details.
*
* @param key The dependency key
*/
private <T> void bindBeanImport( final Key<T> key )
{
final Annotation qualifier = key.getAnnotation();
if ( qualifier instanceof Named )
{
if ( ( (Named) qualifier ).value().length() == 0 )
{
// special case for wildcard @Named dependencies: match any @Named bean regardless of actual name
binder.bind( key ).toProvider( beanProviders.firstOf( Key.get( key.getTypeLiteral(), Named.class ) ) );
}
else
{
binder.bind( key ).toProvider( beanProviders.placeholderOf( key ) );
}
}
else if ( qualifier instanceof Dynamic )
{
final Provider<T> delegate = beanProviders.firstOf( Key.get( key.getTypeLiteral() ) );
binder.bind( key ).toInstance( GlueLoader.dynamicGlue( key.getTypeLiteral(), delegate ) );
}
else
{
binder.bind( key ).toProvider( beanProviders.firstOf( key ) );
// capture original implicit binding?
if ( null == key.getAnnotationType() )
{
bindImplicitType( key.getTypeLiteral() );
}
}
}
/**
* Captures the original implicit binding that would have been used by Guice; see the {@link BeanLocator} code.
*
* @param type The implicit type
*/
private void bindImplicitType( final TypeLiteral type )
{
try
{
final Class<?> clazz = type.getRawType();
if ( TypeArguments.isConcrete( clazz ) )
{
final Member ctor = InjectionPoint.forConstructorOf( type ).getMember();
binder.bind( TypeArguments.implicitKey( clazz ) ).toConstructor( (Constructor) ctor );
}
else
{
final ImplementedBy implementedBy = clazz.getAnnotation( ImplementedBy.class );
if ( null != implementedBy )
{
binder.bind( TypeArguments.implicitKey( clazz ) ).to( (Class) implementedBy.value() );
}
else
{
final ProvidedBy providedBy = clazz.getAnnotation( ProvidedBy.class );
if ( null != providedBy )
{
binder.bind( TypeArguments.implicitKey( clazz ) ).toProvider( (Class) providedBy.value() );
}
}
}
}
catch ( final RuntimeException e ) // NOPMD
{
// can safely ignore
}
catch ( final LinkageError e ) // NOPMD
{
// can safely ignore
}
}
}