Skip to content

Commit

Permalink
feat(extensions)!: improve and simplify extensions system (#205)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
joshuasing and LooFifteen authored Apr 4, 2023
1 parent 4960589 commit 3ab8972
Show file tree
Hide file tree
Showing 75 changed files with 2,752 additions and 715 deletions.
119 changes: 24 additions & 95 deletions api/src/main/java/dev/hypera/chameleon/Chameleon.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -62,56 +57,51 @@ public abstract class Chameleon {

private final @NotNull ChameleonPlugin plugin;
private final @NotNull ChameleonPluginData pluginData;
private final @NotNull Collection<ChameleonExtension<?>> extensions;
private final @NotNull EventBus eventBus;

private boolean enabled = false;
private final @NotNull ExtensionManager extensionManager;

@Internal
protected Chameleon(@NotNull Class<? extends ChameleonPlugin> plugin, @NotNull Collection<ChameleonExtension<?>> extensions, @NotNull ChameleonPluginData pluginData, @NotNull ChameleonLogger logger) throws ChameleonInstantiationException {
protected Chameleon(@NotNull Class<? extends ChameleonPlugin> 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();
}

/**
* 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;
}


Expand All @@ -133,75 +123,6 @@ public void onDisable() {
return this.pluginData;
}

/**
* Get a loaded extension.
*
* @param extension Chameleon extension implementation class.
* @param <T> Chameleon extension type.
*
* @return an optional containing the extension, if found, otherwise an empty optional.
*/
public final <T extends ChameleonExtension<?>> @NotNull Optional<T> getExtension(@NotNull Class<T> 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 <T> Extension type.
* @param <C> Chameleon type.
*
* @return the loaded extension.
*/
@SuppressWarnings("unchecked")
public final <T extends ChameleonExtension<?>, C extends Chameleon> @NotNull T loadExtension(@NotNull Class<T> 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<T, ?, C> platformExtension = (ChameleonPlatformExtension<T, ?, C>) 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.
*
Expand Down Expand Up @@ -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.
Expand Down
98 changes: 46 additions & 52 deletions api/src/main/java/dev/hypera/chameleon/ChameleonBootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>Allows for runtime dependency loading, etc. before Chameleon is actually loaded.</p>
* <p>Allows you to perform actions and load extensions before Chameleon has been loaded.</p>
*
* @param <T> Chameleon implementation type.
* @param <E> Chameleon platform extension implementation type.
*/
public abstract class ChameleonBootstrap<T extends Chameleon, E extends ChameleonPlatformExtension<?, ?, T>> {
public abstract class ChameleonBootstrap<T extends Chameleon> {

private @Nullable Consumer<ChameleonLogger> preLoad;
private @NotNull Consumer<ChameleonLogger> 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<E> 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<T, E> 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 <E> Extension type.
*
* @return {@code this}.
*/
@Contract("_ -> this")
public final @NotNull ChameleonBootstrap<T, E> withExtensions(@NotNull Collection<E> extensions) {
Preconditions.checkNotNull("extensions", extensions);
Preconditions.checkArgument(
extensions.stream().noneMatch(Objects::isNull),
"extensions must not contain null"
);
this.platformExtensions.addAll(extensions);
public final <E extends ChameleonExtension> @NotNull ChameleonBootstrap<T> withExtension(@NotNull ChameleonExtensionFactory<E> 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<T, E> onPreLoad(@NotNull Consumer<ChameleonLogger> preLoad) {
public final @NotNull ChameleonBootstrap<T> onPreLoad(@NotNull Consumer<ChameleonLogger> preLoad) {
Preconditions.checkNotNull("preLoad", preLoad);
this.preLoad = preLoad;
return this;
Expand All @@ -104,26 +99,25 @@ public abstract class ChameleonBootstrap<T extends Chameleon, E extends Chameleo
* Load Chameleon implementation.
*
* @return Chameleon implementation instance.
* @throws ChameleonInstantiationException if something goes wrong while loading the Chameleon implementation.
* @throws ChameleonInstantiationException if something goes wrong while loading the Chameleon
* implementation.
*/
@Contract("-> 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<ChameleonPlatformExtension> sortedExtensions = this.extensions.loadSort();
sortedExtensions.forEach(ext -> ext.init(this.logger, this.eventBus));

Collection<ChameleonExtension<?>> 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<ChameleonExtension<?>> extensions) throws ChameleonInstantiationException;

protected abstract @NotNull ChameleonLogger createLogger();
protected abstract @NotNull T loadInternal() throws ChameleonInstantiationException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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.</p>
*/
@Internal
@Experimental
public final class AdventureMapper {

public static final @NotNull String ORIGINAL_PACKAGE = "net.ky".concat("ori.adventure.");
Expand Down
22 changes: 9 additions & 13 deletions api/src/main/java/dev/hypera/chameleon/event/EventBusImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,15 @@ public EventBusImpl(@NotNull ChameleonLogger logger) {
@Override
public void dispatch(@NotNull ChameleonEvent event) {
Preconditions.checkNotNull("event", event);
List<EventSubscriber<? super ChameleonEvent>> 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());
}
});
}
}
});
}

/**
Expand Down Expand Up @@ -150,7 +146,7 @@ public void unsubscribeIf(@NotNull Predicate<EventSubscriber<? super ChameleonEv
}
}

private @NotNull List<EventSubscriber<? super ChameleonEvent>> getSubscribers(@NotNull Class<? extends ChameleonEvent> event) {
private synchronized @NotNull List<EventSubscriber<? super ChameleonEvent>> getSubscribers(@NotNull Class<? extends ChameleonEvent> event) {
Preconditions.checkNotNull("event", event);
List<EventSubscriber<? super ChameleonEvent>> subscribers = this.sortedSubscriptions.entrySet().stream()
.filter(entry -> entry.getKey().isAssignableFrom(event)).map(Entry::getValue).findFirst().orElse(null);
Expand Down
Loading

0 comments on commit 3ab8972

Please sign in to comment.