diff --git a/pom.xml b/pom.xml index 8923361..d6b3b75 100644 --- a/pom.xml +++ b/pom.xml @@ -132,10 +132,10 @@ 2.38.0 0.8.13 - 2.58.0 - 4.9.3.0 - 2.45.0 - 4.9.3 + 2.83.0 + 4.9.8.2 + 3.1.0 + 4.9.8 3.1.1 1.37 diff --git a/src/main/java/com/github/packageurl/PackageURL.java b/src/main/java/com/github/packageurl/PackageURL.java index a474651..2530f7c 100644 --- a/src/main/java/com/github/packageurl/PackageURL.java +++ b/src/main/java/com/github/packageurl/PackageURL.java @@ -908,6 +908,10 @@ public static final class StandardTypes { @Deprecated public static final String NIXPKGS = "nix"; + public static final String BAZEL = "bazel"; + + public static final String JULIA = "julia"; + private StandardTypes() {} } } diff --git a/src/main/java/com/github/packageurl/PackageURLBuilders.java b/src/main/java/com/github/packageurl/PackageURLBuilders.java new file mode 100644 index 0000000..05a3020 --- /dev/null +++ b/src/main/java/com/github/packageurl/PackageURLBuilders.java @@ -0,0 +1,4079 @@ +/* + * MIT License + * + * 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 com.github.packageurl; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; +import org.jspecify.annotations.Nullable; + +/** + * Builder factories for creating type-safe Package URLs (PURLs). + *

+ * Each method returns a builder enforcing correct fields and allowed qualifiers + * for that PURL type based on the official + * PURL specification. + * + * @since 2.0.0 + */ +public final class PackageURLBuilders { + private PackageURLBuilders() {} + + /** + * Returns a builder for Arch Linux packages and other users of the libalpm/pacman package manager. + * + * @return a new AlpmBuilder + */ + public static AlpmBuilder alpm() { + return new AlpmBuilder(); + } + + /** + * Returns an alpine Linux APK packages. + * + * @return a new ApkBuilder + */ + public static ApkBuilder apk() { + return new ApkBuilder(); + } + + /** + * Returns a builder for Bazel modules as specified in + * Bazel modules. + * + * @return a new BazelBuilder + */ + public static BazelBuilder bazel() { + return new BazelBuilder(); + } + + /** + * Returns a builder for Bitbucket packages. + * + * @return a new BitbucketBuilder + */ + public static BitbucketBuilder bitbucket() { + return new BitbucketBuilder(); + } + + /** + * Returns a builder for Bitnami packages. + * + * @return a new BitnamiBuilder + */ + public static BitnamiBuilder bitnami() { + return new BitnamiBuilder(); + } + + /** + * Returns a builder for Cargo packages for Rust. + * + * @return a new CargoBuilder + */ + public static CargoBuilder cargo() { + return new CargoBuilder(); + } + + /** + * Returns a builder for CocoaPods pods. + * + * @return a new CocoapodsBuilder + */ + public static CocoapodsBuilder cocoapods() { + return new CocoapodsBuilder(); + } + + /** + * Returns a builder for Composer PHP packages. + * + * @return a new ComposerBuilder + */ + public static ComposerBuilder composer() { + return new ComposerBuilder(); + } + + /** + * Returns a builder for Conan C/C++ packages. + *

+ * The PURL is designed to closely resemble the Conan-native {@code /@/} + * syntax for package references as specified in + * Package terminology. + * + * @return a new ConanBuilder + */ + public static ConanBuilder conan() { + return new ConanBuilder(); + } + + /** + * Returns a builder for Conda packages. + * + * @return a new CondaBuilder + */ + public static CondaBuilder conda() { + return new CondaBuilder(); + } + + /** + * Returns a builder for perl package distributions published on CPAN. + * + * @return a new CpanBuilder + */ + public static CpanBuilder cpan() { + return new CpanBuilder(); + } + + /** + * Returns a build for CRAN R packages. + * + * @return a new CranBuilder + */ + public static CranBuilder cran() { + return new CranBuilder(); + } + + /** + * Returns a builder for debian packages, Debian derivatives, and Ubuntu packages. + * + * @return a new DebBuilder + */ + public static DebBuilder deb() { + return new DebBuilder(); + } + + /** + * Returns a builder for Docker images. + * + * @return a new DockerBuilder + */ + public static DockerBuilder docker() { + return new DockerBuilder(); + } + + /** + * Returns a builder for RubyGems. + * + * @return a new GemBuilder + */ + public static GemBuilder gem() { + return new GemBuilder(); + } + + /** + * Returns the builder for plain, generic packages that do not fit anywhere + * else such as for "upstream-from-distro" packages. In + * particular this is handy for a plain version control repository such as + * a bare git repo in combination with a {@code vcs_url}. + * + * @return a new GenericBuilder + */ + public static GenericBuilder generic() { + return new GenericBuilder(); + } + + /** + * Returns a builder for GitHub packages. + * + * @return a new GithubBuilder + */ + public static GithubBuilder github() { + return new GithubBuilder(); + } + + /** + * Returns a builder Go packages. + * + * @return a new GolangBuilder + */ + public static GolangBuilder golang() { + return new GolangBuilder(); + } + + /** + * Returns a builder Haskell packages. + * + * @return a new HackageBuilder + */ + public static HackageBuilder hackage() { + return new HackageBuilder(); + } + + /** + * Returns a builder for Hex packages. + * + * @return a new HexBuilder + */ + public static HexBuilder hex() { + return new HexBuilder(); + } + + /** + * Returns a builder for Hugging Face ML models. + * + * @return a new HuggingfaceBuilder + */ + public static HuggingfaceBuilder huggingface() { + return new HuggingfaceBuilder(); + } + + /** + * Returns a builder for Julia packages + * + * @return a new JuliaBuilder + */ + public static JuliaBuilder julia() { + return new JuliaBuilder(); + } + + /** + * Returns a builder for Lua packages installed with LuaRocks. + * + * @return a new LuarocksBuilder + */ + public static LuarocksBuilder luarocks() { + return new LuarocksBuilder(); + } + + /** + * Returns a builder for Maven JARs and related artifacts. + * + * @return a new MavenBuilder + */ + public static MavenBuilder maven() { + return new MavenBuilder(); + } + + /** + * Returns a builder for MLflow ML models (Azure ML, Databricks, etc.) + * + * @return a new MlflowBuilder + */ + public static MlflowBuilder mlflow() { + return new MlflowBuilder(); + } + + /** + * Returns a builder for NPM packages. + * + * @return a new NpmBuilder + */ + public static NpmBuilder npm() { + return new NpmBuilder(); + } + + /** + * Returns a builder for NuGet .NET packages. + * + * @return a new NugetBuilder + */ + public static NugetBuilder nuget() { + return new NugetBuilder(); + } + + /** + * Returns a builder for artifacts stored in registries that conform to the + * OCI Distribution Specification + * including container images built by Docker and others. + * + * @return a new OciBuilder + */ + public static OciBuilder oci() { + return new OciBuilder(); + } + + /** + * Returns a builder for Dart and Flutter pub packages. + * + * @return a new PubBuilder + */ + public static PubBuilder pub() { + return new PubBuilder(); + } + + /** + * Returns a builder for Python packages. + *

+ * a python packages + * + * @return a new PypiBuilder + */ + public static PypiBuilder pypi() { + return new PypiBuilder(); + } + + /** + * Returns a QNX packages. + * + * @return a new QpkgBuilder + */ + public static QpkgBuilder qpkg() { + return new QpkgBuilder(); + } + + /** + * Returns an RPM packages. + * + * @return a new RpmBuilder + */ + public static RpmBuilder rpm() { + return new RpmBuilder(); + } + + /** + * Returns a builder for ISO-IEC 19770-2 Software Identification (SWID) tags. + * + * @return a new SwidBuilder + */ + public static SwidBuilder swid() { + return new SwidBuilder(); + } + + /** + * Returns a builder for Swift packages. + * + * @return a new SwiftBuilder + */ + public static SwiftBuilder swift() { + return new SwiftBuilder(); + } + + /** + * Common builder shared between the different types. + * + * @param the builder type + */ + public abstract static class Builder> { + protected final Map qualifiers = new TreeMap<>(); + + protected @Nullable String subpath; + + protected Builder() {} + + /** + * Sets the subpath. + * + * @param subpath the subpath + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withSubpath(String subpath) { + this.subpath = subpath; + return (T) this; + } + + /** + * Removes the subpath + * + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withoutSubpath() { + this.subpath = null; + return (T) this; + } + + /** + * Allows the specification of a version range. The value must adhere to the Version Range Specification. + * + * @param vers the value + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withVers(String vers) { + qualifiers.put("vers", vers); + return (T) this; + } + + /** + * Removes the vers. + * + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withoutVers() { + qualifiers.remove("vers"); + return (T) this; + } + + /** + * An extra URL for an alternative, non-default package repository or registry. + * + * @param repositoryUrl the repository URL + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withRepositoryUrl(String repositoryUrl) { + qualifiers.put("repository_url", repositoryUrl); + return (T) this; + } + + /** + * Removes the repository URL. + * + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withoutRepositoryUrl() { + qualifiers.remove("repository_url"); + return (T) this; + } + + /** + * An extra URL for a direct package web download URL to optionally qualify a PURL. + * + * @param downloadUrl the value + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withDownloadUrl(String downloadUrl) { + qualifiers.put("download_url", downloadUrl); + return (T) this; + } + + /** + * Removes the download URL. + * + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withoutDownloadUrl() { + qualifiers.remove("download_url"); + return (T) this; + } + + /** + * An extra URL for a package version control system URL to optionally qualify a PURL. + * + * @param vcsUrl the value + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withVcsUrl(String vcsUrl) { + qualifiers.put("vcs_url", vcsUrl); + return (T) this; + } + + /** + * Removes the VCS URL. + * + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withoutVcsUrl() { + qualifiers.remove("vcs_url"); + return (T) this; + } + + /** + * An extra file name of a package archive. + * + * @param fileName the value + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withFileName(String fileName) { + qualifiers.put("file_name", fileName); + return (T) this; + } + + /** + * Removes the file name. + * + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withoutFileName() { + qualifiers.remove("file_name"); + return (T) this; + } + + /** + * A qualifier for one or more checksums stored as a comma-separated list. + * + * @param checksum the value + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withChecksum(String checksum) { + qualifiers.put("checksum", checksum); + return (T) this; + } + + /** + * Removes the checksum + * + * @return this builder instance + */ + @SuppressWarnings("unchecked") + public T withoutChecksum() { + qualifiers.remove("checksum"); + return (T) this; + } + + /** + * Returns empty. This builder has no default repository. + * + * @return empty + */ + public Optional getDefaultRepositoryUrl() { + return Optional.empty(); + } + + /** + * Constructs the final Package URL. + * + * @return a fully constructed Package URL + * @throws MalformedPackageURLException if the Package URL is invalid + */ + public abstract PackageURL build() throws MalformedPackageURLException; + } + + /** + * Builder for Arch Linux packages and other users of the libalpm/pacman package manager. + */ + public static final class AlpmBuilder extends Builder { + private @Nullable String vendor; + + private @Nullable String name; + + private @Nullable String version; + + private AlpmBuilder() {} + + /** + * Sets the vendor such as arch, arch32, archarm, manjaro or msys. + * + * @param vendor the vendor value + * @return this builder instance + */ + public AlpmBuilder withVendor(String vendor) { + this.vendor = vendor; + return this; + } + + /** + * Removes the namespace. + * + * @return this builder instance + */ + public AlpmBuilder withoutVendor() { + this.vendor = null; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public AlpmBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version string + * @return this builder instance + */ + public AlpmBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public AlpmBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the package architecture. + * + * @param arch the package architecture + * @return this builder instance + */ + public AlpmBuilder withArch(String arch) { + this.qualifiers.put("arch", arch); + return this; + } + + /** + * Removes the package architecture. + * + * @return this builder instance + */ + public AlpmBuilder withoutArch() { + this.qualifiers.remove("arch"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.vendor); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.ALPM) + .withNamespace(this.vendor) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Alpine Linux APK-based packages. + */ + public static final class ApkBuilder extends Builder { + private @Nullable String vendor; + + private @Nullable String name; + + private @Nullable String version; + + private ApkBuilder() {} + + /** + * Sets the vendor such as alpine or openwrt. It is not case-sensitive and must be lowercased. + * + * @param vendor the vendor such as alpine or openwrt + * @return this builder instance + */ + public ApkBuilder withVendor(String vendor) { + this.vendor = vendor; + return this; + } + + /** + * Removes the vendor. + * + * @return this builder instance + */ + public ApkBuilder withoutVendor() { + this.vendor = null; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public ApkBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version string + * @return this builder instance + */ + public ApkBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public ApkBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the package architecture. + * + * @param arch the package architecture + * @return this builder instance + */ + public ApkBuilder withArch(String arch) { + this.qualifiers.put("arch", arch); + return this; + } + + /** + * Removes the package architecture. + * + * @return this builder instance + */ + public ApkBuilder withoutArch() { + this.qualifiers.remove("arch"); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.vendor); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.APK) + .withNamespace(this.vendor) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Bazel modules as specified in + * Bazel modules. + */ + public static final class BazelBuilder extends Builder { + /** + * The default repository is the Bazel Central Registry (BCR). + */ + public static final String DEFAULT_REPOSITORY_URL = "https://bcr.bazel.build"; + + private @Nullable String name; + + private @Nullable String version; + + private @Nullable String label; + + private BazelBuilder() {} + + /** + * Sets the name as defined in the {@code MODULE.bazel} file. + * + * @param name the name + * @return this builder instance + */ + public BazelBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version string + * @return this builder instance + */ + public BazelBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public BazelBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the subpath. + * + * @param subpath the subpath + * @return this builder instance + * @deprecated use {@link #withLabel(String)} + */ + @Override + @Deprecated + public BazelBuilder withSubpath(String subpath) { + return withLabel(subpath); + } + + /** + * Removes the subpath. + * + * @return this builder instance + * @deprecated use {@link #withoutLabel()} + */ + @Override + @Deprecated + public BazelBuilder withoutSubpath() { + return withoutLabel(); + } + + /** + * Sets the optional subpath which MAY refer to a label of a particular + * package or target in the module + * (Labels). The + * label MUST NOT include a repo name and the leading {@code '//'} MUST + * be omitted. When referring to targets, the label MUST include the + * name of the target, separated from the package by {@code ':'}. If + * there is no target name, subpath is assumed to refer to the whole + * package. + * + * @param label the optional subpath + * @return this builder instance + */ + public BazelBuilder withLabel(String label) { + this.label = label; + return this; + } + + /** + * Removes the optional subpath. + * + * @return this builder instance + */ + public BazelBuilder withoutLabel() { + this.label = null; + return this; + } + + /** + * The URL of the registry that hosts this Bazel module. If not specified, it defaults to the + * {@link #DEFAULT_REPOSITORY_URL BCR URL}. + * + * @param registry the URL of the registry that hosts this Bazel module + * @return this builder instance + */ + public BazelBuilder withRegistry(String registry) { + this.qualifiers.put("repository_url", registry); + return this; + } + + /** + * Removes the URL of the registry that hosts this Bazel module. + * + * @return this builder instance + */ + public BazelBuilder withoutRegistry() { + this.qualifiers.remove("repository_url"); + return this; + } + + /** + * Returns the Bazel Central Registry (BCR). + * + * @return the Bazel Central Registry (BCR) + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.version); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.BAZEL) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.label) + .build(); + } + } + + /** + * Builder for Bitbucket packages. + */ + public static final class BitbucketBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://bitbucket.org"; + + private @Nullable String organization; + + private @Nullable String name; + + private @Nullable String commit; + + private BitbucketBuilder() {} + + /** + * Sets the user or organization. + * + * @param organization the user or organization + * @return this builder instance + */ + public BitbucketBuilder withOrganization(String organization) { + this.organization = organization; + return this; + } + + /** + * Removes the user or organization. + * + * @return this builder instance + */ + public BitbucketBuilder withoutOrganization() { + this.organization = null; + return this; + } + + /** + * Sets the repository name. + * + * @param name the name + * @return this builder instance + */ + public BitbucketBuilder withRepositoryName(String name) { + this.name = name; + return this; + } + + /** + * Sets the commit or tag. + * + * @param commit the commit or tag + * @return this builder instance + */ + public BitbucketBuilder withCommit(String commit) { + this.commit = commit; + return this; + } + + /** + * Removes the commit or tag. + * + * @return this builder instance + */ + public BitbucketBuilder withoutCommit() { + this.commit = null; + return this; + } + + /** + * Returns the default Bitbucket repository URL. + * + * @return the default Bitbucket repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.organization); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.BITBUCKET) + .withNamespace(this.organization) + .withName(this.name) + .withVersion(this.commit) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Bitnami packages. + */ + public static final class BitnamiBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://downloads.bitnami.com/files/stacksmith"; + + private @Nullable String name; + + private @Nullable String version; + + private BitnamiBuilder() {} + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public BitnamiBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the full package version, including version and revision. + * + * @param version the version string + * @return this builder instance + */ + public BitnamiBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the full package version. + * + * @return this builder instance + */ + public BitnamiBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the package architecture. Available values are amd64 (default) and arm64. + * + * @param arch the package architecture + * @return this builder instance + */ + public BitnamiBuilder withArch(String arch) { + this.qualifiers.put("arch", arch); + return this; + } + + /** + * Removes the package architecture. + * + * @return this builder instance + */ + public BitnamiBuilder withoutArch() { + this.qualifiers.remove("arch"); + return this; + } + + /** + * Sets the distribution associated with the package. + * + * @param distro the distribution associated with the package + * @return this builder instance + */ + public BitnamiBuilder withDistro(String distro) { + this.qualifiers.put("distro", distro); + return this; + } + + /** + * Removes the distribution associated with the package. + * + * @return this builder instance + */ + public BitnamiBuilder withoutDistro() { + this.qualifiers.remove("distro"); + return this; + } + + /** + * Returns the default Bitnami repository URL. + * + * @return the default Bitnami repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.BITNAMI) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Cargo packages for Rust. + */ + public static final class CargoBuilder extends Builder { + private @Nullable String name; + + private @Nullable String version; + + private CargoBuilder() {} + + /** + * Sets the repository name. + * + * @param name the repository name + * @return this builder instance + */ + public CargoBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the package version. + * + * @param version the package version + * @return this builder instance + */ + public CargoBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public CargoBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.CARGO) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for CocoaPods pods. + */ + public static final class CocoapodsBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://cdn.cocoapods.org/"; + + private @Nullable String name; + + private @Nullable String packageVersion; + + private CocoapodsBuilder() {} + + /** + * Sets the pod name. + * + * @param name the pod name + * @return this builder instance + */ + public CocoapodsBuilder withPodName(String name) { + this.name = name; + return this; + } + + /** + * Sets the package version. + * + * @param packageVersion the version string + * @return this builder instance + */ + public CocoapodsBuilder withPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + return this; + } + + /** + * Removes the package version. + * + * @return this builder instance + */ + public CocoapodsBuilder withoutVersion() { + this.packageVersion = null; + return this; + } + + /** + * Sets the pods subspec. + * + * @param subpath the subpath + * @return this builder instance + */ + @Override + public CocoapodsBuilder withSubpath(String subpath) { + this.subpath = subpath; + return this; + } + + /** + * Removes the pods subspec. + * + * @return this builder instance + */ + @Override + public CocoapodsBuilder withoutSubpath() { + this.subpath = null; + return this; + } + + /** + * Returns the default CocoaPods repository URL. + * + * @return the default CocoaPods repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.COCOAPODS) + .withName(this.name) + .withVersion(this.packageVersion) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Composer PHP packages + */ + public static final class ComposerBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://packagist.org"; + + private @Nullable String vendor; + + private @Nullable String name; + + private @Nullable String version; + + private ComposerBuilder() {} + + /** + * Sets the namespace. + * + * @param vendor the namespace + * @return this builder instance + */ + public ComposerBuilder withVendor(String vendor) { + this.vendor = vendor; + return this; + } + + /** + * Removes the namespace. + * + * @return this builder instance + */ + public ComposerBuilder withoutVendor() { + this.vendor = null; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public ComposerBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version string + * @return this builder instance + */ + public ComposerBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public ComposerBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Returns the default Composer repository URL. + * + * @return the default Composer repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.vendor); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.COMPOSER) + .withNamespace(this.vendor) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Conan C/C++ packages. The purl is designed to closely resemble the + * Conan-native {@code /@/} syntax + * for package references as specified in + * Package terminology. + */ + public static final class ConanBuilder extends Builder { + private @Nullable String vendor; + + private @Nullable String name; + + private @Nullable String packageVersion; + + private ConanBuilder() {} + + /** + * Sets the vendor of the package. + * + * @param vendor the vendor of the package + * @return this builder instance + */ + public ConanBuilder withVendor(String vendor) { + this.vendor = vendor; + return this; + } + + /** + * Removes the vendor of the package. + * + * @return this builder instance + */ + public ConanBuilder withoutVendor() { + this.vendor = null; + return this; + } + + /** + * Sets the Conan {@code }. + * + * @param name the name + * @return this builder instance + */ + public ConanBuilder withPackageName(String name) { + this.name = name; + return this; + } + + /** + * Sets the Conan {@code }. + * + * @param packageVersion the version string + * @return this builder instance + */ + public ConanBuilder withPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + return this; + } + + /** + * Removes the Conan {@code }. + * + * @return this builder instance + */ + public ConanBuilder withoutPackageVersion() { + this.packageVersion = null; + return this; + } + + /** + * Sets the Conan {@code }. Only required if the Conan package was published with {@code }. + * + * @param user the Conan {@code } + * @return this builder instance + */ + public ConanBuilder withUser(String user) { + this.qualifiers.put("user", user); + return this; + } + + /** + * Remove the Conan {@code }. + * + * @return this builder instance + */ + public ConanBuilder withoutUser() { + this.qualifiers.remove("user"); + return this; + } + + /** + * Sets the Conan {@code }. Only required if the Conan package was published with Conan {@code }. + * + * @param channel the Conan {@code } + * @return this builder instance + */ + public ConanBuilder withChannel(String channel) { + this.qualifiers.put("channel", channel); + return this; + } + + /** + * Removes the Conan {@code }. + * + * @return this builder instance + */ + public ConanBuilder withoutChannel() { + this.qualifiers.remove("channel"); + return this; + } + + /** + * Sets the Conan recipe revision (optional). If omitted, the PURL + * refers to the latest recipe revision available for the given version. + * + * @param recipleRevision the Conan recipe revision + * @return this builder instance + */ + public ConanBuilder withRecipeRevision(String recipleRevision) { + this.qualifiers.put("rrev", recipleRevision); + return this; + } + + /** + * Removes the Conan recipe revision. + * + * @return this builder instance + */ + public ConanBuilder withoutRecipeRevision() { + this.qualifiers.remove("rrev"); + return this; + } + + /** + * Sets the Conan package revision (optional). If omitted, the PURL + * refers to the latest package revision available for the given version and recipe revision. + * + * @param packageRevision the Conan package revision + * @return this builder instance + */ + public ConanBuilder withPackageRevision(String packageRevision) { + this.qualifiers.put("prev", packageRevision); + return this; + } + + /** + * Removes the Conan package revision. + * + * @return this builder instance + */ + public ConanBuilder withoutPackageRevision() { + this.qualifiers.remove("prev"); + return this; + } + + /** + * Sets the Conan package architecture. + * + * @param arch the architecture string + * @return this builder instance + */ + public ConanBuilder withArch(String arch) { + this.qualifiers.put("arch", arch); + return this; + } + + /** + * Removes the Conan package architecture. + * + * @return this builder instance + */ + public ConanBuilder withoutArch() { + this.qualifiers.remove("arch"); + return this; + } + + /** + * Sets the Conan build type. + * + * @param buildType the build type + * @return this builder instance + */ + public ConanBuilder withBuildType(String buildType) { + this.qualifiers.put("build_type", buildType); + return this; + } + + /** + * Removes the Conan build type. + * + * @return this builder instance + */ + public ConanBuilder withoutBuildType() { + this.qualifiers.remove("build_type"); + return this; + } + + /** + * Sets the Conan compiler. + * + * @param compiler the compiler name + * @return this builder instance + */ + public ConanBuilder withCompiler(String compiler) { + this.qualifiers.put("compiler", compiler); + return this; + } + + /** + * Removes the Conan compiler. + * + * @return this builder instance + */ + public ConanBuilder withoutCompiler() { + this.qualifiers.remove("compiler"); + return this; + } + + /** + * Sets the Conan compiler runtime. + * + * @param compilerRuntime the compiler runtime string + * @return this builder instance + */ + public ConanBuilder withCompilerRuntime(String compilerRuntime) { + this.qualifiers.put("compiler.runtime", compilerRuntime); + return this; + } + + /** + * Removes the Conan compiler runtime. + * + * @return this builder instance + */ + public ConanBuilder withoutCompilerRuntime() { + this.qualifiers.remove("compiler.runtime"); + return this; + } + + /** + * Sets the Conan compiler version. + * + * @param compilerVersion the compiler version + * @return this builder instance + */ + public ConanBuilder withCompilerVersion(String compilerVersion) { + this.qualifiers.put("compiler.version", compilerVersion); + return this; + } + + /** + * Removes the Conan compiler version. + * + * @return this builder instance + */ + public ConanBuilder withoutCompilerVersion() { + this.qualifiers.remove("compiler.version"); + return this; + } + + /** + * Sets the Conan operating system. + * + * @param os the operating system string + * @return this builder instance + */ + public ConanBuilder withOs(String os) { + this.qualifiers.put("os", os); + return this; + } + + /** + * Removes the Conan operating system. + * + * @return this builder instance + */ + public ConanBuilder withoutOs() { + this.qualifiers.remove("os"); + return this; + } + + /** + * Sets whether the Conan package is shared. + * + * @param shared {@code "True"} if shared or {@code "False"} if not + * @return this builder instance + */ + public ConanBuilder withShared(String shared) { + this.qualifiers.put("shared", shared); + return this; + } + + /** + * Removes the shared value. + * + * @return this builder instance + */ + public ConanBuilder withoutShared() { + this.qualifiers.remove("shared"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.CONAN) + .withNamespace(this.vendor) + .withName(this.name) + .withVersion(this.packageVersion) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Conda packages. + */ + public static final class CondaBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://repo.anaconda.com"; + + private @Nullable String name; + + private @Nullable String version; + + private CondaBuilder() {} + + /** + * Sets the package name. + * + * @param name the package name + * @return this builder instance + */ + public CondaBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the package version. + * + * @param version the package version + * @return this builder instance + */ + public CondaBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the package version. + * + * @return this builder instance + */ + public CondaBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the build string. + * + * @param build the build string + * @return this builder instance + */ + public CondaBuilder withBuild(String build) { + this.qualifiers.put("build", build); + return this; + } + + /** + * Removes the build string. + * + * @return this builder instance + */ + public CondaBuilder withoutBuild() { + this.qualifiers.remove("build"); + return this; + } + + /** + * Sets the package stored location. + * + * @param channel the package stored location + * @return this builder instance + */ + public CondaBuilder withChannel(String channel) { + this.qualifiers.put("channel", channel); + return this; + } + + /** + * Removes the package stored location. + * + * @return this builder instance + */ + public CondaBuilder withoutChannel() { + this.qualifiers.remove("channel"); + return this; + } + + /** + * Sets the associated platform. + * + * @param subdir the associated platform + * @return this builder instance + */ + public CondaBuilder withSubdir(String subdir) { + this.qualifiers.put("subdir", subdir); + return this; + } + + /** + * Removes the associated platform. + * + * @return this builder instance + */ + public CondaBuilder withoutSubdir() { + this.qualifiers.remove("subdir"); + return this; + } + + /** + * Sets the package type. + * + * @param packageType the package type + * @return this builder instance + */ + public CondaBuilder withPackageType(String packageType) { + this.qualifiers.put("type", packageType); + return this; + } + + /** + * Removes the package type. + * + * @return this builder instance + */ + public CondaBuilder withoutPackageType() { + this.qualifiers.remove("type"); + return this; + } + + /** + * Returns the default Conda repository URL. + * + * @return the default Conda repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.CONDA) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Perl package distributions published on CPAN. + */ + public static final class CpanBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://www.cpan.org/"; + + private @Nullable String cpanId; + + private @Nullable String name; + + private @Nullable String version; + + private CpanBuilder() {} + + /** + * Sets the namespace. + * + * @param cpanId the namespace + * @return this builder instance + */ + public CpanBuilder withCpanId(String cpanId) { + this.cpanId = cpanId; + return this; + } + + /** + * Removes the namespace. + * + * @return this builder instance + */ + public CpanBuilder withoutCpanId() { + this.cpanId = null; + return this; + } + + /** + * Sets the distribution name. + * + * @param name the name + * @return this builder instance + */ + public CpanBuilder withDistributionName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version string + * @return this builder instance + */ + public CpanBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public CpanBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Returns the default CPAN repository URL. + * + * @return the default CPAN repository URL. + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.cpanId); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.CPAN) + .withNamespace(this.cpanId) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for CRAN R packages. + */ + public static final class CranBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://cran.r-project.org"; + + private @Nullable String name; + + private @Nullable String version; + + private CranBuilder() {} + + /** + * Sets the package name. + * + * @param name the package name + * @return this builder instance + */ + public CranBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the package version. + * + * @param version the package version + * @return this builder instance + */ + public CranBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public CranBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Returns the default CRAN repository URL. + * + * @return the default CRAN repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.CRAN) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Debian packages, Debian derivatives, and Ubuntu packages. + */ + public static final class DebBuilder extends Builder { + private @Nullable String vendor; + + private @Nullable String name; + + private @Nullable String version; + + private DebBuilder() {} + + /** + * Sets the "vendor" name such as "debian" or "ubuntu". + * + * @param vendor the vendor + * @return this builder instance + */ + public DebBuilder withVendor(String vendor) { + this.vendor = vendor; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public DebBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version of the binary (or source) package. + * + * @param version the version string + * @return this builder instance + */ + public DebBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version of the binary (or source) package. + * + * @return this builder instance + */ + public DebBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the package architecture. + * + * @param arch the package architecture + * @return this builder instance + */ + public DebBuilder withArch(String arch) { + this.qualifiers.put("arch", arch); + return this; + } + + /** + * Removes the package architecture. + * + * @return this builder instance + */ + public DebBuilder withoutArch() { + this.qualifiers.remove("arch"); + return this; + } + + /** + * Sets the distribution associated with the package. + * + * @param distro the distribution associated with the package + * @return this builder instance + */ + public DebBuilder withDistro(String distro) { + this.qualifiers.put("distro", distro); + return this; + } + + /** + * Removes the distribution associated with the package. + * + * @return this builder instance + */ + public DebBuilder withoutDistro() { + this.qualifiers.remove("distro"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + Objects.requireNonNull(this.vendor); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.DEB) + .withNamespace(this.vendor) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Docker images. + */ + public static final class DockerBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://hub.docker.com"; + + private @Nullable String registry; + + private @Nullable String name; + + private @Nullable String version; + + private DockerBuilder() {} + + /** + * Sets the registry/user/organization. + * + * @param registry the namespace + * @return this builder instance + */ + public DockerBuilder withRegistry(String registry) { + this.registry = registry; + return this; + } + + /** + * Removes the registry/user/organization. + * + * @return this builder instance + */ + public DockerBuilder withoutRegistry() { + this.registry = null; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public DockerBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the sha256 image id or tag. Since tags can be moved, a sha256 image id is preferred. + * + * @param version the version string + * @return this builder instance + */ + public DockerBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the sha256 image id or tag. + * + * @return this builder instance + */ + public DockerBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Returns the default Docker repository URL. + * + * @return the default Docker repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.DOCKER) + .withNamespace(this.registry) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for RubyGems. + */ + public static final class GemBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://rubygems.org"; + + private @Nullable String name; + + private @Nullable String version; + + private GemBuilder() {} + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public GemBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version string + * @return this builder instance + */ + public GemBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public GemBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets an alternative platform, such as java for JRuby. The implied default is ruby for Ruby MRI. + * + * @param platform the platform string + * @return this builder instance + */ + public GemBuilder withPlatform(String platform) { + this.qualifiers.put("platform", platform); + return this; + } + + /** + * Removes the platform. + * + * @return this builder instance + */ + public GemBuilder withoutPlatform() { + this.qualifiers.remove("platform"); + return this; + } + + /** + * Returns the default RubyGems repository URL. + * + * @return the default RubyGems repository URL. + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.GEM) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * The generic type is for plain, generic packages that do not fit anywhere + * else such as for "upstream-from-distro" packages. In + * particular this is handy for a plain version control + * repository such as a bare git repo in combination with a {@code vcs_url}. + */ + public static final class GenericBuilder extends Builder { + private @Nullable String namespace; + + private @Nullable String name; + + private @Nullable String version; + + private GenericBuilder() {} + + /** + * Sets the namespace. + * + * @param namespace the namespace + * @return this builder instance + */ + public GenericBuilder withNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + /** + * Removes the namespace. + * + * @return this builder instance + */ + public GenericBuilder withoutNamespace() { + this.namespace = null; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public GenericBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version string + * @return this builder instance + */ + public GenericBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public GenericBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.GENERIC) + .withNamespace(this.namespace) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for GitHub packages. + */ + public static final class GithubBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://github.com"; + + private @Nullable String organization; + + private @Nullable String repositoryName; + + private @Nullable String commit; + + private GithubBuilder() {} + + /** + * Sets the namespace. + * + * @param organization the namespace + * @return this builder instance + */ + public GithubBuilder withOrganization(String organization) { + this.organization = organization; + return this; + } + + /** + * Removes the namespace. + * + * @return this builder instance + */ + public GithubBuilder withoutOrganization() { + this.organization = null; + return this; + } + + /** + * Sets the repository name. + * + * @param repositoryName the repository name + * @return this builder instance + */ + public GithubBuilder withRepositoryName(String repositoryName) { + this.repositoryName = repositoryName; + return this; + } + + /** + * Sets the commit or tag. + * + * @param commit the commit or tag + * @return this builder instance + */ + public GithubBuilder withCommit(String commit) { + this.commit = commit; + return this; + } + + /** + * Removes the commit or tag. + * + * @return this builder instance + */ + public GithubBuilder withoutCommit() { + this.commit = null; + return this; + } + + /** + * Gets the default GitHub repository URL. + * + * @return the default GitHub repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.repositoryName); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.GITHUB) + .withNamespace(this.organization) + .withName(this.repositoryName) + .withVersion(this.commit) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Go packages. + */ + public static final class GolangBuilder extends Builder { + private @Nullable String namespace; + + private @Nullable String name; + + private @Nullable String version; + + private GolangBuilder() {} + + /** + * Sets the namespace. + * + * @param namespace the namespace + * @return this builder instance + */ + public GolangBuilder withNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public GolangBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public GolangBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public GolangBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.namespace); + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.GOLANG) + .withNamespace(this.namespace) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Haskell packages. + */ + public static final class HackageBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://hackage.haskell.org"; + + private @Nullable String name; + + private @Nullable String version; + + private HackageBuilder() {} + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public HackageBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the package version. + * + * @param version the package version + * @return this builder instance + */ + public HackageBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the package version. + * + * @return this builder instance + */ + public HackageBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Gets the default Haskell repository URL. + * + * @return the default Haskell repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.HACKAGE) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Hex packages. + */ + public static final class HexBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://repo.hex.pm"; + + private @Nullable String organization; + + private @Nullable String name; + + private @Nullable String version; + + private HexBuilder() {} + + /** + * Sets the organization for private packages. + * + * @param organization the organization for private packages + * @return this builder instance + */ + public HexBuilder withOrganization(String organization) { + this.organization = organization; + return this; + } + + /** + * Removes the organization. + * + * @return this builder instance + */ + public HexBuilder withoutOrganization() { + this.organization = null; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public HexBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public HexBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public HexBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Gets the default Hex repository URL. + * + * @return the default Hex repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.HEX) + .withNamespace(this.organization) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Hugging Face ML models. + */ + public static final class HuggingfaceBuilder extends Builder { + private @Nullable String organization; + + private @Nullable String name; + + private @Nullable String commit; + + private HuggingfaceBuilder() {} + + /** + * Sets the model repository username or organization. + * + * @param namespace the model repository username or organization + * @return this builder instance + */ + public HuggingfaceBuilder withOrganization(String namespace) { + this.organization = namespace; + return this; + } + + /** + * Sets the model repository name. + * + * @param name the name + * @return this builder instance + */ + public HuggingfaceBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the model revision Git commit hash. + * + * @param commit model revision Git commit hash + * @return this builder instance + */ + public HuggingfaceBuilder withCommit(String commit) { + this.commit = commit; + return this; + } + + /** + * Removes the commit. + * + * @return this builder instance + */ + public HuggingfaceBuilder withoutCommit() { + this.commit = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.HUGGINGFACE) + .withNamespace(this.organization) + .withName(this.name) + .withVersion(this.commit) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Julia packages. + */ + public static final class JuliaBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://github.com/JuliaRegistries/General"; + + private @Nullable String name; + + private @Nullable String version; + + private JuliaBuilder() {} + + /** + * Sets the package name (without a `.jl` suffix). + * + * @param name the package name + * @return this builder instance + */ + public JuliaBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public JuliaBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public JuliaBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the Julia package UUID. + * @param uuid the Julia package UUID + * + * @return this builder instance + */ + public JuliaBuilder withUuid(String uuid) { + this.qualifiers.put("uuid", uuid); + return this; + } + + /** + * Gets the default Julia repository URL. + * + * @return the default Julia repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.JULIA) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Lua packages installed with LuaRocks. + */ + public static final class LuarocksBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://luarocks.org"; + + private @Nullable String manifest; + + private @Nullable String name; + + private @Nullable String versionRevision; + + private LuarocksBuilder() {} + + /** + * Sets the user manifest under which the package is registered. + * + * @param manifest the user manifest + * @return this builder instance + */ + public LuarocksBuilder withManifest(String manifest) { + this.manifest = manifest; + return this; + } + + /** + * Removes the user manifest. + * + * @return this builder instance + */ + public LuarocksBuilder withoutManifest() { + this.manifest = null; + return this; + } + + /** + * Sets the LuaRocks package name. + * + * @param name the LuaRocks package name + * @return this builder instance + */ + public LuarocksBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the full package version, including module version and rockspec revision. + * + * @param versionRevision the full package version + * @return this builder instance + */ + public LuarocksBuilder withVersionRevision(String versionRevision) { + this.versionRevision = versionRevision; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public LuarocksBuilder withoutVersionRevision() { + this.versionRevision = null; + return this; + } + + /** + * Sets the LuaRocks rocks server to be used; useful in case a private + * server is used (optional). If omitted, + * https://luarocks.org as the + * default server is assumed. + * + * @param repositoryUrl the repository URL + * @return this builder instance + */ + @Override + public LuarocksBuilder withRepositoryUrl(String repositoryUrl) { + qualifiers.put("repository_url", repositoryUrl); + return this; + } + + /** + * Gets the default Luarocks repository URL. + * + * @return the default Luarocks repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.LUAROCKS) + .withNamespace(this.manifest) + .withName(this.name) + .withVersion(this.versionRevision) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Maven JARs and related artifacts. + */ + public static final class MavenBuilder extends Builder { + /** + * The Maven Central repository is the public repository for Apache + * Maven packages. This repository is also mirrored at + * + * https://repo1.maven.org/maven2/. Use the standard + * {@code repository_url} qualifier to point to another repository. + */ + public static final String DEFAULT_REPOSITORY_URL = "https://repo.maven.apache.org/maven2/"; + + private @Nullable String groupId; + + private @Nullable String artifactId; + + private @Nullable String version; + + private MavenBuilder() {} + + /** + * Sets the group identifier. + * + * @param groupId the group identifier + * @return this builder instance + */ + public MavenBuilder withGroupId(String groupId) { + this.groupId = groupId; + return this; + } + + /** + * Removes the group identifier. + * + * @return this builder instance + */ + public MavenBuilder withoutGroupId() { + this.groupId = null; + return this; + } + + /** + * Sets the artifact identifier. + * + * @param artifactId the artifact identifier + * @return this builder instance + */ + public MavenBuilder withArtifactId(String artifactId) { + this.artifactId = artifactId; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public MavenBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public MavenBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the Maven classifier as defined in the POM documentation. + * + * @param classifier the classifier + * @return this builder instance + */ + public MavenBuilder withClassifier(String classifier) { + this.qualifiers.put("classifier", classifier); + return this; + } + + /** + * Removes the classifier. + * + * @return this builder instance + */ + public MavenBuilder withoutClassifier() { + this.qualifiers.remove("classifier"); + return this; + } + + /** + * Sets the Maven type as defined in the POM documentation. Note that + * Maven uses a concept/coordinate called packaging which does not map + * directly 1:1 to a file extension. In this use case, we need to + * construct a link to one of many possible artifacts. Maven itself + * uses type in a dependency declaration when needed to disambiguate between them. + * + * @param type the type + * @return this builder instance + */ + public MavenBuilder withType(String type) { + this.qualifiers.put("type", type); + return this; + } + + /** + * Removes the type. + * + * @return this builder instance + */ + public MavenBuilder withoutType() { + this.qualifiers.remove("type"); + return this; + } + + /** + * Gets the default Maven repository URL. + * + * @return the default Maven repository URL. + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.groupId); + Objects.requireNonNull(this.artifactId); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.MAVEN) + .withNamespace(this.groupId) + .withName(this.artifactId) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for MLflow ML models (Azure ML, Databricks, etc.). + */ + public static final class MlflowBuilder extends Builder { + private @Nullable String name; + + private @Nullable String version; + + private MlflowBuilder() {} + + /** + * Sets the model name. + * + * @param name the model name + * @return this builder instance + */ + public MlflowBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public MlflowBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public MlflowBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the {@code model_uuid} as defined in the MLflow documentation. + * + * @param modelUuid the {@code model_uuid} + * @return this builder instance + */ + public MlflowBuilder withModelUuid(String modelUuid) { + this.qualifiers.put("model_uuid", modelUuid); + return this; + } + + /** + * Removes the {@code model_uuid}. + * @return this builder instance + */ + public MlflowBuilder withoutModelUuid() { + this.qualifiers.remove("model_uuid"); + return this; + } + + /** + * Sets the {@code run_id} as defined in the MLflow documentation. + * + * @param runId the {@code run_id} + * @return this builder instance + */ + public MlflowBuilder withRunId(String runId) { + this.qualifiers.put("run_id", runId); + return this; + } + + /** + * Removes the {@code run_id}. + * + * @return this builder instance + */ + public MlflowBuilder withoutRunId() { + this.qualifiers.remove("run_id"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.MLFLOW) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for NPM packages. + */ + public static final class NpmBuilder extends Builder { + /** + * The default repository is the npm Registry at + * https://registry.npmjs.org. + */ + public static final String DEFAULT_REPOSITORY_URL = "https://registry.npmjs.org/"; + + private @Nullable String scope; + + private @Nullable String name; + + private @Nullable String version; + + private NpmBuilder() {} + + /** + * Sets the scope of a scoped NPM package. + * + * @param scope the scope of a scoped NPM package + * @return this builder instance + */ + public NpmBuilder withScope(String scope) { + this.scope = scope; + return this; + } + + /** + * Removes the scope. + * + * @return this builder instance + */ + public NpmBuilder withoutScope() { + this.scope = null; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public NpmBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public NpmBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public NpmBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Gets the default NPM repository URL. + * + * @return the default NPM repository URL + */ + @Override + public Optional getDefaultRepositoryUrl() { + return Optional.of(DEFAULT_REPOSITORY_URL); + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.NPM) + .withNamespace(this.scope) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for NuGet .NET packages. + */ + public static final class NugetBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://www.nuget.org"; + + private @Nullable String name; + + private @Nullable String version; + + private NugetBuilder() {} + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public NugetBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public NugetBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public NugetBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.NUGET) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for artifacts stored in registries that conform to the OCI Distribution + * Specification + * .https://github.com/opencontainers/distribution-spec + * including container images built by Docker and others. + */ + public static final class OciBuilder extends Builder { + private @Nullable String name; + + private @Nullable String version; + + private OciBuilder() {} + + /** + * Sets the name. + *

+ * The name is the last fragment of the repository name. For example if + * the repository name is {@code library/debian} then the name is {@code debian}. + * + * @param name the name + * @return this builder instance + */ + public OciBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public OciBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public OciBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Sets the package architecture. + * + * @param arch the package architecture + * @return this builder instance + */ + public OciBuilder withArch(String arch) { + this.qualifiers.put("arch", arch); + return this; + } + + /** + * Removes the package architecture. + * + * @return this builder instance + */ + public OciBuilder withoutArch() { + this.qualifiers.remove("arch"); + return this; + } + + /** + * Sets the artifact tag that may have been associated with the digest at the time. + * + * @param tag the artifact tag + * @return this builder instance + */ + public OciBuilder withTag(String tag) { + this.qualifiers.put("tag", tag); + return this; + } + + /** + * Removes the package tag. + * + * @return this builder instance + */ + public OciBuilder withoutTag() { + this.qualifiers.remove("tag"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.OCI) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Dart and Flutter pub packages. + */ + public static final class PubBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://pub.dartlang.org"; + + private @Nullable String name; + + private @Nullable String version; + + private PubBuilder() {} + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public PubBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public PubBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public PubBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.PUB) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Python packages. + */ + public static final class PypiBuilder extends Builder { + public static final String DEFAULT_REPOSITORY_URL = "https://pypi.org"; + + private @Nullable String name; + + private @Nullable String version; + + private PypiBuilder() {} + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public PypiBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public PypiBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public PypiBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * Selects a particular distribution file (case-sensitive). + *

+ * For naming convention, see the Python Packaging User Guide on + * source distributions + * and on + * binary distributions + * and the rules for + * platform compatibility tags. + * + * @param fileName the distribution file (case-sensitive) + * @return this builder instance + */ + @Override + public PypiBuilder withFileName(String fileName) { + this.qualifiers.put("file_name", fileName); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public PypiBuilder withoutFileName() { + this.qualifiers.remove("file_name"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.PYPI) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for QNX packages. + */ + public static final class QpkgBuilder extends Builder { + private @Nullable String vendor; + + private @Nullable String name; + + private @Nullable String version; + + private QpkgBuilder() {} + + /** + * Sets the vendor of the package. + * + * @param vendor the vendor of the package + * @return this builder instance + */ + public QpkgBuilder withVendor(String vendor) { + this.vendor = vendor; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public QpkgBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version. + * + * @param version the version + * @return this builder instance + */ + public QpkgBuilder withVersion(String version) { + this.version = version; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public QpkgBuilder withoutVersion() { + this.version = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.QPKG) + .withNamespace(this.vendor) + .withName(this.name) + .withVersion(this.version) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for RPM packages. + */ + public static final class RpmBuilder extends Builder { + private @Nullable String vendor; + + private @Nullable String name; + + private @Nullable String versionRelease; + + private RpmBuilder() {} + + /** + * Sets the vendor such as Fedora or OpenSUSE. + * + * @param vendor the vendor + * @return this builder instance + */ + public RpmBuilder withVendor(String vendor) { + this.vendor = vendor; + return this; + } + + /** + * Sets the name. + * + * @param name the name + * @return this builder instance + */ + public RpmBuilder withName(String name) { + this.name = name; + return this; + } + + /** + * Sets the version-release. + * + * @param versionRelease the version-release + * @return this builder instance + */ + public RpmBuilder withVersionRelease(String versionRelease) { + this.versionRelease = versionRelease; + return this; + } + + /** + * Removes the version-release. + * + * @return this builder instance + */ + public RpmBuilder withoutVersionRelease() { + this.versionRelease = null; + return this; + } + + /** + * Sets the package epoch. + * + * @param epoch the package epoch + * @return this builder instance + */ + public RpmBuilder withEpoch(String epoch) { + this.qualifiers.put("epoch", epoch); + return this; + } + + /** + * Removes the package epoch. + * + * @return this builder instance + */ + public RpmBuilder withoutEpoch() { + this.qualifiers.remove("epoch"); + return this; + } + + /** + * Sets the package architecture. + * + * @param arch the package architecture + * @return this builder instance + */ + public RpmBuilder withArch(String arch) { + this.qualifiers.put("arch", arch); + return this; + } + + /** + * Removes the package architecture. + * + * @return this builder instance + */ + public RpmBuilder withoutArch() { + this.qualifiers.remove("arch"); + return this; + } + + /** + * Sets the distribution associated with the package. + * + * @param distro the distribution associated with the package + * @return this builder instance + */ + public RpmBuilder withDistro(String distro) { + this.qualifiers.put("distro", distro); + return this; + } + + /** + * Removes the distribution associated with the package. + * + * @return this builder instance + */ + public RpmBuilder withoutDistro() { + this.qualifiers.remove("distro"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.vendor); + Objects.requireNonNull(this.name); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.RPM) + .withNamespace(this.vendor) + .withName(this.name) + .withVersion(this.versionRelease) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for ISO-IEC 19770-2 Software Identification (SWID) tags. + */ + public static final class SwidBuilder extends Builder { + private @Nullable String softwareCreator; + + private @Nullable String softwareIdentityName; + + private @Nullable String softwareIdentityVersion; + + private SwidBuilder() {} + + /** + * Sets the optional name and regid of the entity with a role of {@code softwareCreator}. + * + * @param softwareCreator name and regid + * @return this builder instance + */ + public SwidBuilder withSoftwareCreator(String softwareCreator) { + this.softwareCreator = softwareCreator; + return this; + } + + /** + * Removes the name and regid of the entity with a role of {@code softwareCreator}. + * + * @return this builder instance + */ + public SwidBuilder withoutSoftwareCreator() { + this.softwareCreator = null; + return this; + } + + /** + * Sets the name as defined in the SWID {@code SoftwareIdentity} element. + * + * @param name the name + * @return this builder instance + */ + public SwidBuilder withSoftwareIdentityName(String name) { + this.softwareIdentityName = name; + return this; + } + + /** + * Sets the version as defined in the SWID {@code SoftwareIdentity} element. + * + * @param softwareIdentityVersion the version + * @return this builder instance + */ + public SwidBuilder withSoftwareIdentityVersion(String softwareIdentityVersion) { + this.softwareIdentityVersion = softwareIdentityVersion; + return this; + } + + /** + * Removes the version. + * + * @return this builder instance + */ + public SwidBuilder withoutSoftwareIdentityVersion() { + this.softwareIdentityVersion = null; + return this; + } + + /** + * Sets the {@code tagId} as defined in the SWID + * {@code SoftwareIdentity} element. Per the SWID specification, GUIDs + * are recommended. + * + * @param tagId the {@code tagId} + * @return this builder instance + */ + public SwidBuilder withTagId(String tagId) { + this.qualifiers.put("tag_id", tagId); + return this; + } + + /** + * Removes the {@code tagId}. + * + * @return this builder instance + */ + public SwidBuilder withoutTagId() { + this.qualifiers.remove("tag_id"); + return this; + } + + /** + * Sets the {@code tagVersion} as defined in the SWID + * {@code SoftwareIdentity} element. Per the SWID specification, GUIDs + * are recommended. + * + * @param tagVersion the {@code tagVersion} + * @return this builder instance + */ + public SwidBuilder withTagVersion(String tagVersion) { + this.qualifiers.put("tag_version", tagVersion); + return this; + } + + /** + * Removes the {@code tagVersion}. + * + * @return this builder instance + */ + public SwidBuilder withoutTagVersion() { + this.qualifiers.remove("tag_version"); + return this; + } + + /** + * Sets the {@code patch} as defined in the SWID {@code + * SoftwareIdentity} element. + * + * @param patch {@code the patch} + * @return this builder instance + */ + public SwidBuilder withPatch(String patch) { + this.qualifiers.put("patch", patch); + return this; + } + + /** + * Removes the {@code patch}. + * + * @return this builder instance + */ + public SwidBuilder withoutPatch() { + this.qualifiers.remove("patch"); + return this; + } + + /** + * Sets the {@code tagCreatorName} as defined in the SWID + * {@code SoftwareIdentity} element. + * + * @param tagCreatorName the {@code tagCreatorName} + * @return this builder instance + */ + public SwidBuilder withTagCreatorName(String tagCreatorName) { + this.qualifiers.put("tag_creator_name", tagCreatorName); + return this; + } + + /** + * Removes the {@code tagCreatorName}. + * + * @return this builder instance + */ + public SwidBuilder withoutTagCreatorName() { + this.qualifiers.remove("tag_creator_name"); + return this; + } + + /** + * Sets the {@code tagCreatorRegid} as defined in the SWID + * {@code SoftwareIdentity} element. + * + * @param tagCreatorRegid the {@code tagCreatorRegid} + * @return this builder instance + */ + public SwidBuilder withTagCreatorRegid(String tagCreatorRegid) { + this.qualifiers.put("tag_creator_regid", tagCreatorRegid); + return this; + } + + /** + * Removes the {@code tagCreatorRegid}. + * + * @return this builder instance + */ + public SwidBuilder withoutTagCreatorRegid() { + this.qualifiers.remove("tag_creator_regid"); + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.softwareIdentityName); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.SWID) + .withNamespace(this.softwareCreator) + .withName(this.softwareIdentityName) + .withVersion(this.softwareIdentityVersion) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } + + /** + * Builder for Swift packages. + */ + public static final class SwiftBuilder extends Builder { + private @Nullable String namespace; + + private @Nullable String repositoryName; + + private @Nullable String packageVersion; + + private SwiftBuilder() {} + + /** + * Sets the source host and user/organization. + * + * @param namespace the source host and user/organization + * @return this builder instance + */ + public SwiftBuilder withNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + /** + * Sets the repository repositoryName. + * + * @param repositoryName the repository repositoryName + * @return this builder instance + */ + public SwiftBuilder withRepositoryName(String repositoryName) { + this.repositoryName = repositoryName; + return this; + } + + /** + * Sets the package version. + * + * @param packageVersion the package version + * @return this builder instance + */ + public SwiftBuilder withPackageVersion(String packageVersion) { + this.packageVersion = packageVersion; + return this; + } + + /** + * Removes the package version. + * + * @return this builder instance + */ + public SwiftBuilder withoutPackageVersion() { + this.packageVersion = null; + return this; + } + + /** + * {@inheritDoc} + */ + public PackageURL build() throws MalformedPackageURLException { + Objects.requireNonNull(this.namespace); + Objects.requireNonNull(this.repositoryName); + return PackageURLBuilder.aPackageURL() + .withType(PackageURL.StandardTypes.SWIFT) + .withNamespace(this.namespace) + .withName(this.repositoryName) + .withVersion(this.packageVersion) + .withQualifiers(!this.qualifiers.isEmpty() ? this.qualifiers : null) + .withSubpath(this.subpath) + .build(); + } + } +} diff --git a/src/test/java/com/github/packageurl/PackageURLBuildersTest.java b/src/test/java/com/github/packageurl/PackageURLBuildersTest.java new file mode 100644 index 0000000..b5d8da1 --- /dev/null +++ b/src/test/java/com/github/packageurl/PackageURLBuildersTest.java @@ -0,0 +1,981 @@ +/* + * MIT License + * + * 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 com.github.packageurl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public final class PackageURLBuildersTest { + @Test + void testAlpm() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:alpm/arch/pacman@6.0.1-1?arch=x86_64") + .build(), + PackageURLBuilders.alpm() + .withVendor("arch") + .withName("pacman") + .withVersion("6.0.1-1") + .withArch("x86_64") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:alpm/arch/python-pip@21.0-1?arch=any") + .build(), + PackageURLBuilders.alpm() + .withVendor("arch") + .withName("python-pip") + .withVersion("21.0-1") + .withArch("any") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:alpm/arch/containers-common@1:0.47.4-4?arch=x86_64") + .build(), + PackageURLBuilders.alpm() + .withVendor("arch") + .withName("containers-common") + .withVersion("1:0.47.4-4") + .withArch("x86_64") + .build()); + } + + @Test + void testApk() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:apk/alpine/curl@7.83.0-r0?arch=x86") + .build(), + PackageURLBuilders.apk() + .withVendor("alpine") + .withName("curl") + .withVersion("7.83.0-r0") + .withArch("x86") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:apk/alpine/apk@2.12.9-r3?arch=x86") + .build(), + PackageURLBuilders.apk() + .withVendor("alpine") + .withName("apk") + .withVersion("2.12.9-r3") + .withArch("x86") + .build()); + } + + @Test + void testBazel() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bazel/rules_java@7.8.0").build(), + PackageURLBuilders.bazel() + .withName("rules_java") + .withVersion("7.8.0") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bazel/curl@8.8.0.bcr.1").build(), + PackageURLBuilders.bazel() + .withName("curl") + .withVersion("8.8.0.bcr.1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bazel/curl@8.8.0?repository_url=https://example.org/bazel-registry") + .build(), + PackageURLBuilders.bazel() + .withName("curl") + .withVersion("8.8.0") + .withRepositoryUrl("https://example.org/bazel-registry") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bazel/rules_java@8.5.0#java/runfiles") + .build(), + PackageURLBuilders.bazel() + .withName("rules_java") + .withVersion("8.5.0") + .withLabel("java/runfiles") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bazel/rules_java@8.5.0#java/runfiles:runfiles") + .build(), + PackageURLBuilders.bazel() + .withName("rules_java") + .withVersion("8.5.0") + .withLabel("java/runfiles:runfiles") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bazel/rules_go@0.48.0#go").build(), + PackageURLBuilders.bazel() + .withName("rules_go") + .withVersion("0.48.0") + .withLabel("go") + .build()); + } + + @Test + void testBitbucket() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c") + .build(), + PackageURLBuilders.bitbucket() + .withOrganization("birkenfeld") + .withRepositoryName("pygments-main") + .withCommit("244fd47e07d1014f0aed9c") + .build()); + } + + @Test + void testBitnami() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bitnami/wordpress?distro=debian-12") + .build(), + PackageURLBuilders.bitnami() + .withName("wordpress") + .withDistro("debian-12") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bitnami/wordpress@6.2.0?distro=debian-12") + .build(), + PackageURLBuilders.bitnami() + .withName("wordpress") + .withVersion("6.2.0") + .withDistro("debian-12") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=debian-12") + .build(), + PackageURLBuilders.bitnami() + .withName("wordpress") + .withVersion("6.2.0") + .withArch("arm64") + .withDistro("debian-12") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:bitnami/wordpress@6.2.0?arch=arm64&distro=photon-4") + .build(), + PackageURLBuilders.bitnami() + .withName("wordpress") + .withVersion("6.2.0") + .withArch("arm64") + .withDistro("photon-4") + .build()); + } + + @Test + void testCargo() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cargo/rand@0.7.2").build(), + PackageURLBuilders.cargo().withName("rand").withVersion("0.7.2").build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cargo/clap@2.33.0").build(), + PackageURLBuilders.cargo() + .withName("clap") + .withVersion("2.33.0") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cargo/structopt@0.3.11").build(), + PackageURLBuilders.cargo() + .withName("structopt") + .withVersion("0.3.11") + .build()); + } + + @Test + void testCocoapods() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cocoapods/AFNetworking@4.0.1") + .build(), + PackageURLBuilders.cocoapods() + .withPodName("AFNetworking") + .withPackageVersion("4.0.1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cocoapods/MapsIndoors@3.24.0") + .build(), + PackageURLBuilders.cocoapods() + .withPodName("MapsIndoors") + .withPackageVersion("3.24.0") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cocoapods/ShareKit@2.0#Twitter") + .build(), + PackageURLBuilders.cocoapods() + .withPodName("ShareKit") + .withPackageVersion("2.0") + .withSubpath("Twitter") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib") + .build(), + PackageURLBuilders.cocoapods() + .withPodName("GoogleUtilities") + .withPackageVersion("7.5.2") + .withSubpath("NSData+zlib") + .build()); + } + + @Test + void testComposer() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:composer/laravel/laravel@5.5.0") + .build(), + PackageURLBuilders.composer() + .withVendor("laravel") + .withName("laravel") + .withVersion("5.5.0") + .build()); + } + + @Test + void testConan() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:conan/openssl@3.0.3").build(), + PackageURLBuilders.conan() + .withPackageName("openssl") + .withPackageVersion("3.0.3") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:conan/openssl.org/openssl@3.0.3?user=bincrafters&channel=stable") + .build(), + PackageURLBuilders.conan() + .withVendor("openssl.org") + .withPackageName("openssl") + .withPackageVersion("3.0.3") + .withUser("bincrafters") + .withChannel("stable") + .build()); + // FIXME: Definition missing these qualifiers + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:conan/openssl.org/openssl@3.0.3?arch=x86_64&build_type=Debug&compiler=Visual%20Studio&compiler.runtime=MDd&compiler.version=16&os=Windows&shared=True&rrev=93a82349c31917d2d674d22065c7a9ef9f380c8e&prev=b429db8a0e324114c25ec387bfd8281f330d7c5c") + .build(), + PackageURLBuilders.conan() + .withVendor("openssl.org") + .withPackageName("openssl") + .withPackageVersion("3.0.3") + .withArch("x86_64") + .withBuildType("Debug") + .withCompiler("Visual Studio") + .withCompilerRuntime("MDd") + .withCompilerVersion("16") + .withOs("Windows") + .withShared("True") + .withRecipeRevision("93a82349c31917d2d674d22065c7a9ef9f380c8e") + .withPackageRevision("b429db8a0e324114c25ec387bfd8281f330d7c5c") + .build()); + } + + @Test + void testConda() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:conda/absl-py@0.4.1?build=py36h06a4308_0&channel=main&subdir=linux-64&type=tar.bz2") + .build(), + PackageURLBuilders.conda() + .withName("absl-py") + .withVersion("0.4.1") + .withBuild("py36h06a4308_0") + .withChannel("main") + .withSubdir("linux-64") + .withPackageType("tar.bz2") + .build()); + } + + @Test + void testCpan() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cpan/GDT/URI-PackageURL").build(), + PackageURLBuilders.cpan() + .withCpanId("GDT") + .withDistributionName("URI-PackageURL") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cpan/OALDERS/libwww-perl@6.76") + .build(), + PackageURLBuilders.cpan() + .withCpanId("OALDERS") + .withDistributionName("libwww-perl") + .withVersion("6.76") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cpan/DROLSKY/DateTime@1.55?repository_url=backpan.perl.org") + .build(), + PackageURLBuilders.cpan() + .withCpanId("DROLSKY") + .withDistributionName("DateTime") + .withVersion("1.55") + .withRepositoryUrl("backpan.perl.org") + .build()); + } + + @Test + void testCran() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cran/A3@1.0.0").build(), + PackageURLBuilders.cran().withName("A3").withVersion("1.0.0").build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cran/rJava@1.0-4").build(), + PackageURLBuilders.cran().withName("rJava").withVersion("1.0-4").build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:cran/caret@6.0-88").build(), + PackageURLBuilders.cran() + .withName("caret") + .withVersion("6.0-88") + .build()); + } + + @Test + void testDeb() throws MalformedPackageURLException { + // FIXME: Definition missing distro qualifier + assertEquals( + PackageURLBuilder.aPackageURL("pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie") + .build(), + PackageURLBuilders.deb() + .withVendor("debian") + .withName("curl") + .withVersion("7.50.3-1") + .withArch("i386") + .withDistro("jessie") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:deb/debian/dpkg@1.19.0.4?arch=amd64&distro=stretch") + .build(), + PackageURLBuilders.deb() + .withVendor("debian") + .withName("dpkg") + .withVersion("1.19.0.4") + .withArch("amd64") + .withDistro("stretch") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:deb/ubuntu/dpkg@1.19.0.4?arch=amd64") + .build(), + PackageURLBuilders.deb() + .withVendor("ubuntu") + .withName("dpkg") + .withVersion("1.19.0.4") + .withArch("amd64") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:deb/debian/attr@1:2.4.47-2?arch=source") + .build(), + PackageURLBuilders.deb() + .withVendor("debian") + .withName("attr") + .withVersion("1:2.4.47-2") + .withArch("source") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:deb/debian/attr@1:2.4.47-2%2Bb1?arch=amd64") + .build(), + PackageURLBuilders.deb() + .withVendor("debian") + .withName("attr") + .withVersion("1:2.4.47-2+b1") + .withArch("amd64") + .build()); + } + + @Test + void testDocker() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:docker/cassandra@latest").build(), + PackageURLBuilders.docker() + .withName("cassandra") + .withVersion("latest") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:docker/smartentry/debian@dc437cc87d10") + .build(), + PackageURLBuilders.docker() + .withName("debian") + .withVersion("dc437cc87d10") + .withRegistry("smartentry") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:docker/customer/dockerimage@sha256%3A244fd47e07d10?repository_url=gcr.io") + .build(), + PackageURLBuilders.docker() + .withName("dockerimage") + .withVersion("sha256:244fd47e07d10") + .withRegistry("customer") + .withRepositoryUrl("gcr.io") + .build()); + } + + @Test + void testGem() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:gem/ruby-advisory-db-check@0.12.4") + .build(), + PackageURLBuilders.gem() + .withName("ruby-advisory-db-check") + .withVersion("0.12.4") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:gem/jruby-launcher@1.1.2?platform=java") + .build(), + PackageURLBuilders.gem() + .withName("jruby-launcher") + .withVersion("1.1.2") + .withPlatform("java") + .build()); + } + + @Test + void testGeneric() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:generic/openssl@1.1.10g").build(), + PackageURLBuilders.generic() + .withName("openssl") + .withVersion("1.1.10g") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:generic/openssl@1.1.10g?download_url=https://openssl.org/source/openssl-1.1.0g.tar.gz&checksum=sha256:de4d501267da") + .build(), + PackageURLBuilders.generic() + .withName("openssl") + .withVersion("1.1.10g") + .withDownloadUrl("https://openssl.org/source/openssl-1.1.0g.tar.gz") + .withChecksum("sha256:de4d501267da") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:generic/bitwarderl?vcs_url=git%2Bhttps://git.fsfe.org/dxtr/bitwarderl%40cc55108da32") + .build(), + PackageURLBuilders.generic() + .withName("bitwarderl") + .withVcsUrl("git+https://git.fsfe.org/dxtr/bitwarderl@cc55108da32") + .build()); + } + + @Test + void testGithub() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:github/package-url/purl-spec@244fd47e07d1004") + .build(), + PackageURLBuilders.github() + .withOrganization("package-url") + .withRepositoryName("purl-spec") + .withCommit("244fd47e07d1004") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:github/package-url/purl-spec@244fd47e07d1004#everybody/loves/dogs") + .build(), + PackageURLBuilders.github() + .withOrganization("package-url") + .withRepositoryName("purl-spec") + .withCommit("244fd47e07d1004") + .withSubpath("everybody/loves/dogs") + .build()); + } + + @Test + void testGolang() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:golang/github.com/gorilla%2Fcontext@234fd47e07d1004f0aed9c") + .build(), + PackageURLBuilders.golang() + .withNamespace("github.com") + .withName("gorilla/context") + .withVersion("234fd47e07d1004f0aed9c") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:golang/google.golang.org/genproto#googleapis/api/annotations") + .build(), + PackageURLBuilders.golang() + .withNamespace("google.golang.org") + .withName("genproto") + .withSubpath("googleapis/api/annotations") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:golang/github.com/gorilla%2Fcontext@234fd47e07d1004f0aed9c#api") + .build(), + PackageURLBuilders.golang() + .withNamespace("github.com") + .withName("gorilla/context") + .withVersion("234fd47e07d1004f0aed9c") + .withSubpath("api") + .build()); + } + + @Test + void testHackage() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:hackage/a50@0.5").build(), + PackageURLBuilders.hackage().withName("a50").withVersion("0.5").build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:hackage/AC-HalfInteger@1.2.1") + .build(), + PackageURLBuilders.hackage() + .withName("AC-HalfInteger") + .withVersion("1.2.1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:hackage/3d-graphics-examples@0.0.0.2") + .build(), + PackageURLBuilders.hackage() + .withName("3d-graphics-examples") + .withVersion("0.0.0.2") + .build()); + } + + @Test + void testHex() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:hex/jason@1.1.2").build(), + PackageURLBuilders.hex().withName("jason").withVersion("1.1.2").build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:hex/acme/foo@2.3.").build(), + PackageURLBuilders.hex() + .withOrganization("acme") + .withName("foo") + .withVersion("2.3.") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:hex/phoenix_html@2.13.3#priv/static/phoenix_html.js") + .build(), + PackageURLBuilders.hex() + .withName("phoenix_html") + .withVersion("2.13.3") + .withSubpath("priv/static/phoenix_html.js") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:hex/bar@1.2.3?repository_url=https://myrepo.example.com") + .build(), + PackageURLBuilders.hex() + .withName("bar") + .withVersion("1.2.3") + .withRepositoryUrl("https://myrepo.example.com") + .build()); + } + + @Test + void testHuggingface() throws MalformedPackageURLException { + // FIXME: Example missing required organization + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:huggingface/distilbert/distilbert-base-uncased@043235d6088ecd3dd5fb5ca3592b6913fd516027") + .build(), + PackageURLBuilders.huggingface() + .withOrganization("distilbert") + .withName("distilbert-base-uncased") + .withCommit("043235d6088ecd3dd5fb5ca3592b6913fd516027") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https://hub-ci.huggingface.co") + .build(), + PackageURLBuilders.huggingface() + .withOrganization("microsoft") + .withName("deberta-v3-base") + .withCommit("559062ad13d311b87b2c455e67dcd5f1c8f65111") + .withRepositoryUrl("https://hub-ci.huggingface.co") + .build()); + } + + @Test + void testJulia() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:julia/Dates@1.9.0?uuid=ade2ca70-3891-5945-98fb-dc099432e06a") + .build(), + PackageURLBuilders.julia() + .withName("Dates") + .withVersion("1.9.0") + .withUuid("ade2ca70-3891-5945-98fb-dc099432e06a") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:julia/Dates?uuid=ade2ca70-3891-5945-98fb-dc099432e06a") + .build(), + PackageURLBuilders.julia() + .withName("Dates") + .withUuid("ade2ca70-3891-5945-98fb-dc099432e06a") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:julia/RegisterQD@0.3.1?uuid=ac24ea0c-1830-11e9-18d4-81f172323054") + .build(), + PackageURLBuilders.julia() + .withName("RegisterQD") + .withVersion("0.3.1") + .withUuid("ac24ea0c-1830-11e9-18d4-81f172323054") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:julia/RegisterQD@0.3.1?uuid=ac24ea0c-1830-11e9-18d4-81f172323054&repository_url=https://github.com/HolyLab/HolyLabRegistry") + .build(), + PackageURLBuilders.julia() + .withName("RegisterQD") + .withVersion("0.3.1") + .withUuid("ac24ea0c-1830-11e9-18d4-81f172323054") + .withRepositoryUrl("https://github.com/HolyLab/HolyLabRegistry") + .build()); + } + + @Test + void testLuarocks() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:luarocks/luasocket@3.1.0-1").build(), + PackageURLBuilders.luarocks() + .withName("luasocket") + .withVersionRevision("3.1.0-1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:luarocks/hisham/luafilesystem@1.8.0-1") + .build(), + PackageURLBuilders.luarocks() + .withManifest("hisham") + .withName("luafilesystem") + .withVersionRevision("1.8.0-1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:luarocks/username/packagename@0.1.0-1?repository_url=https://example.com/private_rocks_server/") + .build(), + PackageURLBuilders.luarocks() + .withManifest("username") + .withName("packagename") + .withVersionRevision("0.1.0-1") + .withRepositoryUrl("https://example.com/private_rocks_server/") + .build()); + } + + @Test + void testMaven() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1") + .build(), + PackageURLBuilders.maven() + .withGroupId("org.apache.xmlgraphics") + .withArtifactId("batik-anim") + .withVersion("1.9.1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?type=pom") + .build(), + PackageURLBuilders.maven() + .withGroupId("org.apache.xmlgraphics") + .withArtifactId("batik-anim") + .withVersion("1.9.1") + .withType("pom") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources") + .build(), + PackageURLBuilders.maven() + .withGroupId("org.apache.xmlgraphics") + .withArtifactId("batik-anim") + .withVersion("1.9.1") + .withClassifier("sources") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?type=zip&classifier=dist") + .build(), + PackageURLBuilders.maven() + .withGroupId("org.apache.xmlgraphics") + .withArtifactId("batik-anim") + .withVersion("1.9.1") + .withType("zip") + .withClassifier("dist") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:maven/net.sf.jacob-projec/jacob@1.14.3?classifier=x86&type=dll") + .build(), + PackageURLBuilders.maven() + .withGroupId("net.sf.jacob-projec") + .withArtifactId("jacob") + .withVersion("1.14.3") + .withClassifier("x86") + .withType("dll") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:maven/net.sf.jacob-projec/jacob@1.14.3?classifier=x64&type=dll") + .build(), + PackageURLBuilders.maven() + .withGroupId("net.sf.jacob-projec") + .withArtifactId("jacob") + .withVersion("1.14.3") + .withClassifier("x64") + .withType("dll") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:maven/groovy/groovy@1.0?repository_url=https://maven.google.com") + .build(), + PackageURLBuilders.maven() + .withGroupId("groovy") + .withArtifactId("groovy") + .withVersion("1.0") + .withRepositoryUrl("https://maven.google.com") + .build()); + } + + @Test + void testMlflow() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:mlflow/creditfraud@3?repository_url=https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace") + .build(), + PackageURLBuilders.mlflow() + .withName("creditfraud") + .withVersion("3") + .withRepositoryUrl( + "https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a&repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow") + .build(), + PackageURLBuilders.mlflow() + .withName("trafficsigns") + .withVersion("10") + .withModelUuid("36233173b22f4c89b451f1228d700d49") + .withRunId("410a3121-2709-4f88-98dd-dba0ef056b0a") + .withRepositoryUrl("https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow") + .build()); + } + + @Test + void testNpm() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:npm/foobar@12.3.1").build(), + PackageURLBuilders.npm() + .withName("foobar") + .withVersion("12.3.1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:npm/%40angular/animation@12.3.1") + .build(), + PackageURLBuilders.npm() + .withScope("@angular") + .withName("animation") + .withVersion("12.3.1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:npm/mypackage@12.4.5?vcs_url=git://host.com/path/to/repo.git%404345abcd34343") + .build(), + PackageURLBuilders.npm() + .withName("mypackage") + .withVersion("12.4.5") + .withVcsUrl("git://host.com/path/to/repo.git@4345abcd34343") + .build()); + } + + @Test + void testNuget() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:nuget/EnterpriseLibrary.Common@6.0.1304") + .build(), + PackageURLBuilders.nuget() + .withName("EnterpriseLibrary.Common") + .withVersion("6.0.1304") + .build()); + } + + @Test + void testOci() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:oci/debian@sha256%3A244fd47e07d10?repository_url=docker.io/library/debian&arch=amd64&tag=latest") + .build(), + PackageURLBuilders.oci() + .withName("debian") + .withVersion("sha256:244fd47e07d10") + .withRepositoryUrl("docker.io/library/debian") + .withArch("amd64") + .withTag("latest") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:oci/debian@sha256%3A244fd47e07d10?repository_url=ghcr.io/debian&tag=bullseye") + .build(), + PackageURLBuilders.oci() + .withName("debian") + .withVersion("sha256:244fd47e07d10") + .withRepositoryUrl("ghcr.io/debian") + .withTag("bullseye") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:oci/static@sha256%3A244fd47e07d10?repository_url=gcr.io/distroless/static&tag=latest") + .build(), + PackageURLBuilders.oci() + .withName("static") + .withVersion("sha256:244fd47e07d10") + .withRepositoryUrl("gcr.io/distroless/static") + .withTag("latest") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:oci/hello-wasm@sha256:244fd47e07d10?tag=v1") + .build(), + PackageURLBuilders.oci() + .withName("hello-wasm") + .withVersion("sha256:244fd47e07d10") + .withTag("v1") + .build()); + } + + @Test + void testPub() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:pub/characters@1.2.0").build(), + PackageURLBuilders.pub() + .withName("characters") + .withVersion("1.2.0") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:pub/flutter@0.0.0").build(), + PackageURLBuilders.pub() + .withName("flutter") + .withVersion("0.0.0") + .build()); + } + + @Test + void testPypi() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:pypi/django@1.11.1").build(), + PackageURLBuilders.pypi() + .withName("django") + .withVersion("1.11.1") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:pypi/django@1.11.1?file_name=Django-1.11.1.tar.gz") + .build(), + PackageURLBuilders.pypi() + .withName("django") + .withVersion("1.11.1") + .withFileName("Django-1.11.1.tar.gz") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:pypi/django@1.11.1?file_name=Django-1.11.1-py2.py3-none-any.whl") + .build(), + PackageURLBuilders.pypi() + .withName("django") + .withVersion("1.11.1") + .withFileName("Django-1.11.1-py2.py3-none-any.whl") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:pypi/django-allauth@12.23").build(), + PackageURLBuilders.pypi() + .withName("django-allauth") + .withVersion("12.23") + .build()); + } + + @Test + void testQpkg() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:qpkg/blackberry/com.qnx.sdp@7.0.0.SGA201702151847") + .build(), + PackageURLBuilders.qpkg() + .withVendor("blackberry") + .withName("com.qnx.sdp") + .withVersion("7.0.0.SGA201702151847") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:qpkg/blackberry/com.qnx.qnx710.foo.bar.qux@0.0.4.01449T202205040833L") + .build(), + PackageURLBuilders.qpkg() + .withVendor("blackberry") + .withName("com.qnx.qnx710.foo.bar.qux") + .withVersion("0.0.4.01449T202205040833L") + .build()); + } + + @Test + void testRpm() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:rpm/fedora/curl@7.50.3-1.fc25?arch=i386&distro=fedora-25") + .build(), + PackageURLBuilders.rpm() + .withVendor("fedora") + .withName("curl") + .withVersionRelease("7.50.3-1.fc25") + .withArch("i386") + .withDistro("fedora-25") + .build()); + // FIXME: Example missing required vendor + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:rpm/fedora/centerim@4.22.10-1.el6?arch=i686&epoch=1&distro=fedora-25") + .build(), + PackageURLBuilders.rpm() + .withVendor("fedora") + .withName("centerim") + .withVersionRelease("4.22.10-1.el6") + .withArch("i686") + .withEpoch("1") + .withDistro("fedora-25") + .build()); + } + + @Test + void testSwid() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:swid/Acme/example.com%2FEnterprise+Server@1.0.0?tag_id=75b8c285-fa7b-485b-b199-4745e3004d0d") + .build(), + PackageURLBuilders.swid() + .withSoftwareCreator("Acme") + .withSoftwareIdentityName("example.com/Enterprise+Server") + .withSoftwareIdentityVersion("1.0.0") + .withTagId("75b8c285-fa7b-485b-b199-4745e3004d0d") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:swid/Fedora@29?tag_id=org.fedoraproject.Fedora-29") + .build(), + PackageURLBuilders.swid() + .withSoftwareIdentityName("Fedora") + .withSoftwareIdentityVersion("29") + .withTagId("org.fedoraproject.Fedora-29") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL( + "pkg:swid/Adobe%2BSystems%2BIncorporated/Adobe%2BInDesign@CC?tag_id=CreativeCloud-CS6-Win-GM-MUL") + .build(), + PackageURLBuilders.swid() + .withSoftwareCreator("Adobe+Systems+Incorporated") + .withSoftwareIdentityName("Adobe+InDesign") + .withSoftwareIdentityVersion("CC") + .withTagId("CreativeCloud-CS6-Win-GM-MUL") + .build()); + } + + @Test + void testSwift() throws MalformedPackageURLException { + assertEquals( + PackageURLBuilder.aPackageURL("pkg:swift/github.com/Alamofire/Alamofire@5.4.3") + .build(), + PackageURLBuilders.swift() + .withNamespace("github.com/Alamofire") + .withRepositoryName("Alamofire") + .withPackageVersion("5.4.3") + .build()); + assertEquals( + PackageURLBuilder.aPackageURL("pkg:swift/github.com/RxSwiftCommunity/RxFlow@2.12.4") + .build(), + PackageURLBuilders.swift() + .withNamespace("github.com/RxSwiftCommunity") + .withRepositoryName("RxFlow") + .withPackageVersion("2.12.4") + .build()); + } +}