All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.util.inject.PluginFinder Maven / Gradle / Ivy

The newest version!
package org.robolectric.util.inject;

import static java.util.Collections.reverseOrder;
import static java.util.Comparator.comparing;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.Priority;
import org.robolectric.util.PerfStatsCollector;

@SuppressWarnings({"NewApi", "AndroidJdkLibsChecker"})
class PluginFinder {

  private final ServiceFinderAdapter serviceFinderAdapter;

  public PluginFinder() {
    this(new ServiceFinderAdapter(null));
  }

  /**
   * @param classLoader the classloader to be used to load provider-configuration files and provider
   *     classes, or null if the system classloader (or, failing that, the bootstrap classloader) is
   *     to be used
   */
  public PluginFinder(ClassLoader classLoader) {
    this(new ServiceFinderAdapter(classLoader));
  }

  PluginFinder(ServiceFinderAdapter serviceFinderAdapter) {
    this.serviceFinderAdapter = serviceFinderAdapter;
  }

  /**
   * Returns an implementation class for the specified plugin.
   *
   * 

If there is more than such one candidate, the classes will be sorted by {@link Priority} and * the one with the highest priority will be returned. If multiple classes claim the same * priority, a {@link ServiceConfigurationError} will be thrown. Classes without a Priority are * treated as {@code @Priority(0)}. * * @param pluginType the class of the plugin type * @param the class of the plugin type * @return the implementing class with the highest priority */ @Nullable Class findPlugin(Class pluginType) { return best(pluginType, findPlugins(pluginType)); } /** * Returns a list of implementation classes for the specified plugin, ordered from highest to * lowest priority. If no implementing classes can be found, an empty list is returned. * * @param pluginType the class of the plugin type * @param the class of the plugin type * @return a prioritized list of implementation classes */ @Nonnull List> findPlugins(Class pluginType) { return prioritize(filter(serviceFinderAdapter.load(pluginType))); } private Iterable> filter(Iterable> classes) { Set> superseded = new HashSet<>(); for (Class clazz : classes) { Supersedes supersedes = clazz.getAnnotation(Supersedes.class); if (supersedes != null) { superseded.add(supersedes.value()); } else { // Check for the misspelled annotation. Supercedes supercedes = clazz.getAnnotation(Supercedes.class); if (supercedes != null) { superseded.add(supercedes.value()); } } } if (superseded.isEmpty()) { return classes; } else { return () -> new Filterator<>(classes.iterator(), o -> !superseded.contains(o)); } } @Nullable private Class best( Class pluginType, List> serviceClasses) { if (serviceClasses.isEmpty()) { return null; } Class first = serviceClasses.get(0); if (serviceClasses.size() == 1) { return first; } int topPriority = priority(first); serviceClasses = serviceClasses.stream() .filter(it -> priority(it) == topPriority) .collect(Collectors.toList()); if (serviceClasses.size() == 1) { return serviceClasses.get(0); } else { throw new InjectionException(pluginType, "too many implementations: " + serviceClasses); } } static class ServiceFinderAdapter { private final ClassLoader classLoader; ServiceFinderAdapter(ClassLoader classLoader) { this.classLoader = classLoader; } @Nonnull Iterable> load(Class pluginType) { return PerfStatsCollector.getInstance() .measure( "loadPlugins", () -> { ClassLoader serviceClassLoader = classLoader; if (serviceClassLoader == null) { serviceClassLoader = Thread.currentThread().getContextClassLoader(); } HashSet> result = new HashSet<>(); try { Enumeration urls = serviceClassLoader.getResources("META-INF/services/" + pluginType.getName()); while (urls.hasMoreElements()) { URL url = urls.nextElement(); BufferedReader reader = new BufferedReader( new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)); while (reader.ready()) { String s = reader.readLine(); result.add( Class.forName(s, false, serviceClassLoader).asSubclass(pluginType)); } reader.close(); } return result; } catch (IOException | ClassNotFoundException e) { throw new AssertionError(e); } }); } } @Nonnull private List> prioritize(Iterable> iterable) { List> serviceClasses = new ArrayList<>(); for (Class serviceClass : iterable) { serviceClasses.add(serviceClass); } Comparator> c = reverseOrder(comparing(PluginFinder::priority)); c = c.thenComparing(Class::getName); serviceClasses.sort(c); return serviceClasses; } private static int priority(Class pluginClass) { Priority priority = pluginClass.getAnnotation(Priority.class); return priority == null ? 0 : priority.value(); } private static class Filterator implements Iterator { private final Iterator delegate; private final Predicate predicate; private T next; public Filterator(Iterator delegate, Predicate predicate) { this.delegate = delegate; this.predicate = predicate; findNext(); } void findNext() { while (delegate.hasNext()) { next = delegate.next(); if (predicate.test(next)) { return; } } next = null; } @Override public boolean hasNext() { return next != null; } @Override public T next() { try { return next; } finally { findNext(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy