/*******************************************************************************
* 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.inject;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.sisu.Hidden;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
/**
* Publisher of {@link Binding}s from a single {@link Injector}; ranked according to a given {@link RankingFunction}.
*/
@Singleton
public final class InjectorBindings
implements BindingPublisher
{
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
private static final Key<BindingPublisher> BINDING_PUBLISHER_KEY = Key.get( BindingPublisher.class );
private static final Key<RankingFunction> RANKING_FUNCTION_KEY = Key.get( RankingFunction.class );
private static final RankingFunction DEFAULT_RANKING_FUNCTION = new DefaultRankingFunction();
private static final TypeLiteral<Object> OBJECT_TYPE_LITERAL = TypeLiteral.get( Object.class );
private static final Binding<?>[] NO_BINDINGS = {};
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private final Injector injector;
private final RankingFunction function;
private volatile Binding<?>[] wildcards;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
public InjectorBindings( final Injector injector, final RankingFunction function )
{
this.injector = injector;
this.function = function;
}
@Inject
public InjectorBindings( final Injector injector )
{
this( injector, findRankingFunction( injector ) );
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
public static BindingPublisher findBindingPublisher( final Injector injector )
{
// check the injector's local explicit bindings first for custom publisher
Binding<?> binding = injector.getBindings().get( BINDING_PUBLISHER_KEY );
if ( null != binding )
{
return (BindingPublisher) binding.getProvider().get();
}
// otherwise check any parents for an explicit binding to use as a template
binding = findExplicitBinding( injector.getParent(), BINDING_PUBLISHER_KEY );
if ( null != binding )
{
final Class<?> impl = Implementations.find( binding );
if ( null != impl )
{
try
{
// create a new instance from the parent template, this time using the current injector
return (BindingPublisher) impl.getConstructor( Injector.class ).newInstance( injector );
}
catch ( final Exception e )
{
final Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
Logs.trace( "Problem creating: {}", impl, cause );
}
catch ( final LinkageError e )
{
Logs.trace( "Problem creating: {}", impl, e );
}
}
}
// fall back to default implementation
return new InjectorBindings( injector );
}
public static RankingFunction findRankingFunction( final Injector injector )
{
final Binding<RankingFunction> binding = findExplicitBinding( injector, RANKING_FUNCTION_KEY );
return null != binding ? binding.getProvider().get() : DEFAULT_RANKING_FUNCTION;
}
public <T> void subscribe( final BindingSubscriber<T> subscriber )
{
final TypeLiteral<T> type = subscriber.type();
final Class<?> clazz = type.getRawType();
if ( clazz != Object.class )
{
publishExactMatches( type, subscriber );
if ( clazz != type.getType() )
{
publishGenericMatches( type, subscriber, clazz );
}
}
publishWildcardMatches( type, subscriber );
}
public <T> void unsubscribe( final BindingSubscriber<T> subscriber )
{
final Map<Key<?>, ?> ourBindings = injector.getBindings();
for ( final Binding<T> binding : subscriber.bindings() )
{
if ( binding == ourBindings.get( binding.getKey() ) )
{
subscriber.remove( binding );
}
}
}
public int maxBindingRank()
{
return function.maxRank();
}
@SuppressWarnings( "unchecked" )
public <T> T adapt( final Class<T> type )
{
return Injector.class == type ? (T) injector : null;
}
@Override
public int hashCode()
{
return injector.hashCode();
}
@Override
public boolean equals( final Object rhs )
{
if ( this == rhs )
{
return true;
}
if ( rhs instanceof InjectorBindings )
{
return injector.equals( ( (InjectorBindings) rhs ).injector );
}
return false;
}
@Override
public String toString()
{
return Logs.toString( injector );
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
/**
* Searches {@link Injector} and its parents for an explicit binding of the given {@link Key}.
*
* @param injector The injector
* @param key The binding key
* @return Explicit binding of the key; {@code null} if it doesn't exist
*/
@SuppressWarnings( { "rawtypes", "unchecked" } )
private static <T> Binding<T> findExplicitBinding( final Injector injector, final Key<T> key )
{
Binding binding = null;
for ( Injector i = injector; i != null; i = i.getParent() )
{
binding = i.getBindings().get( key );
if ( binding != null )
{
break;
}
}
return binding;
}
private static <T, S> boolean isAssignableFrom( final TypeLiteral<T> type, final Binding<S> binding )
{
final Class<?> implementation = Implementations.find( binding );
if ( null != implementation && type.getRawType() != implementation )
{
return TypeArguments.isAssignableFrom( type, TypeLiteral.get( implementation ) );
}
// either the implementation couldn't be deduced or we're looking up the exact implementation;
// Guice includes an untargeted binding for the implementation which will have been reported
// by publishExactMatches, so ignore generic/wildcard matches here to avoid duplicate results.
return false;
}
private <T> void publishExactMatches( final TypeLiteral<T> type, final BindingSubscriber<T> subscriber )
{
final List<Binding<T>> bindings = injector.findBindingsByType( type );
for ( int i = 0, size = bindings.size(); i < size; i++ )
{
final Binding<T> binding = bindings.get( i );
if ( null == Sources.getAnnotation( binding, Hidden.class ) )
{
subscriber.add( binding, function.rank( binding ) );
}
}
}
@SuppressWarnings( { "rawtypes", "unchecked" } )
private <T, S> void publishGenericMatches( final TypeLiteral<T> type, final BindingSubscriber<T> subscriber,
final Class<S> rawType )
{
final List<Binding<S>> bindings = injector.findBindingsByType( TypeLiteral.get( rawType ) );
for ( int i = 0, size = bindings.size(); i < size; i++ )
{
final Binding binding = bindings.get( i );
if ( null == Sources.getAnnotation( binding, Hidden.class ) && isAssignableFrom( type, binding ) )
{
subscriber.add( binding, function.rank( binding ) );
}
}
}
@SuppressWarnings( { "rawtypes", "unchecked" } )
private <T> void publishWildcardMatches( final TypeLiteral<T> type, final BindingSubscriber<T> subscriber )
{
final boolean untyped = type.getRawType() == Object.class;
for ( final Binding binding : getWildcardBindings() )
{
if ( untyped || isAssignableFrom( type, binding ) )
{
subscriber.add( binding, function.rank( binding ) );
}
}
}
private Binding<?>[] getWildcardBindings()
{
if ( null == wildcards )
{
synchronized ( this )
{
if ( null == wildcards )
{
final List<Binding<?>> visible = new ArrayList<Binding<?>>();
final List<Binding<Object>> candidates = injector.findBindingsByType( OBJECT_TYPE_LITERAL );
for ( int i = 0, size = candidates.size(); i < size; i++ )
{
final Binding<?> binding = candidates.get( i );
if ( null == Sources.getAnnotation( binding, Hidden.class ) )
{
visible.add( binding );
}
}
wildcards = visible.isEmpty() ? NO_BINDINGS : visible.toArray( new Binding[visible.size()] );
}
}
}
return wildcards;
}
}