blob: 55c86676d2b15334d1d46b5271205126e2cee289 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2015 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.space;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.inject.Qualifier;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.PrivateBinder;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import com.google.inject.spi.MembersInjectorLookup;
import com.google.inject.spi.PrivateElements;
import com.google.inject.spi.ProviderLookup;
/**
* Guice {@link Module} that automatically binds types annotated with {@link Qualifier} annotations.
*/
public final class SpaceModule
implements Module
{
// ----------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------
private static final String NAMED_INDEX = AbstractSisuIndex.INDEX_FOLDER + AbstractSisuIndex.NAMED;
public static final ClassFinder LOCAL_INDEX = new IndexedClassFinder( NAMED_INDEX, false );
public static final ClassFinder GLOBAL_INDEX = new IndexedClassFinder( NAMED_INDEX, true );
public static final ClassFinder LOCAL_SCAN = SpaceScanner.DEFAULT_FINDER;
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private static final class RecordedElements
{
static final ConcurrentMap<String, List<Element>> cache = new ConcurrentHashMap<String, List<Element>>();
}
private final boolean caching;
private final ClassSpace space;
private final ClassFinder finder;
private Strategy strategy = Strategy.DEFAULT;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
public SpaceModule( final ClassSpace space )
{
this( space, BeanScanning.ON );
}
public SpaceModule( final ClassSpace space, final ClassFinder finder )
{
caching = false;
this.space = space;
this.finder = finder;
}
public SpaceModule( final ClassSpace space, final BeanScanning scanning )
{
caching = BeanScanning.CACHE == scanning;
this.space = space;
switch ( scanning )
{
case OFF:
finder = null;
break;
case INDEX:
finder = LOCAL_INDEX;
break;
case GLOBAL_INDEX:
finder = GLOBAL_INDEX;
break;
default:
finder = LOCAL_SCAN;
break;
}
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* Applies a new visitor {@link Strategy} to the current module.
*
* @param _strategy The new strategy
* @return Updated module
*/
public Module with( final Strategy _strategy )
{
strategy = _strategy;
return this;
}
public void configure( final Binder binder )
{
binder.bind( ClassSpace.class ).toInstance( space );
if ( caching )
{
recordAndReplayElements( binder );
}
else if ( null != finder )
{
scanForElements( binder );
}
}
// ----------------------------------------------------------------------
// Public types
// ----------------------------------------------------------------------
/**
* Visitor strategy.
*/
public interface Strategy
{
/**
* Selects the {@link SpaceVisitor} to be used for the given {@link Binder}.
*
* @param binder The binder
* @return Selected visitor
*/
SpaceVisitor visitor( Binder binder );
/**
* Default visitor strategy; scan and bind implementations with {@link Qualifier}s.
*/
Strategy DEFAULT = new Strategy()
{
public SpaceVisitor visitor( final Binder binder )
{
return new QualifiedTypeVisitor( new QualifiedTypeBinder( binder ) );
}
};
}
// ----------------------------------------------------------------------
// Implementation methods
// ----------------------------------------------------------------------
void scanForElements( final Binder binder )
{
new SpaceScanner( space, finder ).accept( strategy.visitor( binder ) );
}
private void recordAndReplayElements( final Binder binder )
{
final String key = space.toString();
List<Element> elements = RecordedElements.cache.get( key );
if ( null == elements )
{
// record results of scanning plus any custom module bindings
final List<Element> recording = Elements.getElements( new Module()
{
public void configure( final Binder recorder )
{
scanForElements( recorder );
}
} );
elements = RecordedElements.cache.putIfAbsent( key, recording );
if ( null == elements )
{
// shortcut, no need to reset state first time round
Elements.getModule( recording ).configure( binder );
return;
}
}
replayRecordedElements( binder, elements );
}
private static void replayRecordedElements( final Binder binder, final List<Element> elements )
{
for ( final Element e : elements )
{
// lookups have state so we replace them with duplicates when replaying...
if ( e instanceof ProviderLookup<?> )
{
binder.getProvider( ( (ProviderLookup<?>) e ).getKey() );
}
else if ( e instanceof MembersInjectorLookup<?> )
{
binder.getMembersInjector( ( (MembersInjectorLookup<?>) e ).getType() );
}
else if ( e instanceof PrivateElements )
{
// Follows example set by Guice Modules when applying private elements:
final PrivateElements privateElements = (PrivateElements) e;
// 1. create new private binder, using the elements source token
final PrivateBinder privateBinder = binder.withSource( e.getSource() ).newPrivateBinder();
// 2. for all elements, apply each element to the private binder
replayRecordedElements( privateBinder, privateElements.getElements() );
// 3. re-expose any exposed keys using their exposed source token
for ( final Key<?> k : privateElements.getExposedKeys() )
{
privateBinder.withSource( privateElements.getExposedSource( k ) ).expose( k );
}
}
else
{
e.applyTo( binder );
}
}
}
}