All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.artipie.nuget.metadata.Nuspec Maven / Gradle / Ivy

/*
 * The MIT License (MIT) Copyright (c) 2020-2023 artipie.com
 * https://github.com/nuget-adapter/artipie/LICENSE.txt
 */
package com.artipie.nuget.metadata;

import com.artipie.ArtipieException;
import com.artipie.asto.ArtipieIOException;
import com.jcabi.xml.XML;
import com.jcabi.xml.XMLDocument;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Package description in .nuspec format.
 * @since 0.6
 */
@SuppressWarnings("PMD.TooManyMethods")
public interface Nuspec {

    /**
     * Package identifier: original case sensitive and lowercase.
     * @return Package id
     * @throws ArtipieException If id field is not found
     * @checkstyle MethodNameCheck (3 lines)
     */
    @SuppressWarnings("PMD.ShortMethodName")
    NuspecField id();

    /**
     * Package versions: original version field value or normalised.
     * @return Version of the package
     * @throws ArtipieException If version field is not found
     */
    NuspecField version();

    /**
     * Package description.
     * @return Description
     * @throws ArtipieException If description field is not found
     */
    String description();

    /**
     * Package authors.
     * @return Authors
     * @throws ArtipieException If authors field is not found
     */
    String authors();

    /**
     * Returns `minClientVersion` attribute value. Check
     * docs.
     * @return Optional with value if present
     */
    Optional minClientVersion();

    /**
     * Returns optional field by name.
     * @param name Field name
     * @return Optional with value if found
     */
    Optional fieldByName(OptFieldName name);

    /**
     * List of the dependencies formatted as
     * dependency_id:dependency_version:group_targetFramework
     * For more details about format please check
     * docs.
     * @return Dependencies list
     */
    Collection dependencies();

    /**
     * List of the package types formatted as
     * type_name:version
     * For more details about format please check
     * docs.
     * @return Dependencies list
     */
    Set packageTypes();

    /**
     * Nuspec file bytes.
     * @return Bytes
     * @throws ArtipieIOException On OI error
     */
    byte[] bytes();

    /**
     * Implementation of {@link Nuspec}, reads fields values from byte xml source.
     *
     * @since 0.6
     */
    final class Xml implements Nuspec {

        /**
         * Xml tag name `version`.
         */
        private static final String VRSN = "version";

        /**
         * Xml document.
         */
        private final XMLDocument content;

        /**
         * Binary content in .nuspec format.
         */
        private final byte[] bytes;

        /**
         * Ctor.
         *
         * @param bytes Binary content of in .nuspec format.
         */
        public Xml(final byte[] bytes) {
            this.bytes = bytes;
            this.content = new XMLDocument(bytes);
        }

        /**
         * Ctor.
         * @param input Input stream with nuspec content
         * @throws ArtipieIOException On IO error
         */
        public Xml(final InputStream input) {
            this(Xml.read(input));
        }

        @Override
        @SuppressWarnings("PMD.ShortMethodName")
        public NuspecField id() {
            return new PackageId(
                single(
                    this.content, "/*[name()='package']/*[name()='metadata']/*[name()='id']/text()"
                )
            );
        }

        @Override
        public NuspecField version() {
            final String version = single(
                this.content,
                "/*[name()='package']/*[name()='metadata']/*[name()='version']/text()"
            );
            return new Version(version);
        }

        @Override
        public String description() {
            return single(
                this.content,
                "/*[name()='package']/*[name()='metadata']/*[name()='description']/text()"
            );
        }

        @Override
        public String authors() {
            return single(
                this.content,
                "/*[name()='package']/*[name()='metadata']/*[name()='authors']/text()"
            );
        }

        @Override
        public Optional minClientVersion() {
            return this.content.xpath("/*[name()='package']/*[name()='metadata']/@minClientVersion")
                .stream().findFirst();
        }

        @Override
        public Optional fieldByName(final OptFieldName name) {
            final List values = this.content.xpath(
                String.format(
                    "/*[name()='package']/*[name()='metadata']/*[name()='%s']/text()", name.get()
                )
            );
            Optional res = Optional.empty();
            if (!values.isEmpty()) {
                res = Optional.of(values.get(0));
            }
            return res;
        }

        @Override
        public Collection dependencies() {
            final List deps = this.content.nodes(
                "/*[name()='package']/*[name()='metadata']/*[name()='dependencies']"
            );
            final Collection res = new ArrayList<>(10);
            if (!deps.isEmpty()) {
                //@checkstyle LineLengthCheck (1 line)
                final List groups = this.content.nodes("/*[name()='package']/*[name()='metadata']/*[name()='dependencies']/*[name()='group']");
                for (final XML group : groups) {
                    final String tfv = Optional.ofNullable(
                        group.node().getAttributes().getNamedItem("targetFramework")
                    ).map(Node::getNodeValue).orElse("");
                    final NodeList list = group.node().getChildNodes();
                    boolean empty = true;
                    for (int cnt = 0; cnt < list.getLength(); cnt = cnt + 1) {
                        final Node item = list.item(cnt);
                        if ("dependency".equals(item.getLocalName())) {
                            empty = false;
                            final String id = Optional.ofNullable(
                                item.getAttributes().getNamedItem("id")
                            ).map(Node::getNodeValue).orElse("");
                            final String version = Optional.ofNullable(
                                item.getAttributes().getNamedItem(Xml.VRSN)
                            ).map(Node::getNodeValue).orElse("");
                            res.add(String.format("%s:%s:%s", id, version, tfv));
                        }
                    }
                    if (empty) {
                        res.add(String.format("::%s", tfv));
                    }
                }
            }
            return res;
        }

        @Override
        public Set packageTypes() {
            final List root = this.content.nodes(
                "/*[name()='package']/*[name()='metadata']/*[name()='packageTypes']"
            );
            final Set res = new HashSet<>(1);
            if (!root.isEmpty()) {
                //@checkstyle LineLengthCheck (1 line)
                final List types = this.content.nodes("/*[name()='package']/*[name()='metadata']/*[name()='packageTypes']/*[name()='packageType']");
                for (final XML type : types) {
                    res.add(
                        String.format(
                            "%s:%s",
                            type.node().getAttributes().getNamedItem("name").getNodeValue(),
                            Optional.ofNullable(
                                type.node().getAttributes().getNamedItem(Xml.VRSN)
                            ).map(Node::getNodeValue).orElse("")
                        )
                    );
                }
            }
            return res;
        }

        @Override
        public byte[] bytes() {
            return this.bytes;
        }

        @Override
        public String toString() {
            return new String(this.bytes(), StandardCharsets.UTF_8);
        }

        /**
         * Reads single string value from XML via XPath.
         * Exception is thrown if zero or more then 1 values found
         *
         * @param xml XML document to read from.
         * @param xpath XPath expression to select data from the XML.
         * @return Value found by XPath
         */
        private static String single(final XML xml, final String xpath) {
            final List values = xml.xpath(xpath);
            if (values.isEmpty()) {
                throw new ArtipieException(
                    new IllegalArgumentException(
                        String.format("No values found in path: '%s'", xpath)
                    )
                );
            }
            if (values.size() > 1) {
                throw new ArtipieException(
                    new IllegalArgumentException(
                        String.format("Multiple values found in path: '%s'", xpath)
                    )
                );
            }
            return values.get(0);
        }

        /**
         * Read bytes from input stream.
         * @param input Input to read from
         * @return Bytes
         */
        private static byte[] read(final InputStream input) {
            try {
                return IOUtils.toByteArray(input);
            } catch (final IOException err) {
                throw new ArtipieIOException(err);
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy