blob: 13c6f72cd89756a649802e5db1bab678d5602522 [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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.sisu.Parameters;
import org.eclipse.sisu.inject.BeanLocator;
import org.eclipse.sisu.inject.DefaultBeanLocator;
import org.eclipse.sisu.inject.Guice4;
import org.eclipse.sisu.inject.Logs;
import org.eclipse.sisu.inject.MutableBeanLocator;
import org.eclipse.sisu.inject.RankingFunction;
import org.eclipse.sisu.inject.TypeArguments;
import org.eclipse.sisu.wire.WireModule.Strategy;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.PrivateBinder;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.DefaultElementVisitor;
import com.google.inject.spi.Element;
import com.google.inject.spi.ElementVisitor;
import com.google.inject.spi.Elements;
import com.google.inject.spi.InjectionRequest;
import com.google.inject.spi.InstanceBinding;
import com.google.inject.spi.PrivateElements;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderLookup;
import com.google.inject.spi.RequireExplicitBindingsOption;
import com.google.inject.spi.StaticInjectionRequest;
/**
* {@link ElementVisitor} that analyzes {@link Binding}s for unresolved injection dependencies.
*/
final class ElementAnalyzer
extends DefaultElementVisitor<Void>
{
// ----------------------------------------------------------------------
// Static initialization
// ----------------------------------------------------------------------
static
{
final Map<Key<?>, Key<?>> aliases = new HashMap<Key<?>, Key<?>>();
try
{
addLegacyKeyAlias( aliases, BeanLocator.class );
addLegacyKeyAlias( aliases, MutableBeanLocator.class );
addLegacyKeyAlias( aliases, RankingFunction.class );
}
catch ( final Exception e ) // NOPMD
{
// legacy wrappers are not available
}
catch ( final LinkageError e ) // NOPMD
{
// legacy wrappers are not available
}
LEGACY_KEY_ALIASES = aliases.isEmpty() ? null : aliases;
}
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
private static final Map<Key<?>, Key<?>> LEGACY_KEY_ALIASES;
private static final List<Element> JIT_BINDINGS = Elements.getElements( new Module()
{
public void configure( final Binder binder )
{
binder.bind( BeanLocator.class ).to( MutableBeanLocator.class );
binder.bind( MutableBeanLocator.class ).to( DefaultBeanLocator.class );
binder.bind( TypeConverterCache.class );
}
} );
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private final Set<Key<?>> localKeys = new HashSet<Key<?>>();
private final DependencyAnalyzer analyzer = new DependencyAnalyzer();
private final List<ElementAnalyzer> privateAnalyzers = new ArrayList<ElementAnalyzer>();
private final List<Map<?, ?>> properties = new ArrayList<Map<?, ?>>();
private final List<String> arguments = new ArrayList<String>();
private final Binder binder;
private boolean requireExplicitBindings;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
ElementAnalyzer( final Binder binder )
{
this.binder = binder;
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
public void ignoreKeys( final Set<Key<?>> keys )
{
localKeys.addAll( keys );
}
public void apply( final Strategy strategy )
{
if ( requireExplicitBindings )
{
makeJitBindingsExplicit();
}
// calculate which dependencies are missing from the module elements
final Set<Key<?>> missingKeys = analyzer.findMissingKeys( localKeys );
final Map<?, ?> mergedProperties = new MergedProperties( properties );
final Wiring wiring = strategy.wiring( binder );
for ( final Key<?> key : missingKeys )
{
if ( isParameters( key ) )
{
wireParameters( key, mergedProperties );
}
else if ( !isRestricted( key ) )
{
wiring.wire( key );
}
}
for ( final ElementAnalyzer privateAnalyzer : privateAnalyzers )
{
// ignore parent local/wired dependencies
privateAnalyzer.ignoreKeys( localKeys );
privateAnalyzer.ignoreKeys( missingKeys );
privateAnalyzer.apply( strategy );
}
}
@Override
public <T> Void visit( final Binding<T> binding )
{
final Key<T> key = binding.getKey();
if ( !localKeys.contains( key ) )
{
if ( isParameters( key ) )
{
mergeParameters( binding );
}
else if ( Boolean.TRUE.equals( binding.acceptTargetVisitor( analyzer ) ) )
{
localKeys.add( key );
binding.applyTo( binder );
if ( null != LEGACY_KEY_ALIASES )
{
@SuppressWarnings( "unchecked" )
final Key<T> alias = (Key<T>) LEGACY_KEY_ALIASES.get( key );
if ( null != alias && localKeys.add( alias ) )
{
binder.bind( alias ).to( key ); // chain to legacy binding
}
}
}
else
{
Logs.trace( "Discard binding: {}", binding, null );
}
}
return null;
}
@Override
public Void visit( final PrivateElements elements )
{
// Follows example set by Guice Modules when rewriting private elements:
//
// 1. create new private binder, using the elements source token
// 2. for all elements, apply each element to the private binder
// 3. re-expose any exposed keys using their exposed source token
final PrivateBinder privateBinder = binder.withSource( elements.getSource() ).newPrivateBinder();
final ElementAnalyzer privateAnalyzer = new ElementAnalyzer( privateBinder );
privateAnalyzers.add( privateAnalyzer );
// ignore bindings already in the parent
privateAnalyzer.ignoreKeys( localKeys );
for ( final Element e : elements.getElements() )
{
e.acceptVisitor( privateAnalyzer );
}
for ( final Key<?> k : elements.getExposedKeys() )
{
// only expose valid bindings that won't conflict with existing ones
if ( privateAnalyzer.localKeys.contains( k ) && localKeys.add( k ) )
{
privateBinder.withSource( elements.getExposedSource( k ) ).expose( k );
}
}
return null;
}
@Override
public <T> Void visit( final ProviderLookup<T> lookup )
{
analyzer.visit( lookup );
lookup.applyTo( binder );
return null;
}
@Override
public Void visit( final StaticInjectionRequest request )
{
analyzer.visit( request );
request.applyTo( binder );
return null;
}
@Override
public Void visit( final InjectionRequest<?> request )
{
analyzer.visit( request );
request.applyTo( binder );
return null;
}
@Override
public Void visit( final RequireExplicitBindingsOption option )
{
requireExplicitBindings = true;
option.applyTo( binder );
return null;
}
@Override
public Void visitOther( final Element element )
{
element.applyTo( binder );
return null;
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
private void makeJitBindingsExplicit()
{
for ( final Element element : JIT_BINDINGS )
{
if ( element instanceof Binding<?> && localKeys.add( ( (Binding<?>) element ).getKey() ) )
{
element.applyTo( binder );
}
}
}
private void mergeParameters( final Binding<?> binding )
{
Object parameters = null;
if ( binding instanceof InstanceBinding<?> )
{
parameters = ( (InstanceBinding<?>) binding ).getInstance();
}
else if ( binding instanceof ProviderInstanceBinding<?> )
{
parameters = Guice4.getProviderInstance( (ProviderInstanceBinding<?>) binding ).get();
}
if ( parameters instanceof Map )
{
properties.add( (Map<?, ?>) parameters );
}
else if ( parameters instanceof String[] )
{
Collections.addAll( arguments, (String[]) parameters );
}
else
{
Logs.warn( "Ignoring incompatible @Parameters binding: {}", binding, null );
}
}
@SuppressWarnings( { "rawtypes", "unchecked" } )
private void wireParameters( final Key key, final Map mergedProperties )
{
if ( ParameterKeys.PROPERTIES.equals( key ) )
{
binder.bind( key ).toInstance( mergedProperties );
}
else
{
final TypeLiteral<?> type = key.getTypeLiteral();
final Class<?> clazz = type.getRawType();
if ( Map.class == clazz )
{
final TypeLiteral<?>[] constraints = TypeArguments.get( type );
if ( constraints.length == 2 && String.class == constraints[1].getRawType() )
{
binder.bind( key ).to( StringProperties.class );
}
else
{
binder.bind( key ).to( ParameterKeys.PROPERTIES );
}
}
else if ( String[].class == clazz )
{
binder.bind( key ).toInstance( arguments.toArray( new String[arguments.size()] ) );
}
}
}
@SuppressWarnings( "deprecation" )
private static boolean isParameters( final Key<?> key )
{
final Class<? extends Annotation> qualifierType = key.getAnnotationType();
return Parameters.class == qualifierType || org.sonatype.inject.Parameters.class == qualifierType;
}
private static boolean isRestricted( final Key<?> key )
{
final String name = key.getTypeLiteral().getRawType().getName();
if ( name.startsWith( "org.eclipse.sisu.inject" ) || name.startsWith( "org.sonatype.guice.bean.locators" ) )
{
return name.endsWith( "BeanLocator" ) || name.endsWith( "BindingPublisher" )
|| name.endsWith( "RankingFunction" );
}
return "org.slf4j.Logger".equals( name );
}
private static void addLegacyKeyAlias( final Map<Key<?>, Key<?>> aliases, final Class<?> clazz )
throws ClassNotFoundException
{
final String legacyName = "org.sonatype.guice.bean.locators." + clazz.getSimpleName();
final Class<?> legacyType = ElementAnalyzer.class.getClassLoader().loadClass( legacyName );
if ( clazz.isAssignableFrom( legacyType ) )
{
aliases.put( Key.get( legacyType ), Key.get( clazz ) );
}
}
}