From a2611d1aa18ead7bf4450aed23ac6935c709c91f Mon Sep 17 00:00:00 2001 From: Seppe Volkaerts Date: Sat, 6 Jan 2018 18:47:51 +0100 Subject: [PATCH] Work on datapacks --- .gitmodules | 2 +- SpongeAPI | 2 +- .../asset/AbstractMultiAssetRepository.java | 2 +- .../asset/ClassLoaderAssetRepository.java | 1 + .../server/plugin/InternalPluginsInfo.java | 1 + .../server/resource/FileSystemPack.java | 223 ++++++++++++++++ .../resource/FileSystemResourceData.java | 67 +++++ .../server/resource/IResourceData.java | 32 +++ .../server/resource/IResourceProvider.java | 106 ++++++++ .../server/resource/LanternPack.java | 77 ++++++ .../server/resource/LanternPackBuilder.java | 146 +++++++++++ .../server/resource/LanternResource.java | 85 +++++++ .../resource/LanternResourceManager.java | 237 ++++++++++++++++++ .../server/resource/LanternResourcePath.java | 190 ++++++++++++++ .../resource/LanternResourcePathBuilder.java | 101 ++++++++ .../server/resource/SuppliedPack.java | 143 +++++++++++ .../server/resource/package-info.java | 27 ++ 17 files changed, 1439 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/lanternpowered/server/resource/FileSystemPack.java create mode 100644 src/main/java/org/lanternpowered/server/resource/FileSystemResourceData.java create mode 100644 src/main/java/org/lanternpowered/server/resource/IResourceData.java create mode 100644 src/main/java/org/lanternpowered/server/resource/IResourceProvider.java create mode 100644 src/main/java/org/lanternpowered/server/resource/LanternPack.java create mode 100644 src/main/java/org/lanternpowered/server/resource/LanternPackBuilder.java create mode 100644 src/main/java/org/lanternpowered/server/resource/LanternResource.java create mode 100644 src/main/java/org/lanternpowered/server/resource/LanternResourceManager.java create mode 100644 src/main/java/org/lanternpowered/server/resource/LanternResourcePath.java create mode 100644 src/main/java/org/lanternpowered/server/resource/LanternResourcePathBuilder.java create mode 100644 src/main/java/org/lanternpowered/server/resource/SuppliedPack.java create mode 100644 src/main/java/org/lanternpowered/server/resource/package-info.java diff --git a/.gitmodules b/.gitmodules index ac10677b1..f37c52f35 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "SpongeAPI"] path = SpongeAPI - url = https://github.com/SpongePowered/SpongeAPI.git + url = https://github.com/LanternPowered/SpongeAPI.git ignore = dirty \ No newline at end of file diff --git a/SpongeAPI b/SpongeAPI index 60ba6a66d..25e1f66c4 160000 --- a/SpongeAPI +++ b/SpongeAPI @@ -1 +1 @@ -Subproject commit 60ba6a66dbae55cfdeefa7ff4a2893e755062bd7 +Subproject commit 25e1f66c4b57c2d85623021393abda41d4cbf6d5 diff --git a/src/main/java/org/lanternpowered/server/asset/AbstractMultiAssetRepository.java b/src/main/java/org/lanternpowered/server/asset/AbstractMultiAssetRepository.java index 4fd7c8023..dcf7f86fb 100644 --- a/src/main/java/org/lanternpowered/server/asset/AbstractMultiAssetRepository.java +++ b/src/main/java/org/lanternpowered/server/asset/AbstractMultiAssetRepository.java @@ -98,7 +98,7 @@ public Multimap getAssetsMap(String path, boolean checkChildDirec }); } final ImmutableMultimap.Builder builder = ImmutableMultimap.builder(); - map.entrySet().forEach(entry -> builder.putAll(entry.getKey(), entry.getValue().values())); + map.forEach((key, value) -> builder.putAll(key, value.values())); return builder.build(); } diff --git a/src/main/java/org/lanternpowered/server/asset/ClassLoaderAssetRepository.java b/src/main/java/org/lanternpowered/server/asset/ClassLoaderAssetRepository.java index 19d961246..e88163ea8 100644 --- a/src/main/java/org/lanternpowered/server/asset/ClassLoaderAssetRepository.java +++ b/src/main/java/org/lanternpowered/server/asset/ClassLoaderAssetRepository.java @@ -31,6 +31,7 @@ import java.io.File; import java.net.URL; +import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; diff --git a/src/main/java/org/lanternpowered/server/plugin/InternalPluginsInfo.java b/src/main/java/org/lanternpowered/server/plugin/InternalPluginsInfo.java index 2e5bf107f..e9f23dd97 100644 --- a/src/main/java/org/lanternpowered/server/plugin/InternalPluginsInfo.java +++ b/src/main/java/org/lanternpowered/server/plugin/InternalPluginsInfo.java @@ -27,6 +27,7 @@ import com.google.common.collect.ImmutableList; import org.spongepowered.api.Platform; +import org.spongepowered.api.plugin.Plugin; import java.util.List; diff --git a/src/main/java/org/lanternpowered/server/resource/FileSystemPack.java b/src/main/java/org/lanternpowered/server/resource/FileSystemPack.java new file mode 100644 index 000000000..73f557bf6 --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/FileSystemPack.java @@ -0,0 +1,223 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.lanternpowered.server.game.Lantern; +import org.spongepowered.api.data.DataView; +import org.spongepowered.api.data.persistence.DataFormats; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.resource.Resource; +import org.spongepowered.api.resource.ResourcePath; +import org.spongepowered.api.text.Text; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +public final class FileSystemPack extends LanternPack { + + private final Path dataRoot; + private final Cache cache = Caffeine.newBuilder().build(); + + FileSystemPack(Text name, @Nullable DataView metadata, + @Nullable PluginContainer plugin, Path root) { + super(name, metadata, plugin); + // Already go inside the data directory + // data//path/to/file + this.dataRoot = root.resolve("data"); + } + + @Override + void reload() { + this.cache.cleanUp(); + } + + private LanternResource load(LanternResourcePath resourcePath, Path path) { + final Path mcmetaPath = path.resolveSibling(path.getFileName().toString() + ".mcmeta"); + DataView mcmeta = null; + if (Files.exists(mcmetaPath)) { + try (BufferedReader reader = Files.newBufferedReader(mcmetaPath)) { + mcmeta = DataFormats.JSON.readFrom(reader); + } catch (IOException e) { + Lantern.getLogger().warn("Unable to load mcmeta file for: {} (path: {})", resourcePath, path, e); + } + } + return new LanternResource(resourcePath, this, new FileSystemResourceData(path, mcmeta)); + } + + @Override + public Stream walkResourcePaths(ResourcePath path, int maxDepth) { + checkNotNull(path, "path"); + final LanternResourcePath path1 = (LanternResourcePath) path; + if (path1.isFile() || maxDepth <= 0) { + return Stream.empty(); + } + Stream stream = null; + try { + // If a wildcard is used, there is some special handling required + if (path1.getNamespace().isEmpty()) { + // A list with all the namespace directories + for (Path namespacePath : Files.list(this.dataRoot).collect(Collectors.toList())) { + final String namespace = namespacePath.getFileName().toString(); + // Check if the namespace is valid, don't want to break things + if (LanternResourcePath.NAMESPACE_PATTERN.matcher(namespace).matches()) { + Lantern.getLogger().warn("Found a invalid namespace {} within the {} pack.", namespace, getName().toPlain()); + } else { + final LanternResourcePath newPath = LanternResourcePath.uncheckedOf(namespace, path.getPath()); + final Stream extraStream = walkResourcePaths( + newPath, namespacePath.resolve(path.getPath()), maxDepth); + if (extraStream != null) { + stream = stream == null ? extraStream : Stream.concat(stream, extraStream); + } + } + } + } else { + stream = walkResourcePaths(path1, this.dataRoot.resolve(path.getNamespace()).resolve(path.getPath()), maxDepth); + } + } catch (IOException e) { + Lantern.getLogger().error("Failed to walk resource path: {}", path, e); + } + return stream == null ? Stream.empty() : stream; + } + + @Nullable + private Stream walkResourcePaths(LanternResourcePath resourcePath, Path start, int maxDepth) { + if (!Files.exists(start)) { + return null; + } + try { + return Files.walk(start, maxDepth).map(path -> { + final Path relPath = path.relativize(start); + // Backslashes are not supported so replace them + final String path1 = relPath.toString().replace('\\', '/'); + // Check if it's a invalid path for a resource and ignore .mcmeta files + if (path1.endsWith(".mcmeta") || + !LanternResourcePath.PATH_PATTERN.matcher(path1).matches()) { + return null; + } + // Create the new path + return LanternResourcePath.uncheckedOf(resourcePath.getNamespace(), + resourcePath.getPath() + '/' + path1); + }); + } catch (IOException e) { + Lantern.getLogger().error("Failed to walk resource path: {}", resourcePath, e); + } + return null; + } + + @Override + public Stream walkResources(ResourcePath path, int maxDepth) { + checkNotNull(path, "path"); + final LanternResourcePath path1 = (LanternResourcePath) path; + if (path1.isFile() || maxDepth <= 0) { + return Stream.empty(); + } + Stream stream = null; + try { + // If a wildcard is used, there is some special handling required + if (path1.getNamespace().isEmpty()) { + // A list with all the namespace directories + for (Path namespacePath : Files.list(this.dataRoot).collect(Collectors.toList())) { + final String namespace = namespacePath.getFileName().toString(); + // Check if the namespace is valid, don't want to break things + if (LanternResourcePath.NAMESPACE_PATTERN.matcher(namespace).matches()) { + Lantern.getLogger().warn("Found a invalid namespace {} within the {} pack.", namespace, getName().toPlain()); + } else { + final LanternResourcePath newPath = LanternResourcePath.uncheckedOf(namespace, path.getPath()); + final Stream extraStream = walkResources( + newPath, namespacePath.resolve(path.getPath()), maxDepth); + if (extraStream != null) { + stream = stream == null ? extraStream : Stream.concat(stream, extraStream); + } + } + } + } else { + stream = walkResources(path1, this.dataRoot.resolve(path.getNamespace()).resolve(path.getPath()), maxDepth); + } + } catch (IOException e) { + Lantern.getLogger().error("Failed to walk resource path: {}", path, e); + } + return stream == null ? Stream.empty() : stream; + } + + @Nullable + private Stream walkResources(LanternResourcePath resourcePath, Path start, int maxDepth) { + if (!Files.exists(start)) { + return null; + } + try { + return Files.walk(start, maxDepth).map(path -> { + final Path relPath = path.relativize(start); + // Backslashes are not supported so replace them + final String path1 = relPath.toString().replace('\\', '/'); + // Check if it's a invalid path for a resource and ignore .mcmeta files + if (path1.endsWith(".mcmeta") || + !LanternResourcePath.PATH_PATTERN.matcher(path1).matches()) { + return null; + } + final LanternResourcePath newResourcePath = LanternResourcePath.uncheckedOf(resourcePath.getNamespace(), + resourcePath.getPath() + '/' + path1); + // Create the new path + return this.cache.get(newResourcePath, newResourcePath1 -> load(newResourcePath1, path)); + }); + } catch (IOException e) { + Lantern.getLogger().error("Failed to walk resource path: {}", resourcePath, e); + } + return null; + } + + @Override + public Collection getAllResources() { + return walkResources(LanternResourcePath.ALL).collect(Collectors.toList()); + } + + @Override + public Optional getResource(ResourcePath path) { + checkNotNull(path, "path"); + final LanternResourcePath path1 = (LanternResourcePath) path; + if (path1.isDirectory() || path1.getNamespace().isEmpty()) { + return Optional.empty(); + } + return Optional.ofNullable(this.cache.get(path1, path2 -> { + final Path filePath = this.dataRoot.resolve(path.getNamespace()).resolve(path.getPath()); + if (!Files.exists(filePath)) { + return null; + } + return load(path1, filePath); + })); + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/FileSystemResourceData.java b/src/main/java/org/lanternpowered/server/resource/FileSystemResourceData.java new file mode 100644 index 000000000..1143e6bbd --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/FileSystemResourceData.java @@ -0,0 +1,67 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import com.google.common.base.MoreObjects; +import org.spongepowered.api.data.DataView; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import javax.annotation.Nullable; + +public final class FileSystemResourceData implements IResourceData { + + private final Path path; + @Nullable private final DataView metadata; + + FileSystemResourceData(Path path, @Nullable DataView metadata) { + this.path = path; + this.metadata = metadata; + } + + @Override + public InputStream openStream() throws IOException { + return Files.newInputStream(this.path); + } + + @Override + public Optional getMetadata() throws IOException { + return Optional.ofNullable(this.metadata); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("path", this.path) + .add("metadata", this.metadata) + .omitNullValues() + .toString(); + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/IResourceData.java b/src/main/java/org/lanternpowered/server/resource/IResourceData.java new file mode 100644 index 000000000..bbfd13dec --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/IResourceData.java @@ -0,0 +1,32 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import org.spongepowered.api.resource.ResourceData; + +public interface IResourceData extends ResourceData { + +} diff --git a/src/main/java/org/lanternpowered/server/resource/IResourceProvider.java b/src/main/java/org/lanternpowered/server/resource/IResourceProvider.java new file mode 100644 index 000000000..667963213 --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/IResourceProvider.java @@ -0,0 +1,106 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import org.spongepowered.api.resource.Resource; +import org.spongepowered.api.resource.ResourcePath; +import org.spongepowered.api.resource.ResourceProvider; + +import java.util.Collection; +import java.util.stream.Stream; + +public interface IResourceProvider extends ResourceProvider { + + Collection getAllResources(); + + /** + * Lists all the {@link ResourcePath}s within the directory + * {@link ResourcePath}. If it's not a directory nothing will + * be listed. + * + * @param resourcePath The resource path + * @return The resource paths + */ + default Stream listResourcePaths(ResourcePath resourcePath) { + return walkResourcePaths(resourcePath, 1); + } + + /** + * Walks through the {@link LanternResource}s of the {@link ResourceProvider}. The + * depth specifies how deep the walked structure can be, this only defines + * the depth starting inside the {code data/namespace/} directory. + * It is also allowed to specify wildcards for the namespace. + *

