package denominator; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; import static denominator.common.Preconditions.checkArgument; import static denominator.common.Preconditions.checkNotNull; /** * Utilities for working with denominator providers. */ public final class Providers { /** * Returns the currently configured {@link Provider providers} from {@link * ServiceLoader#load(Class)}. * * <br> <br> <b>Performance Note</b><br> * * The implicit call {@link ServiceLoader#load(Class)} can add delays measurable in 10s to * hundreds of milliseconds depending on the number of jars in your classpath. If you desire * speed, it is best to instantiate Providers directly. */ public static Iterable<Provider> list() { return ServiceLoader.load(Provider.class); } /** * Key look up from {@link #list()}. * * @param providerName corresponds to {@link Provider#name()}. ex {@code ultradns} * @throws IllegalArgumentException if the providerName is not configured * @see #list() */ public static Provider getByName(String providerName) { checkNotNull(providerName, "providerName"); Provider matchedProvider = null; List<String> providerNames = new ArrayList<String>(); for (Provider provider : list()) { if (provider.name().equals(providerName)) { matchedProvider = provider; break; } providerNames.add(provider.name()); } checkArgument(matchedProvider != null, "provider %s not in set of configured providers: %s", providerName, providerNames); return matchedProvider; } /** * Overrides the {@link Provider#url()} of a given provider via reflectively calling its url * constructor. * * ex. * * <pre> * provider = withUrl(getByName(providerName), overrideUrl); * module = getByName(provider); * ultraDns = ObjectGraph.create(provide(provider), module, credentials(username, * password)).get(DNSApiManager.class); * </pre> * * @param url corresponds to {@link Provider#url()}. ex {@code http://apiendpoint} * @throws IllegalArgumentException if the there's no constructor that accepts a string argument. */ public static Provider withUrl(Provider provider, String url) { checkNotNull(provider, "provider"); checkNotNull(url, "url"); try { Constructor<?> ctor = provider.getClass().getDeclaredConstructor(String.class); // allow private or package protected ctors ctor.setAccessible(true); return Provider.class.cast(ctor.newInstance(url)); } catch (NoSuchMethodException e) { throw new IllegalArgumentException( provider.getClass() + " does not have a String parameter constructor", e); } catch (Exception e) { throw new IllegalArgumentException( "exception attempting to instantiate " + provider.getClass() + " for provider " + provider, e); } } /** * Instantiates the dagger module associated with the provider reflectively. Use this when * building {@link DNSApiManager} via Dagger when looking up a provider by name. * * ex. * * <pre> * provider = withUrl(getByName(providerName), overrideUrl); * module = getByName(provider); * ultraDns = ObjectGraph.create(provide(provider), module, credentials(username, * password)).get(DNSApiManager.class); * </pre> * * @throws IllegalArgumentException if there is no static inner class named {@code Module}, or it * is not possible to instantiate it. */ public static Object instantiateModule(Provider in) throws IllegalArgumentException { String moduleClassName; if (in.getClass().isAnonymousClass()) { moduleClassName = in.getClass().getSuperclass().getName() + "$Module"; } else { moduleClassName = in.getClass().getName() + "$Module"; } Class<?> moduleClass; try { moduleClass = Class.forName(moduleClassName); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(in.getClass().getName() + " should have a static inner class named Module", e); } catch (Exception e) { throw new IllegalArgumentException("exception attempting to instantiate " + moduleClassName + " for provider " + in.name(), e); } try { Constructor<?> ctor = moduleClass.getDeclaredConstructor(); // allow private or package protected ctors ctor.setAccessible(true); return ctor.newInstance(); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("ensure " + moduleClassName + " has a no-args constructor", e); } catch (Exception e) { throw new IllegalArgumentException("exception attempting to instantiate " + moduleClassName + " for provider " + in.name(), e); } } /** * Use this when building {@link DNSApiManager} via Dagger. * * ex. when no runtime changes to the provider are necessary: * * <pre> * ultraDns = ObjectGraph.create(provide(new UltraDNSProvider()), new UltraDNSProvider.Module(), * credentials(username, password)).get(DNSApiManager.class); * </pre> * * ex. for dynamic provider * * <pre> * Provider fromDiscovery = new UltraDNSProvider() { * public String getUrl() { * return discovery.getUrlFor("ultradns"); * } * }; * * ultraDns = ObjectGraph.create(provide(fromDiscovery), new UltraDNSProvider.Module(), * credentials(username, password)) * .get(DNSApiManager.class); * </pre> */ public static Object provide(denominator.Provider provider) { return new ProvideProvider(provider); } @Module(injects = DNSApiManager.class, complete = false) static final class ProvideProvider implements javax.inject.Provider<Provider> { private final denominator.Provider provider; private ProvideProvider(denominator.Provider provider) { this.provider = checkNotNull(provider, "provider"); } @Provides @Singleton public Provider get() { return provider; } @Override public String toString() { return "Provides(" + provider + ")"; } } }