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 extends Service, ?>, TypeToken extends Service, ?>>> services =
+ AnnotatedMethodServiceFactory.INSTANCE.lookupServices(instance);
+ for (final Map.Entry extends Service, ?>, TypeToken extends Service, ?>>> serviceEntry : services
+ .entrySet()) {
+ final TypeToken extends Service, ?>> 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 extends Service> 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 extends Service> 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 extends Service> 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 extends ServiceRepository.ServiceWrapper extends Service>>
+ queue = repository.getQueue();
+ queue.sort(null);
+ Collections.reverse(queue);
+ for (ServiceRepository.ServiceWrapper extends Service> wrapper : queue) {
+ collection
+ .add((TypeToken extends S>) TypeToken.of(wrapper.getImplementation().getClass()));
+ }
+ return Collections.unmodifiableList(collection);
+ }
+
+ @Nonnull
+ Executor getExecutor() {
+ return this.executor;
+ }
+
+}
diff --git a/cloud-services/src/main/java/cloud/commandframework/services/ServicePipelineBuilder.java b/cloud-services/src/main/java/cloud/commandframework/services/ServicePipelineBuilder.java
new file mode 100644
index 00000000..866d5975
--- /dev/null
+++ b/cloud-services/src/main/java/cloud/commandframework/services/ServicePipelineBuilder.java
@@ -0,0 +1,66 @@
+//
+// 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.base.Preconditions;
+
+import javax.annotation.Nonnull;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Builder for {@link ServicePipeline}
+ */
+@SuppressWarnings("unused")
+public final class ServicePipelineBuilder {
+
+ private Executor executor = Executors.newSingleThreadExecutor();
+
+ ServicePipelineBuilder() {
+ }
+
+ /**
+ * Construct a new {@link ServicePipeline} using the options specified in the builder
+ *
+ * @return New service pipeline
+ */
+ @Nonnull
+ public ServicePipeline build() {
+ return new ServicePipeline(this.executor);
+ }
+
+ /**
+ * Set the executor that will be used by the pipeline when evaluating results asynchronously.
+ * Unless specified, the pipeline will use a single threaded executor.
+ *
+ * @param executor New executor
+ * @return Builder instance
+ */
+ @Nonnull
+ public ServicePipelineBuilder withExecutor(@Nonnull final Executor executor) {
+ this.executor = Preconditions.checkNotNull(executor, "Executor may not be null");
+ return this;
+ }
+
+}
diff --git a/cloud-services/src/main/java/cloud/commandframework/services/ServicePump.java b/cloud-services/src/main/java/cloud/commandframework/services/ServicePump.java
new file mode 100644
index 00000000..5d7f91f9
--- /dev/null
+++ b/cloud-services/src/main/java/cloud/commandframework/services/ServicePump.java
@@ -0,0 +1,72 @@
+//
+// 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;
+
+/**
+ * Class that forwards a context to a service type that consumes said context
+ *
+ * @param Context to pump
+ */
+public final class ServicePump {
+
+ private final ServicePipeline servicePipeline;
+ private final Context context;
+
+ ServicePump(final ServicePipeline servicePipeline, final Context context) {
+ this.servicePipeline = servicePipeline;
+ this.context = context;
+ }
+
+ /**
+ * Specify the service type that the context will be pumped through
+ *
+ * @param type Service type
+ * @param Result type
+ * @return Service spigot instance
+ */
+ @Nonnull
+ public ServiceSpigot through(
+ @Nonnull final TypeToken extends Service> type) {
+ return new ServiceSpigot<>(this.servicePipeline, this.context, type);
+ }
+
+ /**
+ * Specify the service type that the context will be pumped through
+ *
+ * @param clazz Service type
+ * @param Result type
+ * @return Service spigot instance
+ */
+ @Nonnull
+ public ServiceSpigot through(
+ @Nonnull final Class extends Service> clazz) {
+ return this.through(TypeToken.of(clazz));
+ }
+
+}
diff --git a/cloud-services/src/main/java/cloud/commandframework/services/annotations/Order.java b/cloud-services/src/main/java/cloud/commandframework/services/annotations/Order.java
new file mode 100644
index 00000000..0d6792bc
--- /dev/null
+++ b/cloud-services/src/main/java/cloud/commandframework/services/annotations/Order.java
@@ -0,0 +1,46 @@
+//
+// 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.annotations;
+
+import cloud.commandframework.services.ExecutionOrder;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to specify the relative priority of a service implementation
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Order {
+
+ /**
+ * The priority of the implementation
+ *
+ * @return Priority
+ */
+ ExecutionOrder value() default ExecutionOrder.SOON;
+}
diff --git a/cloud-services/src/main/java/cloud/commandframework/services/annotations/package-info.java b/cloud-services/src/main/java/cloud/commandframework/services/annotations/package-info.java
new file mode 100644
index 00000000..feb91864
--- /dev/null
+++ b/cloud-services/src/main/java/cloud/commandframework/services/annotations/package-info.java
@@ -0,0 +1,28 @@
+//
+// 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.
+//
+
+/**
+ * Service related annotations
+ */
+package cloud.commandframework.services.annotations;