These {@link Stream}s are by default not sorted, + * call {@link Stream#sorted()} if this is desired. + * + * @param resourcePath The resource path + * @return The stream to walk through the resources + */ + Stream walkResourcePaths(ResourcePath resourcePath, int maxDepth); + + /** + * Walks through the {@link LanternResource}s of the {@link ResourceProvider}. The + * depth specifies how deep the walked structure can be, this only defines + * the depth starting inside the {code data/namespace/} directory. + * It is also allowed to specify wildcards for the namespace. + *

These {@link Stream}s are by default not sorted, + * call {@link Stream#sorted()} if this is desired. + * + * @param resourcePath The resource path + * @return The stream to walk through the resources + */ + default Stream walkResourcePaths(ResourcePath resourcePath) { + return walkResourcePaths(resourcePath, Integer.MAX_VALUE); + } + + /** + * Walks through the {@link LanternResource}s of the {@link ResourceProvider}. The + * depth specifies how deep the walked structure can be, this only defines + * the depth starting inside the {code data/namespace/} directory. + * It is also allowed to specify wildcards for the namespace. + *

These {@link Stream}s are by default not sorted, + * call {@link Stream#sorted()} if this is desired. + * + * @param resourcePath The resource path + * @return The stream to walk through the resources + */ + Stream walkResources(ResourcePath resourcePath, int maxDepth); + + /** + * Walks through the {@link LanternResource}s of the {@link ResourceProvider}. The + * depth specifies how deep the walked structure can be, this only defines + * the depth starting inside the {code data/namespace/} directory. + * It is also allowed to specify wildcards for the namespace. + *

These {@link Stream}s are by default not sorted, + * call {@link Stream#sorted()} if this is desired. + * + * @param resourcePath The resource path + * @return The stream to walk through the resources + */ + default Stream walkResources(ResourcePath resourcePath) { + return walkResources(resourcePath, Integer.MAX_VALUE); + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/LanternPack.java b/src/main/java/org/lanternpowered/server/resource/LanternPack.java new file mode 100644 index 000000000..d89fe6d69 --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/LanternPack.java @@ -0,0 +1,77 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import org.spongepowered.api.data.DataView; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.resource.Pack; +import org.spongepowered.api.resource.Resource; +import org.spongepowered.api.text.Text; + +import java.util.Optional; + +import javax.annotation.Nullable; + +public abstract class LanternPack implements Pack, IResourceProvider { + + private final Text name; + @Nullable private final DataView metadata; + @Nullable final PluginContainer plugin; + + // Whether the pack is currently active + private boolean active; + + LanternPack(Text name, @Nullable DataView metadata, + @Nullable PluginContainer plugin) { + this.metadata = metadata; + this.plugin = plugin; + this.name = name; + } + + boolean isActive() { + return this.active; + } + + void setActive(boolean active) { + this.active = active; + } + + @Override + public Text getName() { + return this.name; + } + + @Override + public Optional getMetadata() { + return Optional.ofNullable(this.metadata); + } + + /** + * Reloads all the {@link Resource}s within + * this pack file system. + */ + abstract void reload(); +} diff --git a/src/main/java/org/lanternpowered/server/resource/LanternPackBuilder.java b/src/main/java/org/lanternpowered/server/resource/LanternPackBuilder.java new file mode 100644 index 000000000..64fc02b8f --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/LanternPackBuilder.java @@ -0,0 +1,146 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableMap; +import org.spongepowered.api.data.DataView; +import org.spongepowered.api.resource.Pack; +import org.spongepowered.api.resource.ResourceData; +import org.spongepowered.api.resource.ResourcePath; +import org.spongepowered.api.resource.ResourceProvider; +import org.spongepowered.api.text.Text; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +@SuppressWarnings({"ConstantConditions", "NullableProblems"}) +public class LanternPackBuilder implements Pack.Builder { + + private Text name; + @Nullable private DataView metadata; + + private final Map resources = new HashMap<>(); + @Nullable private Supplier> resourcesSupplier; + + @Override + public Pack.Builder name(Text name) { + checkNotNull(name, "name"); + this.name = name; + return this; + } + + @Override + public Pack.Builder metadata(DataView metadata) { + checkNotNull(metadata, "metadata"); + this.metadata = metadata; + return this; + } + + @Override + public Pack.Builder resources(ResourceProvider provider) { + throw new UnsupportedOperationException(); + } + + @Override + public Pack.Builder resource(ResourcePath path, ResourceData resource) { + checkNotNull(path, "path"); + checkNotNull(resource, "resource"); + checkState(((LanternResourcePath) path).isFile(), "Resource path must be a file"); + this.resources.put(path, resource); + return this; + } + + @Override + public Pack.Builder resources(Map resources) { + resources.forEach(this::resource); + return this; + } + + @Override + public Pack.Builder resources(Supplier> resources) { + checkNotNull(resources, "resources"); + this.resourcesSupplier = resources; + return this; + } + + @Override + public Pack build() { + checkState(this.name != null, "The name must be set"); + final ResourcesSupplier supplier = new ResourcesSupplier( + ImmutableMap.copyOf(this.resources), this.resourcesSupplier); + return new SuppliedPack(this.name, this.metadata, supplier); + } + + @Override + public Pack.Builder from(Pack value) { + return this; + } + + @Override + public Pack.Builder reset() { + this.name = null; + this.metadata = null; + this.resources.clear(); + this.resourcesSupplier = null; + return this; + } + + private static final class ResourcesSupplier implements Supplier> { + + private final Map resources; + @Nullable private final Supplier> supplier; + + private ResourcesSupplier(Map resources, + @Nullable Supplier> supplier) { + this.resources = resources; + this.supplier = supplier; + } + + @Override + public Map get() { + if (this.supplier == null) { + return this.resources; + } + final ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.putAll(this.resources); + final Map supplied = this.supplier.get(); + checkNotNull(supplied, "Supplied resources couldn't be found"); + for (Map.Entry entry : supplied.entrySet()) { + checkNotNull(entry.getKey(), "path"); + checkNotNull(entry.getValue(), "resource"); + checkState(((LanternResourcePath) entry.getKey()).isFile(), "Resource path must be a file"); + builder.put(entry.getKey(), entry.getValue()); + } + return builder.build(); + } + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/LanternResource.java b/src/main/java/org/lanternpowered/server/resource/LanternResource.java new file mode 100644 index 000000000..fcb5b99f0 --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/LanternResource.java @@ -0,0 +1,85 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import com.google.common.base.MoreObjects; +import org.spongepowered.api.data.DataView; +import org.spongepowered.api.resource.Pack; +import org.spongepowered.api.resource.Resource; +import org.spongepowered.api.resource.ResourceData; +import org.spongepowered.api.resource.ResourcePath; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +public final class LanternResource implements Resource, Comparable { + + private final LanternResourcePath resourcePath; + private final ResourceData resourceData; + private final Pack pack; + + LanternResource(ResourcePath resourcePath, Pack pack, ResourceData resourceData) { + this.resourcePath = (LanternResourcePath) resourcePath; + this.resourceData = resourceData; + this.pack = pack; + } + + @Override + public LanternResourcePath getResourcePath() { + return this.resourcePath; + } + + @Override + public Pack getPack() { + return this.pack; + } + + @Override + public InputStream openStream() throws IOException { + return this.resourceData.openStream(); + } + + @Override + public Optional getMetadata() throws IOException { + return this.resourceData.getMetadata(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("pack", this.pack.getName().toPlain()) + .add("path", this.resourcePath) + .add("data", this.resourceData instanceof IResourceData ? this.resourceData : null) + .omitNullValues() + .toString(); + } + + @Override + public int compareTo(LanternResource o) { + return this.resourcePath.compareTo(o.resourcePath); + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/LanternResourceManager.java b/src/main/java/org/lanternpowered/server/resource/LanternResourceManager.java new file mode 100644 index 000000000..9e5aaf83a --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/LanternResourceManager.java @@ -0,0 +1,237 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.lanternpowered.server.util.Conditions.checkPlugin; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.lanternpowered.server.event.CauseStack; +import org.lanternpowered.server.game.Lantern; +import org.lanternpowered.server.util.collect.Iterables2; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.SpongeEventFactory; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.resource.Pack; +import org.spongepowered.api.resource.Resource; +import org.spongepowered.api.resource.ResourceManager; +import org.spongepowered.api.resource.ResourcePath; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@SuppressWarnings("SuspiciousMethodCalls") +public class LanternResourceManager implements ResourceManager, IResourceProvider { + + private final Map packs = new HashMap<>(); + private final List sortedPacks = new ArrayList<>(); + private final Iterable reverseSortedPacks = Iterables2.reverse(this.sortedPacks); + + @Override + public Pack getPack(Object plugin) { + return this.packs.get(checkPlugin(plugin, "plugin").getId()); + } + + @Override + public Optional getPack(String name) { + checkNotNull(name, "name"); + return Optional.ofNullable(this.packs.get(name)); + } + + @Override + public List getActivePacks() { + return this.sortedPacks.stream() + .filter(LanternPack::isActive) + .collect(ImmutableList.toImmutableList()); + } + + @Override + public Map getPacks() { + return ImmutableMap.copyOf(this.packs); + } + + @Override + public void registerPack(String name, Pack pack) { + checkNotNull(name, "name"); + checkNotNull(pack, "pack"); + checkState(this.packs.containsKey(name), + "There is already a pack registered with the name %s", name); + checkState(this.packs.containsValue(pack), + "The pack is already registered under a different name"); + this.packs.put(name, (LanternPack) pack); + this.sortedPacks.add((LanternPack) pack); + } + + @Override + public Optional unregisterPack(String name) { + checkNotNull(name, "name"); + final LanternPack pack = this.packs.get(name); + if (pack == null) { + return Optional.empty(); + } + checkState(pack.plugin == null, + "Cannot unregister plugin pack: %s", name); + this.packs.remove(name); + this.sortedPacks.remove(pack); + return Optional.of(pack); + } + + @Override + public Collection getResources(ResourcePath path) { + checkNotNull(path, "path"); + final ImmutableList.Builder builder = ImmutableList.builder(); + for (LanternPack pack : this.sortedPacks) { + if (pack.isActive()) { + pack.getResource(path).ifPresent(builder::add); + } + } + return builder.build(); + } + + @Override + public Optional getResource(ResourcePath path) { + checkNotNull(path, "path"); + for (LanternPack pack : this.reverseSortedPacks) { + final Optional optResource = pack.getResource(path); + if (optResource.isPresent()) { + return optResource; + } + } + return Optional.empty(); + } + + @Override + public CompletableFuture reload() { + return reload(getActivePacks()); + } + + @Override + public CompletableFuture reload(List packs) { + final Cause cause = CauseStack.current().getCurrentCause(); + // Call the pre event + Sponge.getEventManager().post(SpongeEventFactory.createResourceReloadEventPre(cause, this)); + // Reload/cleanup all the current packs + for (Pack pack : packs) { + ((LanternPack) pack).reload(); + } + // Trigger the reload event across the server, outside + // of the world tick + Lantern.getScheduler().callSync(() -> { + // Post event? + // Sponge.getEventManager().post(SpongeEventFactory.createResourceReloadEventPost(cause, this)); + }); + return CompletableFuture.completedFuture(null); + } + + @Override + public Collection getAllResources() { + return walkResources(LanternResourcePath.ALL).collect(Collectors.toList()); + } + + @Override + public Stream walkResourcePaths(ResourcePath resourcePath, int maxDepth) { + // Get all the active packs + final Iterator it = Iterables2.reverse(this.sortedPacks.stream() + .filter(LanternPack::isActive) + .collect(Collectors.toList())) + .iterator(); + // Should never happen, someone disabled all the packs? + if (!it.hasNext()) { + return Stream.empty(); + } + Stream stream = it.next().walkResourcePaths(resourcePath, maxDepth); + while (it.hasNext()) { + stream = Stream.concat(stream, it.next().walkResourcePaths(resourcePath, maxDepth)); + } + // Make it a distinct stream to avoid duplicates + // A distinct stream suggests to make it sequential + // for better performance, while not breaking the + // possibility of using sorted + return stream.sequential().distinct(); + } + + private static final class ResourceMapping { + + private final LanternResource resource; + + private ResourceMapping(LanternResource resource) { + this.resource = resource; + } + + LanternResource unwrap() { + return this.resource; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ResourceMapping)) { + return false; + } + final ResourceMapping that = (ResourceMapping) obj; + return that.resource.getResourcePath() + .equals(this.resource.getResourcePath()); + } + + @Override + public int hashCode() { + return this.resource.getResourcePath().hashCode(); + } + } + + @Override + public Stream walkResources(ResourcePath resourcePath, int maxDepth) { + // Get all the active packs + final Iterator it = Iterables2.reverse(this.sortedPacks.stream() + .filter(LanternPack::isActive) + .collect(Collectors.toList())) + .iterator(); + // Should never happen, someone disabled all the packs? + if (!it.hasNext()) { + return Stream.empty(); + } + Stream stream = it.next().walkResources(resourcePath, maxDepth); + while (it.hasNext()) { + stream = Stream.concat(stream, it.next().walkResources(resourcePath, maxDepth)); + } + // Make it a distinct stream to avoid duplicates + // A distinct stream suggests to make it sequential + // for better performance, while not breaking the + // possibility of using sorted + // The LanternResource is temporarily wrapped into a + // ResourceMapping for custom `equals` behavior + return stream.sequential().map(ResourceMapping::new).distinct().map(ResourceMapping::unwrap); + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/LanternResourcePath.java b/src/main/java/org/lanternpowered/server/resource/LanternResourcePath.java new file mode 100644 index 000000000..a290aa10a --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/LanternResourcePath.java @@ -0,0 +1,190 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import com.google.common.base.Splitter; +import org.spongepowered.api.plugin.Plugin; +import org.spongepowered.api.resource.ResourcePath; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +public final class LanternResourcePath implements ResourcePath { + + private static final String RAW_PATH_PATTERN = "/?((?:[a-z][a-z0-9_\\-]*/)*(?:[a-z][a-z0-9_\\-.]*)*)"; + private static final String RAW_NAMESPACE_PATTERN = "(" + Plugin.ID_PATTERN.pattern() + ")?"; + + public static final Pattern PATH_PATTERN = Pattern.compile( + '^' + RAW_PATH_PATTERN + '$'); + public static final Pattern NAMESPACE_PATTERN = Pattern.compile( + '^' + RAW_NAMESPACE_PATTERN + '$'); + public static final Pattern NAMESPACE_PATH_PATTERN = Pattern.compile( + '^' + RAW_NAMESPACE_PATTERN + ":" + RAW_PATH_PATTERN + "$"); + + /** + * All the files will be matched when using this {@link ResourcePath}. + */ + public static final LanternResourcePath ALL = new LanternResourcePath("", ""); + + public static LanternResourcePath uncheckedOf(String namespace, String path) { + return new LanternResourcePath(namespace, path); + } + + private final static Splitter PATH_SPLITTER = Splitter.on('/'); + + private final String namespace; + private final String path; + + // Some cached values, big chance that they will be reused + + @Nullable private List names; + @Nullable private LanternResourcePath parent; + private int hashCode; + + LanternResourcePath(String namespace, String path) { + this.namespace = namespace; + this.path = path; + } + + @Override + public String getNamespace() { + return this.namespace; + } + + @Override + public String getPath() { + return this.path; + } + + public List getNames() { + if (this.names == null) { + this.names = this.path.isEmpty() ? Collections.emptyList() : PATH_SPLITTER.splitToList(this.path); + } + return names; + } + + public int getNamesCount() { + return getNames().size(); + } + + /** + * Gets the parent {@link ResourcePath} of this path. Will + * return itself if this is the root path. + * + * @return The parent + */ + public LanternResourcePath getParent() { + if (this.parent == null) { + // No more parents + if (this.path.isEmpty()) { + this.parent = this; + } else { + final int index = this.path.lastIndexOf('/'); + // We reached the root path + if (index == -1) { + this.parent = new LanternResourcePath(this.namespace, ""); + } else { + this.parent = new LanternResourcePath(this.namespace, this.path.substring(0, index)); + } + } + } + return this.parent; + } + + /** + * Gets whether this {@link ResourcePath} starts with the + * given {@link ResourcePath}. The namespace of the given resource + * path may be empty and this will be handled as a wildcard + * to allow every namespace. + * + * @param resourcePath The resource path + * @return Starts with target path + */ + public boolean startsWith(ResourcePath resourcePath) { + final LanternResourcePath that = (LanternResourcePath) resourcePath; + if (!that.namespace.isEmpty() && + !this.namespace.equals(that.namespace)) { + return false; + } + // If that is a file, the paths should match + if (that.isFile()) { + return this.path.equals(that.path); + } + return this.path.startsWith(that.path + '/'); + } + + /** + * Gets whether this {@link LanternResourcePath} + * points to a file. + * + * @return Is a file + */ + public boolean isFile() { + return this.path.indexOf('.') != -1; + } + + /** + * Gets whether this {@link LanternResourcePath} + * points to a directory. + * + * @return Is a directory + */ + public boolean isDirectory() { + return !isFile(); + } + + @Override + public int hashCode() { + if (this.hashCode == 0) { + this.hashCode = Objects.hash(this.namespace, this.path); + } + return this.hashCode; + } + + @Override + public String toString() { + return this.namespace + ':' + this.path; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ResourcePath)) { + return false; + } + final ResourcePath that = (ResourcePath) obj; + return that.getNamespace().equals(this.namespace) && + that.getPath().equals(this.path); + } + + @Override + public int compareTo(ResourcePath o) { + return toString().compareTo(o.toString()); + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/LanternResourcePathBuilder.java b/src/main/java/org/lanternpowered/server/resource/LanternResourcePathBuilder.java new file mode 100644 index 000000000..42c4576e2 --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/LanternResourcePathBuilder.java @@ -0,0 +1,101 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static org.lanternpowered.server.resource.LanternResourcePath.NAMESPACE_PATH_PATTERN; +import static org.lanternpowered.server.resource.LanternResourcePath.NAMESPACE_PATTERN; +import static org.lanternpowered.server.resource.LanternResourcePath.PATH_PATTERN; +import static org.lanternpowered.server.util.Conditions.checkPlugin; + +import org.spongepowered.api.resource.ResourcePath; + +import java.util.regex.Matcher; + +@SuppressWarnings({"ConstantConditions", "NullableProblems"}) +public final class LanternResourcePathBuilder implements ResourcePath.Builder { + + private String namespace; + private String path; + + @Override + public ResourcePath.Builder namespace(String namespace) throws IllegalArgumentException { + checkNotNull(namespace, "namespace"); + checkState(NAMESPACE_PATTERN.matcher(namespace).matches(), + "%s doesn't match the namespace pattern: %s", namespace, NAMESPACE_PATTERN.pattern()); + this.namespace = namespace; + return this; + } + + @Override + public ResourcePath.Builder plugin(Object plugin) { + this.namespace = checkPlugin(plugin, "plugin").getId(); + return this; + } + + @Override + public ResourcePath.Builder path(String path) throws IllegalArgumentException { + checkNotNull(path, "path"); + final Matcher matcher = PATH_PATTERN.matcher(path); + checkState(matcher.matches(), + "%s doesn't match the path pattern: %s", path, PATH_PATTERN.pattern()); + this.namespace = matcher.group(1); + return this; + } + + @Override + public ResourcePath.Builder parse(String path) throws IllegalArgumentException { + checkNotNull(path, "path"); + final Matcher matcher = NAMESPACE_PATH_PATTERN.matcher(path); + checkState(matcher.matches(), + "%s doesn't match the namespaced path pattern: %s", path, NAMESPACE_PATH_PATTERN.pattern()); + this.namespace = matcher.group(1); + this.path = matcher.group(2); + return this; + } + + @Override + public ResourcePath build() throws IllegalStateException { + checkState(this.namespace != null, "The namespace must be set"); + checkState(this.path != null, "The path must be set"); + return new LanternResourcePath(this.namespace, this.path); + } + + @Override + public ResourcePath.Builder from(ResourcePath value) { + this.namespace = value.getNamespace(); + this.path = value.getPath(); + return this; + } + + @Override + public ResourcePath.Builder reset() { + this.namespace = null; + this.path = null; + return this; + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/SuppliedPack.java b/src/main/java/org/lanternpowered/server/resource/SuppliedPack.java new file mode 100644 index 000000000..23f8fd4b5 --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/SuppliedPack.java @@ -0,0 +1,143 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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 org.lanternpowered.server.resource; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import org.spongepowered.api.data.DataView; +import org.spongepowered.api.resource.Resource; +import org.spongepowered.api.resource.ResourceData; +import org.spongepowered.api.resource.ResourcePath; +import org.spongepowered.api.text.Text; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import javax.annotation.Nullable; + +public class SuppliedPack extends LanternPack { + + private final Supplier> supplier; + + private Map resources = Collections.emptyMap(); + + SuppliedPack(Text name, @Nullable DataView metadata, + Supplier> supplier) { + super(name, metadata, null); + this.supplier = supplier; + } + + @Override + public Collection getAllResources() { + return ImmutableList.copyOf(this.resources.values()); + } + + @Override + public Stream walkResourcePaths(ResourcePath path, int maxDepth) { + checkNotNull(path, "path"); + final LanternResourcePath path1 = (LanternResourcePath) path; + // Not possible for files + if (path1.isFile() || maxDepth <= 0) { + return Stream.empty(); + } + int startDepth = path1.getNamesCount(); + // The max depth is increased based on the start path + maxDepth += startDepth; + // Start searching at one higher level + startDepth++; + // A set to avoid duplicate parent paths + final Set set = new HashSet<>(); + for (ResourcePath resPath : this.resources.keySet()) { + final LanternResourcePath resPath1 = (LanternResourcePath) resPath; + int depth = resPath1.getNamesCount(); + // Out of scope or in different directory + if (depth < startDepth || !resPath1.startsWith(path)) { + continue; + } + if (depth <= maxDepth) { + set.add(resPath1); + } else { + depth = maxDepth; + } + // Add parent directories + if (depth != startDepth) { + LanternResourcePath parent = resPath1.getParent(); + while (depth-- > startDepth) { + set.add(parent); + parent = parent.getParent(); + } + } + } + return set.stream(); + } + + @Override + public Stream walkResources(ResourcePath path, int maxDepth) { + checkNotNull(path, "path"); + final LanternResourcePath path1 = (LanternResourcePath) path; + // Not possible for files + if (path1.isFile() || maxDepth <= 0) { + return Stream.empty(); + } + int startDepth = path1.getNamesCount(); + // The max depth is increased based on the start path + maxDepth += startDepth; + // Start searching at one higher level + startDepth++; + // A set to avoid duplicate parent paths + final Set set = new HashSet<>(); + for (Map.Entry entry : this.resources.entrySet()) { + final LanternResourcePath resPath = (LanternResourcePath) entry.getKey(); + int depth = resPath.getNamesCount(); + // Out of scope or in different directory + if (depth >= startDepth && depth <= maxDepth && resPath.startsWith(path)) { + set.add((LanternResource) entry.getValue()); + } + } + return set.stream(); + } + + @Override + public Optional getResource(ResourcePath path) { + checkNotNull(path, "path"); + return Optional.ofNullable(this.resources.get(path)); + } + + @SuppressWarnings("ConstantConditions") + @Override + void reload() { + this.resources = Maps.transformEntries(this.supplier.get(), + (key, value) -> new LanternResource(key, this, value)); + } +} diff --git a/src/main/java/org/lanternpowered/server/resource/package-info.java b/src/main/java/org/lanternpowered/server/resource/package-info.java new file mode 100644 index 000000000..537edcded --- /dev/null +++ b/src/main/java/org/lanternpowered/server/resource/package-info.java @@ -0,0 +1,27 @@ +/* + * This file is part of LanternServer, licensed under the MIT License (MIT). + * + * Copyright (c) LanternPowered + * Copyright (c) SpongePowered + * 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. + */ +@org.spongepowered.api.util.annotation.NonnullByDefault +package org.lanternpowered.server.resource;