Extensions

This commit is contained in:
Luck 2019-08-26 19:31:55 +01:00
parent b286faa1c5
commit 7a31edea09
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
7 changed files with 369 additions and 0 deletions

View File

@ -0,0 +1,47 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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 net.luckperms.api.event.extension;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.Param;
import net.luckperms.api.extension.Extension;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Called when an {@link Extension} is loaded.
*/
public interface ExtensionLoadEvent extends LuckPermsEvent {
/**
* Gets the extension that was loaded.
*
* @return the extension
*/
@Param(0)
@NonNull Extension getExtension();
}

View File

@ -0,0 +1,50 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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 net.luckperms.api.extension;
import net.luckperms.api.LuckPerms;
/**
* Represents a simple extension "plugin" for LuckPerms.
*
* <p>Yes, that's right. A plugin for a plugin.</p>
*
* <p>Extensions should either declare a no-arg constructor, or a constructor
* that accepts a single {@link LuckPerms} parameter as it's only argument.</p>
*/
public interface Extension {
/**
* Loads the extension.
*/
void load();
/**
* Unloads the extension.
*/
void unload();
}

View File

@ -0,0 +1,61 @@
/*
* This file is part of luckperms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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 net.luckperms.api.extension;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
/**
* Manages extensions.
*/
public interface ExtensionManager {
/**
* Loads the given extension.
*
* @param extension the extension to load
*/
void loadExtension(Extension extension);
/**
* Loads the extension at the given path.
*
* @param path the path to the extension
* @throws IOException if the extension could not be loaded
*/
@NonNull Extension loadExtension(Path path) throws IOException;
/**
* Gets a collection of all loaded extensions.
*
* @return the loaded extensions
*/
@NonNull Collection<Extension> getLoadedExtensions();
}

View File

@ -48,6 +48,7 @@ import net.luckperms.api.event.Cancellable;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.cause.CreationCause;
import net.luckperms.api.event.cause.DeletionCause;
import net.luckperms.api.event.extension.ExtensionLoadEvent;
import net.luckperms.api.event.group.GroupCacheLoadEvent;
import net.luckperms.api.event.group.GroupCreateEvent;
import net.luckperms.api.event.group.GroupDataRecalculateEvent;
@ -82,6 +83,7 @@ import net.luckperms.api.event.user.UserFirstLoginEvent;
import net.luckperms.api.event.user.UserLoadEvent;
import net.luckperms.api.event.user.track.UserDemoteEvent;
import net.luckperms.api.event.user.track.UserPromoteEvent;
import net.luckperms.api.extension.Extension;
import net.luckperms.api.model.DataType;
import net.luckperms.api.model.PlayerSaveResult;
import net.luckperms.api.node.Node;
@ -133,6 +135,10 @@ public final class EventFactory {
return (T) GeneratedEventSpec.lookup(eventClass).newInstance(this.eventBus.getApiProvider(), params);
}
public void handleExtensionLoad(Extension extension) {
post(ExtensionLoadEvent.class, () -> generate(ExtensionLoadEvent.class, extension));
}
public void handleGroupCacheLoad(Group group, GroupCachedDataManager data) {
post(GroupCacheLoadEvent.class, () -> generate(GroupCacheLoadEvent.class, group.getApiDelegate(), data));
}

View File

