/* * Copyright 2005 by Oracle USA * 500 Oracle Parkway, Redwood Shores, California, 94065, U.S.A. * All rights reserved. */ package javax.ide.spi; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import javax.ide.Service; /** * The LookupProvider provides a mechanism to look up implementations * of service classes.
* * A default implementation is provided that searches the classpath for * service files in the META-INF directory of classpath entries. Each service * file is named after the abstract service class and contains the fully * qualified class name of a service implementation.
* * It is possible to change the default lookup provider by calling * {@link #setDefault( LookupProvider )}. This may be convenient * for unit testing, for example. */ public abstract class LookupProvider { private static LookupProvider PRIMORDIAL_PROVIDER = new DefaultImpl(); private static LookupProvider s_defaultProvider = PRIMORDIAL_PROVIDER; /** * Looks up all available implementations of the specified service class * on the specified loader. * * @param loader a loader on which to look for services. * @param serviceClass the service class to look for. * @return all available implementations of the specified service class. * * @throws ProviderNotFoundException if no service implementation was found. * * @since 2.0 */ public abstract Collection lookupAllImpl( ClassLoader loader, Class serviceClass ) throws ProviderNotFoundException; private static LookupProvider getDefault() { return s_defaultProvider; } /** * Sets the default lookup provider. Normally it is not necessary to call * this API. However, it may be useful for creating stub services for * unit testing.
* * Note: this method will reset all activated services, causing new instances * of those services to be created when the service is next retreived. * * @param defaultProvider the default provider. If null, the provider will * be restored to the standard lookup provider. If anything other than * the current provider is passed, all services are reset. * @since 2.0 */ public static final void setDefault( LookupProvider defaultProvider ) { LookupProvider old = s_defaultProvider; if ( defaultProvider == null ) { s_defaultProvider = PRIMORDIAL_PROVIDER; } else { s_defaultProvider = defaultProvider; } if ( old != defaultProvider ) { Service.resetAllServices(); } } /** * Look up and create the an implementation of a specified * class.
* * The implementation class is stored on a jar in the classpath in the * META-INF/services/some.class.Name file. This file contains the fully * qualified name of the implementation class of some.class.Name.
* * If there are multiple META-INF/services/some.class.Name files on the * classpath, the last one wins. This is intentionally different * from the normal classpath precendence order so that it is possible to * override a more general implementation (e.g. a JSR-198 default * implementation of some service) with a specific one (e.g. an IDE * specific service). * * @param loader the class loader to look up implementations on. * @param clazz the class to look up an implementation of. * @return the first available implementation of the specified class on the * classpath. * * @throws ProviderNotFoundException if no implementation for the specified * class could be loaded. */ public final static Object lookup( ClassLoader loader, Class clazz ) throws ProviderNotFoundException { Collection impls = getDefault().lookupAllImpl( loader, clazz ); if ( impls.isEmpty() ) { throw new ProviderNotFoundException( "No provider for " + clazz ); } for ( Iterator i = impls.iterator(); i.hasNext(); ) { Object o = i.next(); if ( !i.hasNext() ) { return o; } } throw new IllegalStateException(); } /** * Look up and create all available implementations of a specified class.
* * The implementation class is stored on a jar in the classpath in the * META-INF/services/some.class.Name file. This file contains the fully * qualified name of the implementation class of some.class.Name. * * @param loader the class loader to look up implementations on. * @param clazz the class to look up an implementation of. * @return all available implementations of the specified class on the * classpath. * * @throws ProviderNotFoundException if no implementation for the specified * class could be loaded. */ public static Collection lookupAll( ClassLoader loader, Class clazz ) throws ProviderNotFoundException { return getDefault().lookupAllImpl( loader, clazz ); } private static final class DefaultImpl extends LookupProvider { public Collection lookupAllImpl( ClassLoader loader, Class clazz ) throws ProviderNotFoundException { Enumeration en; try { en = loader.getResources( "META-INF/services/" + clazz.getName() ); Collection all = lookupAll( loader, clazz, en ); all.addAll( lookupAll( loader, clazz, loader.getResources( "meta-inf/services/" + clazz.getName() ) ) ); return all; } catch ( IOException ioe ) { throw new ProviderNotFoundException( clazz.getName(), ioe ); } } private Collection lookupAll(ClassLoader loader, Class clazz, Enumeration en) throws ProviderNotFoundException { Collection results = new ArrayList(); while (en.hasMoreElements()) { URL url = (URL) en.nextElement(); try { InputStream is = url.openStream(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); while (true) { String line = reader.readLine(); if (line == null) { break; } line = line.trim(); if (line.length() == 0) continue; if (line.charAt(0) == '#') continue; try { Class inst = Class.forName(line, false, loader); if (!clazz.isAssignableFrom(inst)) { throw new ProviderNotFoundException(inst + " is not correct type for service " + clazz); } results.add(inst.newInstance()); } catch (Exception e) { throw new ProviderNotFoundException("Failed to create service class " + line, e); } } } finally { is.close(); } } catch (IOException ioe) { throw new ProviderNotFoundException("Failed to load service class " + clazz, ioe); } } return results; } } }