/*******************************************************************************
* Copyright (c) 2010, 2014 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.launch;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.sisu.inject.Logs;
import org.eclipse.sisu.space.ClassSpace;
import org.eclipse.sisu.space.ClassVisitor;
import org.eclipse.sisu.space.IndexedClassFinder;
import org.eclipse.sisu.space.SpaceModule;
import org.eclipse.sisu.space.SpaceVisitor;
import org.eclipse.sisu.wire.WireModule;
import org.eclipse.sisu.wire.Wiring;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Module;
/**
* SPI mechanism for discovering {@link Module} and {@code Strategy} extensions.
*/
public final class SisuExtensions
implements SpaceModule.Strategy, WireModule.Strategy
{
// ----------------------------------------------------------------------
// Implementation fields
// ----------------------------------------------------------------------
private final ClassSpace space;
private final boolean global;
// ----------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------
private SisuExtensions( final ClassSpace space, final boolean global )
{
this.space = space;
this.global = global;
}
// ----------------------------------------------------------------------
// Public methods
// ----------------------------------------------------------------------
/**
* Returns local {@link SisuExtensions} from the containing class space.
*
* @param space The class space
* @return Local extensions
*/
public static SisuExtensions local( final ClassSpace space )
{
return new SisuExtensions( space, false );
}
/**
* Returns global {@link SisuExtensions} from the surrounding class space.
*
* @param space The class space
* @return Global extensions
*/
public static SisuExtensions global( final ClassSpace space )
{
return new SisuExtensions( space, true );
}
/**
* Installs modules listed under {@code META-INF/services/com.google.inject.Module}; modules must have a public
* no-arg constructor.
*
* @param binder The current binder
*/
public void install( final Binder binder )
{
install( binder, null, null );
}
/**
* Installs modules listed under {@code META-INF/services/com.google.inject.Module}; modules must either have a
* public no-arg constructor or one with the declared context type.
*
* @param binder The current binder
* @param contextType Optional context type
* @param context Optional context instance
*/
public <C> void install( final Binder binder, final Class<C> contextType, final C context )
{
for ( final Module m : create( Module.class, contextType, context ) )
{
binder.install( m );
}
}
/**
* {@link WireModule} strategy that lets {@code META-INF/services/org.eclipse.sisu.wire.Wiring} extensions override
* the default wiring.
*
* @param binder The binder
* @return Extended wiring
*/
public Wiring wiring( final Binder binder )
{
final Wiring defaultWiring = WireModule.Strategy.DEFAULT.wiring( binder );
final List<Wiring> customWiring = create( Wiring.class, Binder.class, binder );
return customWiring.isEmpty() ? defaultWiring : new Wiring()
{
public boolean wire( final Key<?> key )
{
for ( final Wiring w : customWiring )
{
if ( w.wire( key ) )
{
return true;
}
}
return defaultWiring.wire( key );
}
};
}
/**
* {@link SpaceModule} strategy that lets {@code META-INF/services/org.eclipse.sisu.space.SpaceVisitor} extensions
* override the default scanning.
*
* @param binder The binder
* @return Extended visitor
*/
public SpaceVisitor visitor( final Binder binder )
{
final SpaceVisitor defaultVisitor = SpaceModule.Strategy.DEFAULT.visitor( binder );
final List<SpaceVisitor> customVisitors = create( SpaceVisitor.class, Binder.class, binder );
return customVisitors.isEmpty() ? defaultVisitor : new SpaceVisitor()
{
public void enterSpace( final ClassSpace _space )
{
for ( final SpaceVisitor v : customVisitors )
{
v.enterSpace( _space );
}
defaultVisitor.enterSpace( _space );
}
public ClassVisitor visitClass( final URL url )
{
for ( final SpaceVisitor v : customVisitors )
{
final ClassVisitor cv = v.visitClass( url );
if ( null != cv )
{
return cv;
}
}
return defaultVisitor.visitClass( url );
}
public void leaveSpace()
{
for ( final SpaceVisitor v : customVisitors )
{
v.leaveSpace();
}
defaultVisitor.leaveSpace();
}
};
}
/**
* Creates instances of extensions listed under {@code META-INF/services/ fully-qualified-SPI-name} ;
* implementations must have a public no-arg constructor.
*
* @param spi The extension SPI
* @return List of extensions
*/
public <T> List<T> create( final Class<T> spi )
{
return create( spi, null, null );
}
/**
* Creates instances of extensions listed under {@code META-INF/services/ fully-qualified-SPI-name} ;
* implementations must either have a public no-arg constructor or one with the declared context type.
*
* @param spi The extension SPI
* @param contextType Optional context type
* @param context Optional context instance
* @return List of extensions
*/
public <T, C> List<T> create( final Class<T> spi, final Class<C> contextType, final C context )
{
final List<T> extensions = new ArrayList<T>();
for ( final Class<? extends T> impl : load( spi ) )
{
try
{
T instance = null;
if ( null != contextType )
{
try
{
instance = impl.getConstructor( contextType ).newInstance( context );
}
catch ( final NoSuchMethodException e )
{
// fall-back to default constructor
}
}
extensions.add( null != instance ? instance : impl.newInstance() );
}
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 );
}
}
return extensions;
}
/**
* Loads extension types listed under {@code META-INF/services/ fully-qualified-SPI-name}.
*
* @param spi The extension SPI
* @return List of extension types
*/
public <T> List<Class<? extends T>> load( final Class<T> spi )
{
final String index = "META-INF/services/" + spi.getName();
final List<Class<? extends T>> extensionTypes = new ArrayList<Class<? extends T>>();
for ( final String name : new IndexedClassFinder( index, global ).indexedNames( space ) )
{
try
{
extensionTypes.add( space.loadClass( name ).asSubclass( spi ) );
}
catch ( final Exception e )
{
Logs.trace( "Problem loading: {}", name, e );
}
catch ( final LinkageError e )
{
Logs.trace( "Problem loading: {}", name, e );
}
}
return extensionTypes;
}
}