@ -0,0 +1,183 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* 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 me.lucko.luckperms.common.extension;
import com.google.gson.JsonElement;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.extension.Extension;
import net.luckperms.api.extension.ExtensionManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SimpleExtensionManager implements ExtensionManager, AutoCloseable {
private final LuckPermsPlugin plugin;
private final Set<LoadedExtension> extensions = new HashSet<>();
public SimpleExtensionManager(LuckPermsPlugin plugin) {
this.plugin = plugin;
}
@Override
public void close() {
for (LoadedExtension extension : this.extensions) {
try {
extension.instance.unload();
if (extension.classLoader != null) {
extension.classLoader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void loadExtension(Extension extension) {
if (this.extensions.stream().anyMatch(e -> e.instance.equals(extension))) {
return;
}
this.extensions.add(new LoadedExtension(extension, null, null));
extension.load();
this.plugin.getEventFactory().handleExtensionLoad(extension);
}
public void loadExtensions(Path directory) {
if (!Files.exists(directory) || !Files.isDirectory(directory)) {
return;
}
try (Stream<Path> stream = Files.list(directory)) {
stream.forEach(path -> {
try {
loadExtension(path);
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public @NonNull Extension loadExtension(Path path) throws IOException {
if (this.extensions.stream().anyMatch(e -> path.equals(e.path))) {
throw new IllegalStateException("Extension at path " + path.toString() + " already loaded.");
}
if (!Files.exists(path)) {
throw new NoSuchFileException("No file at " + path);
}
URLClassLoader classLoader = new URLClassLoader(new URL[]{path.toUri().toURL()}, getClass().getClassLoader());
String className;
try (InputStream in = classLoader.getResourceAsStream("extension.json")) {
if (in == null) {
throw new RuntimeException("extension.json not present in " + path.toString());
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
JsonElement parsed = GsonProvider.parser().parse(reader);
className = parsed.getAsJsonObject().get("class").getAsString();
}
}
if (className == null) {
throw new IllegalArgumentException("class is null");
}
Class<? extends Extension> extensionClass;
try {
extensionClass = classLoader.loadClass(className).asSubclass(Extension.class);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Extension extension = null;
try {
Constructor<? extends Extension> constructor = extensionClass.getConstructor(LuckPerms.class);
extension = constructor.newInstance(this.plugin.getApiProvider());
} catch (NoSuchMethodException e) {
// ignore
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
if (extension == null) {
try {
Constructor<? extends Extension> constructor = extensionClass.getConstructor();
extension = constructor.newInstance();
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
this.extensions.add(new LoadedExtension(extension, classLoader, path));
extension.load();
this.plugin.getEventFactory().handleExtensionLoad(extension);
return extension;
}
@Override
public @NonNull Collection<Extension> getLoadedExtensions() {
return this.extensions.stream().map(e -> e.instance).collect(Collectors.toSet());
}
private static final class LoadedExtension {
private final Extension instance;
private final URLClassLoader classLoader;
private final Path path;
private LoadedExtension(Extension instance, URLClassLoader classLoader, Path path) {
this.instance = instance;
this.classLoader = classLoader;
this.path = path;
}
}
}

View File

@ -38,6 +38,7 @@ import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.event.AbstractEventBus;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.extension.SimpleExtensionManager;
import me.lucko.luckperms.common.inheritance.InheritanceHandler;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.message.Message;
@ -82,6 +83,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
private CalculatorFactory calculatorFactory;
private LuckPermsApiProvider apiProvider;
private EventFactory eventFactory;
private SimpleExtensionManager extensionManager;
/**
* Performs the initial actions to load the plugin
@ -166,6 +168,10 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
ApiRegistrationUtil.registerProvider(this.apiProvider);
registerApiOnPlatform(this.apiProvider);
// setup extension manager
this.extensionManager = new SimpleExtensionManager(this);
this.extensionManager.loadExtensions(getBootstrap().getConfigDirectory().resolve("extensions"));
// schedule update tasks
int mins = getConfiguration().get(ConfigKeys.SYNC_TIME);
if (mins > 0) {
@ -194,6 +200,9 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.permissionRegistry.stop();
this.verboseHandler.stop();
// unload extensions
this.extensionManager.close();
// remove any hooks into the platform
removePlatformHooks();
@ -332,6 +341,11 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
return this.apiProvider;
}
@Override
public SimpleExtensionManager getExtensionManager() {
return this.extensionManager;
}
@Override
public EventFactory getEventFactory() {
return this.eventFactory;

View File

@ -34,6 +34,7 @@ import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.context.ContextManager;
import me.lucko.luckperms.common.dependencies.DependencyManager;
import me.lucko.luckperms.common.event.EventFactory;
import me.lucko.luckperms.common.extension.SimpleExtensionManager;
import me.lucko.luckperms.common.inheritance.InheritanceHandler;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
@ -146,6 +147,13 @@ public interface LuckPermsPlugin {
*/
LuckPermsApiProvider getApiProvider();
/**
* Gets the extension manager.
*
* @return the extension manager
*/
SimpleExtensionManager getExtensionManager();
/**
* Gets the command manager
*