mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
Added basic extension loading
This commit is contained in:
parent
9084c59003
commit
8627787ea9
9 changed files with 918 additions and 0 deletions
|
@ -52,6 +52,7 @@ import org.geysermc.geyser.api.GeyserApi;
|
|||
import org.geysermc.geyser.command.CommandManager;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.extension.ExtensionManager;
|
||||
import org.geysermc.geyser.level.WorldManager;
|
||||
import org.geysermc.geyser.network.ConnectorServerEventHandler;
|
||||
import org.geysermc.geyser.pack.ResourcePack;
|
||||
|
@ -161,6 +162,8 @@ public class GeyserImpl implements GeyserApi {
|
|||
|
||||
ResourcePack.loadPacks();
|
||||
|
||||
ExtensionManager.init();
|
||||
|
||||
if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) {
|
||||
// Set the remote address to localhost since that is where we are always connecting
|
||||
try {
|
||||
|
@ -444,6 +447,8 @@ public class GeyserImpl implements GeyserApi {
|
|||
newsHandler.shutdown();
|
||||
this.getCommandManager().getCommands().clear();
|
||||
|
||||
ExtensionManager.getExtensionManager().disableExtensions();
|
||||
|
||||
bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done"));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface Extension {
|
||||
void onLoad();
|
||||
void onEnable();
|
||||
void onDisable();
|
||||
|
||||
boolean isEnabled();
|
||||
boolean isDisabled();
|
||||
|
||||
File getDataFolder();
|
||||
ExtensionDescription getDescription();
|
||||
String getName();
|
||||
|
||||
InputStream getResource(String filename);
|
||||
void saveResource(String filename, boolean replace);
|
||||
|
||||
GeyserImpl getGeyser();
|
||||
ClassLoader getClassLoader();
|
||||
ExtensionLoader getExtensionLoader();
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension;
|
||||
|
||||
import org.geysermc.geyser.extension.exception.InvalidExtensionException;
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ExtensionClassLoader extends URLClassLoader {
|
||||
private ExtensionLoader loader;
|
||||
private Map<String, Class> classes = new HashMap<>();
|
||||
public GeyserExtension extension;
|
||||
|
||||
public ExtensionClassLoader(ExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException {
|
||||
super(new URL[] { file.toURI().toURL() }, parent);
|
||||
this.loader = loader;
|
||||
|
||||
try {
|
||||
Class<?> jarClass;
|
||||
try {
|
||||
jarClass = Class.forName(description.getMain(), true, this);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
throw new InvalidExtensionException("Class " + description.getMain() + " not found, extension cannot be loaded", ex);
|
||||
}
|
||||
|
||||
Class<? extends GeyserExtension> extensionClass;
|
||||
try {
|
||||
extensionClass = jarClass.asSubclass(GeyserExtension.class);
|
||||
} catch (ClassCastException ex) {
|
||||
throw new InvalidExtensionException("Main class " + description.getMain() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex);
|
||||
}
|
||||
|
||||
extension = extensionClass.newInstance();
|
||||
} catch (IllegalAccessException ex) {
|
||||
throw new InvalidExtensionException("No public constructor", ex);
|
||||
} catch (InstantiationException ex) {
|
||||
throw new InvalidExtensionException("Abnormal extension type", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
return this.findClass(name, true);
|
||||
}
|
||||
|
||||
protected Class<?> findClass(String name, boolean checkGlobal) throws ClassNotFoundException {
|
||||
if (name.startsWith("org.geysermc.geyser.") || name.startsWith("org.geysermc.connector.") || name.startsWith("net.minecraft.")) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
Class<?> result = classes.get(name);
|
||||
if(result == null) {
|
||||
if(checkGlobal) {
|
||||
result = loader.getClassByName(name);
|
||||
}
|
||||
if(result == null) {
|
||||
result = super.findClass(name);
|
||||
if (result != null) {
|
||||
loader.setClass(name, result);
|
||||
}
|
||||
}
|
||||
classes.put(name, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<String> getClasses() {
|
||||
return classes.keySet();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension;
|
||||
|
||||
import org.geysermc.geyser.extension.exception.InvalidDescriptionException;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ExtensionDescription {
|
||||
private String name;
|
||||
private String main;
|
||||
private List<String> api;
|
||||
private String version;
|
||||
private final List<String> authors = new ArrayList<>();
|
||||
|
||||
public ExtensionDescription(String yamlString) throws InvalidDescriptionException {
|
||||
DumperOptions dumperOptions = new DumperOptions();
|
||||
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
Yaml yaml = new Yaml(dumperOptions);
|
||||
this.loadMap(yaml.loadAs(yamlString, LinkedHashMap.class));
|
||||
}
|
||||
|
||||
private void loadMap(Map<String, Object> yamlMap) throws InvalidDescriptionException {
|
||||
this.name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", "");
|
||||
if (this.name.equals("")) {
|
||||
throw new InvalidDescriptionException("Invalid extension name");
|
||||
}
|
||||
this.name = this.name.replace(" ", "_");
|
||||
this.version = String.valueOf(yamlMap.get("version"));
|
||||
this.main = (String) yamlMap.get("main");
|
||||
|
||||
Object api = yamlMap.get("api");
|
||||
if (api instanceof List) {
|
||||
this.api = (List<String>) api;
|
||||
} else {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add((String) api);
|
||||
this.api = list;
|
||||
}
|
||||
|
||||
if (yamlMap.containsKey("author")) {
|
||||
this.authors.add((String) yamlMap.get("author"));
|
||||
}
|
||||
|
||||
if (yamlMap.containsKey("authors")) {
|
||||
this.authors.addAll((Collection<? extends String>) yamlMap.get("authors"));
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getMain() {
|
||||
return this.main;
|
||||
}
|
||||
|
||||
public List<String> getAPIVersions() {
|
||||
return api;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public List<String> getAuthors() {
|
||||
return this.authors;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.extension.exception.InvalidDescriptionException;
|
||||
import org.geysermc.geyser.extension.exception.InvalidExtensionException;
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ExtensionLoader {
|
||||
private final Map<String, Class> classes = new HashMap<>();
|
||||
private final Map<String, ExtensionClassLoader> classLoaders = new HashMap<>();
|
||||
|
||||
public GeyserExtension loadExtension(File file) throws InvalidExtensionException {
|
||||
if (file == null) {
|
||||
throw new InvalidExtensionException("File is null");
|
||||
}
|
||||
|
||||
if (!file.exists()) {
|
||||
throw new InvalidExtensionException(new FileNotFoundException(file.getPath()) + " does not exist");
|
||||
}
|
||||
|
||||
final ExtensionDescription description;
|
||||
try {
|
||||
description = getExtensionDescription(file);
|
||||
} catch (InvalidDescriptionException e) {
|
||||
throw new InvalidExtensionException(e);
|
||||
}
|
||||
|
||||
final File parentFile = file.getParentFile();
|
||||
final File dataFolder = new File(parentFile, description.getName());
|
||||
if (dataFolder.exists() && !dataFolder.isDirectory()) {
|
||||
throw new InvalidExtensionException("The folder " + dataFolder.getPath() + " is not a directory and is the data folder for the extension " + description.getName() + "!");
|
||||
}
|
||||
|
||||
final ExtensionClassLoader loader;
|
||||
try {
|
||||
loader = new ExtensionClassLoader(this, getClass().getClassLoader(), description, file);
|
||||
} catch (Throwable e) {
|
||||
throw new InvalidExtensionException(e);
|
||||
}
|
||||
classLoaders.put(description.getName(), loader);
|
||||
|
||||
setup(loader.extension, description, dataFolder, file);
|
||||
return loader.extension;
|
||||
}
|
||||
|
||||
private void setup(GeyserExtension extension, ExtensionDescription description, File dataFolder, File file) {
|
||||
extension.init(GeyserImpl.getInstance(), description, dataFolder, file, this);
|
||||
extension.onLoad();
|
||||
}
|
||||
|
||||
public ExtensionDescription getExtensionDescription(File file) throws InvalidDescriptionException {
|
||||
JarFile jarFile = null;
|
||||
InputStream stream = null;
|
||||
|
||||
try {
|
||||
jarFile = new JarFile(file);
|
||||
|
||||
JarEntry descriptionEntry = jarFile.getJarEntry("extension.yml");
|
||||
if (descriptionEntry == null) {
|
||||
throw new InvalidDescriptionException(new FileNotFoundException("extension.yml") + " does not exist in the jar file!");
|
||||
}
|
||||
|
||||
stream = jarFile.getInputStream(descriptionEntry);
|
||||
|
||||
InputStreamReader reader = new InputStreamReader(stream);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String temp;
|
||||
BufferedReader bufferedReader = new BufferedReader(reader);
|
||||
temp = bufferedReader.readLine();
|
||||
while (temp != null) {
|
||||
if (builder.length() != 0) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(temp);
|
||||
temp = bufferedReader.readLine();
|
||||
}
|
||||
|
||||
return new ExtensionDescription(builder.toString());
|
||||
} catch (IOException e) {
|
||||
throw new InvalidDescriptionException(e);
|
||||
} finally {
|
||||
if (jarFile != null) {
|
||||
try {
|
||||
jarFile.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Pattern[] getExtensionFilters() {
|
||||
return new Pattern[] { Pattern.compile("^.+\\.jar$") };
|
||||
}
|
||||
|
||||
public Class<?> getClassByName(final String name) throws ClassNotFoundException{
|
||||
Class<?> clazz = classes.get(name);
|
||||
try {
|
||||
for(ExtensionClassLoader loader : classLoaders.values()) {
|
||||
try {
|
||||
clazz = loader.findClass(name,false);
|
||||
} catch(NullPointerException e) {
|
||||
}
|
||||
}
|
||||
return clazz;
|
||||
} catch(NullPointerException s) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setClass(String name, final Class<?> clazz) {
|
||||
if(!classes.containsKey(name)) {
|
||||
classes.put(name,clazz);
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeClass(String name) {
|
||||
Class<?> clazz = classes.remove(name);
|
||||
}
|
||||
|
||||
public void enableExtension(Extension extension) {
|
||||
if (extension instanceof GeyserExtension) {
|
||||
if(!extension.isEnabled()) {
|
||||
GeyserImpl.getInstance().getLogger().info("Enabled extension " + extension.getDescription().getName());
|
||||
((GeyserExtension) extension).setEnabled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disableExtension(Extension extension) {
|
||||
if (extension instanceof GeyserExtension) {
|
||||
if(extension.isEnabled()) {
|
||||
GeyserImpl.getInstance().getLogger().info("Disabled extension " + extension.getDescription().getName());
|
||||
((GeyserExtension) extension).setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ExtensionManager {
|
||||
private static ExtensionManager extensionManager = null;
|
||||
|
||||
protected Map<String, Extension> extensions = new LinkedHashMap<>();
|
||||
protected Map<Pattern, ExtensionLoader> fileAssociations = new HashMap<>();
|
||||
|
||||
public static void init() {
|
||||
GeyserImpl.getInstance().getLogger().info("Loading extensions...");
|
||||
extensionManager = new ExtensionManager();
|
||||
extensionManager.registerInterface(ExtensionLoader.class);
|
||||
extensionManager.loadExtensions(new File("extensions"));
|
||||
GeyserImpl.getInstance().getLogger().info("Loaded " + extensionManager.extensions.size() + " extensions.");
|
||||
|
||||
for (Extension extension : extensionManager.getExtensions().values()) {
|
||||
if (!extension.isEnabled()) {
|
||||
extensionManager.enableExtension(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ExtensionManager getExtensionManager() {
|
||||
return extensionManager;
|
||||
}
|
||||
|
||||
public Extension getExtension(String name) {
|
||||
if (this.extensions.containsKey(name)) {
|
||||
return this.extensions.get(name);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, Extension> getExtensions() {
|
||||
return this.extensions;
|
||||
}
|
||||
|
||||
public void registerInterface(Class<? extends ExtensionLoader> loader) {
|
||||
ExtensionLoader instance;
|
||||
|
||||
if (ExtensionLoader.class.isAssignableFrom(loader)) {
|
||||
Constructor<? extends ExtensionLoader> constructor;
|
||||
|
||||
try {
|
||||
constructor = loader.getConstructor();
|
||||
instance = constructor.newInstance();
|
||||
} catch (NoSuchMethodException ex) { // This should never happen
|
||||
String className = loader.getName();
|
||||
|
||||
throw new IllegalArgumentException("Class " + className + " does not have a public constructor", ex);
|
||||
} catch (Exception ex) { // This should never happen
|
||||
throw new IllegalArgumentException("Unexpected exception " + ex.getClass().getName() + " while attempting to construct a new instance of " + loader.getName(), ex);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Class " + loader.getName() + " does not implement interface ExtensionLoader");
|
||||
}
|
||||
|
||||
Pattern[] patterns = instance.getExtensionFilters();
|
||||
|
||||
synchronized (this) {
|
||||
for (Pattern pattern : patterns) {
|
||||
fileAssociations.put(pattern, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GeyserExtension loadExtension(File file, Map<Pattern, ExtensionLoader> loaders) {
|
||||
for (ExtensionLoader loader : (loaders == null ? this.fileAssociations : loaders).values()) {
|
||||
for (Pattern pattern : loader.getExtensionFilters()) {
|
||||
if (pattern.matcher(file.getName()).matches()) {
|
||||
try {
|
||||
ExtensionDescription description = loader.getExtensionDescription(file);
|
||||
if (description != null) {
|
||||
GeyserExtension extension = loader.loadExtension(file);
|
||||
|
||||
if (extension != null) {
|
||||
this.extensions.put(extension.getDescription().getName(), extension);
|
||||
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could not load extension", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, Extension> loadExtensions(File dictionary) {
|
||||
if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) {
|
||||
GeyserImpl.getInstance().getLogger().error("Cannot load extensions in a development environment, aborting extension loading");
|
||||
return new HashMap<>();
|
||||
}
|
||||
if (!GeyserImpl.VERSION.contains(".")) {
|
||||
GeyserImpl.getInstance().getLogger().error("Something went wrong with the Geyser version number, aborting extension loading");
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
if (!dictionary.exists()) {
|
||||
dictionary.mkdir();
|
||||
}
|
||||
if (!dictionary.isDirectory()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, File> extensions = new LinkedHashMap<>();
|
||||
Map<String, Extension> loadedExtensions = new LinkedHashMap<>();
|
||||
|
||||
for (final ExtensionLoader loader : this.fileAssociations.values()) {
|
||||
for (File file : dictionary.listFiles((dir, name) -> {
|
||||
for (Pattern pattern : loader.getExtensionFilters()) {
|
||||
if (pattern.matcher(name).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
if (file.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
ExtensionDescription description = loader.getExtensionDescription(file);
|
||||
if (description != null) {
|
||||
String name = description.getName();
|
||||
|
||||
if (extensions.containsKey(name) || this.getExtension(name) != null) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Found duplicate extension '" + name + "', ignoring '" + file.getName() + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean compatible = false;
|
||||
|
||||
for (String version : description.getAPIVersions()) {
|
||||
try {
|
||||
//Check the format: majorVersion.minorVersion.patch
|
||||
if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", version)) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Could't load extension " + name + ": Wrong API format");
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] versionArray = version.split("\\.");
|
||||
String[] apiVersion = GeyserImpl.VERSION.split("\\.");
|
||||
|
||||
//Completely different API version
|
||||
if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) {
|
||||
GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
//If the extension requires new API features, being backwards compatible
|
||||
if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) {
|
||||
GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name + ": Wrong API version, current version: " + apiVersion[0] + "." + apiVersion[1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
compatible = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!compatible) {
|
||||
GeyserImpl.getInstance().getLogger().error("Couldn't load extension " + name +": Incompatible API version");
|
||||
}
|
||||
|
||||
extensions.put(name, file);
|
||||
loadedExtensions.put(name, this.loadExtension(file, this.fileAssociations));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Couldn't load " +file.getName()+ " in folder " + dictionary + ": ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return loadedExtensions;
|
||||
}
|
||||
|
||||
public void enableExtension(Extension extension) {
|
||||
if (!extension.isEnabled()) {
|
||||
try {
|
||||
extension.getExtensionLoader().enableExtension(extension);
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error enabling extension " + extension.getName() + ": ", e);
|
||||
this.disableExtension(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disableExtension(Extension extension) {
|
||||
if (extension.isEnabled()) {
|
||||
try {
|
||||
extension.getExtensionLoader().disableExtension(extension);
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error disabling extension " + extension.getName() + ": ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disableExtensions() {
|
||||
for (Extension extension : this.getExtensions().values()) {
|
||||
this.disableExtension(extension);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension;
|
||||
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
public class GeyserExtension implements Extension {
|
||||
private boolean initialized = false;
|
||||
private boolean enabled = false;
|
||||
private File file = null;
|
||||
private File dataFolder = null;
|
||||
private ClassLoader classLoader = null;
|
||||
private GeyserImpl geyser = null;
|
||||
private ExtensionLoader loader;
|
||||
private ExtensionDescription description = null;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean value) {
|
||||
if (this.enabled != value) {
|
||||
this.enabled = value;
|
||||
if (this.enabled) {
|
||||
onEnable();
|
||||
} else {
|
||||
onDisable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisabled() {
|
||||
return !this.enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return this.dataFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionDescription getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.description.getName();
|
||||
}
|
||||
|
||||
public void init(GeyserImpl geyser, ExtensionDescription description, File dataFolder, File file, ExtensionLoader loader) {
|
||||
if (!this.initialized) {
|
||||
this.initialized = true;
|
||||
this.file = file;
|
||||
this.dataFolder = dataFolder;
|
||||
this.classLoader = this.getClass().getClassLoader();
|
||||
this.geyser = geyser;
|
||||
this.loader = loader;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResource(String filename) {
|
||||
if (filename == null) {
|
||||
throw new IllegalArgumentException("Filename cannot be null");
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = this.classLoader.getResource(filename);
|
||||
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URLConnection connection = url.openConnection();
|
||||
connection.setUseCaches(false);
|
||||
return connection.getInputStream();
|
||||
} catch (IOException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveResource(String filename, boolean replace) {
|
||||
if (filename == null || filename.equals("")) {
|
||||
throw new IllegalArgumentException("ResourcePath cannot be null or empty");
|
||||
}
|
||||
|
||||
filename = filename.replace('\\', '/');
|
||||
InputStream in = getResource(filename);
|
||||
if (in == null) {
|
||||
throw new IllegalArgumentException("The embedded resource '" + filename + "' cannot be found in " + file);
|
||||
}
|
||||
|
||||
File outFile = new File(dataFolder, filename);
|
||||
int lastIndex = filename.lastIndexOf('/');
|
||||
File outDir = new File(dataFolder, filename.substring(0, Math.max(lastIndex, 0)));
|
||||
|
||||
if (!outDir.exists()) {
|
||||
outDir.mkdirs();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!outFile.exists() || replace) {
|
||||
OutputStream out = new FileOutputStream(outFile);
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
} else {
|
||||
this.geyser.getLogger().warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists.");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
this.geyser.getLogger().severe("Could not save " + outFile.getName() + " to " + outFile, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeyserImpl getGeyser() {
|
||||
return this.geyser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
return this.classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionLoader getExtensionLoader() {
|
||||
return this.loader;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension.exception;
|
||||
|
||||
public class InvalidDescriptionException extends Exception {
|
||||
public InvalidDescriptionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public InvalidDescriptionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidDescriptionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.extension.exception;
|
||||
|
||||
public class InvalidExtensionException extends Exception {
|
||||
public InvalidExtensionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public InvalidExtensionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InvalidExtensionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue