1 package org.robolectric.util.inject; 2 3 import static java.util.Collections.reverseOrder; 4 import static java.util.Comparator.comparing; 5 6 import java.io.BufferedReader; 7 import java.io.IOException; 8 import java.io.InputStreamReader; 9 import java.net.URL; 10 import java.nio.charset.StandardCharsets; 11 import java.util.ArrayList; 12 import java.util.Comparator; 13 import java.util.Enumeration; 14 import java.util.HashSet; 15 import java.util.Iterator; 16 import java.util.List; 17 import java.util.ServiceConfigurationError; 18 import java.util.Set; 19 import java.util.function.Predicate; 20 import java.util.stream.Collectors; 21 import javax.annotation.Nonnull; 22 import javax.annotation.Nullable; 23 import javax.annotation.Priority; 24 import org.robolectric.util.PerfStatsCollector; 25 26 @SuppressWarnings({"NewApi", "AndroidJdkLibsChecker"}) 27 class PluginFinder { 28 29 private final ServiceFinderAdapter serviceFinderAdapter; 30 PluginFinder()31 public PluginFinder() { 32 this(new ServiceFinderAdapter(null)); 33 } 34 35 /** 36 * @param classLoader the classloader to be used to load provider-configuration files and provider 37 * classes, or null if the system classloader (or, failing that, the bootstrap classloader) is 38 * to be used 39 */ PluginFinder(ClassLoader classLoader)40 public PluginFinder(ClassLoader classLoader) { 41 this(new ServiceFinderAdapter(classLoader)); 42 } 43 PluginFinder(ServiceFinderAdapter serviceFinderAdapter)44 PluginFinder(ServiceFinderAdapter serviceFinderAdapter) { 45 this.serviceFinderAdapter = serviceFinderAdapter; 46 } 47 48 /** 49 * Returns an implementation class for the specified plugin. 50 * 51 * <p>If there is more than such one candidate, the classes will be sorted by {@link Priority} and 52 * the one with the highest priority will be returned. If multiple classes claim the same 53 * priority, a {@link ServiceConfigurationError} will be thrown. Classes without a Priority are 54 * treated as {@code @Priority(0)}. 55 * 56 * @param pluginType the class of the plugin type 57 * @param <T> the class of the plugin type 58 * @return the implementing class with the highest priority 59 */ 60 @Nullable findPlugin(Class<T> pluginType)61 <T> Class<? extends T> findPlugin(Class<T> pluginType) { 62 return best(pluginType, findPlugins(pluginType)); 63 } 64 65 /** 66 * Returns a list of implementation classes for the specified plugin, ordered from highest to 67 * lowest priority. If no implementing classes can be found, an empty list is returned. 68 * 69 * @param pluginType the class of the plugin type 70 * @param <T> the class of the plugin type 71 * @return a prioritized list of implementation classes 72 */ 73 @Nonnull findPlugins(Class<T> pluginType)74 <T> List<Class<? extends T>> findPlugins(Class<T> pluginType) { 75 return prioritize(filter(serviceFinderAdapter.load(pluginType))); 76 } 77 filter(Iterable<Class<? extends T>> classes)78 private <T> Iterable<Class<? extends T>> filter(Iterable<Class<? extends T>> classes) { 79 Set<Class<?>> superceded = new HashSet<>(); 80 for (Class<? extends T> clazz : classes) { 81 Supercedes supercedes = clazz.getAnnotation(Supercedes.class); 82 if (supercedes != null) { 83 superceded.add(supercedes.value()); 84 } 85 } 86 if (superceded.isEmpty()) { 87 return classes; 88 } else { 89 return () -> new Filterator<>(classes.iterator(), o -> !superceded.contains(o)); 90 } 91 } 92 93 @Nullable best(Class<T> pluginType, List<Class<? extends T>> serviceClasses)94 private <T> Class<? extends T> best(Class<T> pluginType, 95 List<Class<? extends T>> serviceClasses) { 96 if (serviceClasses.isEmpty()) { 97 return null; 98 } 99 100 Class<? extends T> first = serviceClasses.get(0); 101 if (serviceClasses.size() == 1) { 102 return first; 103 } 104 105 int topPriority = priority(first); 106 serviceClasses = serviceClasses.stream() 107 .filter(it -> priority(it) == topPriority) 108 .collect(Collectors.toList()); 109 110 if (serviceClasses.size() == 1) { 111 return serviceClasses.get(0); 112 } else { 113 throw new InjectionException(pluginType, "too many implementations: " + serviceClasses); 114 } 115 } 116 117 static class ServiceFinderAdapter { 118 119 private final ClassLoader classLoader; 120 ServiceFinderAdapter(ClassLoader classLoader)121 ServiceFinderAdapter(ClassLoader classLoader) { 122 this.classLoader = classLoader; 123 } 124 125 @Nonnull load(Class<T> pluginType)126 <T> Iterable<Class<? extends T>> load(Class<T> pluginType) { 127 return PerfStatsCollector.getInstance() 128 .measure( 129 "loadPlugins", 130 () -> { 131 ClassLoader serviceClassLoader = classLoader; 132 if (serviceClassLoader == null) { 133 serviceClassLoader = Thread.currentThread().getContextClassLoader(); 134 } 135 HashSet<Class<? extends T>> result = new HashSet<>(); 136 137 try { 138 Enumeration<URL> urls = 139 serviceClassLoader.getResources("META-INF/services/" + pluginType.getName()); 140 while (urls.hasMoreElements()) { 141 URL url = urls.nextElement(); 142 BufferedReader reader = 143 new BufferedReader( 144 new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)); 145 while (reader.ready()) { 146 String s = reader.readLine(); 147 result.add( 148 Class.forName(s, false, serviceClassLoader).asSubclass(pluginType)); 149 } 150 reader.close(); 151 } 152 return result; 153 } catch (IOException | ClassNotFoundException e) { 154 throw new AssertionError(e); 155 } 156 }); 157 } 158 } 159 160 @Nonnull prioritize(Iterable<Class<? extends T>> iterable)161 private <T> List<Class<? extends T>> prioritize(Iterable<Class<? extends T>> iterable) { 162 List<Class<? extends T>> serviceClasses = new ArrayList<>(); 163 164 for (Class<? extends T> serviceClass : iterable) { 165 serviceClasses.add(serviceClass); 166 } 167 168 Comparator<Class<? extends T>> c = reverseOrder(comparing(PluginFinder::priority)); 169 c = c.thenComparing(Class::getName); 170 serviceClasses.sort(c); 171 172 return serviceClasses; 173 } 174 priority(Class<? extends T> pluginClass)175 private static <T> int priority(Class<? extends T> pluginClass) { 176 Priority priority = pluginClass.getAnnotation(Priority.class); 177 return priority == null ? 0 : priority.value(); 178 } 179 180 private static class Filterator<T> implements Iterator<T> { 181 182 private final Iterator<T> delegate; 183 private final Predicate<T> predicate; 184 private T next; 185 Filterator(Iterator<T> delegate, Predicate<T> predicate)186 public Filterator(Iterator<T> delegate, Predicate<T> predicate) { 187 this.delegate = delegate; 188 this.predicate = predicate; 189 findNext(); 190 } 191 findNext()192 void findNext() { 193 while (delegate.hasNext()) { 194 next = delegate.next(); 195 if (predicate.test(next)) { 196 return; 197 } 198 } 199 next = null; 200 } 201 202 @Override hasNext()203 public boolean hasNext() { 204 return next != null; 205 } 206 207 @Override next()208 public T next() { 209 try { 210 return next; 211 } finally { 212 findNext(); 213 } 214 } 215 } 216 } 217