Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/main/java/org/tudo/sse/model/ArtifactIdent.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tudo.sse.semver.SemanticVersionNumber;
import org.tudo.sse.semver.SemanticVersionParsingException;
import org.tudo.sse.utils.MavenCentralRepository;


Expand Down Expand Up @@ -47,6 +49,11 @@ public class ArtifactIdent {
*/
private String customRepository;

private boolean didParseSemVer = false;
private boolean semVerValid = false;
private SemanticVersionNumber semVer = null;
private SemanticVersionParsingException semVerException = null;

private static final Logger log = LoggerFactory.getLogger(ArtifactIdent.class);

/**
Expand Down Expand Up @@ -135,6 +142,9 @@ public String getVersion() {
public void setVersion(String version) {
this.version = version;
this.GAV = groupID + ":" + artifactID + ":" + version;

this.semVer = null;
this.didParseSemVer = false;
}

/**
Expand Down Expand Up @@ -209,6 +219,47 @@ public URI getMavenCentralXMLUri() {
}
}

/**
* Parses this identifier's version according to the semantic versioning 2.0.0 standard. Returns the parsed number,
* or throws an exception if the version does not comply to the required syntax. Note that the result of this method
* invocation is cached, meaning that future calls will return the same parsed semantic version or throw the same
* exception - parsing is only ever attempted once.
*
* @return Semantic version number if parsing was successful.
* @throws SemanticVersionParsingException If version does not adhere to required syntax
*/
public SemanticVersionNumber getSemanticVersion() throws SemanticVersionParsingException {

if(this.didParseSemVer && this.semVerValid) return this.semVer;
if(this.didParseSemVer) throw this.semVerException;

try {
this.didParseSemVer = true;
this.semVer = SemanticVersionNumber.parse(this.version);
this.semVerValid = true;
} catch (SemanticVersionParsingException svpx){
this.semVerException = svpx;
this.semVerValid = false;
throw this.semVerException;
}

return this.semVer;
}

/**
* Checks whether this identifier's version is valid according to the semantic versioning 2.0.0 standard. The results
* of this method invocation are cached so that future calls do not have to parse the version string again.
* @return True if this identifier has a valid semantic version, false otherwise
*/
public boolean hasValidSemanticVersion() {
try {
this.getSemanticVersion();
return true;
} catch(SemanticVersionParsingException svpx){
return false;
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
107 changes: 107 additions & 0 deletions src/main/java/org/tudo/sse/semver/MavenVersionRangeParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.tudo.sse.semver;

import java.util.ArrayList;
import java.util.List;

/**
* Parser for semantic version ranges as specified by the Maven build system. See <a href="https://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification">the specification</a>
* for details on the supported range types and syntaxes.
*
* @author Johannes Düsing
*/
public class MavenVersionRangeParser {

private static final char START_INCLUSIVE = '[';
private static final char END_INCLUSIVE = ']';

private static final char START_EXCLUSIVE = '(';
private static final char END_EXCLUSIVE = ')';

private static final char RANGE_SEPARATOR = ',';

private MavenVersionRangeParser(){}

/**
* Parses the given textual representation of a Maven version range into the corresponding version range object.
* @param value Value to parse
* @return Parsed version range, if successful
* @throws SemanticVersionParsingException If either the range syntax or the version number syntax were invalid.
*/
public static SemanticVersionRange parseRange(String value) throws SemanticVersionParsingException {
final char[] chars = value.toCharArray();
final StringBuilder currentValue = new StringBuilder();

boolean inRange = false;
boolean sawSeparator = false;
boolean lowerInclusive = false;
SemanticVersionNumber lowerBound = null;

List<SemanticVersionRange> ranges = new ArrayList<>();

for(int i = 0; i < chars.length; i++){
char currentChar = chars[i];

// Ignore whitespaces
if(Character.isWhitespace(currentChar)){
continue;
}

if(!inRange){
if(currentChar == START_INCLUSIVE){
inRange = true;
lowerInclusive = true;
} else if(currentChar == START_EXCLUSIVE){
inRange = true;
lowerInclusive = false;
} else if(currentChar != RANGE_SEPARATOR){
throw new SemanticVersionParsingException(value, "Expected begin of new range or range separator but got " + currentChar, i);
}
} else {
if(currentChar == RANGE_SEPARATOR){
String lowerBoundStr = currentValue.toString();
currentValue.setLength(0);

sawSeparator = true;

if(lowerBoundStr.isBlank()) lowerBound = null;
else lowerBound = SemanticVersionNumber.parse(lowerBoundStr);
} else if(currentChar == END_INCLUSIVE || currentChar == END_EXCLUSIVE){
String upperBoundStr = currentValue.toString();
currentValue.setLength(0);

SemanticVersionNumber upperBound = null;
if(!upperBoundStr.isBlank()) upperBound = SemanticVersionNumber.parse(upperBoundStr);
boolean upperInclusive = (currentChar == END_INCLUSIVE);

// If we do not see a separator (e.g. range '[1.0]'), we have a hard requirement for one specific
// version. This means lower and upper bound are equal, and both bounds must be inclusive
if(!sawSeparator){
lowerBound = upperBound;

if(!lowerInclusive || !upperInclusive){
throw new SemanticVersionParsingException(value, "Hard requirements for one specific version must have inclusive range delimiters.", i);
}
}

SimpleSemanticVersionRange range = new SimpleSemanticVersionRange(lowerBound, lowerInclusive, upperBound, upperInclusive);
ranges.add(range);

inRange = false;
sawSeparator = false;
} else {
currentValue.append(currentChar);
}
}
}

if(inRange)
throw new SemanticVersionParsingException(value, "Input ended with range not being closed");

if(ranges.isEmpty())
throw new SemanticVersionParsingException(value, "No ranges found in expression: " + value);
else if(ranges.size() == 1)
return ranges.get(0);
else
return new MultiPartSemanticVersionRange(ranges);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.tudo.sse.semver;

import java.util.List;

/**
* Implementation of composite semantic version ranges, which consist of multiple "smaller" range specifications. Those
* ranges are concatenated in a way that this composite range is the union of all partial ranges - if a given version
* number is contained in at least one of the partial ranges, it is also contained in this range.
*
* @author Johannes Düsing
*/
public class MultiPartSemanticVersionRange implements SemanticVersionRange {

private final List<SemanticVersionRange> partialRanges;

/**
* Creates a new range with the given list of partial ranges. Containment of any given version number is checked
* in the same order in which partial ranges a provided here.
*
* @param ranges List of partial ranges
*/
public MultiPartSemanticVersionRange(List<SemanticVersionRange> ranges){
this.partialRanges = ranges;
}

@Override
public boolean contains(SemanticVersionNumber number) {
for(SemanticVersionRange range : partialRanges){
if(range.contains(number)) return true;
}
return false;
}

@Override
public String toString(){
StringBuilder sb = new StringBuilder();

for(int i = 0; i < partialRanges.size(); i++){
SemanticVersionRange range = partialRanges.get(i);
sb.append(range.toString());
if(i != partialRanges.size()-1) sb.append(".");
}

return sb.toString();
}
}
Loading
Loading