// // MIT License // // Copyright (c) 2020 IntellectualSites // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // package cloud.commandframework.services; import com.google.common.reflect.TypeToken; import cloud.commandframework.services.types.Service; import javax.annotation.Nonnull; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.function.Predicate; /** * Service pipeline */ public final class ServicePipeline { private final Object lock = new Object(); private final Map>, ServiceRepository> repositories; private final Executor executor; ServicePipeline(@Nonnull final Executor executor) { this.repositories = new HashMap<>(); this.executor = executor; } /** * Create a new {@link ServicePipelineBuilder} * * @return Builder instance */ @Nonnull public static ServicePipelineBuilder builder() { return new ServicePipelineBuilder(); } /** * Register a service type so that it is recognized by the pipeline * * @param type Service type * @param defaultImplementation Default implementation of the service. This must *always* be * supplied and will be the last service implementation that the * pipeline attempts to access. This will never be filtered out. * @param Service context type * @param Service result type * @return ServicePipeline The service pipeline instance */ public ServicePipeline registerServiceType( @Nonnull final TypeToken> type, @Nonnull final Service defaultImplementation) { synchronized (this.lock) { if (repositories.containsKey(type)) { throw new IllegalArgumentException(String .format("Service of type '%s' has already been registered", type.toString())); } final ServiceRepository repository = new ServiceRepository<>(type); repository.registerImplementation(defaultImplementation, Collections.emptyList()); this.repositories.put(type, repository); return this; } } /** * Scan a given class for methods annotated with {@link cloud.commandframework.services.annotations.ServiceImplementation} * and register them as service implementations. *

* The methods should be of the form: *

{@code
     * {@literal @}Nullable
     * {@literal @}ServiceImplementation(YourService.class)
     * public YourResult handle(YourContext) {
     *      return result;
     * }
     * }
* * @param instance Instance of the class to scan * @param Type of the instance * @return Service pipeline instance * @throws Exception Any exceptions thrown during the registration process */ @SuppressWarnings("unchecked") public ServicePipeline registerMethods( @Nonnull final T instance) throws Exception { synchronized (this.lock) { final Map, TypeToken>> services = AnnotatedMethodServiceFactory.INSTANCE.lookupServices(instance); for (final Map.Entry, TypeToken>> serviceEntry : services .entrySet()) { final TypeToken> type = serviceEntry.getValue(); final ServiceRepository repository = this.repositories.get(type); if (repository == null) { throw new IllegalArgumentException( String.format("No service registered for type '%s'", type.toString())); } repository.registerImplementation(serviceEntry.getKey(), Collections.emptyList()); } } return this; } /** * Register a service implementation for a type that is recognized by the pipeline. It is * important that a call to {@link #registerServiceType(TypeToken, Service)} has been made * beforehand, otherwise a {@link IllegalArgumentException} will be thrown * * @param type Service type * @param implementation Implementation of the service * @param filters Filters that will be used to determine whether or not the service gets * used * @param Service context type * @param Service result type * @return ServicePipeline The service pipeline instance */ public ServicePipeline registerServiceImplementation( @Nonnull final TypeToken> type, @Nonnull final Service implementation, @Nonnull final Collection> filters) { synchronized (this.lock) { final ServiceRepository repository = getRepository(type); repository.registerImplementation(implementation, filters); } return this; } /** * Register a service implementation for a type that is recognized by the pipeline. It is * important that a call to {@link #registerServiceType(TypeToken, Service)} has been made * beforehand, otherwise a {@link IllegalArgumentException} will be thrown * * @param type Service type * @param implementation Implementation of the service * @param filters Filters that will be used to determine whether or not the service gets * used * @param Service context type * @param Service result type * @return ServicePipeline The service pipeline instance */ public ServicePipeline registerServiceImplementation( @Nonnull final Class> type, @Nonnull final Service implementation, @Nonnull final Collection> filters) { return registerServiceImplementation(TypeToken.of(type), implementation, filters); } /** * Start traversing the pipeline by providing the context that will be used to generate the * results * * @param context Context * @param Context type * @return Service pumper instance */ @Nonnull public ServicePump pump(@Nonnull final Context context) { return new ServicePump<>(this, context); } @SuppressWarnings("unchecked") @Nonnull ServiceRepository getRepository( @Nonnull final TypeToken> type) { final ServiceRepository repository = (ServiceRepository) this.repositories.get(type); if (repository == null) { throw new IllegalArgumentException( String.format("No service registered for type '%s'", type.toString())); } return repository; } /** * Get a collection of all the recognised service types. * * @return Returns an Immutable collection of the service types registered. */ @Nonnull public Collection>> getRecognizedTypes() { return Collections.unmodifiableCollection(this.repositories.keySet()); } /** * Get a collection of all the {@link TypeToken} of all implementations for a given type. * * @param type The {@link TypeToken} of the service to get implementations for. * @param The context type. * @param The result type. * @param The service type. * @return Returns an collection of the {@link TypeToken}s of the implementations for a given * service. Iterator order matches the priority when pumping contexts through the pipeline */ @Nonnull @SuppressWarnings("unchecked") public > Collection> getImplementations( @Nonnull final TypeToken type) { ServiceRepository repository = getRepository(type); List> collection = new LinkedList<>(); final LinkedList.ServiceWrapper>> queue = repository.getQueue(); queue.sort(null); Collections.reverse(queue); for (ServiceRepository.ServiceWrapper> wrapper : queue) { collection .add((TypeToken) TypeToken.of(wrapper.getImplementation().getClass())); } return Collections.unmodifiableList(collection); } @Nonnull Executor getExecutor() { return this.executor; } }