From 3ab8972ad9f9f6a849cdd0a0b98921852dec3087 Mon Sep 17 00:00:00 2001 From: Joshua Sing Date: Wed, 5 Apr 2023 03:31:41 +1000 Subject: [PATCH] feat(extensions)!: improve and simplify extensions system (#205) * feat(extensions)!: improve and simplify extensions system * chore: bump version to 0.14.0-SNAPSHOT * feat!: make ExtensionManager#loadExtension and #getExtension return a platform rather than internal extension * feat(extensions): add dependencies, add platform extensions * feat(extensions): move dependencies to the factory, bump version * feat(folia): add extensions v2 support --------- Co-authored-by: Luis --- .../java/dev/hypera/chameleon/Chameleon.java | 119 ++-------- .../hypera/chameleon/ChameleonBootstrap.java | 98 ++++---- .../adventure/mapper/AdventureMapper.java | 3 +- .../hypera/chameleon/event/EventBusImpl.java | 22 +- .../event/common/ChameleonDisableEvent.java | 59 +++++ .../event/common/ChameleonEnableEvent.java | 59 +++++ .../event/common/ChameleonLoadEvent.java | 59 +++++ .../ChameleonExtensionException.java | 14 +- .../extension/ChameleonExtension.java | 48 +--- .../ChameleonExtensionDependency.java | 180 +++++++++++++++ .../ChameleonExtensionDependencyImpl.java | 83 +++++++ .../extension/ChameleonExtensionFactory.java | 70 ++++++ .../extension/ChameleonPlatformExtension.java | 61 ++--- .../chameleon/extension/ExtensionManager.java | 75 ++++++ .../extension/ExtensionManagerImpl.java | 116 ++++++++++ .../chameleon/extension/ExtensionMap.java | 149 ++++++++++++ ...atformExtension.java => package-info.java} | 10 +- .../hypera/chameleon/util/ChameleonUtil.java | 13 -- .../java/dev/hypera/chameleon/util/Pair.java | 66 ++++++ .../dev/hypera/chameleon/util/PairImpl.java | 54 +++++ .../hypera/chameleon/util/Preconditions.java | 40 +++- .../chameleon/util/graph/DirectedGraph.java | 187 +++++++++++++++ .../dev/hypera/chameleon/util/graph/Edge.java | 48 +++- .../hypera/chameleon/util/graph/EdgeImpl.java | 81 +++++++ .../hypera/chameleon/util/graph/Graph.java | 218 ++++++++++++++++++ ...DummyChameleon.java => TestChameleon.java} | 38 ++- .../chameleon/TestChameleonBootstrap.java | 41 ++++ .../chameleon/TestChameleonPlatform.java | 66 ++++++ ...onPlugin.java => TestChameleonPlugin.java} | 4 +- .../adventure/ReflectedAudienceTests.java | 7 +- .../mapper/AdventureMapperTests.java | 4 +- .../extension/ExtensionMapTests.java | 90 ++++++++ .../chameleon/extension/ExtensionTests.java | 158 ++++++++++++- .../extension/objects/Test2Extension.java} | 33 ++- .../extension/objects/Test2ExtensionImpl.java | 58 +++++ ...a => TestCircularDetection1Extension.java} | 4 +- .../TestCircularDetection1ExtensionImpl.java | 45 ++++ ...a => TestCircularDetection2Extension.java} | 7 +- .../TestCircularDetection2ExtensionImpl.java | 56 +++++ .../extension/objects/TestExtension.java | 18 +- .../objects/TestExtensionFactory.java | 42 +++- .../extension/objects/TestExtensionImpl.java | 50 ++++ ...TestRequiredDependencyEmptyExtension.java} | 16 +- .../ChameleonUtilTests.java} | 16 +- .../chameleon/util/PreconditionsTests.java | 15 ++ .../chameleon/util/graph/EdgeTests.java | 52 +++++ .../chameleon/util/graph/GraphTests.java | 138 +++++++++++ .../main/kotlin/chameleon.common.gradle.kts | 4 + build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 + .../platform/bukkit/BukkitChameleon.java | 24 +- .../bukkit/BukkitChameleonBootstrap.java | 22 +- .../extension/ChameleonBukkitExtension.java | 39 ---- .../platform/bukkit/user/BukkitUser.java | 10 +- .../bungeecord/BungeeCordChameleon.java | 20 +- .../BungeeCordChameleonBootstrap.java | 19 +- .../ChameleonBungeeCordExtension.java | 39 ---- .../bungeecord/user/BungeeCordUser.java | 10 +- .../platform/folia/FoliaChameleon.java | 23 +- .../folia/FoliaChameleonBootstrap.java | 23 +- .../extension/ChameleonFoliaExtension.java | 39 ---- .../platform/minestom/MinestomChameleon.java | 20 +- .../minestom/MinestomChameleonBootstrap.java | 22 +- .../platform/minestom/user/MinestomUser.java | 10 +- .../platform/nukkit/NukkitChameleon.java | 20 +- .../nukkit/NukkitChameleonBootstrap.java | 28 +-- .../extension/NukkitChameleonExtension.java | 39 ---- .../platform/nukkit/user/NukkitUser.java | 11 +- .../platform/sponge/SpongeChameleon.java | 20 +- .../sponge/SpongeChameleonBootstrap.java | 25 +- .../extension/SpongeChameleonExtension.java | 39 ---- .../platform/sponge/user/SpongeUser.java | 11 +- .../platform/velocity/VelocityChameleon.java | 20 +- .../velocity/VelocityChameleonBootstrap.java | 25 +- .../platform/velocity/user/VelocityUser.java | 10 +- 75 files changed, 2752 insertions(+), 715 deletions(-) create mode 100644 api/src/main/java/dev/hypera/chameleon/event/common/ChameleonDisableEvent.java create mode 100644 api/src/main/java/dev/hypera/chameleon/event/common/ChameleonEnableEvent.java create mode 100644 api/src/main/java/dev/hypera/chameleon/event/common/ChameleonLoadEvent.java create mode 100644 api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependency.java create mode 100644 api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependencyImpl.java create mode 100644 api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionFactory.java create mode 100644 api/src/main/java/dev/hypera/chameleon/extension/ExtensionManager.java create mode 100644 api/src/main/java/dev/hypera/chameleon/extension/ExtensionManagerImpl.java create mode 100644 api/src/main/java/dev/hypera/chameleon/extension/ExtensionMap.java rename api/src/main/java/dev/hypera/chameleon/extension/{CustomPlatformExtension.java => package-info.java} (82%) create mode 100644 api/src/main/java/dev/hypera/chameleon/util/Pair.java create mode 100644 api/src/main/java/dev/hypera/chameleon/util/PairImpl.java create mode 100644 api/src/main/java/dev/hypera/chameleon/util/graph/DirectedGraph.java rename platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/extension/ChameleonMinestomExtension.java => api/src/main/java/dev/hypera/chameleon/util/graph/Edge.java (57%) create mode 100644 api/src/main/java/dev/hypera/chameleon/util/graph/EdgeImpl.java create mode 100644 api/src/main/java/dev/hypera/chameleon/util/graph/Graph.java rename api/src/test/java/dev/hypera/chameleon/{DummyChameleon.java => TestChameleon.java} (73%) create mode 100644 api/src/test/java/dev/hypera/chameleon/TestChameleonBootstrap.java create mode 100644 api/src/test/java/dev/hypera/chameleon/TestChameleonPlatform.java rename api/src/test/java/dev/hypera/chameleon/{DummyChameleonPlugin.java => TestChameleonPlugin.java} (93%) create mode 100644 api/src/test/java/dev/hypera/chameleon/extension/ExtensionMapTests.java rename api/src/{main/java/dev/hypera/chameleon/extension/annotations/PostLoadable.java => test/java/dev/hypera/chameleon/extension/objects/Test2Extension.java} (65%) create mode 100644 api/src/test/java/dev/hypera/chameleon/extension/objects/Test2ExtensionImpl.java rename api/src/test/java/dev/hypera/chameleon/extension/objects/{TestPlatform.java => TestCircularDetection1Extension.java} (90%) create mode 100644 api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection1ExtensionImpl.java rename api/src/test/java/dev/hypera/chameleon/extension/objects/{TestInvalidExtension.java => TestCircularDetection2Extension.java} (85%) create mode 100644 api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection2ExtensionImpl.java rename platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/extension/VelocityChameleonExtension.java => api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionFactory.java (50%) create mode 100644 api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionImpl.java rename api/src/test/java/dev/hypera/chameleon/extension/objects/{TestPlatformExtension.java => TestRequiredDependencyEmptyExtension.java} (72%) rename api/src/test/java/dev/hypera/chameleon/{extension/objects/TestInvalidPlatformExtension.java => util/ChameleonUtilTests.java} (77%) create mode 100644 api/src/test/java/dev/hypera/chameleon/util/graph/EdgeTests.java create mode 100644 api/src/test/java/dev/hypera/chameleon/util/graph/GraphTests.java delete mode 100644 platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/extension/ChameleonBukkitExtension.java delete mode 100644 platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/extension/ChameleonBungeeCordExtension.java delete mode 100644 platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/extension/ChameleonFoliaExtension.java delete mode 100644 platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/extension/NukkitChameleonExtension.java delete mode 100644 platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/extension/SpongeChameleonExtension.java diff --git a/api/src/main/java/dev/hypera/chameleon/Chameleon.java b/api/src/main/java/dev/hypera/chameleon/Chameleon.java index 4edd6009..5a05de89 100644 --- a/api/src/main/java/dev/hypera/chameleon/Chameleon.java +++ b/api/src/main/java/dev/hypera/chameleon/Chameleon.java @@ -26,26 +26,21 @@ import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.command.CommandManager; import dev.hypera.chameleon.event.EventBus; -import dev.hypera.chameleon.event.EventBusImpl; -import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.event.common.ChameleonDisableEvent; +import dev.hypera.chameleon.event.common.ChameleonEnableEvent; +import dev.hypera.chameleon.event.common.ChameleonLoadEvent; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.annotations.PostLoadable; +import dev.hypera.chameleon.extension.ExtensionManager; +import dev.hypera.chameleon.extension.ExtensionManagerImpl; +import dev.hypera.chameleon.extension.ExtensionMap; import dev.hypera.chameleon.logger.ChameleonInternalLogger; import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.PluginManager; import dev.hypera.chameleon.scheduler.Scheduler; import dev.hypera.chameleon.user.UserManager; -import dev.hypera.chameleon.util.ChameleonUtil; import dev.hypera.chameleon.util.Preconditions; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -62,37 +57,34 @@ public abstract class Chameleon { private final @NotNull ChameleonPlugin plugin; private final @NotNull ChameleonPluginData pluginData; - private final @NotNull Collection> extensions; private final @NotNull EventBus eventBus; - - private boolean enabled = false; + private final @NotNull ExtensionManager extensionManager; @Internal - protected Chameleon(@NotNull Class plugin, @NotNull Collection> extensions, @NotNull ChameleonPluginData pluginData, @NotNull ChameleonLogger logger) throws ChameleonInstantiationException { + protected Chameleon(@NotNull Class plugin, @NotNull ChameleonPluginData pluginData, @NotNull EventBus eventBus, @NotNull ChameleonLogger logger, @NotNull ExtensionMap extensions) throws ChameleonInstantiationException { Preconditions.checkNotNull("plugin", plugin); - Preconditions.checkNotNull("extensions", extensions); Preconditions.checkNotNull("pluginData", plugin); + Preconditions.checkNotNull("eventBus", eventBus); Preconditions.checkNotNull("logger", logger); + Preconditions.checkNotNull("extensions", extensions); try { this.logger = logger; this.internalLogger = new ChameleonInternalLogger(logger); this.plugin = plugin.getConstructor(Chameleon.class).newInstance(this); this.pluginData = pluginData; - this.extensions = extensions; - this.eventBus = new EventBusImpl(this.internalLogger); + this.eventBus = eventBus; + this.extensionManager = new ExtensionManagerImpl(this, extensions); } catch (Exception ex) { - throw new ChameleonInstantiationException( - "Failed to initialise instance of " + plugin.getCanonicalName(), ex - ); + throw new ChameleonInstantiationException("Failed to initialise instance of " + plugin.getCanonicalName(), ex); } } - /** * Called after Chameleon has been loaded. */ public void onLoad() { + this.eventBus.dispatch(new ChameleonLoadEvent(this)); this.plugin.onLoad(); } @@ -100,18 +92,16 @@ public void onLoad() { * Called when the platform plugin is enabled. */ public void onEnable() { - this.extensions.forEach(ChameleonExtension::onEnable); + this.eventBus.dispatch(new ChameleonEnableEvent(this)); this.plugin.onEnable(); - this.enabled = true; } /** * Called when the platform plugin is disabled. */ public void onDisable() { - this.extensions.forEach(ChameleonExtension::onDisable); + this.eventBus.dispatch(new ChameleonDisableEvent(this)); this.plugin.onDisable(); - this.enabled = false; } @@ -133,75 +123,6 @@ public void onDisable() { return this.pluginData; } - /** - * Get a loaded extension. - * - * @param extension Chameleon extension implementation class. - * @param Chameleon extension type. - * - * @return an optional containing the extension, if found, otherwise an empty optional. - */ - public final > @NotNull Optional getExtension(@NotNull Class extension) { - return this.extensions.stream() - .filter(ext -> ext.getClass().equals(extension)) - .findFirst() - .map(extension::cast); - } - - /** - * Load and return an extension. - * - * @param extension Extension implementation class. - * @param Extension type. - * @param Chameleon type. - * - * @return the loaded extension. - */ - @SuppressWarnings("unchecked") - public final , C extends Chameleon> @NotNull T loadExtension(@NotNull Class extension) { - if (!extension.isAnnotationPresent(PostLoadable.class)) { - throw new IllegalArgumentException("extension cannot be post loaded"); - } - - if (this.extensions.stream().anyMatch(extension::isInstance)) { - throw new IllegalArgumentException("extension has already been loaded"); - } - - PostLoadable extensionAnnotation = extension.getAnnotation(PostLoadable.class); - Constructor[] platformExtensionConstructors = Arrays.stream(extensionAnnotation.value()) - .filter(p -> ChameleonUtil.getGenericTypeAsClass(p, 2).isAssignableFrom(getClass())) - .findFirst() - .map(Class::getConstructors) - .orElse(new Constructor[0]); - - if (platformExtensionConstructors.length < 1 || Arrays.stream(platformExtensionConstructors) - .noneMatch(c -> c.getParameterCount() == 0)) { - throw new IllegalArgumentException("cannot load platform extension: invalid constructor"); - } - - Constructor constructor = Arrays.stream(platformExtensionConstructors) - .filter(c -> c.getParameterCount() == 0) - .findFirst() - .orElseThrow(IllegalStateException::new); - try { - ChameleonPlatformExtension platformExtension = (ChameleonPlatformExtension) constructor.newInstance(); - platformExtension.onLoad((C) ChameleonUtil.getGenericTypeAsClass( - platformExtension.getClass(), - 2 - ).cast(this)); - platformExtension.getExtension().onLoad(this); - - if (this.enabled) { - platformExtension.getExtension().onEnable(); - } - - this.extensions.add(platformExtension.getExtension()); - return platformExtension.getExtension(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { - throw new ChameleonExtensionException(ex); - } - } - /** * Get the logger instance. * @@ -232,6 +153,14 @@ public void onDisable() { return this.eventBus; } + /** + * Get the extension manager. + * + * @return the extension manager. + */ + public final @NotNull ExtensionManager getExtensionManager() { + return this.extensionManager; + } /** * Get the audience provider. diff --git a/api/src/main/java/dev/hypera/chameleon/ChameleonBootstrap.java b/api/src/main/java/dev/hypera/chameleon/ChameleonBootstrap.java index 6e7c3a14..03afa48a 100644 --- a/api/src/main/java/dev/hypera/chameleon/ChameleonBootstrap.java +++ b/api/src/main/java/dev/hypera/chameleon/ChameleonBootstrap.java @@ -23,78 +23,73 @@ */ package dev.hypera.chameleon; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.event.EventBusImpl; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; import dev.hypera.chameleon.extension.ChameleonExtension; +import dev.hypera.chameleon.extension.ChameleonExtensionFactory; import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import dev.hypera.chameleon.extension.ExtensionMap; import dev.hypera.chameleon.logger.ChameleonLogger; +import dev.hypera.chameleon.util.Pair; import dev.hypera.chameleon.util.Preconditions; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; +import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Chameleon bootstrap. - *

Allows for runtime dependency loading, etc. before Chameleon is actually loaded.

+ *

Allows you to perform actions and load extensions before Chameleon has been loaded.

* * @param Chameleon implementation type. - * @param Chameleon platform extension implementation type. */ -public abstract class ChameleonBootstrap> { +public abstract class ChameleonBootstrap { - private @Nullable Consumer preLoad; + private @NotNull Consumer preLoad = l -> {}; + protected final @NotNull ChameleonLogger logger; + private final @NotNull String platform; + protected final @NotNull EventBus eventBus; + protected final @NotNull ExtensionMap extensions = new ExtensionMap(); - private final @NotNull Collection platformExtensions = new HashSet<>(); - - - /** - * Load with extensions. - * - * @param extensions Chameleon platform extensions to be loaded. - * - * @return {@code this}. - */ - @SafeVarargs - @SuppressWarnings("varargs") - @Contract("_ -> this") - public final @NotNull ChameleonBootstrap withExtensions(@NotNull E... extensions) { - Preconditions.checkNotNull("extensions", extensions); - return withExtensions(Arrays.asList(extensions)); + protected ChameleonBootstrap(@NotNull ChameleonLogger logger, @NotNull String platform) { + this.logger = logger; + this.platform = platform; + this.eventBus = new EventBusImpl(logger); } /** - * Load with extensions. + * Load with a Chameleon extension. * - * @param extensions Chameleon platform extensions to be loaded. + * @param factory The factory to create the Chameleon extension. + * @param Extension type. * * @return {@code this}. */ @Contract("_ -> this") - public final @NotNull ChameleonBootstrap withExtensions(@NotNull Collection extensions) { - Preconditions.checkNotNull("extensions", extensions); - Preconditions.checkArgument( - extensions.stream().noneMatch(Objects::isNull), - "extensions must not contain null" - ); - this.platformExtensions.addAll(extensions); + public final @NotNull ChameleonBootstrap withExtension(@NotNull ChameleonExtensionFactory factory) { + Preconditions.checkNotNull("factory", this.extensions); + ChameleonPlatformExtension extension = factory.create(this.platform); + if (!factory.getType().isAssignableFrom(extension.getClass())) { + throw ChameleonExtensionException.create( + "Cannot load %s: not assignable from %s", + factory.getType().getSimpleName(), extension.getClass().getSimpleName() + ); + } + this.extensions.put(factory.getType(), Pair.of(factory.create(this.platform), factory.getDependencies(this.platform))); return this; } - /** - * Set pre-load handler. + * Set preload handler. * - * @param preLoad Pre-load handler. + * @param preLoad Preload handler. * * @return {@code this}. */ @Contract("_ -> this") - public final @NotNull ChameleonBootstrap onPreLoad(@NotNull Consumer preLoad) { + public final @NotNull ChameleonBootstrap onPreLoad(@NotNull Consumer preLoad) { Preconditions.checkNotNull("preLoad", preLoad); this.preLoad = preLoad; return this; @@ -104,26 +99,25 @@ public abstract class ChameleonBootstrap new") public final @NotNull T load() throws ChameleonInstantiationException { - if (this.preLoad != null) { - this.preLoad.accept(createLogger()); - } + // Run preload and initialise extensions. + this.preLoad.accept(this.logger); + List sortedExtensions = this.extensions.loadSort(); + sortedExtensions.forEach(ext -> ext.init(this.logger, this.eventBus)); - Collection> extensions = this.platformExtensions.stream().map(ext -> ext.getExtension()).collect(Collectors.toSet()); - extensions.forEach(ChameleonExtension::onPreLoad); - - T chameleon = loadInternal(extensions); + // Load Chameleon + T chameleon = loadInternal(); chameleon.onLoad(); - this.platformExtensions.forEach(ext -> ext.onLoad(chameleon)); - extensions.forEach(ext -> ext.onLoad(chameleon)); + + // Load extensions + sortedExtensions.forEach(ext -> ext.load(chameleon)); return chameleon; } - protected abstract @NotNull T loadInternal(@NotNull Collection> extensions) throws ChameleonInstantiationException; - - protected abstract @NotNull ChameleonLogger createLogger(); + protected abstract @NotNull T loadInternal() throws ChameleonInstantiationException; } diff --git a/api/src/main/java/dev/hypera/chameleon/adventure/mapper/AdventureMapper.java b/api/src/main/java/dev/hypera/chameleon/adventure/mapper/AdventureMapper.java index 58899457..ae218e41 100644 --- a/api/src/main/java/dev/hypera/chameleon/adventure/mapper/AdventureMapper.java +++ b/api/src/main/java/dev/hypera/chameleon/adventure/mapper/AdventureMapper.java @@ -29,6 +29,7 @@ import dev.hypera.chameleon.exception.reflection.ChameleonReflectiveException; import dev.hypera.chameleon.util.Preconditions; import java.util.concurrent.atomic.AtomicBoolean; +import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -39,7 +40,7 @@ * version of Adventure without there being problems. To get around this we map the relocated * Adventure objects to platform objects using reflection.

*/ -@Internal +@Experimental public final class AdventureMapper { public static final @NotNull String ORIGINAL_PACKAGE = "net.ky".concat("ori.adventure."); diff --git a/api/src/main/java/dev/hypera/chameleon/event/EventBusImpl.java b/api/src/main/java/dev/hypera/chameleon/event/EventBusImpl.java index 18f145d1..89a66f58 100644 --- a/api/src/main/java/dev/hypera/chameleon/event/EventBusImpl.java +++ b/api/src/main/java/dev/hypera/chameleon/event/EventBusImpl.java @@ -67,19 +67,15 @@ public EventBusImpl(@NotNull ChameleonLogger logger) { @Override public void dispatch(@NotNull ChameleonEvent event) { Preconditions.checkNotNull("event", event); - List> subscribers = getSubscribers(event.getClass()); - - synchronized (subscribers) { - subscribers.iterator().forEachRemaining(subscriber -> { - if (subscriber.acceptsCancelled() || !(event instanceof Cancellable) || !((Cancellable) event).isCancelled()) { - try { - subscriber.on(event); - } catch (Exception ex) { - this.logger.error("An error occurred while dispatching an event to %s", ex, subscriber.getClass().getCanonicalName()); - } + getSubscribers(event.getClass()).iterator().forEachRemaining(subscriber -> { + if (subscriber.acceptsCancelled() || !(event instanceof Cancellable) || !((Cancellable) event).isCancelled()) { + try { + subscriber.on(event); + } catch (Exception ex) { + this.logger.error("An error occurred while dispatching an event to %s", ex, subscriber.getClass().getCanonicalName()); } - }); - } + } + }); } /** @@ -150,7 +146,7 @@ public void unsubscribeIf(@NotNull Predicate> getSubscribers(@NotNull Class event) { + private synchronized @NotNull List> getSubscribers(@NotNull Class event) { Preconditions.checkNotNull("event", event); List> subscribers = this.sortedSubscriptions.entrySet().stream() .filter(entry -> entry.getKey().isAssignableFrom(event)).map(Entry::getValue).findFirst().orElse(null); diff --git a/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonDisableEvent.java b/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonDisableEvent.java new file mode 100644 index 00000000..6b4ec80e --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonDisableEvent.java @@ -0,0 +1,59 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.event.common; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.ChameleonEvent; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; + +/** + * Chameleon disable event. + *

Dispatched when Chameleon#onDisable is called by the plugin.

+ */ +public final class ChameleonDisableEvent implements ChameleonEvent { + + private final @NotNull Chameleon chameleon; + + /** + * ChameleonDisableEvent constructor. + *

This event is intended to be dispatched internally by Chameleon only.

+ * + * @param chameleon Chameleon instance. + */ + @Internal + public ChameleonDisableEvent(@NotNull Chameleon chameleon) { + this.chameleon = chameleon; + } + + /** + * Get the Chameleon instance that triggered this event. + * + * @return Chameleon instance. + */ + public @NotNull Chameleon chameleon() { + return this.chameleon; + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonEnableEvent.java b/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonEnableEvent.java new file mode 100644 index 00000000..f6b324d8 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonEnableEvent.java @@ -0,0 +1,59 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.event.common; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.ChameleonEvent; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; + +/** + * Chameleon enable event. + *

Dispatched when Chameleon#onLoad is called by the plugin.

+ */ +public final class ChameleonEnableEvent implements ChameleonEvent { + + private final @NotNull Chameleon chameleon; + + /** + * ChameleonEnableEvent constructor. + *

This event is intended to be dispatched internally by Chameleon only.

+ * + * @param chameleon Chameleon instance. + */ + @Internal + public ChameleonEnableEvent(@NotNull Chameleon chameleon) { + this.chameleon = chameleon; + } + + /** + * Get the Chameleon instance that triggered this event. + * + * @return Chameleon instance. + */ + public @NotNull Chameleon chameleon() { + return this.chameleon; + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonLoadEvent.java b/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonLoadEvent.java new file mode 100644 index 00000000..3c0cd5ad --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/event/common/ChameleonLoadEvent.java @@ -0,0 +1,59 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.event.common; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.ChameleonEvent; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; + +/** + * Chameleon load event. + *

Dispatched when Chameleon#onLoad is called by the plugin.

+ */ +public final class ChameleonLoadEvent implements ChameleonEvent { + + private final @NotNull Chameleon chameleon; + + /** + * ChameleonLoadEvent constructor. + *

This event is intended to be dispatched internally by Chameleon only.

+ * + * @param chameleon Chameleon instance. + */ + @Internal + public ChameleonLoadEvent(@NotNull Chameleon chameleon) { + this.chameleon = chameleon; + } + + /** + * Get the Chameleon instance that triggered this event. + * + * @return Chameleon instance. + */ + public @NotNull Chameleon chameleon() { + return this.chameleon; + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/exception/extension/ChameleonExtensionException.java b/api/src/main/java/dev/hypera/chameleon/exception/extension/ChameleonExtensionException.java index 3359c784..606570ad 100644 --- a/api/src/main/java/dev/hypera/chameleon/exception/extension/ChameleonExtensionException.java +++ b/api/src/main/java/dev/hypera/chameleon/exception/extension/ChameleonExtensionException.java @@ -24,7 +24,7 @@ package dev.hypera.chameleon.exception.extension; import dev.hypera.chameleon.exception.ChameleonRuntimeException; - +import org.jetbrains.annotations.NotNull; /** * Chameleon extension exception. @@ -72,4 +72,16 @@ protected ChameleonExtensionException(String message, Throwable cause, boolean e super(message, cause, enableSuppression, writableStackTrace); } + /** + * Returns a new Chameleon extension exception with the formatted message. + * + * @param message Message to format. + * @param args Format args. + * + * @return created Chameleon extension exception. + */ + public static @NotNull ChameleonExtensionException create(@NotNull String message, @NotNull Object... args) { + return new ChameleonExtensionException(String.format(message, args)); + } + } diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtension.java b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtension.java index 3bacc747..7b9e45b0 100644 --- a/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtension.java +++ b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtension.java @@ -23,55 +23,11 @@ */ package dev.hypera.chameleon.extension; -import dev.hypera.chameleon.Chameleon; -import org.jetbrains.annotations.NotNull; - /** * Extension. * - * @param Chameleon platform extension type. + * @see ChameleonPlatformExtension */ -public abstract class ChameleonExtension { - - protected final @NotNull T platform; - - /** - * Chameleon extension constructor. - * - * @param platform Chameleon platform extension instance. - */ - protected ChameleonExtension(@NotNull T platform) { - this.platform = platform; - } - - /** - * Called before Chameleon is loaded. - */ - public void onPreLoad() { - - } - - /** - * Called after Chameleon has loaded. - * - * @param chameleon Initialised Chameleon implementation. - */ - public void onLoad(@NotNull Chameleon chameleon) { - - } - - /** - * Called when the platform plugin is enabled. - */ - public void onEnable() { - - } - - /** - * Called when the platform plugin is disabled. - */ - public void onDisable() { - - } +public interface ChameleonExtension { } diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependency.java b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependency.java new file mode 100644 index 00000000..91ab0e01 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependency.java @@ -0,0 +1,180 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension; + +import dev.hypera.chameleon.util.Preconditions; +import java.util.Optional; +import org.jetbrains.annotations.ApiStatus.NonExtendable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Chameleon extension dependency. + */ +@NonExtendable +public interface ChameleonExtensionDependency { + + /** + * Create a new required extension dependency. + * + * @param extension Extension class. + * + * @return new dependency. + */ + @Contract("_ -> new") + static @NotNull ChameleonExtensionDependency required(@NotNull Class extension) { + Preconditions.checkNotNull("extension", extension); + return new ChameleonExtensionDependencyImpl(extension.getSimpleName(), extension, true); + } + + /** + * Create a new required extension dependency with the extension name. + * + * @param name Extension name. + * @param extension Extension class. + * + * @return new dependency. + */ + @Contract("_, _ -> new") + static @NotNull ChameleonExtensionDependency required(@NotNull String name, @NotNull Class extension) { + Preconditions.checkNotNull("name", name); + Preconditions.checkNotNull("extension", extension); + return new ChameleonExtensionDependencyImpl(name, extension, true); + } + + /** + * Create a new required extension dependency. + * + * @param extension Extension class name. + * + * @return new dependency. + */ + @Contract("_ -> new") + static @NotNull ChameleonExtensionDependency required(@NotNull String extension) { + Preconditions.checkNotNull("extension", extension); + return required(extension, extension); + } + + /** + * Create a new required extension dependency with the extension name. + * + * @param name Extension name. + * @param extension Extension class name. + * + * @return new dependency. + */ + @Contract("_, _ -> new") + static @NotNull ChameleonExtensionDependency required(@NotNull String name, @NotNull String extension) { + Preconditions.checkNotNull("name", name); + Preconditions.checkNotNull("extension", extension); + return new ChameleonExtensionDependencyImpl(name, extension, true); + } + + /** + * Returns whether this dependency is a required for loading the extension. + * + * @return required. + */ + boolean required(); + + /** + * Create a new optional extension dependency. + * + * @param extension Extension class. + * + * @return new dependency. + */ + @Contract("_ -> new") + static @NotNull ChameleonExtensionDependency optional(@NotNull Class extension) { + Preconditions.checkNotNull("extension", extension); + return optional(extension.getSimpleName(), extension); + } + + /** + * Create a new optional extension dependency with the extension name. + * + * @param name Extension name. + * @param extension Extension class. + * + * @return new dependency. + */ + @Contract("_, _ -> new") + static @NotNull ChameleonExtensionDependency optional(@NotNull String name, @NotNull Class extension) { + Preconditions.checkNotNull("name", name); + Preconditions.checkNotNull("extension", extension); + return new ChameleonExtensionDependencyImpl(name, extension, false); + } + + /** + * Create a new optional extension dependency. + * + * @param extension Extension class name. + * + * @return new dependency. + */ + @Contract("_ -> new") + static @NotNull ChameleonExtensionDependency optional(@NotNull String extension) { + Preconditions.checkNotNull("extension", extension); + return optional(extension, extension); + } + + /** + * Create a new optional extension dependency with the extension name. + * + * @param name Extension name. + * @param extension Extension class name. + * + * @return new dependency. + */ + @Contract("_, _ -> new") + static @NotNull ChameleonExtensionDependency optional(@NotNull String name, @NotNull String extension) { + Preconditions.checkNotNull("name", name); + Preconditions.checkNotNull("extension", extension); + return new ChameleonExtensionDependencyImpl(name, extension, false); + } + + /** + * Returns whether this dependency is optional for loading the extension. + * + * @return optional. + */ + default boolean optional() { + return !required(); + } + + /** + * Returns the name of the extension this dependency is for. + * + * @return extension name. + */ + @NotNull String name(); + + /** + * Returns the extension class this dependency is for. + * + * @return an optional containing the extension class, if found, otherwise an empty optional. + */ + @NotNull Optional> extension(); + +} diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependencyImpl.java b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependencyImpl.java new file mode 100644 index 00000000..7c25b342 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionDependencyImpl.java @@ -0,0 +1,83 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension; + +import java.util.Optional; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class ChameleonExtensionDependencyImpl implements ChameleonExtensionDependency { + + private final @NotNull String name; + private final boolean required; + private @Nullable String className; + private @Nullable Class extension; + + ChameleonExtensionDependencyImpl(@NotNull String name, @NotNull Class clazz, boolean required) { + this.name = name; + this.required = required; + this.extension = clazz; + } + + ChameleonExtensionDependencyImpl(@NotNull String name, @NotNull String className, boolean required) { + this.name = name; + this.required = required; + this.className = className; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull String name() { + return this.name; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public @NotNull Optional> extension() { + if (this.extension != null) { + return Optional.of(this.extension); + } + + try { + this.extension = (Class) Class.forName(this.className); + return Optional.of(this.extension); + } catch (ClassNotFoundException ex) { + return Optional.empty(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean required() { + return this.required; + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionFactory.java b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionFactory.java new file mode 100644 index 00000000..b6f3af39 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonExtensionFactory.java @@ -0,0 +1,70 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension; + +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import java.util.Collection; +import java.util.Collections; +import org.jetbrains.annotations.NotNull; + +/** + * Chameleon extension factory. + * + * @param Chameleon extension type. + */ +public interface ChameleonExtensionFactory { + + /** + * Create an extension instance for the given platform. + *

Note that the returned ChameleonPlatformExtension must implement + * {@code T}.

+ * + * @param platformId Platform identifier to create the extension for. This identifier can be + * compared against known platform identifiers stored as constants in + * {@link dev.hypera.chameleon.platform.Platform}. + * + * @return new extension instance. + * @throws ChameleonExtensionException if something goes wrong while creating the extension. + */ + @NotNull ChameleonPlatformExtension create(@NotNull String platformId) throws ChameleonExtensionException; + + /** + * Returns the dependencies this extension requires on the given platform. + * + * @param platformId Platform identifier. + * + * @return collection of dependencies. + */ + default @NotNull Collection getDependencies(@NotNull String platformId) { + return Collections.emptySet(); + } + + /** + * Returns the class of the Chameleon extension implementation that this factory supports. + * + * @return Chameleon extension class. + */ + @NotNull Class getType(); + +} diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ChameleonPlatformExtension.java b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonPlatformExtension.java index 515020bb..32c4f919 100644 --- a/api/src/main/java/dev/hypera/chameleon/extension/ChameleonPlatformExtension.java +++ b/api/src/main/java/dev/hypera/chameleon/extension/ChameleonPlatformExtension.java @@ -24,58 +24,41 @@ package dev.hypera.chameleon.extension; import dev.hypera.chameleon.Chameleon; -import dev.hypera.chameleon.util.ChameleonUtil; -import java.lang.reflect.InvocationTargetException; -import org.jetbrains.annotations.ApiStatus.NonExtendable; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.logger.ChameleonLogger; import org.jetbrains.annotations.NotNull; /** * Chameleon platform extension. + *

Classes implementing this interface must also implement {@code T}.

* - * @param Chameleon extension type. - * @param Chameleon platform extension type. - * @param Chameleon implementation type. + * @see ChameleonExtension */ -@NonExtendable -public abstract class ChameleonPlatformExtension, E extends CustomPlatformExtension, C extends Chameleon> { - - protected final @NotNull T extension; - - /** - * Chameleon platform extension constructor. - */ - @SuppressWarnings("unchecked") - protected ChameleonPlatformExtension() { - try { - Class customExtension = (Class) ChameleonUtil.getGenericTypeAsClass(getClass(), 1); - if (!customExtension.isAssignableFrom(getClass())) { - throw new IllegalStateException("ChameleonPlatformExtension must implement the used CustomPlatformExtension"); - } - - this.extension = (T) ChameleonUtil.getGenericTypeAsClass(getClass(), 0) - .getConstructor(customExtension) - .newInstance(customExtension.cast(this)); - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) { - throw new IllegalStateException(ex); - } - } +public interface ChameleonPlatformExtension { /** - * Called after Chameleon has loaded. + * Extension init. * - * @param chameleon Initialised Chameleon implementation. + *

This method will be called when the Extension is initialised by Chameleon, either before + * Chameleon is constructed, or when EventManager#loadExtension is called.

+ * + * @param logger Logger. + * @param eventBus Event bus. */ - public void onLoad(@NotNull C chameleon) { - - } + void init(@NotNull ChameleonLogger logger, @NotNull EventBus eventBus) throws ChameleonExtensionException; /** - * Get Chameleon extension instance. + * Extension load. + * + *

This method will be called when Chameleon has finished loading, or when + * EventManager#loadExtension is called after Chameleon has loaded.

+ * + *

If your extension is platform dependant, then you can cast {@code chameleon} to the + * platform Chameleon implementation, e.g. BukkitChameleon, BungeeCordChameleon, etc.

* - * @return Chameleon extension instance. + * @param chameleon Chameleon instance. */ - public final @NotNull T getExtension() { - return this.extension; - } + void load(@NotNull Chameleon chameleon); } diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ExtensionManager.java b/api/src/main/java/dev/hypera/chameleon/extension/ExtensionManager.java new file mode 100644 index 00000000..2c9c3995 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/extension/ExtensionManager.java @@ -0,0 +1,75 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension; + +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import java.util.Collection; +import java.util.Optional; +import org.jetbrains.annotations.ApiStatus.NonExtendable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Extension manager. + * + * @see ChameleonExtension + * @see ChameleonPlatformExtension + * @see ChameleonExtensionFactory + */ +@NonExtendable +public interface ExtensionManager { + + /** + * Load a Chameleon extension. + * + * @param factory The factory to create the Chameleon extension. + * @param Chameleon extension type. + * + * @return new Chameleon extension. + * @throws ChameleonExtensionException if something goes wrong while loading the extension. + */ + @Contract("_ -> _") + @NotNull T loadExtension(@NotNull ChameleonExtensionFactory factory) throws ChameleonExtensionException; + + /** + * Get a loaded Chameleon extension. + * + * @param clazz Chameleon extension class. + * @param Chameleon extension type. + * + * @return an optional containing the loaded Chameleon extension platform, if loaded, otherwise + * an empty optional. + */ + @Contract(value = "_ -> _", pure = true) + @NotNull Optional getExtension(@NotNull Class clazz); + + /** + * Get all loaded Chameleon extensions. + * + * @return loaded Chameleon extensions. + */ + @Contract(value = "-> _", pure = true) + @NotNull Collection getExtensions(); + +} diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ExtensionManagerImpl.java b/api/src/main/java/dev/hypera/chameleon/extension/ExtensionManagerImpl.java new file mode 100644 index 00000000..a2c6d6f0 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/extension/ExtensionManagerImpl.java @@ -0,0 +1,116 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.util.Pair; +import dev.hypera.chameleon.util.Preconditions; +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.NotNull; + +/** + * Extension manager implementation. + * + * @see ExtensionManager + */ +@Internal +public final class ExtensionManagerImpl implements ExtensionManager { + + private final @NotNull Chameleon chameleon; + private final @NotNull ExtensionMap loadedExtensions; + + /** + * Extension manager constructor. + * + * @param chameleon Chameleon instance. + * @param loadedExtensions Extensions. + */ + @Internal + public ExtensionManagerImpl(@NotNull Chameleon chameleon, @NotNull ExtensionMap loadedExtensions) { + this.chameleon = chameleon; + this.loadedExtensions = loadedExtensions; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull T loadExtension(@NotNull ChameleonExtensionFactory factory) throws ChameleonExtensionException { + Preconditions.checkNotNull("factory", factory); + return getExtension(factory.getType()).orElseGet(() -> { + // Check dependencies + Collection dependencies = factory.getDependencies(this.chameleon.getPlatform().getId()); + Collection missingDependencies = dependencies.parallelStream() + .filter(d -> d.required() && (d.extension().isEmpty() || getExtension(d.extension().get()).isEmpty())) + .collect(Collectors.toSet()); + if (!missingDependencies.isEmpty()) { + throw ChameleonExtensionException.create( + "%s requires dependencies but some are missing: %s", + factory.getType().getSimpleName(), + missingDependencies.parallelStream().map(ChameleonExtensionDependency::name) + .collect(Collectors.joining(", ")) + ); + } + + // Create the extension + ChameleonPlatformExtension extension = factory.create(this.chameleon.getPlatform().getId()); + Preconditions.checkNotNullState("extension", extension); + if (!factory.getType().isAssignableFrom(extension.getClass())) { + throw ChameleonExtensionException.create( + "Cannot load %s: not assignable from %s", + factory.getType().getSimpleName(), extension.getClass().getSimpleName() + ); + } + + // Initialise and load the extension + extension.init(this.chameleon.getLogger(), this.chameleon.getEventBus()); + extension.load(this.chameleon); + this.loadedExtensions.put(factory.getType(), Pair.of(extension, dependencies)); + return factory.getType().cast(extension); + }); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Optional getExtension(@NotNull Class clazz) { + return this.loadedExtensions.getExtension(clazz); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Collection getExtensions() { + return this.loadedExtensions.entrySet() + .parallelStream().map(e -> e.getKey().cast(e.getValue().first())) + .collect(Collectors.toUnmodifiableSet()); + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/extension/ExtensionMap.java b/api/src/main/java/dev/hypera/chameleon/extension/ExtensionMap.java new file mode 100644 index 00000000..51c3b674 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/extension/ExtensionMap.java @@ -0,0 +1,149 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension; + +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.util.Pair; +import dev.hypera.chameleon.util.graph.Graph; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import org.jetbrains.annotations.ApiStatus.Internal; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Extension map. + */ +@Internal +public final class ExtensionMap extends ConcurrentHashMap, Pair>> { + + private static final long serialVersionUID = 9010417357635273389L; + + /** + * Get the extension with the given class. + * + * @param key Extension class. + * @param Extension type. + * + * @return extension. + */ + @Contract(value = "_ -> _", pure = true) + public Optional getExtension(Class key) { + return Optional.ofNullable(super.get(key)).map(Pair::first).map(key::cast); + } + + /** + * Attempt to sort all extensions to be loaded by Chameleon in dependency order, using a + * depth-first search. + * + * @return sorted extensions. + * @throws ChameleonExtensionException if a circular dependency was detected. + */ + public @NotNull List loadSort() { + Graph> graph = Graph.>directed().build(); + for (Entry, Pair>> ext : entrySet()) { + // Add this extension to the graph. + graph.addNode(ext.getKey()); + + // Add the dependencies of this extension as edges to this node. + for (ChameleonExtensionDependency dependency : ext.getValue().second()) { + // Get the dependency extension class, if present. If the dependency extension class + // is not present, it might mean that the class is not in the classpath, there is a + // typo, or the class doesn't exist. + Class dependencyClass = dependency.extension().orElse(null); + if (dependencyClass != null) { + if (get(dependencyClass) != null) { + graph.putEdge(ext.getKey(), dependencyClass); + } + } else if (dependency.required()) { + throw ChameleonExtensionException.create( + "%s requires dependencies but some are missing: %s", + ext.getKey().getSimpleName(), dependency.name() + ); + } + } + } + + // None of the extensions have dependencies, therefore we don't need to sort the extensions. + if (graph.edges().isEmpty()) { + return values().stream().map(Pair::first).collect(Collectors.toUnmodifiableList()); + } + + // Sort the extensions by preforming a depth-first search. + List sortedExtensions = new ArrayList<>(); + Map, VisitState> visitedNodes = new HashMap<>(); + for (Class node : graph.nodes()) { + visitNode(graph, node, visitedNodes, sortedExtensions, new ArrayDeque<>()); + } + return sortedExtensions; + } + + private void visitNode( + @NotNull Graph> graph, + @NotNull Class node, + @NotNull Map, VisitState> visitedNodes, + @NotNull List sortedExtensions, + @NotNull Deque> scanStack + ) { + VisitState state = visitedNodes.get(node); + if (state == VisitState.COMPLETE) { + // This node has already been visited. + return; + } + + scanStack.addLast(node); + if (state == VisitState.PENDING) { + // Detected a circular dependency. + throw new ChameleonExtensionException("Detected circular dependencies: " + + scanStack.parallelStream().map(Class::getSimpleName) + .collect(Collectors.joining(" -> ")) + ); + } + + // Visit dependencies of this extension. + visitedNodes.put(node, VisitState.PENDING); + for (Class edge : graph.successors(node)) { + visitNode(graph, edge, visitedNodes, sortedExtensions, scanStack); + } + + visitedNodes.put(node, VisitState.COMPLETE); + scanStack.removeLast(); + sortedExtensions.add(Objects.requireNonNull(get(node)).first()); + } + + private enum VisitState { + PENDING, + COMPLETE + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/extension/CustomPlatformExtension.java b/api/src/main/java/dev/hypera/chameleon/extension/package-info.java similarity index 82% rename from api/src/main/java/dev/hypera/chameleon/extension/CustomPlatformExtension.java rename to api/src/main/java/dev/hypera/chameleon/extension/package-info.java index d88fdcec..475c6d18 100644 --- a/api/src/main/java/dev/hypera/chameleon/extension/CustomPlatformExtension.java +++ b/api/src/main/java/dev/hypera/chameleon/extension/package-info.java @@ -21,11 +21,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package dev.hypera.chameleon.extension; - /** - * Custom platform extension. + * The extensions package provides a way to create custom libraries that expand Chameleon's core + * functionality. Extensions can leverage platform-specific APIs to create additional events and + * functions, increasing the possibilities for plugins built with Chameleon. */ -public interface CustomPlatformExtension { - -} +package dev.hypera.chameleon.extension; diff --git a/api/src/main/java/dev/hypera/chameleon/util/ChameleonUtil.java b/api/src/main/java/dev/hypera/chameleon/util/ChameleonUtil.java index 1016b8f2..580f5e67 100644 --- a/api/src/main/java/dev/hypera/chameleon/util/ChameleonUtil.java +++ b/api/src/main/java/dev/hypera/chameleon/util/ChameleonUtil.java @@ -23,7 +23,6 @@ */ package dev.hypera.chameleon.util; -import java.lang.reflect.ParameterizedType; import org.jetbrains.annotations.ApiStatus.Experimental; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -53,16 +52,4 @@ private ChameleonUtil() { return s == null ? defaultValue : s; } - /** - * Get the class of a generic type. - * - * @param clazz Class to get the generic type on. - * @param generic Generic type index. - * - * @return Generic type as a class. - */ - public static @NotNull Class getGenericTypeAsClass(@NotNull Class clazz, int generic) { - return (Class) ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[generic]; - } - } diff --git a/api/src/main/java/dev/hypera/chameleon/util/Pair.java b/api/src/main/java/dev/hypera/chameleon/util/Pair.java new file mode 100644 index 00000000..24676b24 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/util/Pair.java @@ -0,0 +1,66 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.util; + +import org.jetbrains.annotations.ApiStatus.NonExtendable; +import org.jetbrains.annotations.NotNull; + +/** + * Pair, stores two values together. + * + * @param First type. + * @param Second type. + */ +@NonExtendable +public interface Pair { + + /** + * Returns a new pair with the given values. + * + * @param first First value. + * @param second Second value. + * @param First type. + * @param Second type. + * + * @return new pair. + */ + static @NotNull Pair of(@NotNull A first, @NotNull B second) { + return new PairImpl<>(first, second); + } + + /** + * Returns the first value. + * + * @return an optional containing the first value, if not null, otherwise an empty optional. + */ + @NotNull A first(); + + /** + * Returns the second value. + * + * @return an optional containing the second value, if not null, otherwise an empty optional. + */ + @NotNull B second(); + +} diff --git a/api/src/main/java/dev/hypera/chameleon/util/PairImpl.java b/api/src/main/java/dev/hypera/chameleon/util/PairImpl.java new file mode 100644 index 00000000..bf0e0d44 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/util/PairImpl.java @@ -0,0 +1,54 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.util; + +import org.jetbrains.annotations.NotNull; + +/** + * Pair implementation. + * + * @param First type. + * @param Second type. + */ +final class PairImpl implements Pair { + + private final @NotNull A first; + private final @NotNull B second; + + PairImpl(@NotNull A first, @NotNull B second) { + this.first = first; + this.second = second; + } + + @Override + public @NotNull A first() { + return this.first; + } + + @Override + public @NotNull B second() { + return this.second; + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/util/Preconditions.java b/api/src/main/java/dev/hypera/chameleon/util/Preconditions.java index 653c4f95..cdc0f542 100644 --- a/api/src/main/java/dev/hypera/chameleon/util/Preconditions.java +++ b/api/src/main/java/dev/hypera/chameleon/util/Preconditions.java @@ -23,6 +23,8 @@ */ package dev.hypera.chameleon.util; +import java.util.Collection; +import java.util.Objects; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,7 +45,6 @@ private Preconditions() { } - /** * Ensures the expression is {@code true} to validate an argument. * @@ -147,7 +148,6 @@ public static void checkState(boolean expression, @NotNull String messageFormat, if (value == null) { throw new NullPointerException(); } - return value; } @@ -166,7 +166,43 @@ public static void checkState(boolean expression, @NotNull String messageFormat, if (value == null) { throw new IllegalArgumentException(name.concat(" cannot be null")); } + return value; + } + + /** + * Ensures that the given {@code value} is not null. + * + * @param name Argument name, used in the exception message if {@code value} is null. + * @param value Argument value. + * @param Value type. + * + * @return {@code value}. + * @throws IllegalStateException if {@code value} is {@code null}. + */ + @Contract("_, !null -> param2; _, null -> fail") + public static @NotNull T checkNotNullState(@NotNull String name, @Nullable T value) { + if (value == null) { + throw new IllegalStateException(name.concat(" cannot be null")); + } + return value; + } + /** + * Ensures that the given {@code value} does not contain null. + * + * @param name Argument name, used in the exception message if {@code value} contains null. + * @param value Argument value. + * @param Value type. + * + * @return {@code value}. + * @throws IllegalArgumentException if {@code value} contains {@code null}. + */ + @Contract("_, !null -> param2; _, null -> fail") + public static @NotNull Collection checkNoneNull(@NotNull String name, @Nullable Collection value) { + Preconditions.checkNotNull(name, value); + if (value.parallelStream().anyMatch(Objects::isNull)) { + throw new IllegalArgumentException(name.concat(" cannot contain null")); + } return value; } diff --git a/api/src/main/java/dev/hypera/chameleon/util/graph/DirectedGraph.java b/api/src/main/java/dev/hypera/chameleon/util/graph/DirectedGraph.java new file mode 100644 index 00000000..9d70dee6 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/util/graph/DirectedGraph.java @@ -0,0 +1,187 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.util.graph; + +import dev.hypera.chameleon.util.Preconditions; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jetbrains.annotations.NotNull; + +/** + * Directed graph implementation. + *

This is not the best directed graph implementation, however it is enough for what Chameleon + * currently needs.

+ *

If you know more about graphs and want to improve this implementation, or maybe add other + * graph implementations, please create a pull request :)

+ * + * @param Node type. + */ +final class DirectedGraph implements Graph { + + private final @NotNull Collection nodes = new HashSet<>(); + private final @NotNull Collection> edges = new HashSet<>(); + private final boolean allowSelfLoops; + + DirectedGraph(boolean allowSelfLoops) { + this.allowSelfLoops = allowSelfLoops; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Collection nodes() { + return Collections.unmodifiableCollection(this.nodes); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Collection> edges() { + return Collections.unmodifiableCollection(this.edges); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Collection adjacentNodes(@NotNull T node) { + Preconditions.checkArgument(this.nodes.contains(node), + "provided node is not a node of this graph" + ); + return Collections.unmodifiableCollection(Stream.concat(predecessors(node).stream(), + successors(node).stream() + ).collect(Collectors.toSet())); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Collection predecessors(@NotNull T node) { + Preconditions.checkArgument(this.nodes.contains(node), + "provided node is not a node of this graph" + ); + return Collections.unmodifiableCollection(this.edges.parallelStream() + .filter(e -> e.target().equals(node)) + .map(Edge::source) + .collect(Collectors.toSet())); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Collection successors(@NotNull T node) { + Preconditions.checkArgument(this.nodes.contains(node), + "provided node is not a node of this graph" + ); + return Collections.unmodifiableCollection(this.edges.parallelStream() + .filter(e -> e.source().equals(node)) + .map(Edge::target) + .collect(Collectors.toSet())); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean addNode(@NotNull T node) { + return this.nodes.add(node); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean removeNode(@NotNull T node) { + boolean modified = this.nodes.remove(node); + modified |= this.edges.removeIf(e -> e.source().equals(node) || e.target().equals(node)); + return modified; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean putEdge(@NotNull Edge edge) { + checkSelfLoops(edge); + boolean modified = this.nodes.add(edge.source()); + modified |= this.nodes.add(edge.target()); + modified |= this.edges.add(edge); + return modified; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean removeEdge(@NotNull Edge edge) { + return this.edges.remove(edge); + } + + private void checkSelfLoops(@NotNull Edge edge) { + if (!this.allowSelfLoops) { + Preconditions.checkArgument(!edge.source().equals(edge.target()), + "self loops are not allowed" + ); + } + } + + static final class BuilderImpl implements Builder { + + private boolean allowSelfLoops = false; + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Builder allowSelfLoops() { + return allowSelfLoops(true); + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Builder allowSelfLoops(boolean allowSelfLoops) { + this.allowSelfLoops = allowSelfLoops; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Graph build() { + return new DirectedGraph<>(this.allowSelfLoops); + } + + } + +} diff --git a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/extension/ChameleonMinestomExtension.java b/api/src/main/java/dev/hypera/chameleon/util/graph/Edge.java similarity index 57% rename from platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/extension/ChameleonMinestomExtension.java rename to api/src/main/java/dev/hypera/chameleon/util/graph/Edge.java index ef7a267e..9545ffec 100644 --- a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/extension/ChameleonMinestomExtension.java +++ b/api/src/main/java/dev/hypera/chameleon/util/graph/Edge.java @@ -21,19 +21,49 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package dev.hypera.chameleon.platform.minestom.extension; +package dev.hypera.chameleon.util.graph; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.CustomPlatformExtension; -import dev.hypera.chameleon.platform.minestom.MinestomChameleon; +import org.jetbrains.annotations.NotNull; /** - * Chameleon Minestom extension. + * An edge between two nodes. * - * @param Chameleon extension type. - * @param Chameleon platform extension type. + * @param Node type. */ -public abstract class ChameleonMinestomExtension, C extends CustomPlatformExtension> extends ChameleonPlatformExtension { +public interface Edge { + + /** + * Create a new edge between {@code source} and {@code target}. + * + * @param source Edge source. + * @param target Edge target. + * @param Node type. + * + * @return new edge. + */ + static @NotNull Edge of(@NotNull T source, @NotNull T target) { + return new EdgeImpl<>(source, target); + } + + /** + * Returns the source of this edge. + * + * @return edge source. + */ + @NotNull T source(); + + /** + * Returns the target of this edge. + * + * @return edge target. + */ + @NotNull T target(); + + /** + * Returns a new edge with the sources and targets of this edge, but reversed. + * + * @return flipped edge. + */ + @NotNull Edge flip(); } diff --git a/api/src/main/java/dev/hypera/chameleon/util/graph/EdgeImpl.java b/api/src/main/java/dev/hypera/chameleon/util/graph/EdgeImpl.java new file mode 100644 index 00000000..16ae07b8 --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/util/graph/EdgeImpl.java @@ -0,0 +1,81 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.util.graph; + +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +final class EdgeImpl implements Edge { + + private final @NotNull T source; + private final @NotNull T target; + + EdgeImpl(@NotNull T source, @NotNull T target) { + this.source = source; + this.target = target; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull T source() { + return this.source; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull T target() { + return this.target; + } + + /** + * {@inheritDoc} + */ + @Override + public @NotNull Edge flip() { + return Edge.of(this.target, this.source); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Edge)) { + return false; + } + + Edge edge = (Edge) o; + return this.source.equals(edge.source()) && this.target.equals(edge.target()); + } + + @Override + public int hashCode() { + return Objects.hash(this.source, this.target); + } + +} diff --git a/api/src/main/java/dev/hypera/chameleon/util/graph/Graph.java b/api/src/main/java/dev/hypera/chameleon/util/graph/Graph.java new file mode 100644 index 00000000..8e31e06f --- /dev/null +++ b/api/src/main/java/dev/hypera/chameleon/util/graph/Graph.java @@ -0,0 +1,218 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.util.graph; + +import dev.hypera.chameleon.util.graph.DirectedGraph.BuilderImpl; +import java.util.Collection; +import org.jetbrains.annotations.ApiStatus.NonExtendable; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; + +/** + * Graph. + * + * @param Node type. + */ +public interface Graph { + + /** + * Create a new directed graph builder. + * + * @param Graph node type. + * + * @return new builder. + */ + @Contract(value = "-> new", pure = true) + static @NotNull Builder directed() { + return new DirectedGraph.BuilderImpl<>(); + } + + /** + * Returns all nodes in this graph. + * + * @return graph nodes. + */ + @Contract(value = "-> _", pure = true) + @NotNull Collection nodes(); + + /** + * Returns all edges in this graph. + * + * @return graph edges. + */ + @Contract(value = "-> _", pure = true) + @NotNull Collection> edges(); + + /** + * Returns the nodes which have an edge with {@code node} in this graph. + *

This is equivalent to the union of {@link #predecessors(Object)} and + * {@link #successors(Object)}

+ * + * @param node Node. + * + * @return adjacent nodes to {@code node}. + */ + @Contract(value = "_ -> _", pure = true) + @NotNull Collection adjacentNodes(@NotNull T node); + + /** + * Returns the nodes in this graph that have an edge with {@code node} as a target. + *

In an undirected graph this is equivalent to {@link #adjacentNodes(Object)}.

+ * + * @param node Node to get predecessors. + * + * @return predecessors to {@code node}. + */ + @Contract(value = "_ -> _", pure = true) + @NotNull Collection predecessors(@NotNull T node); + + /** + * Returns the nodes in this graph that have an edge with {@code node} as a source. + *

In an undirected graph this is equivalent to {@link #adjacentNodes(Object)}.

+ * + * @param node Node to get successors of. + * + * @return successors to {@code node}. + */ + @Contract(value = "_ -> _", pure = true) + @NotNull Collection successors(@NotNull T node); + + /** + * Adds {@code node} to this graph, if it is not already present. + * + * @param node Node to be added. + * + * @return {@code true} if the graph changed as a result of this call. + */ + @Contract("_ -> _") + boolean addNode(@NotNull T node); + + /** + * Removes {@code node} from this graph, if it is present. + *

All edges connecting to {@code node} will also be removed.

+ * + * @param node Node to be removed. + * + * @return {@code true} if the graph changed as a result of this call. + */ + @Contract("_ -> _") + boolean removeNode(@NotNull T node); + + /** + * Adds an edge connecting {@code source} and {@code target}, if not already present. + *

If this graph is directed, the edge will also be directed; otherwise it will be + * undirected.

+ * + * @param source Source node. + * @param target Target node. + * + * @return {@code true} if the graph changed as a result of this call. + * @throws IllegalArgumentException if this graph does not allow self-loops and this edge is a + * self-loop. + */ + @Contract("_, _ -> _") + default boolean putEdge(@NotNull T source, @NotNull T target) { + return putEdge(Edge.of(source, target)); + } + + /** + * Adds an edge connecting {@link Edge#source()} and {@link Edge#target()}, if not already + * present. + *

If this graph is directed, the edge will also be directed; otherwise it will be + * undirected.

+ * + * @param edge Edge to be added. + * + * @return {@code true} if the graph changed as a result of this call. + * @throws IllegalArgumentException if this graph does not allow self-loops and this edge is a + * self-loop. + */ + @Contract("_ -> _") + boolean putEdge(@NotNull Edge edge); + + /** + * Removes the edge connecting {@code source} and {@code target}, if present. + * + * @param source Source node. + * @param target Target node. + * + * @return {@code true} if the graph changed as a result of this call. + */ + @Contract("_, _ -> _") + default boolean removeEdge(@NotNull T source, @NotNull T target) { + return removeEdge(Edge.of(source, target)); + } + + /** + * Removes the edge connecting {@link Edge#source()} and {@link Edge#target()}, if present. + * + * @param edge Edge to be removed. + * + * @return {@code true} if the graph changed as a result of this call. + */ + @Contract("_ -> _") + boolean removeEdge(@NotNull Edge edge); + + /** + * Graph builder. + * + * @param Node type. + */ + @NonExtendable + interface Builder { + + /** + * Allow self-loops. + * + * @return {@code this}. + */ + @Contract("-> this") + @NotNull Builder allowSelfLoops(); + + /** + * Sets whether this graph should allow self-loops. + * + *

Attempting to add a self-loop to a graph with self-loops not allowed will cause an + * IllegalArgumentException to be thrown.

+ * + *

Defaults to {@code false}.

+ * + * @param allowSelfLoops Whether self-loops should be allowed. + * + * @return {@code this}. + */ + @Contract("_ -> this") + @NotNull Builder allowSelfLoops(boolean allowSelfLoops); + + /** + * Build graph. + * + * @return new graph. + */ + @Contract(value = "-> new", pure = true) + @NotNull Graph build(); + + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/DummyChameleon.java b/api/src/test/java/dev/hypera/chameleon/TestChameleon.java similarity index 73% rename from api/src/test/java/dev/hypera/chameleon/DummyChameleon.java rename to api/src/test/java/dev/hypera/chameleon/TestChameleon.java index c19bb689..02c4ede4 100644 --- a/api/src/test/java/dev/hypera/chameleon/DummyChameleon.java +++ b/api/src/test/java/dev/hypera/chameleon/TestChameleon.java @@ -25,7 +25,10 @@ import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.command.CommandManager; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.event.EventBusImpl; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; +import dev.hypera.chameleon.extension.ExtensionMap; import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.logger.DummyChameleonLogger; import dev.hypera.chameleon.platform.Platform; @@ -33,20 +36,22 @@ import dev.hypera.chameleon.scheduler.Scheduler; import dev.hypera.chameleon.user.UserManager; import java.nio.file.Path; -import java.util.Collections; import org.jetbrains.annotations.NotNull; /** * Dummy Chameleon implementation. */ -public final class DummyChameleon extends Chameleon { +public final class TestChameleon extends Chameleon { + + public static final @NotNull String PLATFORM_ID = "Test"; + private final @NotNull Platform platform = new TestChameleonPlatform(); /** * Dummy Chameleon implementation constructor. * * @throws ChameleonInstantiationException if something goes wrong whilst starting. */ - public DummyChameleon() throws ChameleonInstantiationException { + public TestChameleon() throws ChameleonInstantiationException { this(new DummyChameleonLogger()); } @@ -57,14 +62,33 @@ public DummyChameleon() throws ChameleonInstantiationException { * * @throws ChameleonInstantiationException if something goes wrong whilst starting. */ - public DummyChameleon(@NotNull ChameleonLogger logger) throws ChameleonInstantiationException { + public TestChameleon(@NotNull ChameleonLogger logger) throws ChameleonInstantiationException { + this(logger, new EventBusImpl(logger), new ExtensionMap()); + } + + /** + * Dummy Chameleon implementation constructor. + * + * @param logger Logger. + * @param eventBus Event bus. + * @param extensions Extensions. + * + * @throws ChameleonInstantiationException if something goes wrong whilst starting. + */ + public TestChameleon(@NotNull ChameleonLogger logger, @NotNull EventBus eventBus, @NotNull ExtensionMap extensions) throws ChameleonInstantiationException { super( - DummyChameleonPlugin.class, Collections.emptySet(), + TestChameleonPlugin.class, ChameleonPluginData.create("Chameleon", Chameleon.getVersion()), - logger + eventBus, + logger, + extensions ); } + public static @NotNull ChameleonBootstrap create() { + return new TestChameleonBootstrap(); + } + /** * {@inheritDoc} */ @@ -78,7 +102,7 @@ public DummyChameleon(@NotNull ChameleonLogger logger) throws ChameleonInstantia */ @Override public @NotNull Platform getPlatform() { - throw new UnsupportedOperationException("unsupported"); + return this.platform; } /** diff --git a/api/src/test/java/dev/hypera/chameleon/TestChameleonBootstrap.java b/api/src/test/java/dev/hypera/chameleon/TestChameleonBootstrap.java new file mode 100644 index 00000000..cc011ba6 --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/TestChameleonBootstrap.java @@ -0,0 +1,41 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon; + +import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; +import dev.hypera.chameleon.logger.DummyChameleonLogger; +import org.jetbrains.annotations.NotNull; + +final class TestChameleonBootstrap extends ChameleonBootstrap { + + TestChameleonBootstrap() { + super(new DummyChameleonLogger(), TestChameleon.PLATFORM_ID); + } + + @Override + protected @NotNull TestChameleon loadInternal() throws ChameleonInstantiationException { + return new TestChameleon(this.logger, this.eventBus, this.extensions); + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/TestChameleonPlatform.java b/api/src/test/java/dev/hypera/chameleon/TestChameleonPlatform.java new file mode 100644 index 00000000..55ea6ace --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/TestChameleonPlatform.java @@ -0,0 +1,66 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon; + +import dev.hypera.chameleon.platform.Platform; +import org.jetbrains.annotations.NotNull; + +public final class TestChameleonPlatform implements Platform { + + /** + * Get a unique identifier for this Platform. + *

This will return the common name of the API that is in use, e.g. "BungeeCord" or + * "Velocity".

+ * + * @return Platform identifier. + */ + @Override + public @NotNull String getId() { + return TestChameleon.PLATFORM_ID; + } + + /** + * Get the friendly name of this Platform. + *

This will return the name provided by the Platform, which may not match the name of the + * API that is in use.

+ * + * @return Platform friendly name. + */ + @Override + public @NotNull String getName() { + return "Test"; + } + + /** + * Get the version of this Platform. + *

This will return the version provided by the Platform.

+ * + * @return Platform version. + */ + @Override + public @NotNull String getVersion() { + return Chameleon.getVersion() + "-" + Chameleon.getCommit(); + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/DummyChameleonPlugin.java b/api/src/test/java/dev/hypera/chameleon/TestChameleonPlugin.java similarity index 93% rename from api/src/test/java/dev/hypera/chameleon/DummyChameleonPlugin.java rename to api/src/test/java/dev/hypera/chameleon/TestChameleonPlugin.java index ee195074..62dbaf8c 100644 --- a/api/src/test/java/dev/hypera/chameleon/DummyChameleonPlugin.java +++ b/api/src/test/java/dev/hypera/chameleon/TestChameleonPlugin.java @@ -28,14 +28,14 @@ /** * Dummy Chameleon plugin implementation. */ -public final class DummyChameleonPlugin extends ChameleonPlugin { +public final class TestChameleonPlugin extends ChameleonPlugin { /** * Chameleon plugin constructor. * * @param chameleon Chameleon implementation. */ - public DummyChameleonPlugin(@NotNull Chameleon chameleon) { + public TestChameleonPlugin(@NotNull Chameleon chameleon) { super(chameleon); } diff --git a/api/src/test/java/dev/hypera/chameleon/adventure/ReflectedAudienceTests.java b/api/src/test/java/dev/hypera/chameleon/adventure/ReflectedAudienceTests.java index 0f4c7580..48126682 100644 --- a/api/src/test/java/dev/hypera/chameleon/adventure/ReflectedAudienceTests.java +++ b/api/src/test/java/dev/hypera/chameleon/adventure/ReflectedAudienceTests.java @@ -33,7 +33,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import dev.hypera.chameleon.DummyChameleon; +import dev.hypera.chameleon.TestChameleon; import dev.hypera.chameleon.adventure.mapper.AdventureMapper; import dev.hypera.chameleon.adventure.matches.BossBarMatcher; import dev.hypera.chameleon.adventure.matches.BoundMatcher; @@ -70,18 +70,17 @@ final class ReflectedAudienceTests { private static AdventureMapper adventureMapper; - private Audience audience; + private Audience audience = mock(Audience.class); private Audience reflectedAudience; @BeforeAll static void loadAdventureMapper() throws ChameleonInstantiationException { - adventureMapper = new AdventureMapper(new DummyChameleon()); + adventureMapper = new AdventureMapper(new TestChameleon()); assertDoesNotThrow(adventureMapper::load); } @BeforeEach void setup() { - // Note: the first time #mock is called, it may take up to 1,000 ms to fully initialise. this.audience = mock(Audience.class); this.reflectedAudience = adventureMapper.createReflectedAudience(this.audience); } diff --git a/api/src/test/java/dev/hypera/chameleon/adventure/mapper/AdventureMapperTests.java b/api/src/test/java/dev/hypera/chameleon/adventure/mapper/AdventureMapperTests.java index d2357565..50a6a870 100644 --- a/api/src/test/java/dev/hypera/chameleon/adventure/mapper/AdventureMapperTests.java +++ b/api/src/test/java/dev/hypera/chameleon/adventure/mapper/AdventureMapperTests.java @@ -28,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import dev.hypera.chameleon.DummyChameleon; +import dev.hypera.chameleon.TestChameleon; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -39,7 +39,7 @@ final class AdventureMapperTests { @BeforeAll static void setup() throws ChameleonInstantiationException { - mapper = new AdventureMapper(new DummyChameleon()); + mapper = new AdventureMapper(new TestChameleon()); } @Test diff --git a/api/src/test/java/dev/hypera/chameleon/extension/ExtensionMapTests.java b/api/src/test/java/dev/hypera/chameleon/extension/ExtensionMapTests.java new file mode 100644 index 00000000..44e5f971 --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/extension/ExtensionMapTests.java @@ -0,0 +1,90 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.extension.objects.Test2Extension; +import dev.hypera.chameleon.extension.objects.Test2ExtensionImpl; +import dev.hypera.chameleon.extension.objects.TestCircularDetection1Extension; +import dev.hypera.chameleon.extension.objects.TestCircularDetection1ExtensionImpl; +import dev.hypera.chameleon.extension.objects.TestCircularDetection2Extension; +import dev.hypera.chameleon.extension.objects.TestCircularDetection2ExtensionImpl; +import dev.hypera.chameleon.extension.objects.TestExtension; +import dev.hypera.chameleon.extension.objects.TestExtensionImpl; +import dev.hypera.chameleon.util.Pair; +import java.util.Arrays; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +final class ExtensionMapTests { + + @Test + void testLoadSort() { + // Create extension map and extensions. + ExtensionMap extensionMap = new ExtensionMap(); + TestExtensionImpl testExtensionImpl = new TestExtensionImpl(); + Test2ExtensionImpl test2ExtensionImpl = new Test2ExtensionImpl(); + extensionMap.put(Test2Extension.class, Pair.of(test2ExtensionImpl, Arrays.asList( + ChameleonExtensionDependency.required("Test", TestExtension.class), + ChameleonExtensionDependency.optional(TestCircularDetection1Extension.class), + ChameleonExtensionDependency.optional(TestCircularDetection2Extension.class.getCanonicalName()), + ChameleonExtensionDependency.optional("dev.hypera.chameleon.nonexistant.NonexistantExtension") + ))); + extensionMap.put(TestExtension.class, Pair.of(testExtensionImpl, Collections.emptyList())); + + // Verify that the extension map contains the expected extensions. + assertThat(extensionMap).hasSize(2); + + // Verify that the extensions are sorted in the correct order. + assertThat(extensionMap.loadSort()) + .containsExactly(testExtensionImpl, test2ExtensionImpl) + .inOrder(); + } + + @Test + void testLoadSortCircular() { + // Create extension map and extensions. + ExtensionMap extensionMap = new ExtensionMap(); + TestCircularDetection1ExtensionImpl test1Impl = new TestCircularDetection1ExtensionImpl(); + TestCircularDetection2ExtensionImpl test2Impl = new TestCircularDetection2ExtensionImpl(); + extensionMap.put(TestCircularDetection1Extension.class, Pair.of(test1Impl, Collections.singleton( + ChameleonExtensionDependency.required(TestCircularDetection2Extension.class) + ))); + extensionMap.put(TestCircularDetection2Extension.class, Pair.of(test2Impl, Collections.singleton( + ChameleonExtensionDependency.required(TestCircularDetection1Extension.class) + ))); + + // Verify that the extension map contains the expected extensions. + assertThat(extensionMap).hasSize(2); + + // Verify that the circular dependencies are detected. + ChameleonExtensionException ex = assertThrowsExactly( + ChameleonExtensionException.class, extensionMap::loadSort); + assertThat(ex).hasMessageThat().contains("Detected circular dependencies"); + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/extension/ExtensionTests.java b/api/src/test/java/dev/hypera/chameleon/extension/ExtensionTests.java index 6eb906dd..8de686c5 100644 --- a/api/src/test/java/dev/hypera/chameleon/extension/ExtensionTests.java +++ b/api/src/test/java/dev/hypera/chameleon/extension/ExtensionTests.java @@ -23,27 +23,161 @@ */ package dev.hypera.chameleon.extension; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; -import dev.hypera.chameleon.extension.objects.TestInvalidPlatformExtension; -import dev.hypera.chameleon.extension.objects.TestPlatformExtension; +import dev.hypera.chameleon.TestChameleon; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; +import dev.hypera.chameleon.extension.objects.Test2Extension; +import dev.hypera.chameleon.extension.objects.Test2ExtensionImpl; +import dev.hypera.chameleon.extension.objects.TestExtension; +import dev.hypera.chameleon.extension.objects.TestExtensionFactory; +import dev.hypera.chameleon.extension.objects.TestExtensionImpl; +import dev.hypera.chameleon.extension.objects.TestRequiredDependencyEmptyExtension; +import java.util.Collections; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; final class ExtensionTests { + private TestChameleon chameleon; + private @NotNull TestExtensionFactory factory = spy(TestExtension.create(spy(new TestExtensionImpl()))); + + @BeforeEach + void setup() throws ChameleonInstantiationException { + this.chameleon = new TestChameleon(); + this.factory = spy(TestExtension.create(spy(new TestExtensionImpl()))); + } + + @Test + void testDependencies() { + assertTrue(ChameleonExtensionDependency.required("test-required").required()); + assertFalse(ChameleonExtensionDependency.required("test-required").optional()); + assertFalse(ChameleonExtensionDependency.optional("test-optional").required()); + assertTrue(ChameleonExtensionDependency.optional("test-optional").optional()); + } + + @Test + void testExtensionManagerLoad() { + // Load extension + TestExtension ext = this.chameleon.getExtensionManager().loadExtension(this.factory); + assertThat(ext.greet("Chameleon")).isEqualTo("こんにちは、Chameleon!"); + + // Verify that the factory created the extension, and the extension was initialised and loaded. + verify(this.factory, times(1)).create(TestChameleon.PLATFORM_ID); + verify((ChameleonPlatformExtension) ext, times(1)) + .init(this.chameleon.getLogger(), this.chameleon.getEventBus()); + verify((ChameleonPlatformExtension) ext, times(1)).load(this.chameleon); + + // Verify that the extension can be retrieved from the extension manager. + assertThat(this.chameleon.getExtensionManager().getExtensions()).hasSize(1); + assertThat(this.chameleon.getExtensionManager().getExtension(TestExtension.class)).hasValue(ext); + assertThat( + this.chameleon.getExtensionManager() + .loadExtension(TestExtension.create(new TestExtensionImpl())) + ).isEqualTo(ext); + } + + @Test + void testBootstrapLoad() throws ChameleonInstantiationException { + // Create a new Chameleon bootstrap with the extension and load it. + TestChameleon testChameleon = TestChameleon.create().withExtension(this.factory).load(); + + // Verify that the extension was initialised and loaded. + assertThat(testChameleon.getExtensionManager().getExtensions()).hasSize(1); + TestExtension ext = testChameleon.getExtensionManager().getExtension(TestExtension.class) + .orElseGet(() -> fail("extension missing")); + verify((ChameleonPlatformExtension) ext, times(1)) + .init(testChameleon.getLogger(), testChameleon.getEventBus()); + verify((ChameleonPlatformExtension) ext, times(1)).load(testChameleon); + + // Verify that attempting to load the extension again will just return the already loaded ext. + assertThat( + testChameleon.getExtensionManager() + .loadExtension(TestExtension.create(new TestExtensionImpl())) + ).isEqualTo(ext); + } + @Test - void catchesInvalid() { - assertDoesNotThrow(TestPlatformExtension::new); - assertThrows(IllegalStateException.class, TestInvalidPlatformExtension::new); + void testExtensionDependency() { + // Load first extension. + TestExtension ext = this.chameleon.getExtensionManager().loadExtension(this.factory); + assertThat(ext.greet("Chameleon")).isEqualTo("こんにちは、Chameleon!"); + + // Load second extension which depends on the first extension. + Test2Extension ext2 = this.chameleon.getExtensionManager() + .loadExtension(Test2Extension.create(new Test2ExtensionImpl())); + assertThat(ext2.greet("Chameleon")).isEqualTo("こんにちは、Chameleon!"); + + // Verify that the first extension was called by the second extension. + verify(ext, times(2)).greet("Chameleon"); } @Test - void successfullyLoadsParent() { - TestPlatformExtension platformExtension = assertDoesNotThrow(TestPlatformExtension::new); - assertNotNull(platformExtension.extension); - assertNotNull(platformExtension.getExtension()); + void testExtensionMissingDependencyFails() { + ChameleonExtensionException ex = assertThrowsExactly(ChameleonExtensionException.class, () -> + this.chameleon.getExtensionManager().loadExtension(Test2Extension.create( + new Test2ExtensionImpl(), Collections.singleton( + ChameleonExtensionDependency.required("Test", TestExtension.class) + )))); + assertThat(ex).hasMessageThat().isEqualTo( + "Test2Extension requires dependencies but some are missing: Test" + ); + } + + @Test + void testFactoryReturnsInvalidFails() { + // Attempt to load the extension using the extension manager. + ChameleonExtensionException ex = assertThrowsExactly(ChameleonExtensionException.class, () -> + this.chameleon.getExtensionManager().loadExtension(TestExtension.create(new Test2ExtensionImpl()))); + assertThat(ex).hasMessageThat().isEqualTo( + "Cannot load TestExtension: not assignable from Test2ExtensionImpl" + ); + + // Create a new Chameleon bootstrap with the extension and attempt to load it. + ex = assertThrowsExactly(ChameleonExtensionException.class, () -> + TestChameleon.create().withExtension( + TestExtension.create(new Test2ExtensionImpl()) + ).load()); + assertThat(ex).hasMessageThat().isEqualTo( + "Cannot load TestExtension: not assignable from Test2ExtensionImpl" + ); + } + + @Test + void testExtensionRequiredDependencyEmptyFails() { + // Attempt to load the extension using the extension manager. + ChameleonExtensionException ex = assertThrowsExactly(ChameleonExtensionException.class, () -> + this.chameleon.getExtensionManager().loadExtension( + TestExtension.create(new TestRequiredDependencyEmptyExtension(), Collections.singleton( + ChameleonExtensionDependency.required("dev.hypera.chameleon.nonexistant.NonexistantExtension") + )) + )); + assertThat(ex).hasMessageThat().isEqualTo( + "TestExtension requires dependencies but some are missing: " + + "dev.hypera.chameleon.nonexistant.NonexistantExtension" + ); + + // Create a new Chameleon bootstrap with the extension and attempt to load it. + ex = assertThrowsExactly(ChameleonExtensionException.class, () -> + TestChameleon.create().withExtension( + TestExtension.create(new TestRequiredDependencyEmptyExtension(), Collections.singleton( + ChameleonExtensionDependency.required("dev.hypera.chameleon.nonexistant.NonexistantExtension") + )) + ).load()); + assertThat(ex).hasMessageThat().isEqualTo( + "TestExtension requires dependencies but some are missing: " + + "dev.hypera.chameleon.nonexistant.NonexistantExtension" + ); } } diff --git a/api/src/main/java/dev/hypera/chameleon/extension/annotations/PostLoadable.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/Test2Extension.java similarity index 65% rename from api/src/main/java/dev/hypera/chameleon/extension/annotations/PostLoadable.java rename to api/src/test/java/dev/hypera/chameleon/extension/objects/Test2Extension.java index ccb7906c..9f932bb7 100644 --- a/api/src/main/java/dev/hypera/chameleon/extension/annotations/PostLoadable.java +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/Test2Extension.java @@ -21,30 +21,25 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package dev.hypera.chameleon.extension.annotations; +package dev.hypera.chameleon.extension.objects; import dev.hypera.chameleon.extension.ChameleonExtension; +import dev.hypera.chameleon.extension.ChameleonExtensionDependency; import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.util.Collection; +import java.util.Collections; import org.jetbrains.annotations.NotNull; -/** - * Post loadable. Used to mark an extension as capable of being loaded after Chameleon. - *

Warning: When an extension is "post loaded", the {@link ChameleonExtension#onPreLoad()} method - * will not be called.

- */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface PostLoadable { +public interface Test2Extension extends ChameleonExtension { + + static @NotNull TestExtensionFactory create(@NotNull ChameleonPlatformExtension extension) { + return create(extension, Collections.emptySet()); + } + + static @NotNull TestExtensionFactory create(@NotNull ChameleonPlatformExtension extension, @NotNull Collection dependencies) { + return new TestExtensionFactory<>(extension, dependencies, Test2Extension.class); + } - /** - * Get the platform extensions for this extension. - * - * @return platform extensions. - */ - @NotNull Class>[] value(); + @NotNull String greet(@NotNull String name); } diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/Test2ExtensionImpl.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/Test2ExtensionImpl.java new file mode 100644 index 00000000..5c5d1069 --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/Test2ExtensionImpl.java @@ -0,0 +1,58 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension.objects; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import dev.hypera.chameleon.logger.ChameleonLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class Test2ExtensionImpl implements ChameleonPlatformExtension, Test2Extension { + + private @Nullable Chameleon chameleon; + + @Override + public void init(@NotNull ChameleonLogger logger, @NotNull EventBus eventBus) throws ChameleonExtensionException { + + } + + @Override + public void load(@NotNull Chameleon chameleon) { + this.chameleon = chameleon; + } + + @Override + public @NotNull String greet(@NotNull String name) { + if (this.chameleon == null) { + throw new IllegalStateException("extension has not been loaded yet"); + } + + return this.chameleon.getExtensionManager().getExtension(TestExtension.class) + .orElseThrow(IllegalStateException::new).greet(name); + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestPlatform.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection1Extension.java similarity index 90% rename from api/src/test/java/dev/hypera/chameleon/extension/objects/TestPlatform.java rename to api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection1Extension.java index bb339df7..c9348a8b 100644 --- a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestPlatform.java +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection1Extension.java @@ -23,8 +23,8 @@ */ package dev.hypera.chameleon.extension.objects; -import dev.hypera.chameleon.extension.CustomPlatformExtension; +import dev.hypera.chameleon.extension.ChameleonExtension; -public interface TestPlatform extends CustomPlatformExtension { +public interface TestCircularDetection1Extension extends ChameleonExtension { } diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection1ExtensionImpl.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection1ExtensionImpl.java new file mode 100644 index 00000000..25d25c02 --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection1ExtensionImpl.java @@ -0,0 +1,45 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension.objects; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import dev.hypera.chameleon.logger.ChameleonLogger; +import org.jetbrains.annotations.NotNull; + +public final class TestCircularDetection1ExtensionImpl implements ChameleonPlatformExtension, TestCircularDetection1Extension { + + @Override + public void init(@NotNull ChameleonLogger logger, @NotNull EventBus eventBus) throws ChameleonExtensionException { + + } + + @Override + public void load(@NotNull Chameleon chameleon) { + + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestInvalidExtension.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection2Extension.java similarity index 85% rename from api/src/test/java/dev/hypera/chameleon/extension/objects/TestInvalidExtension.java rename to api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection2Extension.java index a254b569..aac13d1d 100644 --- a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestInvalidExtension.java +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection2Extension.java @@ -26,11 +26,8 @@ import dev.hypera.chameleon.extension.ChameleonExtension; import org.jetbrains.annotations.NotNull; -public class TestInvalidExtension extends ChameleonExtension { +public interface TestCircularDetection2Extension extends ChameleonExtension { - // Invalid constructor - public TestInvalidExtension(@NotNull TestPlatform platform, @NotNull String shouldNotBeHere) { - super(platform); - } + void test(@NotNull String name); } diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection2ExtensionImpl.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection2ExtensionImpl.java new file mode 100644 index 00000000..4ecd88ea --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestCircularDetection2ExtensionImpl.java @@ -0,0 +1,56 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension.objects; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import dev.hypera.chameleon.logger.ChameleonLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class TestCircularDetection2ExtensionImpl implements ChameleonPlatformExtension, TestCircularDetection2Extension { + + private @Nullable ChameleonLogger logger; + + @Override + public void init(@NotNull ChameleonLogger logger, @NotNull EventBus eventBus) throws ChameleonExtensionException { + this.logger = logger; + } + + @Override + public void load(@NotNull Chameleon chameleon) { + + } + + @Override + public void test(@NotNull String name) { + if (this.logger == null) { + throw new IllegalStateException("extension has not been initialised yet"); + } + this.logger.info("Hello, %s!", name); + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtension.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtension.java index 5dccfa7a..8f009019 100644 --- a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtension.java +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtension.java @@ -24,12 +24,24 @@ package dev.hypera.chameleon.extension.objects; import dev.hypera.chameleon.extension.ChameleonExtension; +import dev.hypera.chameleon.extension.ChameleonExtensionDependency; +import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import java.util.Collection; +import java.util.Collections; import org.jetbrains.annotations.NotNull; -public class TestExtension extends ChameleonExtension { +public interface TestExtension extends ChameleonExtension { - public TestExtension(@NotNull TestPlatform platform) { - super(platform); + static @NotNull TestExtensionFactory create(@NotNull ChameleonPlatformExtension extension) { + return create(extension, Collections.emptySet()); + } + + static @NotNull TestExtensionFactory create(@NotNull ChameleonPlatformExtension extension, @NotNull Collection dependencies) { + return new TestExtensionFactory<>(extension, dependencies, TestExtension.class); + } + + default @NotNull String greet(@NotNull String name) { + return "Hello, " + name + "!"; } } diff --git a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/extension/VelocityChameleonExtension.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionFactory.java similarity index 50% rename from platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/extension/VelocityChameleonExtension.java rename to api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionFactory.java index bd65fa45..07e5f88c 100644 --- a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/extension/VelocityChameleonExtension.java +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionFactory.java @@ -21,19 +21,41 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package dev.hypera.chameleon.platform.velocity.extension; +package dev.hypera.chameleon.extension.objects; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; import dev.hypera.chameleon.extension.ChameleonExtension; +import dev.hypera.chameleon.extension.ChameleonExtensionDependency; +import dev.hypera.chameleon.extension.ChameleonExtensionFactory; import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.CustomPlatformExtension; -import dev.hypera.chameleon.platform.velocity.VelocityChameleon; +import java.util.Collection; +import org.jetbrains.annotations.NotNull; -/** - * Chameleon Velocity extension. - * - * @param Chameleon extension type. - * @param Chameleon platform extension type. - */ -public abstract class VelocityChameleonExtension, C extends CustomPlatformExtension> extends ChameleonPlatformExtension { +public class TestExtensionFactory implements ChameleonExtensionFactory { + + private final @NotNull ChameleonPlatformExtension extension; + private final @NotNull Collection dependencies; + private final @NotNull Class type; + + public TestExtensionFactory(@NotNull ChameleonPlatformExtension extension, @NotNull Collection dependencies, @NotNull Class type) { + this.extension = extension; + this.dependencies = dependencies; + this.type = type; + } + + @Override + public @NotNull ChameleonPlatformExtension create(@NotNull String platformId) throws ChameleonExtensionException { + return this.extension; + } + + @Override + public @NotNull Collection getDependencies(@NotNull String platformId) { + return this.dependencies; + } + + @Override + public @NotNull Class getType() { + return this.type; + } } diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionImpl.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionImpl.java new file mode 100644 index 00000000..34a89bc9 --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestExtensionImpl.java @@ -0,0 +1,50 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.extension.objects; + +import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; +import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import dev.hypera.chameleon.logger.ChameleonLogger; +import org.jetbrains.annotations.NotNull; + +public class TestExtensionImpl implements ChameleonPlatformExtension, TestExtension { + + @Override + public void init(@NotNull ChameleonLogger logger, @NotNull EventBus eventBus) throws ChameleonExtensionException { + + } + + @Override + public void load(@NotNull Chameleon chameleon) { + + } + + @Override + public @NotNull String greet(@NotNull String name) { + return String.format("こんにちは、%s!", name); + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestPlatformExtension.java b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestRequiredDependencyEmptyExtension.java similarity index 72% rename from api/src/test/java/dev/hypera/chameleon/extension/objects/TestPlatformExtension.java rename to api/src/test/java/dev/hypera/chameleon/extension/objects/TestRequiredDependencyEmptyExtension.java index d8b5f93a..fbf9984a 100644 --- a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestPlatformExtension.java +++ b/api/src/test/java/dev/hypera/chameleon/extension/objects/TestRequiredDependencyEmptyExtension.java @@ -24,8 +24,22 @@ package dev.hypera.chameleon.extension.objects; import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.event.EventBus; +import dev.hypera.chameleon.exception.extension.ChameleonExtensionException; import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import dev.hypera.chameleon.logger.ChameleonLogger; +import org.jetbrains.annotations.NotNull; -public class TestPlatformExtension extends ChameleonPlatformExtension implements TestPlatform { +public class TestRequiredDependencyEmptyExtension implements ChameleonPlatformExtension, TestExtension { + + @Override + public void init(@NotNull ChameleonLogger logger, @NotNull EventBus eventBus) throws ChameleonExtensionException { + + } + + @Override + public void load(@NotNull Chameleon chameleon) { + + } } diff --git a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestInvalidPlatformExtension.java b/api/src/test/java/dev/hypera/chameleon/util/ChameleonUtilTests.java similarity index 77% rename from api/src/test/java/dev/hypera/chameleon/extension/objects/TestInvalidPlatformExtension.java rename to api/src/test/java/dev/hypera/chameleon/util/ChameleonUtilTests.java index 54b65d10..5067f037 100644 --- a/api/src/test/java/dev/hypera/chameleon/extension/objects/TestInvalidPlatformExtension.java +++ b/api/src/test/java/dev/hypera/chameleon/util/ChameleonUtilTests.java @@ -21,12 +21,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package dev.hypera.chameleon.extension.objects; +package dev.hypera.chameleon.util; -import dev.hypera.chameleon.Chameleon; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; +import static org.junit.jupiter.api.Assertions.assertEquals; -// Has parent extension with invalid constructor -public class TestInvalidPlatformExtension extends ChameleonPlatformExtension implements TestPlatform { +import org.junit.jupiter.api.Test; + +final class ChameleonUtilTests { + + @Test + void testGetOrDefault() { + assertEquals("test", ChameleonUtil.getOrDefault("test", "test2")); + assertEquals("test", ChameleonUtil.getOrDefault(null, "test")); + } } diff --git a/api/src/test/java/dev/hypera/chameleon/util/PreconditionsTests.java b/api/src/test/java/dev/hypera/chameleon/util/PreconditionsTests.java index d1a450b0..eb20c2d1 100644 --- a/api/src/test/java/dev/hypera/chameleon/util/PreconditionsTests.java +++ b/api/src/test/java/dev/hypera/chameleon/util/PreconditionsTests.java @@ -26,6 +26,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import java.util.Collections; import org.junit.jupiter.api.Test; final class PreconditionsTests { @@ -34,6 +35,7 @@ final class PreconditionsTests { void checkArgument() { assertDoesNotThrow(() -> Preconditions.checkArgument(true)); assertDoesNotThrow(() -> Preconditions.checkArgument(true, "test")); + assertDoesNotThrow(() -> Preconditions.checkArgument(true, "Hello, %s!", "world")); assertThrowsExactly(IllegalArgumentException.class, () -> Preconditions.checkArgument(false)); assertThrowsExactly(IllegalArgumentException.class, () -> @@ -46,6 +48,7 @@ void checkArgument() { void checkState() { assertDoesNotThrow(() -> Preconditions.checkState(true)); assertDoesNotThrow(() -> Preconditions.checkState(true, "test")); + assertDoesNotThrow(() -> Preconditions.checkState(true, "Hello, %s!", "world")); assertThrowsExactly(IllegalStateException.class, () -> Preconditions.checkState(false)); assertThrowsExactly(IllegalStateException.class, () -> @@ -58,10 +61,22 @@ void checkState() { void checkNotNull() { assertDoesNotThrow(() -> Preconditions.checkNotNull("value")); assertDoesNotThrow(() -> Preconditions.checkNotNull("value", "test")); + assertDoesNotThrow(() -> Preconditions.checkNotNullState("value", "test")); assertThrowsExactly(NullPointerException.class, () -> Preconditions.checkNotNull(null)); assertThrowsExactly(IllegalArgumentException.class, () -> Preconditions.checkNotNull("test", null), "test"); + assertThrowsExactly(IllegalStateException.class, () -> + Preconditions.checkNotNullState("test", null), "test"); + } + + @Test + void checkNoneNull() { + assertDoesNotThrow(() -> Preconditions.checkNoneNull("test", Collections.emptySet())); + assertThrowsExactly(IllegalArgumentException.class, () -> + Preconditions.checkNoneNull("test", null), "test cannot be null"); + assertThrowsExactly(IllegalArgumentException.class, () -> + Preconditions.checkNoneNull("test", Collections.singleton(null)), "test cannot contain null"); } } diff --git a/api/src/test/java/dev/hypera/chameleon/util/graph/EdgeTests.java b/api/src/test/java/dev/hypera/chameleon/util/graph/EdgeTests.java new file mode 100644 index 00000000..b81d2ab5 --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/util/graph/EdgeTests.java @@ -0,0 +1,52 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.util.graph; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +final class EdgeTests { + + @Test + void testEquality() { + Edge edge = Edge.of(1, 2); + assertEquals(edge, edge); + assertNotEquals(null, edge); + assertNotEquals(edge, new Object()); + assertEquals(Edge.of(1, 2), Edge.of(1, 2)); + assertEquals(Edge.of("test", "test2"), Edge.of("test", "test2")); + assertNotEquals(Edge.of(1, 2), Edge.of(1, 3)); + assertNotEquals(Edge.of(2, 3), Edge.of(1, 3)); + } + + @Test + void testFlip() { + assertThat(Edge.of(1, 2).flip()).isEqualTo(Edge.of(2, 1)); + assertThat(Edge.of("test", "test2").flip()).isEqualTo(Edge.of("test2", "test")); + } + +} diff --git a/api/src/test/java/dev/hypera/chameleon/util/graph/GraphTests.java b/api/src/test/java/dev/hypera/chameleon/util/graph/GraphTests.java new file mode 100644 index 00000000..4a63599b --- /dev/null +++ b/api/src/test/java/dev/hypera/chameleon/util/graph/GraphTests.java @@ -0,0 +1,138 @@ +/* + * This file is a part of the Chameleon Framework, licensed under the MIT License. + * + * Copyright (c) 2021-2023 The Chameleon Framework Authors. + * + * 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 dev.hypera.chameleon.util.graph; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import java.util.Collection; +import org.junit.jupiter.api.Test; + +final class GraphTests { + + @Test + void testNodesReturnedCollectionMutability() { + Graph graph = Graph.directed().build(); + graph.addNode(1); + + Collection nodes = graph.nodes(); + assertThrows(UnsupportedOperationException.class, () -> nodes.add(2)); + assertThrows(UnsupportedOperationException.class, () -> nodes.remove(1)); + } + + @Test + void testEdgesReturnedCollectionMutability() { + Graph graph = Graph.directed().build(); + graph.addNode(1); + + Edge testEdge = Edge.of(1, 2); + Collection> edges = graph.edges(); + assertThrows(UnsupportedOperationException.class, () -> edges.add(testEdge)); + assertThrows(UnsupportedOperationException.class, () -> edges.remove(testEdge)); + } + + @Test + void testAdjacentNodesReturnedCollectionMutability() { + Graph graph = Graph.directed().build(); + graph.addNode(1); + + Collection adjacentNodes = graph.adjacentNodes(1); + assertThrows(UnsupportedOperationException.class, () -> adjacentNodes.add(2)); + assertThrows(UnsupportedOperationException.class, () -> adjacentNodes.remove(1)); + } + + @Test + void testPredecessorsReturnedCollectionMutability() { + Graph graph = Graph.directed().build(); + graph.addNode(1); + + Collection predecessors = graph.predecessors(1); + assertThrows(UnsupportedOperationException.class, () -> predecessors.add(2)); + assertThrows(UnsupportedOperationException.class, () -> predecessors.remove(1)); + } + + @Test + void testSuccessorsReturnedCollectionMutability() { + Graph graph = Graph.directed().build(); + graph.addNode(1); + + Collection successors = graph.successors(1); + assertThrows(UnsupportedOperationException.class, () -> successors.add(2)); + assertThrows(UnsupportedOperationException.class, () -> successors.remove(1)); + } + + @Test + void testPredecessorsOneEdge() { + Graph graph = Graph.directed().build(); + graph.putEdge(1, 2); + assertThat(graph.predecessors(2)).containsExactly(1); + assertThat(graph.predecessors(1)).isEmpty(); + } + + @Test + void testSuccessorsOneEdge() { + Graph graph = Graph.directed().build(); + graph.putEdge(1, 2); + assertThat(graph.successors(1)).containsExactly(2); + assertThat(graph.successors(2)).isEmpty(); + } + + @Test + void testRemoveNodeRemovesEdges() { + Graph graph = Graph.directed().build(); + graph.putEdge(1, 2); + graph.putEdge(2, 1); + assertThat(graph.nodes()).containsExactly(1, 2); + assertThat(graph.edges()).hasSize(2); + graph.removeNode(1); + assertThat(graph.nodes()).containsExactly(2); + assertThat(graph.edges()).hasSize(0); + } + + @Test + void testRemoveEdges() { + Graph graph = Graph.directed().build(); + graph.putEdge(1, 2); + assertThat(graph.nodes()).containsExactly(1, 2); + assertThat(graph.edges()).hasSize(1); + graph.removeEdge(1, 2); + assertThat(graph.nodes()).containsExactly(1, 2); + assertThat(graph.edges()).hasSize(0); + } + + @Test + void testPreventsSelfLoops() { + // Verify that a graph that does not allow self-loops will prevent self-loops. + Graph graph = Graph.directed().allowSelfLoops(false).build(); + assertThrowsExactly(IllegalArgumentException.class, () -> + graph.putEdge(1, 1)); + + // Verify that a graph with allow self-loops will actually allow a self-loop. + Graph graph2 = Graph.directed().allowSelfLoops().build(); + assertDoesNotThrow(() -> graph2.putEdge(1, 1)); + } + +} diff --git a/build-logic/src/main/kotlin/chameleon.common.gradle.kts b/build-logic/src/main/kotlin/chameleon.common.gradle.kts index c167bc03..8f11913a 100644 --- a/build-logic/src/main/kotlin/chameleon.common.gradle.kts +++ b/build-logic/src/main/kotlin/chameleon.common.gradle.kts @@ -37,6 +37,10 @@ dependencies { testImplementation(libs.findLibrary("test-junit-engine").get()) testImplementation(libs.findLibrary("test-junit-params").get()) + /* Truth */ + testImplementation(libs.findLibrary("test-truth").get()) + testImplementation(libs.findLibrary("test-truth-java8").get()) + /* Mockito */ testImplementation(platform(libs.findLibrary("test-mockito-bom").get())) testImplementation(libs.findLibrary("test-mockito-core").get()) diff --git a/build.gradle.kts b/build.gradle.kts index bb27e71b..55d42cde 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,7 +32,7 @@ plugins { } group = "dev.hypera" -version = "0.14.0-SNAPSHOT" +version = "0.15.0-SNAPSHOT" description = "Cross-platform Minecraft plugin framework" indraSonatype { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ce8dc70d..404825aa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ build-nullaway-plugin = "1.5.0" # Test test-junit-bom = "5.9.2" +test-truth = "1.1.3" test-mockito-bom = "5.2.0" @@ -92,6 +93,8 @@ test-junit-bom = { module = "org.junit:junit-bom", version.ref = "test-junit-bom test-junit-api = { module = "org.junit.jupiter:junit-jupiter-api" } test-junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine" } test-junit-params = { module = "org.junit.jupiter:junit-jupiter-params" } +test-truth = { module = "com.google.truth:truth", version.ref = "test-truth" } +test-truth-java8 = { module = "com.google.truth.extensions:truth-java8-extension", version.ref = "test-truth" } test-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "test-mockito-bom" } test-mockito-core = { module = "org.mockito:mockito-core" } test-mockito-junit = { module = "org.mockito:mockito-junit-jupiter" } diff --git a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleon.java b/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleon.java index 0c016bfb..e2ac907c 100644 --- a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleon.java +++ b/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleon.java @@ -24,13 +24,15 @@ package dev.hypera.chameleon.platform.bukkit; import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.ChameleonBootstrap; import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.command.CommandManager; +import dev.hypera.chameleon.event.EventBus; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonJavaLogger; +import dev.hypera.chameleon.extension.ExtensionMap; +import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.PluginManager; import dev.hypera.chameleon.platform.bukkit.adventure.BukkitAudienceProvider; @@ -43,7 +45,6 @@ import dev.hypera.chameleon.scheduler.Scheduler; import dev.hypera.chameleon.util.Preconditions; import java.nio.file.Path; -import java.util.Collection; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.ApiStatus.Internal; @@ -53,7 +54,8 @@ /** * Bukkit Chameleon implementation. - * Not final to allow Folia implementation to extend this class. + * + *

Not final to allow Folia implementation to extend this class.

*/ @NonExtendable public class BukkitChameleon extends Chameleon { @@ -68,9 +70,15 @@ public class BukkitChameleon extends Chameleon { private @Nullable ChameleonAudienceProvider audienceProvider; @Internal - // Protected to allow Folia to extend this class. - protected BukkitChameleon(@NotNull Class chameleonPlugin, @NotNull Collection> extensions, @NotNull JavaPlugin bukkitPlugin, @NotNull ChameleonPluginData pluginData) throws ChameleonInstantiationException { - super(chameleonPlugin, extensions, pluginData, new ChameleonJavaLogger(bukkitPlugin.getLogger())); + protected BukkitChameleon( + @NotNull Class chameleonPlugin, + @NotNull JavaPlugin bukkitPlugin, + @NotNull ChameleonPluginData pluginData, + @NotNull EventBus eventBus, + @NotNull ChameleonLogger logger, + @NotNull ExtensionMap extensions + ) throws ChameleonInstantiationException { + super(chameleonPlugin, pluginData, eventBus, logger, extensions); this.plugin = bukkitPlugin; } @@ -83,7 +91,7 @@ protected BukkitChameleon(@NotNull Class chameleonPlu * * @return new Bukkit Chameleon bootstrap. */ - public static @NotNull BukkitChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull JavaPlugin bukkitPlugin, @NotNull ChameleonPluginData pluginData) { + public static @NotNull ChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull JavaPlugin bukkitPlugin, @NotNull ChameleonPluginData pluginData) { return new BukkitChameleonBootstrap(chameleonPlugin, bukkitPlugin, pluginData); } diff --git a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleonBootstrap.java b/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleonBootstrap.java index ab1dd58d..8fd9d4ef 100644 --- a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleonBootstrap.java +++ b/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/BukkitChameleonBootstrap.java @@ -27,11 +27,8 @@ import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; import dev.hypera.chameleon.logger.ChameleonJavaLogger; -import dev.hypera.chameleon.logger.ChameleonLogger; -import dev.hypera.chameleon.platform.bukkit.extension.ChameleonBukkitExtension; -import java.util.Collection; +import dev.hypera.chameleon.platform.Platform; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -39,7 +36,7 @@ /** * Bukkit Chameleon bootstrap implementation. */ -public final class BukkitChameleonBootstrap extends ChameleonBootstrap> { +public final class BukkitChameleonBootstrap extends ChameleonBootstrap { private final @NotNull Class chameleonPlugin; private final @NotNull JavaPlugin bukkitPlugin; @@ -47,21 +44,18 @@ public final class BukkitChameleonBootstrap extends ChameleonBootstrap chameleonPlugin, @NotNull JavaPlugin bukkitPlugin, @NotNull ChameleonPluginData pluginData) { + super(new ChameleonJavaLogger(bukkitPlugin.getLogger()), Platform.BUKKIT); this.chameleonPlugin = chameleonPlugin; this.bukkitPlugin = bukkitPlugin; this.pluginData = pluginData; } - @Internal - @Override - protected @NotNull BukkitChameleon loadInternal(@NotNull Collection> extensions) throws ChameleonInstantiationException { - return new BukkitChameleon(this.chameleonPlugin, extensions, this.bukkitPlugin, this.pluginData); - } - - @Internal @Override - protected @NotNull ChameleonLogger createLogger() { - return new ChameleonJavaLogger(this.bukkitPlugin.getLogger()); + protected @NotNull BukkitChameleon loadInternal() throws ChameleonInstantiationException { + return new BukkitChameleon( + this.chameleonPlugin, this.bukkitPlugin, this.pluginData, + this.eventBus, this.logger, this.extensions + ); } } diff --git a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/extension/ChameleonBukkitExtension.java b/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/extension/ChameleonBukkitExtension.java deleted file mode 100644 index 62366749..00000000 --- a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/extension/ChameleonBukkitExtension.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is a part of the Chameleon Framework, licensed under the MIT License. - * - * Copyright (c) 2021-2023 The Chameleon Framework Authors. - * - * 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 dev.hypera.chameleon.platform.bukkit.extension; - -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.CustomPlatformExtension; -import dev.hypera.chameleon.platform.bukkit.BukkitChameleon; - -/** - * Chameleon Bukkit extension. - * - * @param Chameleon extension type. - * @param Chameleon platform extension type. - */ -public abstract class ChameleonBukkitExtension, C extends CustomPlatformExtension> extends ChameleonPlatformExtension { - -} diff --git a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/user/BukkitUser.java b/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/user/BukkitUser.java index 7ecfea92..5a26c60a 100644 --- a/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/user/BukkitUser.java +++ b/platform-bukkit/src/main/java/dev/hypera/chameleon/platform/bukkit/user/BukkitUser.java @@ -43,7 +43,6 @@ /** * Bukkit server user implementation. */ -@Internal public final class BukkitUser implements ServerUser, ForwardingAudience.Single { private final @NotNull BukkitChameleon chameleon; @@ -151,6 +150,15 @@ public void setGameMode(@NotNull GameMode gameMode) { this.player.setGameMode(convertGameModeToBukkit(gameMode)); } + /** + * Get the Bukkit player for this user. + * + * @return Bukkit player. + */ + public @NotNull Player getPlayer() { + return this.player; + } + /** * Get the audience for this user. * diff --git a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleon.java b/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleon.java index 90d975ea..65a15231 100644 --- a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleon.java +++ b/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleon.java @@ -24,13 +24,15 @@ package dev.hypera.chameleon.platform.bungeecord; import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.ChameleonBootstrap; import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.command.CommandManager; +import dev.hypera.chameleon.event.EventBus; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonJavaLogger; +import dev.hypera.chameleon.extension.ExtensionMap; +import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.PluginManager; import dev.hypera.chameleon.platform.bungeecord.adventure.BungeeCordAudienceProvider; @@ -42,7 +44,6 @@ import dev.hypera.chameleon.platform.bungeecord.user.BungeeCordUserManager; import dev.hypera.chameleon.scheduler.Scheduler; import java.nio.file.Path; -import java.util.Collection; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import org.jetbrains.annotations.ApiStatus.Internal; @@ -62,8 +63,15 @@ public final class BungeeCordChameleon extends Chameleon { private final @NotNull BungeeCordScheduler scheduler = new BungeeCordScheduler(this); @Internal - BungeeCordChameleon(@NotNull Class chameleonPlugin, @NotNull Collection> extensions, @NotNull Plugin bungeePlugin, @NotNull ChameleonPluginData pluginData) throws ChameleonInstantiationException { - super(chameleonPlugin, extensions, pluginData, new ChameleonJavaLogger(bungeePlugin.getLogger())); + BungeeCordChameleon( + @NotNull Class chameleonPlugin, + @NotNull Plugin bungeePlugin, + @NotNull ChameleonPluginData pluginData, + @NotNull EventBus eventBus, + @NotNull ChameleonLogger logger, + @NotNull ExtensionMap extensions + ) throws ChameleonInstantiationException { + super(chameleonPlugin, pluginData, eventBus, logger, extensions); this.plugin = bungeePlugin; this.audienceProvider = new BungeeCordAudienceProvider(this, bungeePlugin); ProxyServer.getInstance().getPluginManager().registerListener(bungeePlugin, new BungeeCordListener(this)); @@ -78,7 +86,7 @@ public final class BungeeCordChameleon extends Chameleon { * * @return new BungeeCord Chameleon boostrap. */ - public static @NotNull BungeeCordChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull Plugin bungeePlugin, @NotNull ChameleonPluginData pluginData) { + public static @NotNull ChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull Plugin bungeePlugin, @NotNull ChameleonPluginData pluginData) { return new BungeeCordChameleonBootstrap(chameleonPlugin, bungeePlugin, pluginData); } diff --git a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleonBootstrap.java b/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleonBootstrap.java index 1ca49604..88d321d8 100644 --- a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleonBootstrap.java +++ b/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/BungeeCordChameleonBootstrap.java @@ -27,11 +27,8 @@ import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; import dev.hypera.chameleon.logger.ChameleonJavaLogger; -import dev.hypera.chameleon.logger.ChameleonLogger; -import dev.hypera.chameleon.platform.bungeecord.extension.ChameleonBungeeCordExtension; -import java.util.Collection; +import dev.hypera.chameleon.platform.Platform; import net.md_5.bungee.api.plugin.Plugin; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -39,7 +36,7 @@ /** * BungeeCord Chameleon bootstrap implementation. */ -public final class BungeeCordChameleonBootstrap extends ChameleonBootstrap> { +final class BungeeCordChameleonBootstrap extends ChameleonBootstrap { private final @NotNull Class chameleonPlugin; private final @NotNull Plugin bungeePlugin; @@ -47,21 +44,15 @@ public final class BungeeCordChameleonBootstrap extends ChameleonBootstrap chameleonPlugin, @NotNull Plugin bungeePlugin, @NotNull ChameleonPluginData pluginData) { + super(new ChameleonJavaLogger(bungeePlugin.getLogger()), Platform.BUNGEECORD); this.chameleonPlugin = chameleonPlugin; this.bungeePlugin = bungeePlugin; this.pluginData = pluginData; } - @Internal - @Override - protected @NotNull BungeeCordChameleon loadInternal(@NotNull Collection> extensions) throws ChameleonInstantiationException { - return new BungeeCordChameleon(this.chameleonPlugin, extensions, this.bungeePlugin, this.pluginData); - } - - @Internal @Override - protected @NotNull ChameleonLogger createLogger() { - return new ChameleonJavaLogger(this.bungeePlugin.getLogger()); + protected @NotNull BungeeCordChameleon loadInternal() throws ChameleonInstantiationException { + return new BungeeCordChameleon(this.chameleonPlugin, this.bungeePlugin, this.pluginData, this.eventBus, this.logger, this.extensions); } } diff --git a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/extension/ChameleonBungeeCordExtension.java b/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/extension/ChameleonBungeeCordExtension.java deleted file mode 100644 index 6010039f..00000000 --- a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/extension/ChameleonBungeeCordExtension.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is a part of the Chameleon Framework, licensed under the MIT License. - * - * Copyright (c) 2021-2023 The Chameleon Framework Authors. - * - * 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 dev.hypera.chameleon.platform.bungeecord.extension; - -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.CustomPlatformExtension; -import dev.hypera.chameleon.platform.bungeecord.BungeeCordChameleon; - -/** - * Chameleon BungeeCord extension. - * - * @param Chameleon extension type. - * @param Chameleon platform extension type. - */ -public abstract class ChameleonBungeeCordExtension, C extends CustomPlatformExtension> extends ChameleonPlatformExtension { - -} diff --git a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/user/BungeeCordUser.java b/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/user/BungeeCordUser.java index 2b928fc3..0be0be76 100644 --- a/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/user/BungeeCordUser.java +++ b/platform-bungeecord/src/main/java/dev/hypera/chameleon/platform/bungeecord/user/BungeeCordUser.java @@ -43,7 +43,6 @@ /** * BungeeCord proxy user implementation. */ -@Internal public final class BungeeCordUser implements ProxyUser, ForwardingAudience.Single { private final @NotNull Chameleon chameleon; @@ -168,4 +167,13 @@ public void connect(@NotNull Server server, @NotNull BiConsumer chameleonPlugin, @NotNull Collection> extensions, @NotNull JavaPlugin foliaPlugin, @NotNull ChameleonPluginData pluginData) throws ChameleonInstantiationException { - super(chameleonPlugin, extensions, foliaPlugin, pluginData); - boolean isFolia = isFolia(); - this.platform = isFolia ? new FoliaPlatform() : null; - this.pluginManager = isFolia ? new FoliaPluginManager() : null; - this.scheduler = isFolia ? new FoliaScheduler(this) : null; + FoliaChameleon(@NotNull Class chameleonPlugin, @NotNull JavaPlugin foliaPlugin, @NotNull ChameleonPluginData pluginData, @NotNull EventBus eventBus, @NotNull ChameleonLogger logger, @NotNull ExtensionMap extensions) throws ChameleonInstantiationException { + super(chameleonPlugin, foliaPlugin, pluginData, eventBus, logger, extensions); + boolean folia = isFolia(); + this.platform = folia ? new FoliaPlatform() : null; + this.pluginManager = folia ? new FoliaPluginManager() : null; + this.scheduler = folia ? new FoliaScheduler(this) : null; } /** @@ -68,8 +70,11 @@ public final class FoliaChameleon extends BukkitChameleon { * @param chameleonPlugin Unsupported. * @param bukkitPlugin Unsupported. * @param pluginData Unsupported. - * @return Unsupported. + * + * @return Unsupported. + * @deprecated Not supported on Folia. */ + @DoNotCall("Always throws java.lang.UnsupportedOperationException") @Deprecated @SuppressWarnings("unused") public static @NotNull BukkitChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull JavaPlugin bukkitPlugin, @NotNull ChameleonPluginData pluginData) { diff --git a/platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/FoliaChameleonBootstrap.java b/platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/FoliaChameleonBootstrap.java index de18d43b..0c359155 100644 --- a/platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/FoliaChameleonBootstrap.java +++ b/platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/FoliaChameleonBootstrap.java @@ -27,11 +27,8 @@ import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonJavaLogger; -import dev.hypera.chameleon.logger.ChameleonLogger; -import dev.hypera.chameleon.platform.folia.extension.ChameleonFoliaExtension; -import java.util.Collection; +import dev.hypera.chameleon.logger.ChameleonSlf4jLogger; +import dev.hypera.chameleon.platform.Platform; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -39,7 +36,7 @@ /** * Folia Chameleon bootstrap implementation. */ -public final class FoliaChameleonBootstrap extends ChameleonBootstrap> { +public final class FoliaChameleonBootstrap extends ChameleonBootstrap { private final @NotNull Class chameleonPlugin; private final @NotNull JavaPlugin foliaPlugin; @@ -47,6 +44,7 @@ public final class FoliaChameleonBootstrap extends ChameleonBootstrap chameleonPlugin, @NotNull JavaPlugin foliaPlugin, @NotNull ChameleonPluginData pluginData) { + super(new ChameleonSlf4jLogger(foliaPlugin.getSLF4JLogger()), Platform.FOLIA); this.chameleonPlugin = chameleonPlugin; this.foliaPlugin = foliaPlugin; this.pluginData = pluginData; @@ -54,14 +52,11 @@ public final class FoliaChameleonBootstrap extends ChameleonBootstrap> extensions) throws ChameleonInstantiationException { - return new FoliaChameleon(this.chameleonPlugin, extensions, this.foliaPlugin, this.pluginData); - } - - @Internal - @Override - protected @NotNull ChameleonLogger createLogger() { - return new ChameleonJavaLogger(this.foliaPlugin.getLogger()); + protected @NotNull FoliaChameleon loadInternal() throws ChameleonInstantiationException { + return new FoliaChameleon( + this.chameleonPlugin, this.foliaPlugin, this.pluginData, + this.eventBus, this.logger, this.extensions + ); } } diff --git a/platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/extension/ChameleonFoliaExtension.java b/platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/extension/ChameleonFoliaExtension.java deleted file mode 100644 index c4a2b8d7..00000000 --- a/platform-folia/src/main/java/dev/hypera/chameleon/platform/folia/extension/ChameleonFoliaExtension.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is a part of the Chameleon Framework, licensed under the MIT License. - * - * Copyright (c) 2021-2023 The Chameleon Framework Authors. - * - * 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 dev.hypera.chameleon.platform.folia.extension; - -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.CustomPlatformExtension; -import dev.hypera.chameleon.platform.folia.FoliaChameleon; - -/** - * Chameleon Folia extension. - * - * @param Chameleon extension type. - * @param Chameleon platform extension type. - */ -public abstract class ChameleonFoliaExtension, C extends CustomPlatformExtension> extends ChameleonPlatformExtension { - -} diff --git a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleon.java b/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleon.java index c219cbd7..57e8c170 100644 --- a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleon.java +++ b/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleon.java @@ -24,15 +24,17 @@ package dev.hypera.chameleon.platform.minestom; import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.ChameleonBootstrap; import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.adventure.mapper.AdventureMapper; import dev.hypera.chameleon.command.CommandManager; +import dev.hypera.chameleon.event.EventBus; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; import dev.hypera.chameleon.exception.reflection.ChameleonReflectiveException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonSlf4jLogger; +import dev.hypera.chameleon.extension.ExtensionMap; +import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.PluginManager; import dev.hypera.chameleon.platform.minestom.adventure.MinestomAudienceProvider; @@ -45,7 +47,6 @@ import dev.hypera.chameleon.scheduler.Scheduler; import dev.hypera.chameleon.user.UserManager; import java.nio.file.Path; -import java.util.Collection; import net.minestom.server.extensions.Extension; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -65,8 +66,15 @@ public final class MinestomChameleon extends Chameleon { private final @NotNull MinestomScheduler scheduler = new MinestomScheduler(); @Internal - MinestomChameleon(@NotNull Class chameleonPlugin, @NotNull Collection> extensions, @NotNull Extension extension, @NotNull ChameleonPluginData pluginData) throws ChameleonInstantiationException { - super(chameleonPlugin, extensions, pluginData, new ChameleonSlf4jLogger(extension.getLogger())); + MinestomChameleon( + @NotNull Class chameleonPlugin, + @NotNull Extension extension, + @NotNull ChameleonPluginData pluginData, + @NotNull EventBus eventBus, + @NotNull ChameleonLogger logger, + @NotNull ExtensionMap extensions + ) throws ChameleonInstantiationException { + super(chameleonPlugin, pluginData, eventBus, logger, extensions); this.extension = extension; new MinestomListener(this); } @@ -80,7 +88,7 @@ public final class MinestomChameleon extends Chameleon { * * @return new Minestom Chameleon bootstrap. */ - public static @NotNull MinestomChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull Extension extension, @NotNull ChameleonPluginData pluginData) { + public static @NotNull ChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull Extension extension, @NotNull ChameleonPluginData pluginData) { return new MinestomChameleonBootstrap(chameleonPlugin, extension, pluginData); } diff --git a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleonBootstrap.java b/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleonBootstrap.java index 70d16d4b..fa356755 100644 --- a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleonBootstrap.java +++ b/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/MinestomChameleonBootstrap.java @@ -27,11 +27,8 @@ import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.logger.ChameleonSlf4jLogger; -import dev.hypera.chameleon.platform.minestom.extension.ChameleonMinestomExtension; -import java.util.Collection; +import dev.hypera.chameleon.platform.Platform; import net.minestom.server.extensions.Extension; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -39,7 +36,7 @@ /** * Minestom Chameleon bootstrap implementation. */ -public final class MinestomChameleonBootstrap extends ChameleonBootstrap> { +final class MinestomChameleonBootstrap extends ChameleonBootstrap { private final @NotNull Class chameleonPlugin; private final @NotNull Extension extension; @@ -47,21 +44,18 @@ public final class MinestomChameleonBootstrap extends ChameleonBootstrap chameleonPlugin, @NotNull Extension extension, @NotNull ChameleonPluginData pluginData) { + super(new ChameleonSlf4jLogger(extension.getLogger()), Platform.MINESTOM); this.chameleonPlugin = chameleonPlugin; this.extension = extension; this.pluginData = pluginData; } - @Internal - @Override - protected @NotNull MinestomChameleon loadInternal(@NotNull Collection> extensions) throws ChameleonInstantiationException { - return new MinestomChameleon(this.chameleonPlugin, extensions, this.extension, this.pluginData); - } - - @Internal @Override - protected @NotNull ChameleonLogger createLogger() { - return new ChameleonSlf4jLogger(this.extension.getLogger()); + protected @NotNull MinestomChameleon loadInternal() throws ChameleonInstantiationException { + return new MinestomChameleon( + this.chameleonPlugin, this.extension, this.pluginData, + this.eventBus, this.logger, this.extensions + ); } } diff --git a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/user/MinestomUser.java b/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/user/MinestomUser.java index 30ed51a5..ce302a91 100644 --- a/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/user/MinestomUser.java +++ b/platform-minestom/src/main/java/dev/hypera/chameleon/platform/minestom/user/MinestomUser.java @@ -40,7 +40,6 @@ /** * Minestom server user implementation. */ -@Internal public final class MinestomUser implements ServerUser, ForwardingAudience.Single { private final @NotNull Player player; @@ -155,4 +154,13 @@ public void setGameMode(@NotNull GameMode gameMode) { return this.audience; } + /** + * Get the Minestom player for this user. + * + * @return Minestom player. + */ + public @NotNull Player getPlayer() { + return this.player; + } + } diff --git a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleon.java b/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleon.java index 72506cfb..efaa63a1 100644 --- a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleon.java +++ b/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleon.java @@ -26,25 +26,26 @@ import cn.nukkit.Server; import cn.nukkit.plugin.PluginBase; import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.ChameleonBootstrap; import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.command.CommandManager; +import dev.hypera.chameleon.event.EventBus; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; +import dev.hypera.chameleon.extension.ExtensionMap; +import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.PluginManager; import dev.hypera.chameleon.platform.nukkit.adventure.NukkitAudienceProvider; import dev.hypera.chameleon.platform.nukkit.command.NukkitCommandManager; import dev.hypera.chameleon.platform.nukkit.event.NukkitListener; -import dev.hypera.chameleon.platform.nukkit.logger.ChameleonNukkitLogger; import dev.hypera.chameleon.platform.nukkit.platform.NukkitPlatform; import dev.hypera.chameleon.platform.nukkit.platform.NukkitPluginManager; import dev.hypera.chameleon.platform.nukkit.scheduler.NukkitScheduler; import dev.hypera.chameleon.platform.nukkit.user.NukkitUserManager; import dev.hypera.chameleon.scheduler.Scheduler; import java.nio.file.Path; -import java.util.Collection; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -62,8 +63,15 @@ public final class NukkitChameleon extends Chameleon { private final @NotNull NukkitScheduler scheduler = new NukkitScheduler(this); @Internal - NukkitChameleon(@NotNull Class chameleonPlugin, @NotNull Collection> extensions, @NotNull PluginBase nukkitPlugin, @NotNull ChameleonPluginData pluginData) throws ChameleonInstantiationException { - super(chameleonPlugin, extensions, pluginData, new ChameleonNukkitLogger(nukkitPlugin.getLogger())); + NukkitChameleon( + @NotNull Class chameleonPlugin, + @NotNull PluginBase nukkitPlugin, + @NotNull ChameleonPluginData pluginData, + @NotNull EventBus eventBus, + @NotNull ChameleonLogger logger, + @NotNull ExtensionMap extensions + ) throws ChameleonInstantiationException { + super(chameleonPlugin, pluginData, eventBus, logger, extensions); this.plugin = nukkitPlugin; } @@ -76,7 +84,7 @@ public final class NukkitChameleon extends Chameleon { * * @return new Nukkit Chameleon bootstrap. */ - public static @NotNull NukkitChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull PluginBase nukkitPlugin, @NotNull ChameleonPluginData pluginData) { + public static @NotNull ChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull PluginBase nukkitPlugin, @NotNull ChameleonPluginData pluginData) { return new NukkitChameleonBootstrap(chameleonPlugin, nukkitPlugin, pluginData); } diff --git a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleonBootstrap.java b/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleonBootstrap.java index b704e863..2313a4b2 100644 --- a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleonBootstrap.java +++ b/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/NukkitChameleonBootstrap.java @@ -28,18 +28,15 @@ import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonLogger; -import dev.hypera.chameleon.platform.nukkit.extension.NukkitChameleonExtension; +import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.nukkit.logger.ChameleonNukkitLogger; -import java.util.Collection; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; /** * Nukkit Chameleon bootstrap implementation. */ -public final class NukkitChameleonBootstrap extends ChameleonBootstrap> { +final class NukkitChameleonBootstrap extends ChameleonBootstrap { private final @NotNull Class chameleonPlugin; private final @NotNull PluginBase nukkitPlugin; @@ -47,27 +44,18 @@ public final class NukkitChameleonBootstrap extends ChameleonBootstrap chameleonPlugin, @NotNull PluginBase nukkitPlugin, @NotNull ChameleonPluginData pluginData) { + super(new ChameleonNukkitLogger(nukkitPlugin.getLogger()), Platform.NUKKIT); this.chameleonPlugin = chameleonPlugin; this.nukkitPlugin = nukkitPlugin; this.pluginData = pluginData; } - /** - * {@inheritDoc} - */ - @Internal - @Override - protected @NotNull NukkitChameleon loadInternal(@NotNull Collection> extensions) throws ChameleonInstantiationException { - return new NukkitChameleon(this.chameleonPlugin, extensions, this.nukkitPlugin, this.pluginData); - } - - /** - * {@inheritDoc} - */ - @Internal @Override - protected @NotNull ChameleonLogger createLogger() { - return new ChameleonNukkitLogger(this.nukkitPlugin.getLogger()); + protected @NotNull NukkitChameleon loadInternal() throws ChameleonInstantiationException { + return new NukkitChameleon( + this.chameleonPlugin, this.nukkitPlugin, this.pluginData, + this.eventBus, this.logger, this.extensions + ); } } diff --git a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/extension/NukkitChameleonExtension.java b/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/extension/NukkitChameleonExtension.java deleted file mode 100644 index f4b7f57a..00000000 --- a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/extension/NukkitChameleonExtension.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is a part of the Chameleon Framework, licensed under the MIT License. - * - * Copyright (c) 2021-2023 The Chameleon Framework Authors. - * - * 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 dev.hypera.chameleon.platform.nukkit.extension; - -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.CustomPlatformExtension; -import dev.hypera.chameleon.platform.nukkit.NukkitChameleon; - -/** - * Chameleon Nukkit extension. - * - * @param Chameleon extension type. - * @param Chameleon platform extension type. - */ -public abstract class NukkitChameleonExtension, C extends CustomPlatformExtension> extends ChameleonPlatformExtension { - -} diff --git a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/user/NukkitUser.java b/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/user/NukkitUser.java index e8f44ec8..412b37ed 100644 --- a/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/user/NukkitUser.java +++ b/platform-nukkit/src/main/java/dev/hypera/chameleon/platform/nukkit/user/NukkitUser.java @@ -49,7 +49,6 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.title.Title.Times; import net.kyori.adventure.title.TitlePart; -import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -58,7 +57,6 @@ /** * Nukkit server user implementation. */ -@Internal public final class NukkitUser implements ServerUser { private final @NotNull Player player; @@ -492,6 +490,15 @@ public void openBook(@NotNull Book book) { return this.pointers; } + /** + * Get the Nukkit player for this user. + * + * @return Nukkit player. + */ + public @NotNull Player getPlayer() { + return this.player; + } + private int convertGameModeToNukkit(@NotNull GameMode gameMode) { switch (gameMode) { case CREATIVE: diff --git a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleon.java b/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleon.java index 94050e63..58383c97 100644 --- a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleon.java +++ b/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleon.java @@ -24,15 +24,17 @@ package dev.hypera.chameleon.platform.sponge; import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.ChameleonBootstrap; import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.adventure.mapper.AdventureMapper; import dev.hypera.chameleon.command.CommandManager; +import dev.hypera.chameleon.event.EventBus; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; import dev.hypera.chameleon.exception.reflection.ChameleonReflectiveException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonLog4jLogger; +import dev.hypera.chameleon.extension.ExtensionMap; +import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.PluginManager; import dev.hypera.chameleon.platform.sponge.adventure.SpongeAudienceProvider; @@ -44,7 +46,6 @@ import dev.hypera.chameleon.platform.sponge.user.SpongeUserManager; import dev.hypera.chameleon.scheduler.Scheduler; import java.nio.file.Path; -import java.util.Collection; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; import org.spongepowered.api.Sponge; @@ -65,8 +66,15 @@ public final class SpongeChameleon extends Chameleon { private final @NotNull SpongeListener listener = new SpongeListener(this); @Internal - SpongeChameleon(@NotNull Class chameleonPlugin, @NotNull Collection> extensions, @NotNull SpongePlugin spongePlugin, @NotNull ChameleonPluginData pluginData) throws ChameleonInstantiationException { - super(chameleonPlugin, extensions, pluginData, new ChameleonLog4jLogger(spongePlugin.getLogger())); + SpongeChameleon( + @NotNull Class chameleonPlugin, + @NotNull SpongePlugin spongePlugin, + @NotNull ChameleonPluginData pluginData, + @NotNull EventBus eventBus, + @NotNull ChameleonLogger logger, + @NotNull ExtensionMap extensions + ) throws ChameleonInstantiationException { + super(chameleonPlugin, pluginData, eventBus, logger, extensions); this.plugin = spongePlugin; } @@ -79,7 +87,7 @@ public final class SpongeChameleon extends Chameleon { * * @return new Sponge Chameleon bootstrap. */ - public static @NotNull SpongeChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull SpongePlugin spongePlugin, @NotNull ChameleonPluginData pluginData) { + public static @NotNull ChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull SpongePlugin spongePlugin, @NotNull ChameleonPluginData pluginData) { return new SpongeChameleonBootstrap(chameleonPlugin, spongePlugin, pluginData); } diff --git a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleonBootstrap.java b/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleonBootstrap.java index fd82e14c..930ab33d 100644 --- a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleonBootstrap.java +++ b/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/SpongeChameleonBootstrap.java @@ -27,18 +27,15 @@ import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; import dev.hypera.chameleon.logger.ChameleonLog4jLogger; -import dev.hypera.chameleon.logger.ChameleonLogger; -import dev.hypera.chameleon.platform.sponge.extension.SpongeChameleonExtension; -import java.util.Collection; +import dev.hypera.chameleon.platform.Platform; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; /** * Sponge Chameleon bootstrap implementation. */ -public final class SpongeChameleonBootstrap extends ChameleonBootstrap> { +final class SpongeChameleonBootstrap extends ChameleonBootstrap { private final @NotNull Class chameleonPlugin; private final @NotNull SpongePlugin spongePlugin; @@ -46,27 +43,15 @@ public final class SpongeChameleonBootstrap extends ChameleonBootstrap chameleonPlugin, @NotNull SpongePlugin spongePlugin, @NotNull ChameleonPluginData pluginData) { + super(new ChameleonLog4jLogger(spongePlugin.getLogger()), Platform.SPONGE); this.chameleonPlugin = chameleonPlugin; this.spongePlugin = spongePlugin; this.pluginData = pluginData; } - /** - * {@inheritDoc} - */ - @Internal - @Override - protected @NotNull SpongeChameleon loadInternal(@NotNull Collection> extensions) throws ChameleonInstantiationException { - return new SpongeChameleon(this.chameleonPlugin, extensions, this.spongePlugin, this.pluginData); - } - - /** - * {@inheritDoc} - */ - @Internal @Override - protected @NotNull ChameleonLogger createLogger() { - return new ChameleonLog4jLogger(this.spongePlugin.getLogger()); + protected @NotNull SpongeChameleon loadInternal() throws ChameleonInstantiationException { + return new SpongeChameleon(this.chameleonPlugin, this.spongePlugin, this.pluginData, this.eventBus, this.logger, this.extensions); } } diff --git a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/extension/SpongeChameleonExtension.java b/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/extension/SpongeChameleonExtension.java deleted file mode 100644 index 80e9fd11..00000000 --- a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/extension/SpongeChameleonExtension.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is a part of the Chameleon Framework, licensed under the MIT License. - * - * Copyright (c) 2021-2023 The Chameleon Framework Authors. - * - * 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 dev.hypera.chameleon.platform.sponge.extension; - -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.extension.ChameleonPlatformExtension; -import dev.hypera.chameleon.extension.CustomPlatformExtension; -import dev.hypera.chameleon.platform.sponge.SpongeChameleon; - -/** - * Chameleon Sponge extension. - * - * @param Chameleon extension type. - * @param Chameleon platform extension type. - */ -public abstract class SpongeChameleonExtension, C extends CustomPlatformExtension> extends ChameleonPlatformExtension { - -} diff --git a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/user/SpongeUser.java b/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/user/SpongeUser.java index 2fb2451d..7c44825e 100644 --- a/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/user/SpongeUser.java +++ b/platform-sponge/src/main/java/dev/hypera/chameleon/platform/sponge/user/SpongeUser.java @@ -36,6 +36,7 @@ import org.jetbrains.annotations.NotNull; import org.spongepowered.api.ResourceKey; import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.entity.living.player.gamemode.GameModes; import org.spongepowered.api.entity.living.player.server.ServerPlayer; import org.spongepowered.api.network.channel.raw.RawDataChannel; @@ -43,7 +44,6 @@ /** * Sponge server user implementation. */ -@Internal public final class SpongeUser implements ServerUser, ForwardingAudience.Single { private final @NotNull ServerPlayer player; @@ -157,6 +157,15 @@ public void setGameMode(@NotNull GameMode gameMode) { return this.audience; } + /** + * Get the Sponge player for this user. + * + * @return Sponge player. + */ + public @NotNull Player getPlayer() { + return this.player; + } + private @NotNull org.spongepowered.api.entity.living.player.gamemode.GameMode convertGameModeToSponge(@NotNull GameMode gameMode) { switch (gameMode) { case CREATIVE: diff --git a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleon.java b/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleon.java index ba272e93..67cee99b 100644 --- a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleon.java +++ b/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleon.java @@ -24,15 +24,17 @@ package dev.hypera.chameleon.platform.velocity; import dev.hypera.chameleon.Chameleon; +import dev.hypera.chameleon.ChameleonBootstrap; import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.adventure.ChameleonAudienceProvider; import dev.hypera.chameleon.adventure.mapper.AdventureMapper; import dev.hypera.chameleon.command.CommandManager; +import dev.hypera.chameleon.event.EventBus; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; import dev.hypera.chameleon.exception.reflection.ChameleonReflectiveException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonSlf4jLogger; +import dev.hypera.chameleon.extension.ExtensionMap; +import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.platform.Platform; import dev.hypera.chameleon.platform.PluginManager; import dev.hypera.chameleon.platform.velocity.adventure.VelocityAudienceProvider; @@ -44,7 +46,6 @@ import dev.hypera.chameleon.platform.velocity.user.VelocityUserManager; import dev.hypera.chameleon.scheduler.Scheduler; import java.nio.file.Path; -import java.util.Collection; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; @@ -63,8 +64,15 @@ public final class VelocityChameleon extends Chameleon { private final @NotNull VelocityUserManager userManager = new VelocityUserManager(this); @Internal - VelocityChameleon(@NotNull Class chameleonPlugin, @NotNull Collection> extensions, @NotNull VelocityPlugin velocityPlugin, @NotNull ChameleonPluginData pluginData) throws ChameleonInstantiationException { - super(chameleonPlugin, extensions, pluginData, new ChameleonSlf4jLogger(velocityPlugin.getLogger())); + VelocityChameleon( + @NotNull Class chameleonPlugin, + @NotNull VelocityPlugin velocityPlugin, + @NotNull ChameleonPluginData pluginData, + @NotNull EventBus eventBus, + @NotNull ChameleonLogger logger, + @NotNull ExtensionMap extensions + ) throws ChameleonInstantiationException { + super(chameleonPlugin, pluginData, eventBus, logger, extensions); this.plugin = velocityPlugin; } @@ -77,7 +85,7 @@ public final class VelocityChameleon extends Chameleon { * * @return new Velocity Chameleon bootstrap. */ - public static @NotNull VelocityChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull VelocityPlugin velocityPlugin, @NotNull ChameleonPluginData pluginData) { + public static @NotNull ChameleonBootstrap create(@NotNull Class chameleonPlugin, @NotNull VelocityPlugin velocityPlugin, @NotNull ChameleonPluginData pluginData) { return new VelocityChameleonBootstrap(chameleonPlugin, velocityPlugin, pluginData); } diff --git a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleonBootstrap.java b/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleonBootstrap.java index 8acb421a..9422d6a2 100644 --- a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleonBootstrap.java +++ b/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/VelocityChameleonBootstrap.java @@ -27,18 +27,15 @@ import dev.hypera.chameleon.ChameleonPlugin; import dev.hypera.chameleon.ChameleonPluginData; import dev.hypera.chameleon.exception.instantiation.ChameleonInstantiationException; -import dev.hypera.chameleon.extension.ChameleonExtension; -import dev.hypera.chameleon.logger.ChameleonLogger; import dev.hypera.chameleon.logger.ChameleonSlf4jLogger; -import dev.hypera.chameleon.platform.velocity.extension.VelocityChameleonExtension; -import java.util.Collection; +import dev.hypera.chameleon.platform.Platform; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; /** * Velocity Chameleon bootstrap implementation. */ -public final class VelocityChameleonBootstrap extends ChameleonBootstrap> { +final class VelocityChameleonBootstrap extends ChameleonBootstrap { private final @NotNull Class chameleonPlugin; private final @NotNull VelocityPlugin velocityPlugin; @@ -46,27 +43,15 @@ public final class VelocityChameleonBootstrap extends ChameleonBootstrap chameleonPlugin, @NotNull VelocityPlugin velocityPlugin, @NotNull ChameleonPluginData pluginData) { + super(new ChameleonSlf4jLogger(velocityPlugin.getLogger()), Platform.VELOCITY); this.chameleonPlugin = chameleonPlugin; this.velocityPlugin = velocityPlugin; this.pluginData = pluginData; } - /** - * {@inheritDoc} - */ - @Internal - @Override - protected @NotNull VelocityChameleon loadInternal(@NotNull Collection> extensions) throws ChameleonInstantiationException { - return new VelocityChameleon(this.chameleonPlugin, extensions, this.velocityPlugin, this.pluginData); - } - - /** - * {@inheritDoc} - */ - @Internal @Override - protected @NotNull ChameleonLogger createLogger() { - return new ChameleonSlf4jLogger(this.velocityPlugin.getLogger()); + protected @NotNull VelocityChameleon loadInternal() throws ChameleonInstantiationException { + return new VelocityChameleon(this.chameleonPlugin, this.velocityPlugin, this.pluginData, this.eventBus, this.logger, this.extensions); } } diff --git a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/user/VelocityUser.java b/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/user/VelocityUser.java index 38366c35..e7cd24be 100644 --- a/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/user/VelocityUser.java +++ b/platform-velocity/src/main/java/dev/hypera/chameleon/platform/velocity/user/VelocityUser.java @@ -43,7 +43,6 @@ /** * Velocity proxy user implementation. */ -@Internal public final class VelocityUser implements ProxyUser, ForwardingAudience.Single { private final @NotNull VelocityChameleon chameleon; @@ -169,4 +168,13 @@ public void connect(@NotNull Server server, @NotNull BiConsumer