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

org.scijava.discovery.Discoverer Maven / Gradle / Ivy

/*-
 * #%L
 * Plugin discovery subsystem for SciJava libraries.
 * %%
 * Copyright (C) 2021 - 2024 SciJava developers.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

package org.scijava.discovery;

import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * Discovers implementations of a given type.
 *
 * @author Gabriel Selzer
 */
@FunctionalInterface
public interface Discoverer {

	/**
	 * Discovers implementations of some {@link Class} {@code c}.
	 *
	 * @param  the {@link Type} of the {@link Class} being searched for
	 * @param c the {@link Class} being searched for
	 * @return a {@link List} of implementations of {@code c}
	 */
	 List discover(Class c);

	/**
	 * Creates a {@link Discoverer} operating via {@link Function}.
	 * 

* NB: The {@link Function} input is extremely important for e.g. JPMS * compatibility. This puts the code loading the services into the user's * module, allowing access to all of the services exposed to (and {@code use}d * by) the calling module. Otherwise, all service interfaces would have to be * {@code use}d by this module, which is not extensible. *

* * @param func the {@link Function} generating a {@link Iterable} of * implementations provided a {@link Class} * @param the {@link Class} we attempt to discover, and consequently the * supertype of all implementations contained in the {@link Iterable} * @return A {@link Discoverer} backed by {@code func} */ static Discoverer using(Function, ? extends Iterable> func) { return new Discoverer() { @Override public List discover(Class c) { // If we can use c, look up the implementations try { Iterable itr = (Iterable) func.apply((Class) c); return StreamSupport.stream(itr.spliterator(), false).collect( Collectors.toList()); } catch (ClassCastException | ServiceConfigurationError e) { return Collections.emptyList(); } } }; } /** * Gets all {@link Discoverer}s made available through {@link Iterable}, as * well as a {@link Discoverer} that is itself backed by the {@link Iterable}. *

* It is highly recommended to call this method using {@code * List discoverers = Discoverers.all(ServiceLoader::load); * } * * @param func the {@link Function} generating a {@link Iterable} of * implementations provided a {@link Class}. Notably, this callback * has the module scope of the caller, which is useful for * circumnavigating module permissions when using e.g. JPMS. If we * instead used {@link ServiceLoader#load(Class)} directly, we'd only * be able to discover implementations whose interface was * {@code use}d by {@code module org.scijava.discovery}. *

* It is in the user's best interest to make this {@link Function} as * general as possible. * @param the {@link Class} we attempt to discover, and consequently the * supertype of all implementations contained in the {@link Iterable} * @return A {@link List} of {@link Discoverer}s, including a * {@code Discoverer} backed by {@code func}, and all * {@link Discoverer}s found by {@code func} */ static List all( Function, ? extends Iterable> func) { // First, create the general-purpose Discoverer using the // using(Function<...>) // method Discoverer d = using(func); // Then, use that Discoverer to discoverer all Discoverers provided to the // caller module Collection allProvided = d.discover(Discoverer.class); // append the general purpose discoverer to the discovered Discoverers, and // return that. List discoverers = new ArrayList<>(allProvided); discoverers.add(d); return discoverers; } /** * Accumulates multiple {@link Discoverer}s into one mega-{@code Discoverer} * * @param discoverers the {@link Discoverer}s to be wrapped * @return the mega-{@link Discoverer} */ static Discoverer union(Iterable discoverers) { return new Discoverer() { @Override public List discover(Class c) { List list = new ArrayList<>(); for (var discoverer : discoverers) { list.addAll(discoverer.discover(c)); } return list; } }; } /** * Wraps up this {@code Discoverer} into a {@link Discoverer} that only * discoverers classes {@code classes} * * @param classes the {@link Class}es * @return the wrapping */ default Discoverer onlyFor(Class... classes) { List> list = Arrays.asList(classes); Discoverer d = this; return new Discoverer() { @Override public List discover(Class c) { if (list.contains(c)) return d.discover(c); return Collections.emptyList(); } }; } /** * Wraps up this {@code Discoverer} into a {@link Discoverer} that only * discoverers classes {@code classes} * * @param classes the {@link Class}es * @return the wrapping */ default Discoverer except(Class... classes) { List> list = Arrays.asList(classes); Discoverer d = this; return new Discoverer() { @Override public List discover(Class c) { if (list.contains(c)) return Collections.emptyList(); return d.discover(c); } }; } /** * Finds the maximum implementation of any {@link Comparable} {@code c}. * * @param c the {@link Class}, extending {@link Comparable} that the returned * implementation must implement * @param the {@link Type} of {@code c} * @return the maximum implementation of {@code c} */ default > Optional discoverMax(Class c) { List discoveries = discover(c); // NB: natural order sorts in ascending order return discoveries.stream().max(Comparator.naturalOrder()); } /** * Finds the minimum implementation of any {@link Comparable} {@code c}. * * @param c the {@link Class}, extending {@link Comparable} that the returned * implementation must implement * @param the {@link Type} of {@code c} * @return the minimum implementation of {@code c} */ default > Optional discoverMin(Class c) { List discoveries = discover(c); // NB: natural order sorts in ascending order return discoveries.stream().min(Comparator.naturalOrder()); } }