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