diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 3b6d94c..c97f14e 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,11 +1,12 @@ module org.spongepowered.plugin.metadata { exports org.spongepowered.plugin.metadata; - exports org.spongepowered.plugin.metadata.builtin; - exports org.spongepowered.plugin.metadata.builtin.model; exports org.spongepowered.plugin.metadata.model; - exports org.spongepowered.plugin.metadata.util; + exports org.spongepowered.plugin.metadata.builtin; + exports org.spongepowered.plugin.metadata.builtin.adapter; + exports org.spongepowered.plugin.metadata.builtin.adapter.model; + exports org.spongepowered.plugin.metadata.builtin.adapter.version; requires transitive com.google.gson; requires static transitive org.checkerframework.checker.qual; requires static transitive maven.artifact; // Generated module name, may change? -} \ No newline at end of file +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/Constants.java b/src/main/java/org/spongepowered/plugin/metadata/Constants.java index 282d645..1334eb8 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/Constants.java +++ b/src/main/java/org/spongepowered/plugin/metadata/Constants.java @@ -35,13 +35,13 @@ public final class Constants { *
  • Must be between 2 and 64 characters in length
  • *
  • Must start with a lower case letter (a-z)
  • *
  • May only contain a mix of lower case letters (a-z), - * numbers (0-9), dashes (-), and underscores (_)
  • + * numbers (0-9) and underscores (_) * */ - public static final Pattern VALID_ID_PATTERN = Pattern.compile("^[a-z][a-z0-9-_]{1,63}$"); + public static final Pattern VALID_ID_PATTERN = Pattern.compile("^[a-z][a-z0-9_]{1,63}$"); public static final String INVALID_ID_REQUIREMENTS_MESSAGE = "IDs can be between 2 and 64 characters long and must start with a lower case" - + " letter, followed by any mix of lower case letters (a-z), numbers (0-9), dashes (-) and underscores (_)."; + + " letter, followed by any mix of lower case letters (a-z), numbers (0-9) and underscores (_)."; private Constants() { } diff --git a/src/main/java/org/spongepowered/plugin/metadata/Container.java b/src/main/java/org/spongepowered/plugin/metadata/Container.java deleted file mode 100644 index 4caa528..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/Container.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata; - -import org.apache.maven.artifact.Artifact; -import org.spongepowered.plugin.metadata.model.ContainerLoader; - -import java.util.Optional; -import java.util.Set; - -/** - * A container joins together {@link Inheritable global metadata} with specific one or more - * {@link PluginMetadata plugin metadata}. - *

    - * How the vendor utilizes this concept is largely left up to their discretion. A - * typical use case would be to load a file as a container. - * - * @see org.spongepowered.plugin.metadata.builtin.MetadataContainer MetadataContainer, for a generic implementation - */ -public interface Container { - - /** - * @return The {@link ContainerLoader loader}. - */ - ContainerLoader loader(); - - /** - * Gets the {@link String license} of the data within this container. - *

    - * Consult the vendor for how this field is used. - *

    - * In the generic implementation, a license's name is expected. - *

    - * Notable examples include MIT or All Rights Reserved. - * - * @return The license - */ - String license(); - - /** - * Gets the {@link Artifact mappings} that code within this container might be written in. - *

    - * The format of this string should be in maven dependency format (group:artifact:version). - *

    - * Consult the vendor for how this field is used. As an example, that entity could use this - * purely for information purposes or go farther and perform artifact remapping from these - * mappings to another. - * - * @return The mappings or {@link Optional#empty()} otherwise - */ - Optional mappings(); - - /** - * @return The {@link Inheritable global metadata} or {@link Optional#empty()} otherwise - */ - Optional globalMetadata(); - - /** - * Gets a {@link PluginMetadata plugin metadata} by its {@link String id}. - *

    - * This maps to {@link PluginMetadata#id()}. - * - * @param id The id - * @return The plugin metadata or {@link Optional#empty()} otherwise - */ - Optional metadata(String id); - - /** - * @return All of the {@link PluginMetadata plugin metadata} as an unmodifiable {@link Set}. - */ - Set metadata(); -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/Inheritable.java b/src/main/java/org/spongepowered/plugin/metadata/Inheritable.java deleted file mode 100644 index ac35191..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/Inheritable.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata; - -import org.apache.maven.artifact.versioning.ArtifactVersion; -import org.spongepowered.plugin.metadata.model.PluginBranding; -import org.spongepowered.plugin.metadata.model.PluginContributor; -import org.spongepowered.plugin.metadata.model.PluginDependency; -import org.spongepowered.plugin.metadata.model.PluginLinks; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -/** - * Represents metadata that is meant to be inherited/overwritten, per contract. - * - * @see org.spongepowered.plugin.metadata.builtin.StandardInheritable StandardInheritable, for a generic implementation - * @see PluginMetadata PluginMetadata, for specific "plugin" metadata - */ -public interface Inheritable { - - /** - * @return The {@link ArtifactVersion version}. - */ - ArtifactVersion version(); - - /** - * @return The {@link PluginBranding branding}. - */ - PluginBranding branding(); - - /** - * @return The {@link PluginLinks links} to various web resources. - */ - PluginLinks links(); - - /** - * @return The {@link PluginContributor contributors} as an unmodifiable {@link List}. - */ - List contributors(); - - /** - * Gets the {@link PluginDependency plugin dependency} by {@link String id}. - *

    - * This maps to {@link PluginDependency#id()}. - * @param id The id - * @return The dependency or {@link Optional#empty()} otherwise. - */ - Optional dependency(String id); - - /** - * @return The {@link PluginDependency dependencies} as an unmodifiable {@link Set}. - */ - Set dependencies(); - - /** - * Gets the {@link T property} by {@link String key}. - * - * @param key The key - * @param The type - * @return The property or {@link Optional#empty()} otherwise. - */ - Optional property(String key); - - /** - * @return The properties as an unmodifiable {@link Map}. - */ - Map properties(); -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/PluginMetadata.java b/src/main/java/org/spongepowered/plugin/metadata/PluginMetadata.java index caf8c12..ea6d4e0 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/PluginMetadata.java +++ b/src/main/java/org/spongepowered/plugin/metadata/PluginMetadata.java @@ -24,20 +24,25 @@ */ package org.spongepowered.plugin.metadata; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.spongepowered.plugin.metadata.model.PluginBranding; +import org.spongepowered.plugin.metadata.model.PluginContributor; +import org.spongepowered.plugin.metadata.model.PluginDependency; +import org.spongepowered.plugin.metadata.model.PluginEntrypoints; +import org.spongepowered.plugin.metadata.model.PluginLinks; +import org.spongepowered.plugin.metadata.model.PluginLoaderSpecification; + +import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Optional; /** - * Represents specific, unique metadata to a plugin. + * Represents the metadata of a plugin. * - * @see Inheritable Inheritable, for metadata that might be shared between multiple plugin metadata * @see org.spongepowered.plugin.metadata.builtin.StandardPluginMetadata StandardPluginMetadata, for a generic implementation */ -public interface PluginMetadata extends Inheritable { - - /** - * @return The {@link Container container}. - */ - Container container(); +public interface PluginMetadata { /** * Gets the {@link String id}. @@ -48,21 +53,26 @@ public interface PluginMetadata extends Inheritable { *

  • Must be between 2 and 64 characters in length
  • *
  • Must start with a lower case letter (a-z)
  • *
  • May only contain a mix of lower case letters (a-z), - * numbers (0-9), dashes (-), and underscores (_)
  • + * numbers (0-9) and underscores (_) * * @return The id */ String id(); /** - * Gets the {@link String entrypoint}. - *

    - * Consult the vendor for how this field is used. As an example, this could be - * the name of a module or a fully realized path to a discrete class. - * - * @return The entrypoint + * @return The {@link PluginEntrypoints entrypoints} */ - String entrypoint(); + PluginEntrypoints entrypoints(); + + /** + * @return The {@link ArtifactVersion version}. + */ + ArtifactVersion version(); + + /** + * @return The {@link PluginLoaderSpecification loader}. + */ + PluginLoaderSpecification loader(); /** * @return The {@link String name} or {@link Optional#empty()} otherwise. @@ -73,4 +83,57 @@ public interface PluginMetadata extends Inheritable { * @return The {@link String description} or {@link Optional#empty()} otherwise. */ Optional description(); + + /** + * Gets the {@link String license} of the data within this container. + *

    + * Notable examples include MIT or All Rights Reserved. + * + * @return The license + */ + Optional license(); + + /** + * @return The {@link PluginBranding branding}. + */ + PluginBranding branding(); + + /** + * @return The {@link PluginLinks links} to various web resources. + */ + PluginLinks links(); + + /** + * @return The {@link PluginContributor contributors} as an unmodifiable {@link List}. + */ + List contributors(); + + /** + * Gets the {@link PluginDependency plugin dependency} by {@link String id}. + *

    + * This maps to {@link PluginDependency#id()}. + * @param id The id + * @return The dependency or {@link Optional#empty()} otherwise. + */ + Optional dependency(String id); + + /** + * @return The {@link PluginDependency dependencies} as an unmodifiable {@link Collection}. + */ + Collection dependencies(); + + /** + * Gets the {@link T property} by {@link String key}. + * + * @param key The key + * @param The type + * @return The property or {@link Optional#empty()} otherwise. + */ + Optional property(String key); + + /** + * @return The properties as an unmodifiable {@link Map}. + */ + Map properties(); + } diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/InheritableMetadata.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/InheritableMetadata.java new file mode 100644 index 0000000..385f0bd --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/InheritableMetadata.java @@ -0,0 +1,335 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin; + +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.plugin.metadata.model.PluginBranding; +import org.spongepowered.plugin.metadata.model.PluginContributor; +import org.spongepowered.plugin.metadata.model.PluginDependency; +import org.spongepowered.plugin.metadata.model.PluginLinks; +import org.spongepowered.plugin.metadata.model.PluginLoaderSpecification; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.StringJoiner; + +public final class InheritableMetadata { + private static final InheritableMetadata NONE = new InheritableMetadata(); + + private final @Nullable ArtifactVersion version; + private final @Nullable PluginLoaderSpecification loader; + private final @Nullable String name; + private final @Nullable String description; + private final @Nullable String license; + private final PluginBranding branding; + private final PluginLinks links; + private final List contributors; + private final Map dependencies; + private final Map properties; + + private InheritableMetadata() { + this.version = null; + this.loader = null; + this.name = null; + this.description = null; + this.license = null; + this.branding = PluginBranding.none(); + this.links = PluginLinks.none(); + this.contributors = List.of(); + this.dependencies = Map.of(); + this.properties = Map.of(); + } + + private InheritableMetadata(final Builder builder) { + this.version = builder.version; + this.loader = builder.loader; + this.name = builder.name; + this.description = builder.description; + this.license = builder.license; + this.branding = builder.branding; + this.links = builder.links; + this.contributors = List.copyOf(builder.contributors); + this.dependencies = Collections.unmodifiableMap(new LinkedHashMap<>(builder.dependencies)); + this.properties = Collections.unmodifiableMap(new LinkedHashMap<>(builder.properties)); + } + + public Optional version() { + return Optional.ofNullable(this.version); + } + + public Optional loader() { + return Optional.ofNullable(this.loader); + } + + public Optional name() { + return Optional.ofNullable(this.name); + } + + public Optional description() { + return Optional.ofNullable(this.description); + } + + public Optional license() { + return Optional.ofNullable(this.license); + } + + public PluginBranding branding() { + return this.branding; + } + + public PluginLinks links() { + return this.links; + } + + public List contributors() { + return this.contributors; + } + + public Map dependencies() { + return this.dependencies; + } + + public Map properties() { + return this.properties; + } + + public InheritableMetadata with(final InheritableMetadata override) { + return this.toBuilder().with(override).build(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof InheritableMetadata other)) { + return false; + } + + return Objects.equals(this.version, other.version) + && Objects.equals(this.loader, other.loader) + && Objects.equals(this.name, other.name) + && Objects.equals(this.description, other.description) + && Objects.equals(this.license, other.license) + && this.branding.equals(other.branding) + && this.links.equals(other.links) + && this.contributors.equals(other.contributors) + && this.dependencies.equals(other.dependencies) + && this.properties.equals(other.properties); + } + + @Override + public int hashCode() { + return Objects.hash(this.version, this.loader, this.name, this.description, this.license, + this.branding, this.links, this.contributors, this.dependencies, this.properties); + } + + @Override + public String toString() { + return new StringJoiner(", ", InheritableMetadata.class.getSimpleName() + "[", "]") + .add("version=" + this.version) + .add("loader=" + this.loader) + .add("license=" + this.license) + .add("branding=" + this.branding) + .add("links=" + this.links) + .add("contributors=" + this.contributors) + .add("dependencies=" + this.dependencies) + .add("properties=" + this.properties) + .toString(); + } + + public InheritableMetadata.Builder toBuilder() { + return new InheritableMetadata.Builder().from(this); + } + + public static InheritableMetadata.Builder builder() { + return new InheritableMetadata.Builder(); + } + + public static InheritableMetadata none() { + return InheritableMetadata.NONE; + } + + public static final class Builder { + private @Nullable ArtifactVersion version; + private @Nullable PluginLoaderSpecification loader; + private @Nullable String name; + private @Nullable String description; + private @Nullable String license; + private PluginBranding branding = PluginBranding.none(); + private PluginLinks links = PluginLinks.none(); + private final List contributors = new LinkedList<>(); + private final Map dependencies = new LinkedHashMap<>(); + private final Map properties = new LinkedHashMap<>(); + + private Builder() { + } + + public Builder version(final @Nullable ArtifactVersion version) { + this.version = version; + return this; + } + + public Builder loader(final @Nullable PluginLoaderSpecification loader) { + this.loader = loader; + return this; + } + + public Builder name(final @Nullable String name) { + this.name = name; + return this; + } + + public Builder description(final @Nullable String description) { + this.description = description; + return this; + } + + public Builder license(final @Nullable String license) { + this.license = license; + return this; + } + + public Builder branding(final PluginBranding branding) { + this.branding = Objects.requireNonNull(branding, "branding"); + return this; + } + + public Builder links(final PluginLinks links) { + this.links = Objects.requireNonNull(links, "links"); + return this; + } + + public Builder contributors(final Collection contributors) { + Objects.requireNonNull(contributors, "contributors"); + this.contributors.clear(); + this.contributors.addAll(contributors); + return this; + } + + public Builder addContributors(final Collection contributors) { + this.contributors.addAll(Objects.requireNonNull(contributors, "contributors")); + return this; + } + + public Builder addContributor(final PluginContributor contributor) { + this.contributors.add(Objects.requireNonNull(contributor, "contributor")); + return this; + } + + public Builder dependencies(final Collection dependencies) { + Objects.requireNonNull(dependencies, "dependencies"); + this.dependencies.clear(); + return this.addDependencies(dependencies); + } + + public Builder addDependencies(final Collection dependencies) { + for (final PluginDependency element : Objects.requireNonNull(dependencies, "dependencies")) { + this.dependencies.put(Objects.requireNonNull(element, "element").id(), element); + } + return this; + } + + public Builder addDependency(final PluginDependency dependency) { + this.dependencies.put(Objects.requireNonNull(dependency, "dependency").id(), dependency); + return this; + } + + public Builder properties(final Map properties) { + Objects.requireNonNull(properties, "properties"); + this.properties.clear(); + this.properties.putAll(properties); + return this; + } + + public Builder addProperties(final Map properties) { + this.properties.putAll(Objects.requireNonNull(properties, "properties")); + return this; + } + + public Builder addProperty(final String key, final Object value) { + this.properties.put(Objects.requireNonNull(key, "key"), Objects.requireNonNull(value, "value")); + return this; + } + + public Builder from(final InheritableMetadata value) { + Objects.requireNonNull(value, "value"); + this.version = value.version; + this.loader = value.loader; + this.name = value.name; + this.description = value.description; + this.license = value.license; + this.branding = value.branding; + this.links = value.links; + this.contributors.clear(); + this.contributors.addAll(value.contributors); + this.dependencies.clear(); + this.dependencies.putAll(value.dependencies); + this.properties.clear(); + this.properties.putAll(value.properties); + return this; + } + + public Builder with(final InheritableMetadata override) { + if (override.version != null) { + this.version = override.version; + } + if (override.loader != null) { + this.loader = override.loader; + } + if (override.name != null) { + this.name = override.name; + } + if (override.description != null) { + this.description = override.description; + } + if (override.license != null) { + this.license = override.license; + } + if (!override.branding.equals(PluginBranding.none())) { + this.branding = override.branding; + } + if (!override.links.equals(PluginLinks.none())) { + this.links = override.links; + } + this.contributors.addAll(override.contributors); + this.dependencies.putAll(override.dependencies); + this.properties.putAll(override.properties); + return this; + } + + public InheritableMetadata build() { + return new InheritableMetadata(this); + } + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataContainer.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataContainer.java index 6896a56..e71c3d8 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataContainer.java +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataContainer.java @@ -24,244 +24,20 @@ */ package org.spongepowered.plugin.metadata.builtin; -import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import org.apache.maven.artifact.ArtifactUtils; -import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.plugin.metadata.Container; -import org.spongepowered.plugin.metadata.Inheritable; -import org.spongepowered.plugin.metadata.PluginMetadata; -import org.spongepowered.plugin.metadata.builtin.model.Adapters; -import org.spongepowered.plugin.metadata.builtin.model.StandardContainerLoader; -import org.spongepowered.plugin.metadata.util.GsonUtils; - -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; - -public final class MetadataContainer implements Container { - - private final String license; - @Nullable private final String mappings; - private final StandardContainerLoader loader; - @Nullable private final Inheritable globalMetadata; - private final Set metadata = new LinkedHashSet<>(); - private final Map metadataById = new LinkedHashMap<>(); - - private MetadataContainer(final Builder builder) { - this.loader = builder.loader; - this.license = builder.license; - this.mappings = builder.mappings; - this.globalMetadata = builder.globalMetadata; - this.metadata.addAll(builder.metadata); - for (final StandardPluginMetadata pm : this.metadata) { - this.metadataById.put(pm.id(), pm); - pm.setContainer(this); - } - } - - @Override - public StandardContainerLoader loader() { - return this.loader; - } - - @Override - public String license() { - return this.license; - } - - @Override - public Optional mappings() { - return Optional.ofNullable(this.mappings); - } - - @Override - public Optional globalMetadata() { - return Optional.ofNullable(this.globalMetadata); - } - - @Override - public Optional metadata(final String id) { - return Optional.ofNullable(this.metadataById.get(Objects.requireNonNull(id, "id"))); - } - - @Override - public Set metadata() { - return Collections.unmodifiableSet(this.metadata); - } - - @Override - public String toString() { - return new StringJoiner(", ", MetadataContainer.class.getSimpleName() + "[", "]") - .add("loader=" + this.loader) - .add("license=" + this.license) - .add("mappings=" + this.mappings) - .add("globalMetadata=" + this.globalMetadata) - .toString(); - } - - public MetadataContainer.Builder toBuilder() { - final Builder builder = new Builder(); - builder.loader = this.loader; - builder.license = this.license; - builder.mappings = this.mappings; - builder.globalMetadata = this.globalMetadata; - builder.metadata.addAll(this.metadata); - return builder; - } - - public static final class Builder { - - final Set metadata = new LinkedHashSet<>(); - @Nullable String license, mappings; - @Nullable StandardContainerLoader loader; - @Nullable Inheritable globalMetadata; - - public Builder loader(final StandardContainerLoader loader) { - this.loader = Objects.requireNonNull(loader, "loader"); - return this; - } - - public Builder license(final String license) { - this.license = Objects.requireNonNull(license, "license"); - return this; - } - - public Builder mappings(@Nullable final String mappings) { - - if (mappings != null) { - final String[] elements = mappings.split("\\.", 3); - // Triggers their sanity checks - ArtifactUtils.key(elements[0], elements[1], elements[2]); - } - - this.mappings = mappings; - return this; - } - - public Builder globalMetadata(final Inheritable globalMetadata) { - this.globalMetadata = Objects.requireNonNull(globalMetadata, "globalMetadata"); - return this; - } - - public Builder metadata(final List metadata) { - Objects.requireNonNull(metadata, "metadata"); - this.metadata.clear(); - this.metadata.addAll(metadata); - return this; - } - - public Builder addMetadata(final List metadata) { - this.metadata.addAll(Objects.requireNonNull(metadata, "metadata")); - return this; - } - - public Builder addMetadata(final StandardPluginMetadata metadata) { - this.metadata.add(Objects.requireNonNull(metadata, "metadata")); - return this; - } - - public MetadataContainer build() throws IllegalStateException, InvalidVersionSpecificationException { - Objects.requireNonNull(this.license, "license"); - Objects.requireNonNull(this.loader, "loader"); - - if (this.metadata.isEmpty()) { - throw new IllegalStateException("A MetadataHolder must hold at least 1 PluginMetadata!"); - } - - return new MetadataContainer(this); - } - } - - public static final class Serializer implements JsonSerializer, JsonDeserializer { - - @Override - public MetadataContainer deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) - throws JsonParseException { - final JsonObject obj = element.getAsJsonObject(); - if (!obj.has("loader")) { - throw new MissingRequiredFieldException("loader"); - } - if (!obj.has("license")) { - throw new MissingRequiredFieldException("license"); - } - if (!obj.has("plugins")) { - throw new MissingRequiredFieldException("plugins"); - } - - final Builder builder = new Builder() - .loader(Adapters.Deserializers.CONTAINER_LOADER.fromJsonTree(obj.get("loader")).build()) - .license(obj.get("license").getAsString()); - - GsonUtils.consumeIfPresent(obj, "mappings", e -> builder.mappings(e.getAsString())); - - final JsonElement globalElement = obj.get("global"); - @Nullable StandardInheritable inheritable = null; - if (globalElement instanceof JsonObject) { - inheritable = context.deserialize(globalElement, StandardInheritable.class); - builder.globalMetadata(inheritable); - } - final JsonElement pluginsElement = obj.get("plugins"); - final List pluginObjects = new LinkedList<>(); +public record MetadataContainer(InheritableMetadata global, List plugins) { - if (pluginsElement.isJsonArray()) { - for (final JsonElement pluginElement : ((JsonArray) pluginsElement)) { - if (pluginElement.isJsonObject()) { - pluginObjects.add((JsonObject) pluginElement); - } - } + public MetadataContainer { + Objects.requireNonNull(global, "global"); + Objects.requireNonNull(plugins, "plugins"); + for (final StandardPluginMetadata plugin : plugins) { + Objects.requireNonNull(plugin, "plugin"); + if (!plugin.global().equals(global)) { + throw new IllegalArgumentException("global metadata mismatch"); } - - if (pluginObjects.isEmpty()) { - throw new JsonParseException("No plugin metadata has been specified for the 'plugins' tag!"); - } - - for (final JsonObject pluginObject : pluginObjects) { - final StandardPluginMetadata.Builder pluginBuilder = context.deserialize(pluginObject, StandardPluginMetadata.Builder.class); - if (inheritable != null) { - pluginBuilder.from(inheritable); - } - - builder.addMetadata(pluginBuilder.build()); - } - - try { - return builder.build(); - } catch (final InvalidVersionSpecificationException e) { - throw new JsonParseException(e); - } - } - - @Override - public JsonElement serialize(final MetadataContainer value, final Type type, final JsonSerializationContext context) { - final JsonObject obj = new JsonObject(); - obj.add("loader", Adapters.Serializers.CONTAINER_LOADER.toJsonTree(value.loader)); - obj.addProperty("license", value.license); - GsonUtils.writeIfPresent(obj, "mappings", value.mappings()); - - final JsonArray plugins = new JsonArray(); - for (final PluginMetadata metadata : value.metadata) { - plugins.add(context.serialize(metadata, StandardPluginMetadata.class)); - } - obj.add("plugins", plugins); - - return obj; } + plugins = List.copyOf(plugins); } } diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataParser.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataParser.java index 05f624c..584d60a 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataParser.java +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/MetadataParser.java @@ -28,6 +28,16 @@ import com.google.gson.GsonBuilder; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.VersionRange; +import org.spongepowered.plugin.metadata.builtin.adapter.InheritableMetadataAdapter; +import org.spongepowered.plugin.metadata.builtin.adapter.MetadataContainerAdapter; +import org.spongepowered.plugin.metadata.builtin.adapter.StandardPluginMetadataBuilderDeserializer; +import org.spongepowered.plugin.metadata.builtin.adapter.StandardPluginMetadataSerializer; +import org.spongepowered.plugin.metadata.builtin.adapter.model.*; +import org.spongepowered.plugin.metadata.builtin.adapter.version.ArtifactVersionAdapter; +import org.spongepowered.plugin.metadata.builtin.adapter.version.VersionRangeAdapter; +import org.spongepowered.plugin.metadata.model.*; import java.io.BufferedReader; import java.io.IOException; @@ -35,38 +45,69 @@ import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Objects; +import java.util.Set; public final class MetadataParser { + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(PluginLoaderSpecification.class, new PluginLoaderSpecificationAdapter()) + .registerTypeAdapter(PluginBranding.class, new PluginBrandingAdapter()) + .registerTypeAdapter(PluginContributor.class, new PluginContributorAdapter()) + .registerTypeAdapter(PluginDependency.class, new PluginDependencyAdapter()) + .registerTypeAdapter(PluginLinks.class, new PluginLinksAdapter()) + .registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionAdapter()) + .registerTypeAdapter(VersionRange.class, new VersionRangeAdapter()) + .registerTypeAdapter(InheritableMetadata.class, new InheritableMetadataAdapter()) + .registerTypeAdapter(MetadataContainer.class, new MetadataContainerAdapter()) + .registerTypeAdapter(StandardPluginMetadata.Builder.class, new StandardPluginMetadataBuilderDeserializer()) + .registerTypeAdapter(StandardPluginMetadata.class, new StandardPluginMetadataSerializer()) + .create(); + + private static final Set warnings = new LinkedHashSet<>(); private MetadataParser() { } - public static GsonBuilder gsonBuilder() { - return new GsonBuilder() - .registerTypeAdapter(MetadataContainer.class, new MetadataContainer.Serializer()) - .registerTypeAdapter(StandardInheritable.class, new StandardInheritable.Serializer()) - .registerTypeAdapter(StandardPluginMetadata.Builder.class, new StandardPluginMetadata.Deserializer()) - .registerTypeAdapter(StandardPluginMetadata.class, new StandardPluginMetadata.Serializer()) - ; + public static Gson gson() { + return MetadataParser.GSON; + } + + /** + * Returns all warnings generated by (de)serializers so that the platform may log them when appropriate. + * + * @return The warnings + */ + public static Set warnings() { + return Collections.unmodifiableSet(MetadataParser.warnings); + } + + /** + * Adds a warning generated by a (de)serializer so that the platform may log it later. + * + * @param warning The warning + */ + public static void addWarning(final String warning) { + MetadataParser.warnings.add(Objects.requireNonNull(warning)); } /** * Reads a {@link MetadataContainer container} from a given {@link Path path} using the default {@link Gson deserializer} - * (retrieved from {@link #gsonBuilder()}). + * (retrieved from {@link #gson()}). * * @param path The path * @return The container * @throws IOException if the container fails to be read */ public static MetadataContainer read(final Path path) throws IOException { - return MetadataParser.read(path, MetadataParser.gsonBuilder().create()); + return MetadataParser.read(path, MetadataParser.gson()); } /** * Reads a {@link MetadataContainer container} from a given {@link Path path} with configured {@link Gson deserializer}. *

    - * To get a standard deserializer, {@link MetadataParser#gsonBuilder()} is available. + * To get a standard deserializer, {@link MetadataParser#gson()} is available. * @param path The path * @param gson The deserializer * @return The container @@ -83,20 +124,20 @@ public static MetadataContainer read(final Path path, final Gson gson) throws IO /** * Reads a {@link MetadataContainer container} from a given {@link Reader reader} using the default {@link Gson deserializer} - * (retrieved from {@link #gsonBuilder()}). + * (retrieved from {@link #gson()}). * * @param reader The reader * @return The container * @throws IOException if the container fails to be read */ public static MetadataContainer read(final Reader reader) throws IOException { - return MetadataParser.read(reader, MetadataParser.gsonBuilder().create()); + return MetadataParser.read(reader, MetadataParser.gson()); } /** * Reads a {@link MetadataContainer container} from a given {@link Reader reader} with configured {@link Gson deserializer}. *

    - * To get a standard deserializer, {@link MetadataParser#gsonBuilder()} is available. + * To get a standard deserializer, {@link MetadataParser#gson()} is available. * @param reader The reader * @param gson The deserializer * @return The container @@ -111,10 +152,23 @@ public static MetadataContainer read(final Reader reader, final Gson gson) throw } } + /** + * Writes a {@link MetadataContainer container} to the given {@link Path path} using the default {@link Gson deserializer} + * (retrieved from {@link #gson()}). + * + * @param path The path + * @param container The container + * @param indent True to indent (pretty print) the resulting JSON, false if not + * @throws IOException If the container fails to serialize + */ + public static void write(final Path path, final MetadataContainer container, final boolean indent) throws IOException { + MetadataParser.write(path, container, MetadataParser.gson(), indent); + } + /** * Writes a {@link MetadataContainer container} to the given {@link Path path} using the configured {@link Gson serializer}. *

    - * To get a standard serializer, {@link MetadataParser#gsonBuilder()} is available. + * To get a standard serializer, {@link MetadataParser#gson()} is available. * @param path The path * @param container The container * @param gson The serializer @@ -134,10 +188,23 @@ public static void write(final Path path, final MetadataContainer container, fin } } + /** + * Writes a {@link MetadataContainer container} to the given {@link Path path} using the default {@link Gson deserializer} + * (retrieved from {@link #gson()}). + * + * @param writer The writer + * @param container The container + * @param indent True to indent (pretty print) the resulting JSON, false if not + * @throws IOException If the container fails to serialize + */ + public static void write(final Writer writer, final MetadataContainer container, final boolean indent) throws IOException { + MetadataParser.write(writer, container, MetadataParser.gson(), indent); + } + /** * Writes a {@link MetadataContainer container} to the given {@link Path path} using the configured {@link Gson serializer}. *

    - * To get a standard serializer, {@link MetadataParser#gsonBuilder()} is available. + * To get a standard serializer, {@link MetadataParser#gson()} is available. * @param writer The writer * @param container The container * @param gson The serializer diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/MissingRequiredFieldException.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/MissingRequiredFieldException.java index 02b8981..402e16f 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/MissingRequiredFieldException.java +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/MissingRequiredFieldException.java @@ -26,8 +26,11 @@ import com.google.gson.JsonParseException; +import java.io.Serial; + public final class MissingRequiredFieldException extends JsonParseException { + @Serial private static final long serialVersionUID = 223908766837278356L; public MissingRequiredFieldException(final String field) { diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/StandardInheritable.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/StandardInheritable.java deleted file mode 100644 index d542ab2..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/StandardInheritable.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.builtin; - -import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonPrimitive; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; -import org.apache.maven.artifact.versioning.ArtifactVersion; -import org.apache.maven.artifact.versioning.DefaultArtifactVersion; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.plugin.metadata.Inheritable; -import org.spongepowered.plugin.metadata.builtin.model.Adapters; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginBranding; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginContributor; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginDependency; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginLinks; -import org.spongepowered.plugin.metadata.model.PluginContributor; -import org.spongepowered.plugin.metadata.model.PluginDependency; -import org.spongepowered.plugin.metadata.util.GsonUtils; - -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -@SuppressWarnings("unchecked") -public class StandardInheritable implements Inheritable { - - protected final ArtifactVersion version; - protected final String rawVersion; - protected final StandardPluginBranding branding; - protected final StandardPluginLinks links; - protected final List contributors = new LinkedList<>(); - protected final Set dependencies = new LinkedHashSet<>(); - protected final Map properties = new LinkedHashMap<>(); - private final Map dependenciesById = new LinkedHashMap<>(); - - @SuppressWarnings("rawtypes") - protected StandardInheritable(final AbstractBuilder builder) { - this.version = builder.version; - this.rawVersion = builder.rawVersion; - this.branding = builder.branding; - this.links = builder.links; - this.contributors.addAll(builder.contributors); - this.dependencies.addAll(builder.dependencies); - for (final StandardPluginDependency dependency : this.dependencies) { - this.dependenciesById.put(dependency.id(), dependency); - } - this.properties.putAll(builder.properties); - } - - @Override - public ArtifactVersion version() { - return this.version; - } - - @Override - public StandardPluginBranding branding() { - return this.branding; - } - - @Override - public StandardPluginLinks links() { - return this.links; - } - - @Override - public List contributors() { - return Collections.unmodifiableList(this.contributors); - } - - @Override - public Optional dependency(final String id) { - return Optional.ofNullable(this.dependenciesById.get(Objects.requireNonNull(id, "id"))); - } - - @Override - public Set dependencies() { - return Collections.unmodifiableSet(this.dependencies); - } - - @SuppressWarnings("unchecked") - @Override - public Optional property(final String key) { - return Optional.ofNullable((T) this.properties.get(Objects.requireNonNull(key, "key"))); - } - - @Override - public Map properties() { - return Collections.unmodifiableMap(this.properties); - } - - @Override - public String toString() { - return this.stringJoiner().toString(); - } - - protected StringJoiner stringJoiner() { - return new StringJoiner(", ", StandardInheritable.class.getSimpleName() + "[", "]") - .add("version=" + this.rawVersion) - .add("branding=" + this.branding) - .add("links=" + this.links) - .add("contributors=" + this.contributors) - .add("dependencies=" + this.dependencies) - .add("properties=" + this.properties) - ; - } - - @SuppressWarnings("unchecked") - public abstract static class AbstractBuilder> { - - final List contributors = new LinkedList<>(); - final Set dependencies = new LinkedHashSet<>(); - final Map properties = new LinkedHashMap<>(); - ArtifactVersion version = NullVersion.instance(); - String rawVersion = "null"; - StandardPluginBranding branding = StandardPluginBranding.none(); - StandardPluginLinks links = StandardPluginLinks.none(); - - protected AbstractBuilder() { - } - - public B version(final @Nullable String version) { - if (version == null) { - this.version = NullVersion.instance(); - this.rawVersion = "null"; - } else { - this.version = new DefaultArtifactVersion(Objects.requireNonNull(version, "version")); - this.rawVersion = version; - } - return (B) this; - } - - public B branding(final StandardPluginBranding branding) { - this.branding = Objects.requireNonNull(branding, "branding"); - return (B) this; - } - - public B links(final StandardPluginLinks links) { - this.links = Objects.requireNonNull(links, "links"); - return (B) this; - } - - public B contributors(final Collection contributors) { - this.contributors.addAll(Objects.requireNonNull(contributors, "contributors")); - return (B) this; - } - - public B addContributor(final StandardPluginContributor contributor) { - this.contributors.add(Objects.requireNonNull(contributor, "contributor")); - return (B) this; - } - - public B dependencies(final Collection dependencies) { - this.dependencies.addAll(Objects.requireNonNull(dependencies, "dependencies")); - return (B) this; - } - - public B addDependency(final StandardPluginDependency dependency) { - this.dependencies.add(Objects.requireNonNull(dependency, "dependency")); - return (B) this; - } - - public B properties(final Map properties) { - this.properties.putAll(Objects.requireNonNull(properties, "properties")); - return (B) this; - } - - public B property(final String key, final Object value) { - this.properties.put(Objects.requireNonNull(key, "key"), Objects.requireNonNull(value, "value")); - return (B) this; - } - - public B from(final StandardInheritable value) { - // TODO If we have "partial" entries, do we want to do deep merging? May start to get out of control... - - // Inheritable - if (this.version == NullVersion.instance()) { - this.version = value.version(); - this.rawVersion = value.rawVersion; - } - if (this.branding == StandardPluginBranding.none()) { - this.branding = value.branding(); - } - if (this.links == StandardPluginLinks.none()) { - this.links = value.links; - } - this.contributors.addAll(value.contributors); - this.dependencies.addAll(value.dependencies); - for (final Map.Entry entry : value.properties().entrySet()) { - this.properties.putIfAbsent(entry.getKey(), entry.getValue()); - } - return (B) this; - } - - public final T build() { - Objects.requireNonNull(this.version, "version"); - - return this.build0(); - } - - protected abstract T build0(); - } - - public static final class Builder extends AbstractBuilder { - - @Override - protected StandardInheritable build0() { - return new StandardInheritable(this); - } - } - - public static final class Serializer implements JsonSerializer, JsonDeserializer { - - @Override - public StandardInheritable deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) - throws JsonParseException { - - final JsonObject obj = element.getAsJsonObject(); - final Builder builder = new Builder(); - builder.version(GsonUtils.get(obj, "version", JsonElement::getAsString).orElse(null)); - GsonUtils.consumeIfPresent(obj, "branding", e -> builder.branding(Adapters.Deserializers.PLUGIN_BRANDING.fromJsonTree(e).build())); - GsonUtils.consumeIfPresent(obj, "links", e -> builder.links(Adapters.Deserializers.PLUGIN_LINKS.fromJsonTree(e).build())); - GsonUtils.consumeIfPresent(obj, "contributors", e -> builder.contributors(GsonUtils.read((JsonArray) e, Adapters.Deserializers.PLUGIN_CONTRIBUTOR, LinkedList::new).stream().map(StandardPluginContributor.Builder::build).collect(Collectors.toList()))); - GsonUtils.consumeIfPresent(obj, "dependencies", e -> builder.dependencies(GsonUtils.read((JsonArray) e, Adapters.Deserializers.PLUGIN_DEPENDENCY, LinkedHashSet::new).stream().map(StandardPluginDependency.Builder::build).collect(Collectors.toList()))); - GsonUtils.consumeIfPresent(obj, "properties", e -> builder.properties(GsonUtils.read((JsonObject) e, JsonElement::getAsString, LinkedHashMap::new))); - - return builder.build(); - } - - @Override - public JsonElement serialize(final StandardInheritable value, final Type type, final JsonSerializationContext context) { - final JsonObject obj = new JsonObject(); - GsonUtils.applyIfValid(obj, value, p -> p.version != NullVersion.instance(), (o, v) -> o.addProperty("version", v.rawVersion)); - GsonUtils.applyIfValid(obj, value, p -> p.branding != StandardPluginBranding.none(), (o, v) -> o.add("branding", Adapters.Serializers.PLUGIN_BRANDING.toJsonTree(v.branding))); - GsonUtils.applyIfValid(obj, value, p -> p.links != StandardPluginLinks.none(), (o, v) -> o.add("links", Adapters.Serializers.PLUGIN_LINKS.toJsonTree(v.links))); - GsonUtils.applyIfValid(obj, value, p -> !p.contributors.isEmpty(), (o, v) -> o.add("contributors", GsonUtils.write(Adapters.Serializers.PLUGIN_CONTRIBUTOR, v.contributors))); - GsonUtils.applyIfValid(obj, value, p -> !p.dependencies.isEmpty(), (o, v) -> o.add("dependencies", GsonUtils.write(Adapters.Serializers.PLUGIN_DEPENDENCY, v.dependencies))); - GsonUtils.applyIfValid(obj, value, p -> !p.properties.isEmpty(), (o, v) -> o.add("properties", GsonUtils.write(e -> new JsonPrimitive(e.toString()), v.properties))); - return obj; - } - } -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/StandardPluginMetadata.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/StandardPluginMetadata.java index ad0f8d9..679f3dd 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/StandardPluginMetadata.java +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/StandardPluginMetadata.java @@ -24,56 +24,86 @@ */ package org.spongepowered.plugin.metadata.builtin; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.google.gson.JsonSerializationContext; -import com.google.gson.JsonSerializer; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.plugin.metadata.Constants; -import org.spongepowered.plugin.metadata.Container; import org.spongepowered.plugin.metadata.PluginMetadata; -import org.spongepowered.plugin.metadata.util.GsonUtils; - -import java.lang.reflect.Type; +import org.spongepowered.plugin.metadata.model.PluginBranding; +import org.spongepowered.plugin.metadata.model.PluginContributor; +import org.spongepowered.plugin.metadata.model.PluginDependency; +import org.spongepowered.plugin.metadata.model.PluginEntrypoints; +import org.spongepowered.plugin.metadata.model.PluginLinks; +import org.spongepowered.plugin.metadata.model.PluginLoaderSpecification; + +import java.util.Collection; +import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; import java.util.StringJoiner; -public final class StandardPluginMetadata extends StandardInheritable implements PluginMetadata { - - private final String id, entrypoint; - @Nullable private final String name, description; - @Nullable private Container container; +public final class StandardPluginMetadata implements PluginMetadata { + private final String id; + private final PluginEntrypoints entrypoints; + private final InheritableMetadata global, override; + + private final ArtifactVersion version; + private final PluginLoaderSpecification loader; + private final @Nullable String name; + private final @Nullable String description; + private final @Nullable String license; + private final PluginBranding branding; + private final PluginLinks links; + private final List contributors; + private final Map dependencies; + private final Map properties; private StandardPluginMetadata(final Builder builder) { - super(builder); this.id = builder.id; - this.entrypoint = builder.entrypoint; - this.name = builder.name; - this.description = builder.description; + this.entrypoints = builder.entrypoints; + this.global = builder.global; + this.override = builder.override; + InheritableMetadata metadata = this.global.with(this.override); + this.version = metadata.version().orElseThrow(() -> new NoSuchElementException("version")); + this.loader = metadata.loader().orElseThrow(() -> new NoSuchElementException("loader")); + this.name = metadata.name().orElse(null); + this.description = metadata.description().orElse(null); + this.license = metadata.license().orElse(null); + this.branding = metadata.branding(); + this.links = metadata.links(); + this.contributors = metadata.contributors(); + this.dependencies = metadata.dependencies(); + this.properties = metadata.properties(); } - public static StandardPluginMetadata.Builder builder() { - return new Builder(); + @Override + public String id() { + return this.id; } @Override - public Container container() { - return this.container; + public PluginEntrypoints entrypoints() { + return this.entrypoints; + } + + public InheritableMetadata global() { + return this.global; + } + + public InheritableMetadata override() { + return this.override; } @Override - public String id() { - return this.id; + public ArtifactVersion version() { + return this.version; } @Override - public String entrypoint() { - return this.entrypoint; + public PluginLoaderSpecification loader() { + return this.loader; } @Override @@ -86,13 +116,50 @@ public Optional description() { return Optional.ofNullable(this.description); } - void setContainer(final MetadataContainer container) { - this.container = container; + @Override + public Optional license() { + return Optional.ofNullable(this.license); + } + + @Override + public PluginBranding branding() { + return this.branding; + } + + @Override + public PluginLinks links() { + return this.links; + } + + @Override + public List contributors() { + return this.contributors; + } + + @Override + public Optional dependency(String id) { + return Optional.ofNullable(this.dependencies.get(Objects.requireNonNull(id, "id"))); + } + + @Override + public Collection dependencies() { + return this.dependencies.values(); + } + + @SuppressWarnings("unchecked") + @Override + public Optional property(final String key) { + return Optional.ofNullable((T) this.properties.get(Objects.requireNonNull(key, "key"))); + } + + @Override + public Map properties() { + return this.properties; } @Override public int hashCode() { - return Objects.hashCode(this.id); + return Objects.hash(this.id, this.entrypoints, this.global, this.override); } @Override @@ -101,107 +168,80 @@ public boolean equals(final Object o) { return true; } - if (!(o instanceof StandardPluginMetadata)) { + if (!(o instanceof StandardPluginMetadata other)) { return false; } - final StandardPluginMetadata other = (StandardPluginMetadata) o; - return this.id.equals(other.id); + return this.id.equals(other.id) && this.entrypoints.equals(other.entrypoints) + && this.global.equals(other.global) && this.override.equals(other.override); } @Override public String toString() { - final StringJoiner joiner = new StringJoiner(", ", StandardPluginMetadata.class.getSimpleName() + "[", "]") + return new StringJoiner(", ", StandardPluginMetadata.class.getSimpleName() + "[", "]") .add("id=" + this.id) - .add("name=" + this.name) - .add("entrypoint=" + this.entrypoint) - .add("description=" + this.description); - joiner.merge(this.stringJoiner()); - return joiner.toString(); + .add("entrypoints=" + this.entrypoints) + .add("version=" + this.version) + .add("loader=" + this.loader) + .add("license=" + this.license) + .add("branding=" + this.branding) + .add("links=" + this.links) + .add("contributors=" + this.contributors) + .add("dependencies=" + this.dependencies) + .add("properties=" + this.properties) + .toString(); + } + + public StandardPluginMetadata.Builder toBuilder() { + return new StandardPluginMetadata.Builder().from(this); } - public static final class Builder extends StandardInheritable.AbstractBuilder { + public static StandardPluginMetadata.Builder builder() { + return new StandardPluginMetadata.Builder(); + } - @Nullable String id, entrypoint, name, description; + public static final class Builder { - public Builder() { - } + private @MonotonicNonNull String id; + private PluginEntrypoints entrypoints = PluginEntrypoints.none(); + private InheritableMetadata global = InheritableMetadata.none(), override = InheritableMetadata.none(); + + private Builder() {} public Builder id(final String id) { this.id = Objects.requireNonNull(id, "id"); return this; } - public Builder entrypoint(final String entrypoint) { - this.entrypoint = Objects.requireNonNull(entrypoint, "entrypoint"); + public Builder entrypoints(final PluginEntrypoints entrypoints) { + this.entrypoints = Objects.requireNonNull(entrypoints, "entrypoints"); + return this; + } + + public Builder global(final InheritableMetadata global) { + this.global = Objects.requireNonNull(global, "global"); return this; } - public Builder name(final String name) { - this.name = Objects.requireNonNull(name, "name"); + public Builder override(final InheritableMetadata override) { + this.override = Objects.requireNonNull(override, "override"); return this; } - public Builder description(final String description) { - this.description = Objects.requireNonNull(description, "description"); + public Builder from(final StandardPluginMetadata value) { + this.id = value.id; + this.entrypoints = value.entrypoints; + this.global = value.global; + this.override = value.override; return this; } - @Override - protected StandardPluginMetadata build0() { + public StandardPluginMetadata build() { if (!Constants.VALID_ID_PATTERN.matcher(Objects.requireNonNull(this.id, "id")).matches()) { throw new IllegalStateException(String.format("PluginMetadata with supplied ID '{%s}' is invalid. %s", this.id, Constants.INVALID_ID_REQUIREMENTS_MESSAGE)); } - if (this.version == NullVersion.instance()) { - throw new IllegalStateException(String.format("PluginMetadata with supplied ID '{%s}' has no version specified.", this.id)); - } - Objects.requireNonNull(this.entrypoint, "entrypoint"); - return new StandardPluginMetadata(this); } } - - public static final class Deserializer implements JsonDeserializer { - - @Override - public StandardPluginMetadata.Builder deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) - throws JsonParseException { - final JsonObject obj = element.getAsJsonObject(); - if (!obj.has("id")) { - throw new MissingRequiredFieldException("id"); - } - if (!obj.has("entrypoint")) { - throw new MissingRequiredFieldException("entrypoint"); - } - final StandardPluginMetadata.Builder builder = new StandardPluginMetadata.Builder(); - builder - .id(obj.get("id").getAsString()) - .entrypoint(obj.get("entrypoint").getAsString()); - GsonUtils.consumeIfPresent(obj, "name", e -> builder.name(e.getAsString())); - GsonUtils.consumeIfPresent(obj, "description", e -> builder.description(e.getAsString())); - builder.from(context.deserialize(element, StandardInheritable.class)); - - return builder; - } - } - - public static final class Serializer implements JsonSerializer { - - @Override - public JsonElement serialize(final StandardPluginMetadata value, final Type type, final JsonSerializationContext context) { - final JsonObject obj = new JsonObject(); - obj.addProperty("id", value.id); - obj.addProperty("entrypoint", value.entrypoint); - GsonUtils.writeIfPresent(obj, "name", value.name()); - GsonUtils.writeIfPresent(obj, "description", value.description()); - - final JsonObject inheritableElement = (JsonObject) context.serialize(value, StandardInheritable.class); - // TODO Sort this? - for (final Map.Entry entry : inheritableElement.entrySet()) { - obj.add(entry.getKey(), entry.getValue()); - } - return obj; - } - } } diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/InheritableMetadataAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/InheritableMetadataAdapter.java new file mode 100644 index 0000000..ddd4c9e --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/InheritableMetadataAdapter.java @@ -0,0 +1,84 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter; + +import com.google.gson.*; +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.spongepowered.plugin.metadata.builtin.InheritableMetadata; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; +import org.spongepowered.plugin.metadata.model.PluginBranding; +import org.spongepowered.plugin.metadata.model.PluginContributor; +import org.spongepowered.plugin.metadata.model.PluginDependency; +import org.spongepowered.plugin.metadata.model.PluginLinks; +import org.spongepowered.plugin.metadata.model.PluginLoaderSpecification; + +import java.lang.reflect.Type; +import java.util.LinkedHashMap; + +public final class InheritableMetadataAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public InheritableMetadata deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return InheritableMetadata.builder() + .version(GsonUtils.optional(obj, "version").map(v -> context.deserialize(v, ArtifactVersion.class)).orElse(null)) + .loader(GsonUtils.optional(obj, "loader").map(v -> context.deserialize(v, PluginLoaderSpecification.class)).orElse(null)) + .name(GsonUtils.optional(obj, "name").map(JsonElement::getAsString).orElse(null)) + .description(GsonUtils.optional(obj, "description").map(JsonElement::getAsString).orElse(null)) + .license(GsonUtils.optional(obj, "license").map(JsonElement::getAsString).orElse(null)) + .branding(GsonUtils.optional(obj, "branding").map(v -> context.deserialize(v, PluginBranding.class)).orElseGet(PluginBranding::none)) + .links(GsonUtils.optional(obj, "links").map(v -> context.deserialize(v, PluginLinks.class)).orElseGet(PluginLinks::none)) + .contributors(GsonUtils.stream(obj, "contributors").map(v -> context.deserialize(v, PluginContributor.class)).toList()) + .dependencies(GsonUtils.stream(obj, "dependencies").map(v -> context.deserialize(v, PluginDependency.class)).toList()) + .properties(GsonUtils.deserializeMap(obj.get("properties"), JsonElement::getAsString, LinkedHashMap::new)) + .build(); + } + + @Override + public JsonElement serialize(final InheritableMetadata value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + value.version().ifPresent(v -> obj.add("version", context.serialize(v, ArtifactVersion.class))); + value.loader().ifPresent(v -> obj.add("loader", context.serialize(v, PluginLoaderSpecification.class))); + value.name().ifPresent(v -> obj.addProperty("name", v)); + value.description().ifPresent(v -> obj.addProperty("description", v)); + value.license().ifPresent(v -> obj.addProperty("license", v)); + if (!value.branding().equals(PluginBranding.none())) { + obj.add("branding", context.serialize(value.branding(), PluginBranding.class)); + } + if (!value.links().equals(PluginLinks.none())) { + obj.add("links", context.serialize(value.links(), PluginLinks.class)); + } + if (!value.contributors().isEmpty()) { + obj.add("contributors", GsonUtils.toArray(value.contributors().stream().map(v -> context.serialize(v, PluginContributor.class)))); + } + if (!value.dependencies().isEmpty()) { + obj.add("dependencies", GsonUtils.toArray(value.dependencies().values().stream().map(v -> context.serialize(v, PluginDependency.class)))); + } + if (!value.properties().isEmpty()) { + obj.add("properties", GsonUtils.serializeMap(value.properties(), v -> new JsonPrimitive(v.toString()))); + } + return obj; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/MetadataContainerAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/MetadataContainerAdapter.java new file mode 100644 index 0000000..db284ab --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/MetadataContainerAdapter.java @@ -0,0 +1,90 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.spongepowered.plugin.metadata.builtin.MetadataContainer; +import org.spongepowered.plugin.metadata.builtin.InheritableMetadata; +import org.spongepowered.plugin.metadata.builtin.StandardPluginMetadata; +import org.spongepowered.plugin.metadata.model.PluginLoaderSpecification; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; + +import java.lang.reflect.Type; +import java.util.LinkedList; +import java.util.List; + +public final class MetadataContainerAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public MetadataContainer deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + + // Read some global data in the root element for retro-compatibility + InheritableMetadata global = InheritableMetadata.builder() + .loader(GsonUtils.optional(obj, "loader").map(v -> context.deserialize(v, PluginLoaderSpecification.class)).orElse(null)) + .license(GsonUtils.optional(obj, "license").map(JsonElement::getAsString).orElse(null)) + .build(); + + final JsonElement globalElement = obj.get("global"); + if (globalElement instanceof JsonObject) { + global = global.with(context.deserialize(globalElement, InheritableMetadata.class)); + } + + final List plugins = new LinkedList<>(); + if (GsonUtils.require(obj, "plugins") instanceof JsonArray pluginsArray) { + for (final JsonElement pluginElement : pluginsArray) { + if (pluginElement.isJsonObject()) { + plugins.add(context.deserialize(pluginElement, StandardPluginMetadata.Builder.class) + .global(global).build()); + } + } + } + + return new MetadataContainer(global, plugins); + } + + @Override + public JsonElement serialize(final MetadataContainer value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + if (!value.global().equals(InheritableMetadata.none())) { + obj.add("global", context.serialize(value.global(), InheritableMetadata.class)); + } + + final JsonArray plugins = new JsonArray(); + for (final StandardPluginMetadata plugin : value.plugins()) { + plugins.add(context.serialize(plugin, StandardPluginMetadata.class)); + } + obj.add("plugins", plugins); + + return obj; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/StandardPluginMetadataBuilderDeserializer.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/StandardPluginMetadataBuilderDeserializer.java new file mode 100644 index 0000000..fe2c132 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/StandardPluginMetadataBuilderDeserializer.java @@ -0,0 +1,64 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter; + +import com.google.gson.*; +import org.spongepowered.plugin.metadata.builtin.InheritableMetadata; +import org.spongepowered.plugin.metadata.builtin.StandardPluginMetadata; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; +import org.spongepowered.plugin.metadata.builtin.adapter.util.LegacyIds; +import org.spongepowered.plugin.metadata.model.PluginEntrypoints; + +import java.lang.reflect.Type; +import java.util.List; + +public final class StandardPluginMetadataBuilderDeserializer implements JsonDeserializer { + + @Override + public StandardPluginMetadata.Builder deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return StandardPluginMetadata.builder() + .id(LegacyIds.fix(GsonUtils.require(obj, "id").getAsString())) + .entrypoints( + GsonUtils.optional(obj, "entrypoints").map(v -> StandardPluginMetadataBuilderDeserializer.deserializeEntrypoints(v, context)) + .or(() -> GsonUtils.optional(obj, "entrypoint").map(v -> new PluginEntrypoints(List.of(v.getAsString())))) // legacy + .orElseGet(PluginEntrypoints::none) + ) + .override(context.deserialize(element, InheritableMetadata.class)); + } + + /** + * Should be in PluginEntrypointsAdapter but Gson refuses to pass a JsonArray to a JsonDeserializer. + */ + private static PluginEntrypoints deserializeEntrypoints(final JsonElement element, final JsonDeserializationContext context) { + if (element.isJsonNull()) { + return PluginEntrypoints.none(); + } + if (element instanceof JsonArray array) { + return new PluginEntrypoints(array.asList().stream().map(JsonElement::getAsString).toList()); + } + return context.deserialize(element, PluginEntrypoints.class); + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/StandardPluginMetadataSerializer.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/StandardPluginMetadataSerializer.java new file mode 100644 index 0000000..5cc32ee --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/StandardPluginMetadataSerializer.java @@ -0,0 +1,63 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.spongepowered.plugin.metadata.builtin.InheritableMetadata; +import org.spongepowered.plugin.metadata.builtin.StandardPluginMetadata; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; +import org.spongepowered.plugin.metadata.model.PluginEntrypoints; + +import java.lang.reflect.Type; +import java.util.Optional; + +public final class StandardPluginMetadataSerializer implements JsonSerializer { + + @Override + public JsonElement serialize(final StandardPluginMetadata value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + obj.addProperty("id", value.id()); + StandardPluginMetadataSerializer.serializeEntrypoints(value.entrypoints(), context).ifPresent(v -> obj.add("entrypoints", v)); + obj.asMap().putAll(((JsonObject) context.serialize(value.override(), InheritableMetadata.class)).asMap()); + return obj; + } + + /** + * Should be in PluginEntrypointsAdapter but Gson refuses to pass a JsonArray to a JsonDeserializer. + */ + private static Optional serializeEntrypoints(final PluginEntrypoints value, final JsonSerializationContext context) { + if (value.server().isEmpty() && value.client().isEmpty()) { + if (value.main().isEmpty()) { + return Optional.empty(); + } + return Optional.of(GsonUtils.toArray(value.main().stream().map(JsonPrimitive::new))); + } + return Optional.of(context.serialize(value, PluginEntrypoints.class)); + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginBrandingAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginBrandingAdapter.java new file mode 100644 index 0000000..0e22b70 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginBrandingAdapter.java @@ -0,0 +1,57 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.model; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.spongepowered.plugin.metadata.model.PluginBranding; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; + +import java.lang.reflect.Type; + +public final class PluginBrandingAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public PluginBranding deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return new PluginBranding( + GsonUtils.optional(obj, "logo").map(JsonElement::getAsString), + GsonUtils.optional(obj, "icon").map(JsonElement::getAsString) + ); + } + + @Override + public JsonElement serialize(final PluginBranding value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + value.logo().ifPresent(v -> obj.addProperty("logo", v)); + value.icon().ifPresent(v -> obj.addProperty("icon", v)); + return obj; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginContributorAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginContributorAdapter.java new file mode 100644 index 0000000..8b89a36 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginContributorAdapter.java @@ -0,0 +1,57 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.model; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.spongepowered.plugin.metadata.model.PluginContributor; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; + +import java.lang.reflect.Type; + +public final class PluginContributorAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public PluginContributor deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return new PluginContributor( + GsonUtils.require(obj, "name").getAsString(), + GsonUtils.optional(obj, "description").map(JsonElement::getAsString) + ); + } + + @Override + public JsonElement serialize(final PluginContributor value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + obj.addProperty("name", value.name()); + value.description().ifPresent(v -> obj.addProperty("description", v)); + return obj; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginDependencyAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginDependencyAdapter.java new file mode 100644 index 0000000..97917f5 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginDependencyAdapter.java @@ -0,0 +1,59 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.model; + +import com.google.gson.*; +import org.apache.maven.artifact.versioning.VersionRange; +import org.spongepowered.plugin.metadata.builtin.adapter.util.LegacyIds; +import org.spongepowered.plugin.metadata.model.PluginDependency; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; + +import java.lang.reflect.Type; + +public final class PluginDependencyAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public PluginDependency deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return new PluginDependency( + LegacyIds.fix(GsonUtils.require(obj, "id").getAsString()), + context.deserialize(GsonUtils.require(obj, "version"), VersionRange.class), + GsonUtils.optional(obj, "load-order") + .map(v -> context.deserialize(v, PluginDependency.LoadOrder.class)) + .orElse(PluginDependency.LoadOrder.UNDEFINED), + GsonUtils.optional(obj, "optional").map(JsonElement::getAsBoolean).orElse(false) + ); + } + + @Override + public JsonElement serialize(final PluginDependency value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + obj.addProperty("id", value.id()); + obj.add("version", context.serialize(value.version(), VersionRange.class)); + obj.add("load-order", context.serialize(value.loadOrder(), PluginDependency.LoadOrder.class)); + obj.addProperty("optional", value.optional()); + return obj; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginEntrypointsAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginEntrypointsAdapter.java new file mode 100644 index 0000000..b425efe --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginEntrypointsAdapter.java @@ -0,0 +1,64 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.model; + +import com.google.gson.*; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; +import org.spongepowered.plugin.metadata.model.PluginEntrypoints; + +import java.lang.reflect.Type; +import java.util.List; + +public final class PluginEntrypointsAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public PluginEntrypoints deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return new PluginEntrypoints( + PluginEntrypointsAdapter.deserialize(obj, "main"), + PluginEntrypointsAdapter.deserialize(obj, "server"), + PluginEntrypointsAdapter.deserialize(obj, "client") + ); + } + + @Override + public JsonElement serialize(final PluginEntrypoints value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + PluginEntrypointsAdapter.serialize(obj, "main", value.main()); + PluginEntrypointsAdapter.serialize(obj, "server", value.server()); + PluginEntrypointsAdapter.serialize(obj, "client", value.client()); + return obj; + } + + private static List deserialize(final JsonObject obj, final String name) { + return GsonUtils.stream(obj, name).map(JsonElement::getAsString).toList(); + } + + private static void serialize(final JsonObject obj, final String name, final List list) { + if (!list.isEmpty()) { + obj.add(name, GsonUtils.toArray(list.stream().map(JsonPrimitive::new))); + } + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginLinksAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginLinksAdapter.java new file mode 100644 index 0000000..d3725a0 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginLinksAdapter.java @@ -0,0 +1,63 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.model; + +import com.google.gson.*; +import org.spongepowered.plugin.metadata.model.PluginLinks; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; + +import java.lang.reflect.Type; +import java.net.URI; +import java.util.Optional; + +public final class PluginLinksAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public PluginLinks deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return new PluginLinks( + PluginLinksAdapter.deserialize(obj, "homepage", context), + PluginLinksAdapter.deserialize(obj, "source", context), + PluginLinksAdapter.deserialize(obj, "issues", context) + ); + } + + @Override + public JsonElement serialize(final PluginLinks value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + PluginLinksAdapter.serialize(obj, "homepage", context, value.homepage()); + PluginLinksAdapter.serialize(obj, "source", context, value.source()); + PluginLinksAdapter.serialize(obj, "issues", context, value.issues()); + return obj; + } + + private static Optional deserialize(final JsonObject obj, final String name, final JsonDeserializationContext context) throws JsonParseException { + return GsonUtils.optional(obj, name).map(v -> context.deserialize(v, URI.class)); + } + + private static void serialize(final JsonObject obj, final String name, final JsonSerializationContext context, final Optional value) throws JsonParseException { + value.ifPresent(v -> obj.add(name, context.serialize(v, URI.class))); + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginLoaderSpecificationAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginLoaderSpecificationAdapter.java new file mode 100644 index 0000000..2a1f752 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/model/PluginLoaderSpecificationAdapter.java @@ -0,0 +1,58 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.model; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.apache.maven.artifact.versioning.VersionRange; +import org.spongepowered.plugin.metadata.model.PluginLoaderSpecification; +import org.spongepowered.plugin.metadata.builtin.adapter.util.GsonUtils; + +import java.lang.reflect.Type; + +public final class PluginLoaderSpecificationAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public PluginLoaderSpecification deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject obj = element.getAsJsonObject(); + return new PluginLoaderSpecification( + GsonUtils.require(obj, "name").getAsString(), + context.deserialize(GsonUtils.require(obj, "version"), VersionRange.class) + ); + } + + @Override + public JsonElement serialize(final PluginLoaderSpecification value, final Type type, final JsonSerializationContext context) { + final JsonObject obj = new JsonObject(); + obj.addProperty("name", value.name()); + obj.add("version", context.serialize(value.version(), VersionRange.class)); + return obj; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/util/GsonUtils.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/util/GsonUtils.java new file mode 100644 index 0000000..dd08293 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/util/GsonUtils.java @@ -0,0 +1,89 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.util; + +import com.google.gson.*; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.plugin.metadata.builtin.MissingRequiredFieldException; + +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * This is not public API as this package is not exported by the module. + */ +public final class GsonUtils { + + public static JsonElement require(final JsonObject obj, final String name) { + final JsonElement element = obj.get(name); + if (element == null || element.isJsonNull()) { + throw new MissingRequiredFieldException(name); + } + return element; + } + + public static Optional optional(final JsonObject obj, final String name) { + final JsonElement element = obj.get(name); + if (element == null || element.isJsonNull()) { + return Optional.empty(); + } + return Optional.of(element); + } + + public static Stream stream(final JsonObject obj, final String name) { + final JsonElement element = obj.get(name); + if (element == null || element.isJsonNull()) { + return Stream.empty(); + } + return element.getAsJsonArray().asList().stream(); + } + + public static JsonArray toArray(final Stream stream) { + final JsonArray array = new JsonArray(); + stream.forEach(array::add); + return array; + } + + public static Map deserializeMap(final @Nullable JsonElement element, final Function valFunc, final Supplier> collector) { + final Map map = collector.get(); + if (element instanceof JsonObject obj) { + for (final Map.Entry entry : obj.entrySet()) { + map.put(entry.getKey(), valFunc.apply(entry.getValue())); + } + } + return map; + } + + public static JsonObject serializeMap(final Map map, final Function valFunc) { + final JsonObject obj = new JsonObject(); + for (final Map.Entry entry : map.entrySet()) { + obj.add(entry.getKey(), valFunc.apply(entry.getValue())); + } + return obj; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/util/package-info.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/util/LegacyIds.java similarity index 68% rename from src/main/java/org/spongepowered/plugin/metadata/util/package-info.java rename to src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/util/LegacyIds.java index 74da21b..9ba5b77 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/util/package-info.java +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/util/LegacyIds.java @@ -22,5 +22,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -@org.checkerframework.framework.qual.DefaultQualifier(org.checkerframework.checker.nullness.qual.NonNull.class) -package org.spongepowered.plugin.metadata.util; +package org.spongepowered.plugin.metadata.builtin.adapter.util; + +import org.spongepowered.plugin.metadata.builtin.MetadataParser; + +/** + * This is not public API as this package is not exported by the module. + */ +public class LegacyIds { + + public static String fix(final String original) { + if (original.indexOf('-') < 0) { + return original; + } + final String fixed = original.replace('-', '_'); + MetadataParser.addWarning("Plugin id '" + original + "' is invalid and has been converted to '" + fixed + "'."); + return fixed; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/NullVersion.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/version/ArtifactVersionAdapter.java similarity index 51% rename from src/main/java/org/spongepowered/plugin/metadata/builtin/NullVersion.java rename to src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/version/ArtifactVersionAdapter.java index 3571187..3fce99a 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/NullVersion.java +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/version/ArtifactVersionAdapter.java @@ -22,50 +22,23 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.plugin.metadata.builtin; +package org.spongepowered.plugin.metadata.builtin.adapter.version; +import com.google.gson.*; import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; -public final class NullVersion implements ArtifactVersion { +import java.lang.reflect.Type; - private static final NullVersion INSTANCE = new NullVersion(); - - public static NullVersion instance() { - return NullVersion.INSTANCE; - } - - @Override - public int getMajorVersion() { - throw new RuntimeException("This is a null version and should not be used!"); - } - - @Override - public int getMinorVersion() { - throw new RuntimeException("This is a null version and should not be used!"); - } - - @Override - public int getIncrementalVersion() { - throw new RuntimeException("This is a null version and should not be used!"); - } - - @Override - public int getBuildNumber() { - throw new RuntimeException("This is a null version and should not be used!"); - } - - @Override - public String getQualifier() { - throw new RuntimeException("This is a null version and should not be used!"); - } +public final class ArtifactVersionAdapter implements JsonSerializer, JsonDeserializer { @Override - public void parseVersion(final String version) { - throw new RuntimeException("This is a null version and should not be used!"); + public ArtifactVersion deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + return new DefaultArtifactVersion(element.getAsString()); } @Override - public int compareTo(final ArtifactVersion o) { - throw new RuntimeException("This is a null version and should not be used!"); + public JsonElement serialize(final ArtifactVersion value, final Type type, final JsonSerializationContext context) { + return new JsonPrimitive(value.toString()); } } diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/version/VersionRangeAdapter.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/version/VersionRangeAdapter.java new file mode 100644 index 0000000..71f6145 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/builtin/adapter/version/VersionRangeAdapter.java @@ -0,0 +1,49 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin.adapter.version; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.apache.maven.artifact.versioning.VersionRange; + +import java.lang.reflect.Type; + +public final class VersionRangeAdapter implements JsonSerializer, JsonDeserializer { + + @Override + public VersionRange deserialize(final JsonElement element, final Type type, final JsonDeserializationContext context) throws JsonParseException { + return VersionRange.createFromVersion(element.getAsString()); + } + + @Override + public JsonElement serialize(final VersionRange value, final Type type, final JsonSerializationContext context) { + return new JsonPrimitive(value.toString()); + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/Adapters.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/model/Adapters.java deleted file mode 100644 index 812914a..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/Adapters.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.builtin.model; - -import com.google.gson.TypeAdapter; - -public final class Adapters { - - private Adapters() { - } - - public static final class Deserializers { - - public static final TypeAdapter CONTAINER_LOADER = new StandardContainerLoader.Deserializer(); - - public static final TypeAdapter PLUGIN_BRANDING = new StandardPluginBranding.Deserializer(); - - public static final TypeAdapter PLUGIN_CONTRIBUTOR = new StandardPluginContributor.Deserializer(); - - public static final TypeAdapter PLUGIN_DEPENDENCY = new StandardPluginDependency.Deserializer(); - - public static final TypeAdapter PLUGIN_LINKS = new StandardPluginLinks.Deserializer(); - - private Deserializers() { - } - } - - public static final class Serializers { - - public static final TypeAdapter CONTAINER_LOADER = new StandardContainerLoader.Serializer(); - - public static final TypeAdapter PLUGIN_BRANDING = new StandardPluginBranding.Serializer(); - - public static final TypeAdapter PLUGIN_CONTRIBUTOR = new StandardPluginContributor.Serializer(); - - public static final TypeAdapter PLUGIN_DEPENDENCY = new StandardPluginDependency.Serializer(); - - public static final TypeAdapter PLUGIN_LINKS = new StandardPluginLinks.Serializer(); - - private Serializers() { - } - } -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardContainerLoader.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardContainerLoader.java deleted file mode 100644 index 7e5908c..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardContainerLoader.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.builtin.model; - -import com.google.gson.JsonParseException; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import org.apache.maven.artifact.versioning.VersionRange; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.plugin.metadata.model.ContainerLoader; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.StringJoiner; - -public final class StandardContainerLoader implements ContainerLoader { - - private final String name; - private final String rawVersion; - private final VersionRange version; - - private StandardContainerLoader(final Builder builder) { - this.name = builder.name; - this.version = builder.version; - this.rawVersion = builder.rawVersion; - } - - public static Builder builder() { - return new Builder(); - } - - @Override - public String name() { - return this.name; - } - - @Override - public VersionRange version() { - return this.version; - } - - public StandardContainerLoader.Builder toBuilder() { - final Builder builder = new Builder(); - builder.name = this.name; - builder.version = this.version; - builder.rawVersion = this.rawVersion; - - return builder; - } - - @Override - public int hashCode() { - return Objects.hashCode(this.name); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (!(o instanceof StandardContainerLoader)) { - return false; - } - final StandardContainerLoader that = (StandardContainerLoader) o; - return this.name.equals(that.name); - } - - @Override - public String toString() { - return new StringJoiner(", ", StandardContainerLoader.class.getSimpleName() + "[", "]") - .add("name=" + this.name) - .add("version=" + this.rawVersion) - .toString(); - } - - public static final class Builder { - - @Nullable String name, rawVersion; - @Nullable VersionRange version; - - private Builder() { - } - - public Builder name(final String name) { - this.name = name; - return this; - } - - public Builder version(final String version) { - this.version = VersionRange.createFromVersion(Objects.requireNonNull(version, "version")); - this.rawVersion = version; - return this; - } - - public StandardContainerLoader build() { - Objects.requireNonNull(this.name, "name"); - Objects.requireNonNull(this.version, "version"); - - return new StandardContainerLoader(this); - } - } - - public static final class Deserializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final Builder builder) throws IOException { - throw new UnsupportedOperationException("This adapter is for reading only"); - } - - @Override - public StandardContainerLoader.Builder read(final JsonReader in) throws IOException { - Objects.requireNonNull(in, "in"); - - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - - in.beginObject(); - final Set processedKeys = new HashSet<>(); - final StandardContainerLoader.Builder builder = StandardContainerLoader.builder(); - while (in.hasNext()) { - final String key = in.nextName(); - if (!processedKeys.add(key)) { - throw new JsonParseException(String.format("Duplicate id key '%s' in %s", key, in)); - } - switch (key) { - case "name": - builder.name(in.nextString()); - break; - case "version": - builder.version(in.nextString()); - break; - } - } - in.endObject(); - - return builder; - } - } - - public static final class Serializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final StandardContainerLoader value) throws IOException { - Objects.requireNonNull(out, "out"); - - if (value == null) { - out.nullValue(); - return; - } - - out.beginObject(); - out.name("name").value(value.name); - out.name("version").value(value.rawVersion); - out.endObject(); - } - - @Override - public StandardContainerLoader read(final JsonReader in) throws IOException { - throw new UnsupportedOperationException("This adapter is for writing only"); - } - } -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginBranding.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginBranding.java deleted file mode 100644 index d7d5b97..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginBranding.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.builtin.model; - -import com.google.gson.JsonParseException; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.plugin.metadata.model.PluginBranding; -import org.spongepowered.plugin.metadata.util.GsonUtils; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; - -public final class StandardPluginBranding implements PluginBranding { - - private static final StandardPluginBranding NONE = new StandardPluginBranding(); - - private final @Nullable String logo, icon; - - private StandardPluginBranding(final Builder builder) { - this.logo = builder.logo; - this.icon = builder.icon; - } - - private StandardPluginBranding() { - this.logo = null; - this.icon = null; - } - - public static Builder builder() { - return new Builder(); - } - - public static StandardPluginBranding none() { - return StandardPluginBranding.NONE; - } - - @Override - public Optional logo() { - return Optional.ofNullable(this.logo); - } - - @Override - public Optional icon() { - return Optional.ofNullable(this.icon); - } - - public StandardPluginBranding.Builder toBuilder() { - final Builder builder = new Builder(); - builder.logo = this.logo; - builder.icon = this.icon; - - return builder; - } - - @Override - public String toString() { - return new StringJoiner(", ", StandardPluginBranding.class.getSimpleName() + "[", "]") - .add("logo=" + this.logo) - .add("icon=" + this.icon) - .toString(); - } - - public static final class Builder { - - @Nullable String logo, icon; - - private Builder() { - } - - public Builder logo(@Nullable final String logo) { - this.logo = logo; - return this; - } - - public Builder icon(@Nullable final String icon) { - this.icon = icon; - return this; - } - - public StandardPluginBranding build() { - return new StandardPluginBranding(this); - } - } - - public static final class Deserializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final Builder builder) throws IOException { - throw new UnsupportedOperationException("This adapter is for reading only"); - } - - @Override - public Builder read(final JsonReader in) throws IOException { - Objects.requireNonNull(in, "in"); - - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - - in.beginObject(); - final Set processedKeys = new HashSet<>(); - final StandardPluginBranding.Builder builder = StandardPluginBranding.builder(); - while (in.hasNext()) { - final String key = in.nextName(); - if (!processedKeys.add(key)) { - throw new JsonParseException(String.format("Duplicate branding key '%s' in %s", key, in)); - } - switch (key) { - case "logo": - builder.logo(in.nextString()); - break; - case "icon": - builder.icon(in.nextString()); - break; - } - } - in.endObject(); - - return builder; - } - } - - public static final class Serializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final StandardPluginBranding value) throws IOException { - Objects.requireNonNull(out, "out"); - - if (value == null) { - out.nullValue(); - return; - } - - out.beginObject(); - GsonUtils.writeIfPresent(out, "logo", value.logo()); - GsonUtils.writeIfPresent(out, "icon", value.icon()); - out.endObject(); - } - - @Override - public StandardPluginBranding read(final JsonReader in) throws IOException { - throw new UnsupportedOperationException("This adapter is for writing only"); - } - } -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginContributor.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginContributor.java deleted file mode 100644 index 75b8ddd..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginContributor.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.builtin.model; - -import com.google.gson.JsonParseException; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.plugin.metadata.model.PluginContributor; -import org.spongepowered.plugin.metadata.util.GsonUtils; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; - -public final class StandardPluginContributor implements PluginContributor { - - private final String name; - @Nullable private final String description; - - private StandardPluginContributor(final Builder builder) { - this.name = builder.name; - this.description = builder.description; - } - - public static Builder builder() { - return new Builder(); - } - - @Override - public String name() { - return this.name; - } - - @Override - public Optional description() { - return Optional.ofNullable(this.description); - } - - public StandardPluginContributor.Builder toBuilder() { - final Builder builder = new Builder(); - builder.name = this.name; - builder.description = this.description; - - return builder; - } - - @Override - public String toString() { - return new StringJoiner(", ", StandardPluginContributor.class.getSimpleName() + "[", "]") - .add("name=" + this.name) - .add("description=" + this.description) - .toString(); - } - - public static final class Builder { - - @Nullable String name, description; - - private Builder() { - } - - public Builder name(final String name) { - this.name = Objects.requireNonNull(name); - return this; - } - - public Builder description(@Nullable final String description) { - this.description = description; - return this; - } - - public StandardPluginContributor build() { - Objects.requireNonNull(this.name, "name"); - - return new StandardPluginContributor(this); - } - } - - public static final class Deserializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final Builder builder) throws IOException { - throw new UnsupportedOperationException("This adapter is for reading only"); - } - - @Override - public Builder read(final JsonReader in) throws IOException { - Objects.requireNonNull(in, "in"); - - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - - in.beginObject(); - final Set processedKeys = new HashSet<>(); - final StandardPluginContributor.Builder builder = StandardPluginContributor.builder(); - while (in.hasNext()) { - final String key = in.nextName(); - if (!processedKeys.add(key)) { - throw new JsonParseException(String.format("Duplicate contributor key '%s' in %s", key, in)); - } - switch (key) { - case "name": - builder.name(in.nextString()); - break; - case "description": - builder.description(in.nextString()); - break; - } - } - in.endObject(); - return builder; - } - } - - public static final class Serializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final StandardPluginContributor value) throws IOException { - Objects.requireNonNull(out, "out"); - - if (value == null) { - out.nullValue(); - return; - } - - out.beginObject(); - out.name("name").value(value.name()); - GsonUtils.writeIfPresent(out, "description", value.description()); - out.endObject(); - } - - @Override - public StandardPluginContributor read(final JsonReader in) throws IOException { - throw new UnsupportedOperationException("This adapter is for writing only"); - } - } -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginDependency.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginDependency.java deleted file mode 100644 index 1fc302c..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginDependency.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.builtin.model; - -import com.google.gson.JsonParseException; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import org.apache.maven.artifact.versioning.VersionRange; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.plugin.metadata.Constants; -import org.spongepowered.plugin.metadata.model.PluginDependency; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Locale; -import java.util.Objects; -import java.util.Set; -import java.util.StringJoiner; - - -public final class StandardPluginDependency implements PluginDependency { - - private final String id; - private final VersionRange version; - private final String rawVersion; - private final LoadOrder loadOrder; - private final boolean optional; - - private StandardPluginDependency(final Builder builder) { - this.id = builder.id; - this.version = builder.version; - this.rawVersion = builder.rawVersion; - this.loadOrder = builder.loadOrder; - this.optional = builder.optional; - } - - /** - * Returns a new {@link Builder} for creating a PluginDependency. - * - * @return A builder - */ - public static Builder builder() { - return new Builder(); - } - - @Override - public String id() { - return this.id; - } - - @Override - public VersionRange version() { - return this.version; - } - - @Override - public LoadOrder loadOrder() { - return this.loadOrder; - } - - @Override - public boolean optional() { - return this.optional; - } - - public StandardPluginDependency.Builder toBuilder() { - final Builder builder = new Builder(); - builder.id = this.id; - builder.version = this.version; - builder.rawVersion = this.rawVersion; - builder.loadOrder = this.loadOrder; - builder.optional = this.optional; - - return builder; - } - - @Override - public int hashCode() { - return Objects.hashCode(this.id); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (!(o instanceof StandardPluginDependency)) { - return false; - } - final StandardPluginDependency that = (StandardPluginDependency) o; - return this.id.equals(that.id); - } - - @Override - public String toString() { - return new StringJoiner(", ", StandardPluginDependency.class.getSimpleName() + "[", "]") - .add("id=" + this.id) - .add("version=" + this.rawVersion) - .add("loadOrder=" + this.loadOrder) - .add("optional=" + this.optional) - .toString(); - } - - public static final class Builder { - - @Nullable String id; - @Nullable VersionRange version; - @Nullable String rawVersion; - LoadOrder loadOrder = LoadOrder.UNDEFINED; - boolean optional = false; - - private Builder() { - } - - public Builder id(final String id) { - this.id = Objects.requireNonNull(id, "id"); - return this; - } - - public Builder version(final String version) { - this.version = VersionRange.createFromVersion(Objects.requireNonNull(version, "version")); - this.rawVersion = version; - return this; - } - - public Builder loadOrder(final LoadOrder loadOrder) { - this.loadOrder = Objects.requireNonNull(loadOrder, "load order"); - return this; - } - - public Builder optional(final boolean optional) { - this.optional = optional; - return this; - } - - public StandardPluginDependency build() { - Objects.requireNonNull(this.id, "id"); - if (!Constants.VALID_ID_PATTERN.matcher(this.id).matches()) { - throw new IllegalStateException(String.format("Dependency with supplied ID '{%s}' is invalid. %s", this.id, - Constants.INVALID_ID_REQUIREMENTS_MESSAGE)); - } - Objects.requireNonNull(this.version, "version"); - - return new StandardPluginDependency(this); - } - } - - public static final class Deserializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final Builder builder) throws IOException { - throw new UnsupportedOperationException("This adapter is for reading only"); - } - - @Override - public Builder read(final JsonReader in) throws IOException { - Objects.requireNonNull(in, "in"); - - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - - in.beginObject(); - final Set processedKeys = new HashSet<>(); - final StandardPluginDependency.Builder builder = StandardPluginDependency.builder(); - while (in.hasNext()) { - final String key = in.nextName(); - if (!processedKeys.add(key)) { - throw new JsonParseException(String.format("Duplicate dependency key '%s' in %s", key, in)); - } - switch (key) { - case "id": - builder.id(in.nextString()); - break; - case "version": - builder.version(in.nextString()); - break; - case "optional": - builder.optional(in.nextBoolean()); - break; - case "load-order": - try { - builder.loadOrder(StandardPluginDependency.LoadOrder.valueOf(in.nextString().toUpperCase())); - } catch (final Exception ex) { - throw new JsonParseException(String.format("Invalid load order found in '%s'", in), ex); - } - break; - } - } - in.endObject(); - return builder; - } - } - - public static final class Serializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final StandardPluginDependency value) throws IOException { - Objects.requireNonNull(out, "out"); - - if (value == null) { - out.nullValue(); - return; - } - - out.beginObject(); - out.name("id").value(value.id()); - out.name("version").value(value.rawVersion); - out.name("load-order").value(value.loadOrder().name().toLowerCase(Locale.ROOT)); - out.name("optional").value(value.optional()); - out.endObject(); - } - - @Override - public StandardPluginDependency read(final JsonReader in) throws IOException { - throw new UnsupportedOperationException("This adapter is for writing only"); - } - } -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginLinks.java b/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginLinks.java deleted file mode 100644 index 4e5aed3..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/builtin/model/StandardPluginLinks.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.builtin.model; - -import com.google.gson.JsonParseException; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.spongepowered.plugin.metadata.model.PluginLinks; -import org.spongepowered.plugin.metadata.util.GsonUtils; - -import java.io.IOException; -import java.net.URL; -import java.util.HashSet; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; - -public final class StandardPluginLinks implements PluginLinks { - - private static final StandardPluginLinks NONE = new StandardPluginLinks(); - - private final @Nullable URL homepage, source, issues; - - private StandardPluginLinks(final Builder builder) { - this.homepage = builder.homepage; - this.source = builder.source; - this.issues = builder.issues; - } - - private StandardPluginLinks() { - this.homepage = null; - this.source = null; - this.issues = null; - } - - public static Builder builder() { - return new Builder(); - } - - public static StandardPluginLinks none() { - return StandardPluginLinks.NONE; - } - - @Override - public Optional homepage() { - return Optional.ofNullable(this.homepage); - } - - @Override - public Optional source() { - return Optional.ofNullable(this.source); - } - - @Override - public Optional issues() { - return Optional.ofNullable(this.issues); - } - - public StandardPluginLinks.Builder toBuilder() { - final Builder builder = new Builder(); - builder.homepage = this.homepage; - builder.source = this.source; - builder.issues = this.issues; - - return builder; - } - - @Override - public String toString() { - return new StringJoiner(", ", StandardPluginLinks.class.getSimpleName() + "[", "]") - .add("homepage=" + this.homepage) - .add("source=" + this.source) - .add("issues=" + this.issues) - .toString(); - } - - public static final class Builder { - - @Nullable URL homepage, source, issues; - - private Builder() { - } - - public Builder homepage(@Nullable final URL homepage) { - this.homepage = homepage; - return this; - } - - public Builder source(@Nullable final URL source) { - this.source = source; - return this; - } - - public Builder issues(@Nullable final URL issues) { - this.issues = issues; - return this; - } - - public StandardPluginLinks build() { - return new StandardPluginLinks(this); - } - } - - public static final class Deserializer extends TypeAdapter { - - @Override - public void write(final JsonWriter in, final Builder builder) throws IOException { - throw new UnsupportedOperationException("This adapter is for reading only"); - } - - @Override - public StandardPluginLinks.Builder read(final JsonReader in) throws IOException { - Objects.requireNonNull(in, "in"); - - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - - in.beginObject(); - final Set processedKeys = new HashSet<>(); - final StandardPluginLinks.Builder builder = StandardPluginLinks.builder(); - while (in.hasNext()) { - final String key = in.nextName(); - if (!processedKeys.add(key)) { - throw new JsonParseException(String.format("Duplicate links key '%s' in %s", key, in)); - } - switch (key) { - case "homepage": - builder.homepage(new URL(in.nextString())); - break; - case "source": - builder.source(new URL(in.nextString())); - break; - case "issues": - builder.issues(new URL(in.nextString())); - break; - } - } - in.endObject(); - - return builder; - } - } - - public static final class Serializer extends TypeAdapter { - - @Override - public void write(final JsonWriter out, final StandardPluginLinks value) throws IOException { - Objects.requireNonNull(out, "out"); - - if (value == null) { - out.nullValue(); - return; - } - - out.beginObject(); - GsonUtils.writeIfPresent(out, "homepage", value.homepage()); - GsonUtils.writeIfPresent(out, "source", value.source()); - GsonUtils.writeIfPresent(out, "issues", value.issues()); - out.endObject(); - } - - @Override - public StandardPluginLinks read(final JsonReader out) throws IOException { - throw new UnsupportedOperationException("This adapter is for writing only"); - } - } -} diff --git a/src/main/java/org/spongepowered/plugin/metadata/model/PluginBranding.java b/src/main/java/org/spongepowered/plugin/metadata/model/PluginBranding.java index 2874221..9b1a37a 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/model/PluginBranding.java +++ b/src/main/java/org/spongepowered/plugin/metadata/model/PluginBranding.java @@ -24,40 +24,33 @@ */ package org.spongepowered.plugin.metadata.model; -import org.spongepowered.plugin.metadata.Inheritable; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.plugin.metadata.PluginMetadata; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginBranding; +import java.util.Objects; import java.util.Optional; /** - * Specification for an entity representing the branding of an {@link Inheritable inheritable} - * or {@link PluginMetadata plugin metadata}. + * Specification for an entity representing the branding of a {@link PluginMetadata plugin metadata}. *

    * Consult the vendor for further information on how this is used. - * @see StandardPluginBranding StandardPluginBranding, for a generic implementation + * + * @param icon The {@link String} that represents the location of the icon. + * @param logo The {@link String} that represents the location of the logo. */ -public interface PluginBranding { +public record PluginBranding(Optional icon, Optional logo) { + private static final PluginBranding NONE = new PluginBranding(Optional.empty(), Optional.empty()); + + public PluginBranding { + Objects.requireNonNull(icon, "icon"); + Objects.requireNonNull(logo, "logo"); + } - /** - * Gets the {@link String} that represents the location of the icon. - *

    - * Consult the vendor on the composition of this value. For example, it - * could be an absolute path or relative path (commonly fed to a {@link java.io.File} or - * {@link java.nio.file.Path}), or a serialized {@link java.net.URI}. - * - * @return The icon or {@link Optional#empty()} otherwise - */ - Optional icon(); + public PluginBranding(@Nullable String icon, @Nullable String logo) { + this(Optional.ofNullable(icon), Optional.ofNullable(logo)); + } - /** - * Gets the {@link String} that represents the location of the logo. - *

    - * Consult the vendor on the composition of this value. For example, it - * could be an absolute path or relative path (commonly fed to a {@link java.io.File} or - * {@link java.nio.file.Path}), or a serialized {@link java.net.URI}. - * - * @return The icon or {@link Optional#empty()} otherwise - */ - Optional logo(); + public static PluginBranding none() { + return PluginBranding.NONE; + } } diff --git a/src/main/java/org/spongepowered/plugin/metadata/model/PluginContributor.java b/src/main/java/org/spongepowered/plugin/metadata/model/PluginContributor.java index 871e4e9..4113cb0 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/model/PluginContributor.java +++ b/src/main/java/org/spongepowered/plugin/metadata/model/PluginContributor.java @@ -24,28 +24,29 @@ */ package org.spongepowered.plugin.metadata.model; -import org.spongepowered.plugin.metadata.Inheritable; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.plugin.metadata.PluginMetadata; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginContributor; +import java.util.Objects; import java.util.Optional; /** - * Specification for an entity considered to be a "contributor" to an {@link Inheritable inheritable} + * Specification for an entity considered to be a "contributor" to a {@link PluginMetadata metadata} * or {@link PluginMetadata plugin metadata}. *

    * Consult the vendor for further information on how this is used. - * @see StandardPluginContributor StandardPluginContributor, for a generic implementation + * + * @param name The {@link String name} + * @param description The {@link String description} or {@link Optional#empty()} otherwise */ -public interface PluginContributor { +public record PluginContributor(String name, Optional description) { - /** - * @return The {@link String name} - */ - String name(); + public PluginContributor { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(description, "description"); + } - /** - * @return The {@link String description} or {@link Optional#empty()} otherwise - */ - Optional description(); + public PluginContributor(String name, @Nullable String description) { + this(name, Optional.ofNullable(description)); + } } diff --git a/src/main/java/org/spongepowered/plugin/metadata/model/PluginDependency.java b/src/main/java/org/spongepowered/plugin/metadata/model/PluginDependency.java index fe20e54..2fbcb4b 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/model/PluginDependency.java +++ b/src/main/java/org/spongepowered/plugin/metadata/model/PluginDependency.java @@ -25,48 +25,42 @@ package org.spongepowered.plugin.metadata.model; import org.apache.maven.artifact.versioning.VersionRange; -import org.spongepowered.plugin.metadata.Inheritable; +import org.spongepowered.plugin.metadata.Constants; import org.spongepowered.plugin.metadata.PluginMetadata; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginDependency; + +import java.util.Objects; /** - * Specification for an entity considered to be a "dependency" for an {@link Inheritable inheritable} - * or {@link PluginMetadata plugin metadata}. + * Specification for an entity considered to be a "dependency" for a {@link PluginMetadata plugin metadata}. *

    * Consult the vendor for further information on how this is used. - * @see StandardPluginDependency StandardPluginDependency, for a generic implementation + * + *

    Ids must conform to the following requirements:

    + * + * + * + * @param id The {@link String id} + * @param version The {@link VersionRange version}, as a maven range. + * @param loadOrder The {@link LoadOrder load order} + * @param optional Whether this dependency is optional */ -public interface PluginDependency { +public record PluginDependency(String id, VersionRange version, LoadOrder loadOrder, boolean optional) { - /** - * Gets the {@link String id}. - * - *

    Ids must conform to the following requirements:

    - * - * - * @return The id - */ - String id(); + public PluginDependency { + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(version, "version"); + Objects.requireNonNull(loadOrder, "loadOrder"); - /** - * @return The {@link VersionRange version}, as a maven range. - */ - VersionRange version(); - - /** - * @return The {@link LoadOrder load order} - */ - LoadOrder loadOrder(); - - /** - * @return True if optional, false if not - */ - boolean optional(); + if (!Constants.VALID_ID_PATTERN.matcher(id).matches()) { + throw new IllegalStateException(String.format("Dependency with supplied ID '{%s}' is invalid. %s", id, + Constants.INVALID_ID_REQUIREMENTS_MESSAGE)); + } + } /** * Represents the ordering of how dependencies are loaded versus others. @@ -75,7 +69,7 @@ public interface PluginDependency { * documented here. It is recommended to consult with that entity on any * further behavioral changes. */ - enum LoadOrder { + public enum LoadOrder { /** * The plugin can be loaded regardless of when the dependency is loaded. */ diff --git a/src/main/java/org/spongepowered/plugin/metadata/model/PluginEntrypoints.java b/src/main/java/org/spongepowered/plugin/metadata/model/PluginEntrypoints.java new file mode 100644 index 0000000..452e712 --- /dev/null +++ b/src/main/java/org/spongepowered/plugin/metadata/model/PluginEntrypoints.java @@ -0,0 +1,58 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.model; + +import org.spongepowered.plugin.metadata.PluginMetadata; + +import java.util.List; +import java.util.Objects; + +/** + * Specification for an entity representing the entrypoints of a {@link PluginMetadata plugin metadata}. + *

    + * Consult the vendor for further information on how this is used. + * Usually these are the fully qualified name of some classes. + * + * @param main The main entrypoints + * @param server The server entrypoints + * @param client The client entrypoints + */ +public record PluginEntrypoints(List main, List server, List client) { + private static final PluginEntrypoints NONE = new PluginEntrypoints(List.of()); + + public PluginEntrypoints { + main = List.copyOf(Objects.requireNonNull(main, "main")); + server = List.copyOf(Objects.requireNonNull(server, "server")); + client = List.copyOf(Objects.requireNonNull(client, "client")); + } + + public PluginEntrypoints(List main) { + this(main, List.of(), List.of()); + } + + public static PluginEntrypoints none() { + return PluginEntrypoints.NONE; + } +} diff --git a/src/main/java/org/spongepowered/plugin/metadata/model/PluginLinks.java b/src/main/java/org/spongepowered/plugin/metadata/model/PluginLinks.java index 9ceb08a..42f1947 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/model/PluginLinks.java +++ b/src/main/java/org/spongepowered/plugin/metadata/model/PluginLinks.java @@ -24,34 +24,36 @@ */ package org.spongepowered.plugin.metadata.model; -import org.spongepowered.plugin.metadata.Inheritable; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.plugin.metadata.PluginMetadata; -import org.spongepowered.plugin.metadata.builtin.model.StandardPluginLinks; -import java.net.URL; +import java.net.URI; +import java.util.Objects; import java.util.Optional; /** - * Specification for an entity representing the links to "web resources" of an {@link Inheritable inheritable} - * or {@link PluginMetadata plugin metadata}. + * Specification for an entity representing the links to "web resources" of a {@link PluginMetadata plugin metadata}. *

    * Consult the vendor for further information on how this is used. - * @see StandardPluginLinks StandardPluginLinks, for a generic implementation + * + * @param homepage The {@link URI homepage} + * @param source The {@link URI source} + * @param issues The {@link URI issues} */ -public interface PluginLinks { +public record PluginLinks(Optional homepage, Optional source, Optional issues) { + private static final PluginLinks NONE = new PluginLinks(Optional.empty(), Optional.empty(), Optional.empty()); - /** - * @return The {@link URL homepage} or {@link Optional#empty()} otherwise - */ - Optional homepage(); + public PluginLinks { + Objects.requireNonNull(homepage, "homepage"); + Objects.requireNonNull(source, "source"); + Objects.requireNonNull(issues, "issues"); + } - /** - * @return The {@link URL source} or {@link Optional#empty()} otherwise - */ - Optional source(); + public PluginLinks(@Nullable URI homepage, @Nullable URI source, @Nullable URI issues) { + this(Optional.ofNullable(homepage), Optional.ofNullable(source), Optional.ofNullable(issues)); + } - /** - * @return The {@link URL issues} or {@link Optional#empty()} otherwise - */ - Optional issues(); + public static PluginLinks none() { + return PluginLinks.NONE; + } } diff --git a/src/main/java/org/spongepowered/plugin/metadata/model/ContainerLoader.java b/src/main/java/org/spongepowered/plugin/metadata/model/PluginLoaderSpecification.java similarity index 76% rename from src/main/java/org/spongepowered/plugin/metadata/model/ContainerLoader.java rename to src/main/java/org/spongepowered/plugin/metadata/model/PluginLoaderSpecification.java index 9b7a948..4f235a2 100644 --- a/src/main/java/org/spongepowered/plugin/metadata/model/ContainerLoader.java +++ b/src/main/java/org/spongepowered/plugin/metadata/model/PluginLoaderSpecification.java @@ -25,24 +25,22 @@ package org.spongepowered.plugin.metadata.model; import org.apache.maven.artifact.versioning.VersionRange; -import org.spongepowered.plugin.metadata.Container; -import org.spongepowered.plugin.metadata.builtin.model.StandardContainerLoader; +import org.spongepowered.plugin.metadata.PluginMetadata; + +import java.util.Objects; /** - * Specification for an entity representing the "loader" of a {@link Container container}. + * Specification for an entity representing the "loader" of a {@link PluginMetadata plugin metadata}. *

    * Consult the vendor for further information on how this is used. - * @see StandardContainerLoader StandardContainerLoader, for a generic implementation + * + * @param name The {@link String name} + * @param version The {@link VersionRange version}, as a maven range. */ -public interface ContainerLoader { - - /** - * @return The {@link String name} - */ - String name(); +public record PluginLoaderSpecification(String name, VersionRange version) { - /** - * @return The {@link VersionRange version}, as a maven range. - */ - VersionRange version(); + public PluginLoaderSpecification { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(version, "version"); + } } diff --git a/src/main/java/org/spongepowered/plugin/metadata/util/GsonUtils.java b/src/main/java/org/spongepowered/plugin/metadata/util/GsonUtils.java deleted file mode 100644 index 85ec3be..0000000 --- a/src/main/java/org/spongepowered/plugin/metadata/util/GsonUtils.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * This file is part of plugin-meta, licensed under the MIT License (MIT). - * - * 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.spongepowered.plugin.metadata.util; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonWriter; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.io.IOException; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; - -public final class GsonUtils { - - public static > V read(final JsonArray in, final TypeAdapter adapter, final Supplier collector) { - final V parsed = collector.get(); - - for (final JsonElement element : in) { - parsed.add(adapter.fromJsonTree(element)); - } - - return parsed; - } - - public static Map read(final JsonObject in, final Function valFunc, final Supplier> collector) { - final Map parsed = collector.get(); - - for (final Map.Entry entry : in.entrySet()) { - parsed.put(entry.getKey(), valFunc.apply(entry.getValue())); - } - - return parsed; - } - - public static JsonArray write(final TypeAdapter adapter, final Collection value) { - final JsonArray array = new JsonArray(); - for (final T val : value) { - array.add(adapter.toJsonTree(val)); - } - return array; - } - - public static JsonObject write(final Function valFunc, final Map value) { - final JsonObject obj = new JsonObject(); - for (final Map.Entry entry : value.entrySet()) { - obj.add(entry.getKey(), valFunc.apply(entry.getValue())); - } - - return obj; - } - - public static void writeIfPresent(final JsonWriter out, final String name, final Optional value) throws IOException { - if (value.isPresent()) { - out.name(name).value(value.get().toString()); - } - } - - public static void writeIfPresent(final JsonObject out, final String key, final Optional value) { - final @Nullable T val = value.orElse(null); - if (val == null) { - return; - } - if (val instanceof JsonElement) { - out.add(key, (JsonElement) val); - } else if (val instanceof String) { - out.addProperty(key, (String) val); - } else if (val instanceof Number) { - out.addProperty(key, (Number) val); - } else if (val instanceof Boolean) { - out.addProperty(key, (Boolean) val); - } else if (val instanceof Character) { - out.addProperty(key, (Character) val); - } - } - - @SuppressWarnings("unchecked") - public static void consumeIfPresent(final JsonObject obj, final String key, final Consumer consumer) { - if (obj.has(key)) { - consumer.accept((T) obj.get(key)); - } - } - - public static void applyIfValid(final JsonObject obj, final T value, final Predicate validator, final BiConsumer consumer) { - if (validator.test(value)) { - consumer.accept(obj, value); - } - } - - public static Optional get(final JsonObject obj, final String key, final Function valFunc) { - if (!obj.has(key)) { - return Optional.empty(); - } - return Optional.of(valFunc.apply(obj.get(key))); - } -} diff --git a/src/test/java/org/spongepowered/plugin/metadata/builtin/MetadataParserTest.java b/src/test/java/org/spongepowered/plugin/metadata/builtin/MetadataParserTest.java new file mode 100644 index 0000000..ca8107c --- /dev/null +++ b/src/test/java/org/spongepowered/plugin/metadata/builtin/MetadataParserTest.java @@ -0,0 +1,213 @@ +/* + * This file is part of plugin-meta, licensed under the MIT License (MIT). + * + * 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.spongepowered.plugin.metadata.builtin; + +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.VersionRange; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.spongepowered.plugin.metadata.PluginMetadata; +import org.spongepowered.plugin.metadata.model.PluginContributor; +import org.spongepowered.plugin.metadata.model.PluginDependency; +import org.spongepowered.plugin.metadata.model.PluginEntrypoints; +import org.spongepowered.plugin.metadata.model.PluginLinks; +import org.spongepowered.plugin.metadata.model.PluginLoaderSpecification; + +import java.io.*; +import java.net.URI; +import java.util.List; +import java.util.Objects; + +public class MetadataParserTest { + + private static Reader resourceReader(final String path) { + return new InputStreamReader(Objects.requireNonNull(MetadataParserTest.class.getResourceAsStream(path))); + } + + private static List readLines(final String path) throws IOException { + try (final BufferedReader reader = new BufferedReader(MetadataParserTest.resourceReader(path))) { + return reader.lines().toList(); + } + } + + private static MetadataContainer readContainer(final String path) throws IOException { + try (final Reader reader = MetadataParserTest.resourceReader(path)) { + return MetadataParser.read(reader); + } + } + + private static List writeContainer(final MetadataContainer container) throws IOException { + final StringWriter writer = new StringWriter(); + MetadataParser.write(writer, container, true); + return List.of(writer.toString().split("\n")); + } + + private static final InheritableMetadata global = InheritableMetadata.builder() + .loader(new PluginLoaderSpecification("java_plain", VersionRange.createFromVersion("1.0"))) + .license("some_license") + .build(); + + private static final InheritableMetadata override = InheritableMetadata.builder() + .name("TestPlugin") + .version(new DefaultArtifactVersion("1.2.3")) + .description("Some test plugin") + .links(new PluginLinks( + URI.create("https://spongepowered.org/"), + URI.create("https://github.com/SpongePowered/Sponge"), + URI.create("https://github.com/SpongePowered/Sponge/issues") + )) + .addContributor(new PluginContributor("Spongie", "Mascot")) + .addDependency(new PluginDependency("spongeapi", VersionRange.createFromVersion("17.0.0"), PluginDependency.LoadOrder.AFTER, false)) + .build(); + + private static final InheritableMetadata full = global.with(override); + + private static final PluginEntrypoints entrypoints = new PluginEntrypoints( + List.of("my.test.package.MyTestPlugin_Main"), + List.of("my.test.package.MyTestPlugin_Server"), + List.of("my.test.package.MyTestPlugin_Client") + ); + + private static final PluginEntrypoints mainOnlyEntrypoints = new PluginEntrypoints(List.of("my.test.package.MyTestPlugin")); + + private static final MetadataContainer mixContainer = new MetadataContainer(global, List.of( + StandardPluginMetadata.builder() + .id("test_plugin") + .entrypoints(entrypoints) + .global(global) + .override(override) + .build() + )); + + private static final MetadataContainer fullGlobalContainer = new MetadataContainer(full, List.of( + StandardPluginMetadata.builder() + .id("test_plugin") + .entrypoints(entrypoints) + .global(full) + .build() + )); + + private static final MetadataContainer fullOverrideContainer = new MetadataContainer(InheritableMetadata.none(), List.of( + StandardPluginMetadata.builder() + .id("test_plugin") + .entrypoints(entrypoints) + .override(full) + .build() + )); + + private static final MetadataContainer mainEntrypointOnlyContainer = new MetadataContainer(global, List.of( + StandardPluginMetadata.builder() + .id("test_plugin") + .entrypoints(mainOnlyEntrypoints) + .global(global) + .override(override) + .build() + )); + + @Test + public void readMix() throws IOException { + final MetadataContainer parsed = MetadataParserTest.readContainer("/valid/mix.json"); + Assertions.assertEquals(mixContainer, parsed); + } + + @Test + public void readMixInRoot() throws IOException { + final MetadataContainer parsed = MetadataParserTest.readContainer("/valid/mix_in_root.json"); + Assertions.assertEquals(mixContainer, parsed); + } + + @Test + public void readFullGlobal() throws IOException { + final MetadataContainer parsed = MetadataParserTest.readContainer("/valid/full_global.json"); + Assertions.assertEquals(fullGlobalContainer, parsed); + } + + @Test + public void readFullOverride() throws IOException { + final MetadataContainer parsed = MetadataParserTest.readContainer("/valid/full_override.json"); + Assertions.assertEquals(fullOverrideContainer, parsed); + } + + @Test + public void readMainEntrypointOnly() throws IOException { + final MetadataContainer parsed = MetadataParserTest.readContainer("/valid/main_entrypoint_only.json"); + Assertions.assertEquals(mainEntrypointOnlyContainer, parsed); + } + + @Test + public void writeMix() throws IOException { + final List expected = MetadataParserTest.readLines("/valid/mix.json"); + final List result = MetadataParserTest.writeContainer(mixContainer); + Assertions.assertLinesMatch(expected, result); + } + + @Test + public void writeFullGlobal() throws IOException { + final List expected = MetadataParserTest.readLines("/valid/full_global.json"); + final List result = MetadataParserTest.writeContainer(fullGlobalContainer); + Assertions.assertLinesMatch(expected, result); + } + + @Test + public void writeFullOverride() throws IOException { + final List expected = MetadataParserTest.readLines("/valid/full_override.json"); + final List result = MetadataParserTest.writeContainer(fullOverrideContainer); + Assertions.assertLinesMatch(expected, result); + } + + @Test + public void writeMainEntrypointOnly() throws IOException { + final List expected = MetadataParserTest.readLines("/valid/main_entrypoint_only.json"); + final List result = MetadataParserTest.writeContainer(mainEntrypointOnlyContainer); + Assertions.assertLinesMatch(expected, result); + } + + @Test + public void readLegacyDashInId() throws IOException { + final String pluginIdWarning = "Plugin id 'test-plugin' is invalid and has been converted to 'test_plugin'."; + final String dependencyIdWarning = "Plugin id 'test-dependency' is invalid and has been converted to 'test_dependency'."; + Assertions.assertFalse(MetadataParser.warnings().contains(pluginIdWarning)); + Assertions.assertFalse(MetadataParser.warnings().contains(dependencyIdWarning)); + + final MetadataContainer parsed = MetadataParserTest.readContainer("/legacy/dash_in_id.json"); + + Assertions.assertEquals(1, parsed.plugins().size()); + final PluginMetadata plugin = parsed.plugins().getFirst(); + Assertions.assertEquals("test_plugin", plugin.id()); + + Assertions.assertEquals(1, plugin.dependencies().size()); + final PluginDependency dependency = plugin.dependencies().iterator().next(); + Assertions.assertEquals("test_dependency", dependency.id()); + + Assertions.assertTrue(MetadataParser.warnings().contains(pluginIdWarning)); + Assertions.assertTrue(MetadataParser.warnings().contains(dependencyIdWarning)); + } + + @Test + public void readLegacyEntrypoint() throws IOException { + final MetadataContainer parsed = MetadataParserTest.readContainer("/legacy/entrypoint.json"); + Assertions.assertEquals(mainEntrypointOnlyContainer, parsed); + } +} diff --git a/src/test/resources/legacy/dash_in_id.json b/src/test/resources/legacy/dash_in_id.json new file mode 100644 index 0000000..e1c2f3b --- /dev/null +++ b/src/test/resources/legacy/dash_in_id.json @@ -0,0 +1,34 @@ +{ + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "license": "some_license", + "plugins": [ + { + "id": "test-plugin", + "name": "TestPlugin", + "description": "Some test plugin", + "entrypoints": { + "main": [ + "my.test.package.MyTestPlugin_Main" + ], + "server": [ + "my.test.package.MyTestPlugin_Server" + ], + "client": [ + "my.test.package.MyTestPlugin_Client" + ] + }, + "version": "1.2.3", + "dependencies": [ + { + "id": "test-dependency", + "version": "4.5.6", + "load-order": "AFTER", + "optional": false + } + ] + } + ] +} diff --git a/src/test/resources/legacy/entrypoint.json b/src/test/resources/legacy/entrypoint.json new file mode 100644 index 0000000..4e6f8c8 --- /dev/null +++ b/src/test/resources/legacy/entrypoint.json @@ -0,0 +1,37 @@ +{ + "global": { + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "license": "some_license" + }, + "plugins": [ + { + "id": "test_plugin", + "entrypoint": "my.test.package.MyTestPlugin", + "version": "1.2.3", + "name": "TestPlugin", + "description": "Some test plugin", + "links": { + "homepage": "https://spongepowered.org/", + "source": "https://github.com/SpongePowered/Sponge", + "issues": "https://github.com/SpongePowered/Sponge/issues" + }, + "contributors": [ + { + "name": "Spongie", + "description": "Mascot" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "17.0.0", + "load-order": "AFTER", + "optional": false + } + ] + } + ] +} diff --git a/src/test/resources/valid/full_global.json b/src/test/resources/valid/full_global.json new file mode 100644 index 0000000..3284035 --- /dev/null +++ b/src/test/resources/valid/full_global.json @@ -0,0 +1,47 @@ +{ + "global": { + "version": "1.2.3", + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "name": "TestPlugin", + "description": "Some test plugin", + "license": "some_license", + "links": { + "homepage": "https://spongepowered.org/", + "source": "https://github.com/SpongePowered/Sponge", + "issues": "https://github.com/SpongePowered/Sponge/issues" + }, + "contributors": [ + { + "name": "Spongie", + "description": "Mascot" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "17.0.0", + "load-order": "AFTER", + "optional": false + } + ] + }, + "plugins": [ + { + "id": "test_plugin", + "entrypoints": { + "main": [ + "my.test.package.MyTestPlugin_Main" + ], + "server": [ + "my.test.package.MyTestPlugin_Server" + ], + "client": [ + "my.test.package.MyTestPlugin_Client" + ] + } + } + ] +} diff --git a/src/test/resources/valid/full_override.json b/src/test/resources/valid/full_override.json new file mode 100644 index 0000000..57475db --- /dev/null +++ b/src/test/resources/valid/full_override.json @@ -0,0 +1,45 @@ +{ + "plugins": [ + { + "id": "test_plugin", + "entrypoints": { + "main": [ + "my.test.package.MyTestPlugin_Main" + ], + "server": [ + "my.test.package.MyTestPlugin_Server" + ], + "client": [ + "my.test.package.MyTestPlugin_Client" + ] + }, + "version": "1.2.3", + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "name": "TestPlugin", + "description": "Some test plugin", + "license": "some_license", + "links": { + "homepage": "https://spongepowered.org/", + "source": "https://github.com/SpongePowered/Sponge", + "issues": "https://github.com/SpongePowered/Sponge/issues" + }, + "contributors": [ + { + "name": "Spongie", + "description": "Mascot" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "17.0.0", + "load-order": "AFTER", + "optional": false + } + ] + } + ] +} diff --git a/src/test/resources/valid/main_entrypoint_only.json b/src/test/resources/valid/main_entrypoint_only.json new file mode 100644 index 0000000..c9efc5a --- /dev/null +++ b/src/test/resources/valid/main_entrypoint_only.json @@ -0,0 +1,39 @@ +{ + "global": { + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "license": "some_license" + }, + "plugins": [ + { + "id": "test_plugin", + "entrypoints": [ + "my.test.package.MyTestPlugin" + ], + "version": "1.2.3", + "name": "TestPlugin", + "description": "Some test plugin", + "links": { + "homepage": "https://spongepowered.org/", + "source": "https://github.com/SpongePowered/Sponge", + "issues": "https://github.com/SpongePowered/Sponge/issues" + }, + "contributors": [ + { + "name": "Spongie", + "description": "Mascot" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "17.0.0", + "load-order": "AFTER", + "optional": false + } + ] + } + ] +} diff --git a/src/test/resources/valid/mix.json b/src/test/resources/valid/mix.json new file mode 100644 index 0000000..1401eb0 --- /dev/null +++ b/src/test/resources/valid/mix.json @@ -0,0 +1,47 @@ +{ + "global": { + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "license": "some_license" + }, + "plugins": [ + { + "id": "test_plugin", + "entrypoints": { + "main": [ + "my.test.package.MyTestPlugin_Main" + ], + "server": [ + "my.test.package.MyTestPlugin_Server" + ], + "client": [ + "my.test.package.MyTestPlugin_Client" + ] + }, + "version": "1.2.3", + "name": "TestPlugin", + "description": "Some test plugin", + "links": { + "homepage": "https://spongepowered.org/", + "source": "https://github.com/SpongePowered/Sponge", + "issues": "https://github.com/SpongePowered/Sponge/issues" + }, + "contributors": [ + { + "name": "Spongie", + "description": "Mascot" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "17.0.0", + "load-order": "AFTER", + "optional": false + } + ] + } + ] +} diff --git a/src/test/resources/valid/mix_in_root.json b/src/test/resources/valid/mix_in_root.json new file mode 100644 index 0000000..b1efd2a --- /dev/null +++ b/src/test/resources/valid/mix_in_root.json @@ -0,0 +1,45 @@ +{ + "loader": { + "name": "java_plain", + "version": "1.0" + }, + "license": "some_license", + "plugins": [ + { + "id": "test_plugin", + "entrypoints": { + "main": [ + "my.test.package.MyTestPlugin_Main" + ], + "server": [ + "my.test.package.MyTestPlugin_Server" + ], + "client": [ + "my.test.package.MyTestPlugin_Client" + ] + }, + "version": "1.2.3", + "name": "TestPlugin", + "description": "Some test plugin", + "links": { + "homepage": "https://spongepowered.org/", + "source": "https://github.com/SpongePowered/Sponge", + "issues": "https://github.com/SpongePowered/Sponge/issues" + }, + "contributors": [ + { + "name": "Spongie", + "description": "Mascot" + } + ], + "dependencies": [ + { + "id": "spongeapi", + "version": "17.0.0", + "load-order": "AFTER", + "optional": false + } + ] + } + ] +}