initializr/initializr-metadata/src/main/java/io/spring/initializr/metadata/Dependency.java
2022-11-10 13:25:33 +01:00

625 lines
16 KiB
Java

/*
* Copyright 2012-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.metadata;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.spring.initializr.generator.version.InvalidVersionException;
import io.spring.initializr.generator.version.Version;
import io.spring.initializr.generator.version.VersionParser;
import io.spring.initializr.generator.version.VersionRange;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Meta-data for a dependency. Each dependency has a primary identifier and an arbitrary
* number of {@code aliases}.
*
* @author Stephane Nicoll
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Dependency extends MetadataElement implements Describable {
/**
* Compile Scope.
*/
public static final String SCOPE_COMPILE = "compile";
/**
* Compile Only Scope.
*/
public static final String SCOPE_COMPILE_ONLY = "compileOnly";
/**
* Annotation Processor Scope.
*/
public static final String SCOPE_ANNOTATION_PROCESSOR = "annotationProcessor";
/**
* Runtime Scope.
*/
public static final String SCOPE_RUNTIME = "runtime";
/**
* Provided Scope.
*/
public static final String SCOPE_PROVIDED = "provided";
/**
* Test Scope.
*/
public static final String SCOPE_TEST = "test";
/**
* All scope types.
*/
public static final List<String> SCOPE_ALL = List.of(SCOPE_COMPILE, SCOPE_RUNTIME, SCOPE_COMPILE_ONLY,
SCOPE_ANNOTATION_PROCESSOR, SCOPE_PROVIDED, SCOPE_TEST);
private List<String> aliases = new ArrayList<>();
private List<String> facets = new ArrayList<>();
private String groupId;
private String artifactId;
private String version;
private String classifier;
private String type;
private List<Mapping> mappings = new ArrayList<>();
private String scope = SCOPE_COMPILE;
private String description;
private String compatibilityRange;
@JsonIgnore
private String versionRequirement;
@JsonIgnore
private VersionRange range;
private String bom;
private String repository;
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
private int weight;
/**
* Specify if the dependency represents a "starter", i.e. the sole presence of that
* dependency is enough to bootstrap the context.
*/
private boolean starter = true;
private List<String> keywords = new ArrayList<>();
private List<Link> links = new ArrayList<>();
public Dependency() {
}
public Dependency(Dependency dependency) {
super(dependency);
this.aliases.addAll(dependency.aliases);
this.facets.addAll(dependency.facets);
this.groupId = dependency.groupId;
this.artifactId = dependency.artifactId;
this.version = dependency.version;
this.classifier = dependency.classifier;
this.type = dependency.type;
this.mappings.addAll(dependency.mappings);
this.scope = dependency.scope;
this.description = dependency.description;
this.compatibilityRange = dependency.compatibilityRange;
this.versionRequirement = dependency.versionRequirement;
this.range = dependency.range;
this.bom = dependency.bom;
this.repository = dependency.repository;
this.weight = dependency.weight;
this.starter = dependency.starter;
this.keywords.addAll(dependency.keywords);
this.links.addAll(dependency.links);
}
public void setScope(String scope) {
if (!SCOPE_ALL.contains(scope)) {
throw new InvalidInitializrMetadataException("Invalid scope " + scope + " must be one of " + SCOPE_ALL);
}
this.scope = scope;
}
public void setCompatibilityRange(String compatibilityRange) {
this.compatibilityRange = (StringUtils.hasText(compatibilityRange) ? compatibilityRange.trim() : null);
}
/**
* Returns if the dependency has its coordinates set, i.e. {@code groupId} and
* {@code artifactId}.
* @return if the dependency has coordinates
*/
private boolean hasCoordinates() {
return this.groupId != null && this.artifactId != null;
}
/**
* Validate the dependency and complete its state based on the available information.
*/
public void resolve() {
if (getId() == null) {
if (!hasCoordinates()) {
throw new InvalidInitializrMetadataException(
"Invalid dependency, should have at least an id or a groupId/artifactId pair.");
}
generateId();
}
else if (!hasCoordinates()) {
// Let"s build the coordinates from the id
StringTokenizer st = new StringTokenizer(getId(), ":");
if (st.countTokens() == 1) { // assume spring-boot-starter
asSpringBootStarter(getId());
}
else if (st.countTokens() == 2 || st.countTokens() == 3) {
this.groupId = st.nextToken();
this.artifactId = st.nextToken();
if (st.hasMoreTokens()) {
this.version = st.nextToken();
}
}
else {
throw new InvalidInitializrMetadataException(
"Invalid dependency, id should have the form groupId:artifactId[:version] but got " + getId());
}
}
this.links.forEach(Link::resolve);
updateCompatibilityRange(VersionParser.DEFAULT);
}
private Dependency asSpringBootStarter(String name) {
this.groupId = "org.springframework.boot";
this.artifactId = (StringUtils.hasText(name) ? "spring-boot-starter-" + name : "spring-boot-starter");
if (StringUtils.hasText(name)) {
setId(name);
}
return this;
}
public void updateCompatibilityRange(VersionParser versionParser) {
if (this.compatibilityRange != null) {
try {
this.range = versionParser.parseRange(this.compatibilityRange);
this.compatibilityRange = this.range.toRangeString();
this.versionRequirement = this.range.toString();
}
catch (InvalidVersionException ex) {
throw new InvalidInitializrMetadataException("Invalid compatibility range '" + this.compatibilityRange
+ " for " + "dependency with id '" + getId() + "'", ex);
}
}
this.mappings.forEach((it) -> {
try {
Assert.notNull(it.compatibilityRange,
"Invalid mapping for " + this + ": compatibility range is mandatory");
it.range = versionParser.parseRange(it.compatibilityRange);
}
catch (InvalidVersionException ex) {
throw new InvalidInitializrMetadataException(
"Invalid compatibility range " + it.compatibilityRange + " for " + this, ex);
}
});
}
/**
* Resolve this instance according to the specified Spring Boot {@link Version}.
* Return a {@link Dependency} instance that has its state resolved against the
* specified version.
* @param bootVersion the Spring Boot version
* @return this instance
*/
public Dependency resolve(Version bootVersion) {
for (Mapping mapping : this.mappings) {
if (mapping.range.match(bootVersion)) {
Dependency dependency = new Dependency(this);
dependency.groupId = (mapping.groupId != null) ? mapping.groupId : this.groupId;
dependency.artifactId = (mapping.artifactId != null) ? mapping.artifactId : this.artifactId;
dependency.version = (mapping.version != null) ? mapping.version : this.version;
dependency.starter = (mapping.starter != null) ? mapping.starter : this.starter;
dependency.bom = (mapping.bom != null) ? mapping.bom : this.bom;
dependency.repository = (mapping.repository != null) ? mapping.repository : this.repository;
dependency.versionRequirement = mapping.range.toString();
dependency.mappings = null;
return dependency;
}
}
return this;
}
/**
* Specify if this dependency is available for the specified Spring Boot version.
* @param version the version the check
* @return of the version matches
*/
public boolean match(Version version) {
if (this.range != null) {
return this.range.match(version);
}
return true;
}
/**
* Generate an id using the groupId and artifactId.
* @return the generated ID
*/
public String generateId() {
if (this.groupId == null || this.artifactId == null) {
throw new IllegalArgumentException(
"Could not generate id for " + this + ": at least groupId and artifactId must be set.");
}
setId(this.groupId + ":" + this.artifactId);
return getId();
}
public List<String> getAliases() {
return this.aliases;
}
public void setAliases(List<String> aliases) {
this.aliases = aliases;
}
public List<String> getFacets() {
return this.facets;
}
public void setFacets(List<String> facets) {
this.facets = facets;
}
public String getGroupId() {
return this.groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getArtifactId() {
return this.artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
/**
* Return the default version, can be {@code null} to indicate that the version is
* managed by the project and does not need to be specified.
* @return the default version or {@code null}
*/
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
/**
* Return the classifier, can be {@code null} to indicate that no classifier is
* available.
* @return the classifier or {@code null}
*/
public String getClassifier() {
return this.classifier;
}
public void setClassifier(String classifier) {
this.classifier = classifier;
}
/**
* Return the type, can be {@code null} to indicate that the default type should be
* used (i.e. {@code jar}).
* @return the type or {@code null}
*/
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
/**
* Return the dependency mapping if an attribute of the dependency differs according
* to the Spring Boot version. If no mapping matches, default attributes are used.
* @return the dependency mappings
*/
public List<Mapping> getMappings() {
return this.mappings;
}
public void setMappings(List<Mapping> mappings) {
this.mappings = mappings;
}
@Override
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public String getVersionRequirement() {
return this.versionRequirement;
}
public void setVersionRequirement(String versionRequirement) {
this.versionRequirement = versionRequirement;
}
public VersionRange getRange() {
return this.range;
}
public void setRange(VersionRange range) {
this.range = range;
}
public String getBom() {
return this.bom;
}
public void setBom(String bom) {
this.bom = bom;
}
public String getRepository() {
return this.repository;
}
public void setRepository(String repository) {
this.repository = repository;
}
public int getWeight() {
return this.weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public boolean isStarter() {
return this.starter;
}
public void setStarter(boolean starter) {
this.starter = starter;
}
public List<String> getKeywords() {
return this.keywords;
}
public void setKeywords(List<String> keywords) {
this.keywords = keywords;
}
public List<Link> getLinks() {
return this.links;
}
public void setLinks(List<Link> links) {
this.links = links;
}
public String getScope() {
return this.scope;
}
public String getCompatibilityRange() {
return this.compatibilityRange;
}
@Override
public String toString() {
return "Dependency{" + "id='" + getId() + '\'' + ", groupId='" + this.groupId + '\'' + ", artifactId='"
+ this.artifactId + '\'' + ", version='" + this.version + '\'' + '}';
}
public static Dependency create(String groupId, String artifactId, String version, String scope) {
Dependency dependency = withId(null, groupId, artifactId, version);
dependency.setScope(scope);
return dependency;
}
public static Dependency createSpringBootStarter(String name) {
return createSpringBootStarter(name, null);
}
public static Dependency createSpringBootStarter(String name, String scope) {
Dependency dependency = new Dependency();
dependency.asSpringBootStarter(name);
if (StringUtils.hasText(scope)) {
dependency.setScope(scope);
}
return dependency;
}
public static Dependency withId(String id, String groupId, String artifactId, String version, String scope) {
Dependency dependency = new Dependency();
dependency.setId(id);
dependency.groupId = groupId;
dependency.artifactId = artifactId;
dependency.version = version;
dependency.scope = (scope != null) ? scope : SCOPE_COMPILE;
return dependency;
}
public static Dependency withId(String id, String groupId, String artifactId, String version) {
return withId(id, groupId, artifactId, version, null);
}
public static Dependency withId(String id, String groupId, String artifactId) {
return withId(id, groupId, artifactId, null);
}
public static Dependency withId(String id, String scope) {
Dependency dependency = withId(id, null, null);
dependency.setScope(scope);
return dependency;
}
public static Dependency withId(String id) {
return withId(id, SCOPE_COMPILE);
}
/**
* Map several attribute of the dependency for a given compatibility range.
*/
public static class Mapping {
/**
* The compatibility range of this mapping.
*/
private String compatibilityRange;
/**
* The version to use for this mapping or {@code null} to use the default.
*/
private String groupId;
/**
* The groupId to use for this mapping or {@code null} to use the default.
*/
private String artifactId;
/**
* The artifactId to use for this mapping or {@code null} to use the default.
*/
private String version;
/**
* The starter setting to use for the mapping or {@code null} to use the default.
*/
private Boolean starter;
/**
* The extra Bill of Materials to use for this mapping or {@code null} to use the
* default.
*/
private String bom;
/**
* The extra repository to use for this mapping or {@code null} to use the
* default.
*/
private String repository;
@JsonIgnore
private VersionRange range;
public String getGroupId() {
return this.groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getArtifactId() {
return this.artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public Boolean getStarter() {
return this.starter;
}
public void setStarter(Boolean starter) {
this.starter = starter;
}
public String getBom() {
return this.bom;
}
public void setBom(String bom) {
this.bom = bom;
}
public String getRepository() {
return this.repository;
}
public void setRepository(String repository) {
this.repository = repository;
}
public VersionRange getRange() {
return this.range;
}
public String getCompatibilityRange() {
return this.compatibilityRange;
}
public void setCompatibilityRange(String compatibilityRange) {
this.compatibilityRange = compatibilityRange;
}
public static Mapping create(String range, String groupId, String artifactId, String version, Boolean starter,
String bom, String repository) {
Mapping mapping = new Mapping();
mapping.compatibilityRange = range;
mapping.groupId = groupId;
mapping.artifactId = artifactId;
mapping.version = version;
mapping.starter = starter;
mapping.bom = bom;
mapping.repository = repository;
return mapping;
}
}
}