package org.neo4j.helpers;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import org.neo4j.helpers.collection.FilteringIterable;
import org.neo4j.helpers.collection.IterableWrapper;
import org.neo4j.helpers.collection.NestingIterable;
import org.neo4j.helpers.collection.PrefetchingIterator;
/**
* A utility for locating services. This implements the same functionality as <a
* href="http://java.sun.com/javase/6/docs/api/java/util/ServiceLoader.html">
* the Java 6 ServiceLoader interface</a>, in fact it uses the
* <code>ServiceLoader</code> if available, but backports the functionality to
* previous Java versions and adds some error handling to ignore misconfigured
* service implementations.
*
* Additionally this class can be used as a base class for implementing services
* that are differentiated by a String key. An example implementation might be:
*
* <pre>
* <code>
* public abstract class StringConverter extends org.neo4j.commons.Service
* {
* protected StringConverter(String id)
* {
* super( id );
* }
*
* public abstract String convert( String input );
*
* public static StringConverter load( String id )
* {
* return org.neo4j.commons.Service.load( StringConverter.class, id );
* }
* }
* </code>
* </pre>
*
* With for example these implementations:
*
* <pre>
* <code>
* public final class UppercaseConverter extends StringConverter
* {
* public UppercaseConverter()
* {
* super( "uppercase" );
* }
*
* public String convert( String input )
* {
* return input.toUpperCase();
* }
* }
*
* public final class ReverseConverter extends StringConverter
* {
* public ReverseConverter()
* {
* super( "reverse" );
* }
*
* public String convert( String input )
* {
* char[] chars = input.toCharArray();
* for ( int i = 0; i < chars.length/2; i++ )
* {
* char intermediate = chars[i];
* chars[i] = chars[chars.length-1-i];
* chars[chars.length-1-i] = chars[i];
* }
* return new String( chars );
* }
* }
* </code>
* </pre>
*
* This would then be used as:
*
* <pre>
* <code>
* String atad = StringConverter.load( "reverse" ).convert( "data" );
* </code>
* </pre>
*
* @author Tobias Ivarsson
*/
public abstract class Service
{
/**
* A base class for services, similar to {@link Service}, that compares keys
* using case insensitive comparison instead of exact comparison.
*
* @author Tobias Ivarsson
*/
public static abstract class CaseInsensitiveService extends Service
{
/**
* Create a new instance of a service implementation identified with the
* specified key(s).
*
* @param key the main key for identifying this service implementation
* @param altKeys alternative spellings of the identifier of this
* service implementation
*/
protected CaseInsensitiveService( String key, String... altKeys )
{
super( key, altKeys );
}
@Override
final boolean matches( String key )
{
for ( String id : keys )
{
if ( id.equalsIgnoreCase( key ) ) return true;
}
return false;
}
}
/**
* Load all implementations of a Service.
*
* @param <T> the type of the Service
* @param type the type of the Service to load
* @return all registered implementations of the Service
*/
public static <T> Iterable<T> load( Class<T> type )
{
Iterable<T> loader;
if ( null != ( loader = java6Loader( type ) ) ) return loader;
if ( null != ( loader = sunJava5Loader( type ) ) ) return loader;
if ( null != ( loader = ourOwnLoader( type ) ) ) return loader;
return Collections.emptyList();
}
/**
* Load the Service implementation with the specified key.
*
* @param <T> the type of the Service
* @param type the type of the Service to load
* @param key the key that identifies the desired implementation
* @return the matching Service implementation
*/
public static <T extends Service> T load( Class<T> type, String key )
{
for ( T impl : load( type ) )
{
if ( impl.matches( key ) ) return impl;
}
throw new NoSuchElementException( String.format(
"Could not find any implementation of %s whith a key=\"%s\"",
type.getName(), key ) );
}
final Set<String> keys;
/**
* Create a new instance of a service implementation identified with the
* specified key(s).
*
* @param key the main key for identifying this service implementation
* @param altKeys alternative spellings of the identifier of this service
* implementation
*/
protected Service( String key, String... altKeys )
{
if ( altKeys == null || altKeys.length == 0 )
{
this.keys = Collections.singleton( key );
}
else
{
this.keys = new HashSet<String>( Arrays.asList( altKeys ) );
this.keys.add( key );
}
}
@Override
public String toString()
{
return getClass().getSuperclass().getName() + "" + keys;
}
boolean matches( String key )
{
return keys.contains( key );
}
private static <T> Iterable<T> filterExceptions( final Iterable<T> iterable )
{
return new Iterable<T>()
{
public Iterator<T> iterator()
{
return new PrefetchingIterator<T>()
{
final Iterator<T> iterator = iterable.iterator();
@Override
protected T fetchNextOrNull()
{
while ( iterator.hasNext() )
{
try
{
return iterator.next();
}
catch ( Throwable e )
{
}
}
return null;
}
};
}
};
}
private static <T> Iterable<T> java6Loader( Class<T> type )
{
try
{
@SuppressWarnings( "unchecked" ) Iterable<T> result = (Iterable<T>) Class.forName(
"java.util.ServiceLoader" ).getMethod( "load", Class.class ).invoke(
null, type );
return filterExceptions( result );
}
catch ( Exception e )
{
return null;
}
}
private static <T> Iterable<T> sunJava5Loader( final Class<T> type )
{
final Method providers;
try
{
providers = Class.forName( "sun.misc.Service" ).getMethod(
"providers", Class.class );
}
catch ( Exception e )
{
return null;
}
return filterExceptions( new Iterable<T>()
{
public Iterator<T> iterator()
{
try
{
@SuppressWarnings( "unchecked" ) Iterator<T> result = (Iterator<T>) providers.invoke(
null, type );
return result;
}
catch ( Exception e )
{
throw new RuntimeException(
"Failed to invoke sun.misc.Service.providers(forClass)",
e );
}
}
} );
}
private static <T> Iterable<T> ourOwnLoader( final Class<T> type )
{
List<URL> urls = new LinkedList<URL>();
try
{
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources(
"META-INF/services/" + type.getName() );
while ( resources.hasMoreElements() )
{
urls.add( resources.nextElement() );
}
}
catch ( IOException e )
{
return null;
}
return new NestingIterable<T, BufferedReader>(
FilteringIterable.notNull( new IterableWrapper<BufferedReader, URL>(
urls )
{
@Override
protected BufferedReader underlyingObjectToObject( URL url )
{
try
{
return new BufferedReader( new InputStreamReader( url.openStream() ) );
}
catch ( IOException e )
{
return null;
}
}
} ) )
{
@Override
protected Iterator<T> createNestedIterator( final BufferedReader input )
{
return new PrefetchingIterator<T>()
{
@Override
protected T fetchNextOrNull()
{
try
{
String line;
while ( null != ( line = input.readLine() ) )
{
try
{
return type.cast( Class.forName( line ).newInstance() );
}
catch ( Exception e )
{
}
}
return null;
}
catch ( IOException e )
{
return null;
}
}
};
}
};
}
}