/******************************************************************************* * 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 ) ); } } }