Skip to content

Commit

Permalink
Mostly working implementation
Browse files Browse the repository at this point in the history
Signed-off-by: nscuro <[email protected]>
  • Loading branch information
nscuro committed Aug 8, 2023
1 parent 296e582 commit ad6f899
Show file tree
Hide file tree
Showing 15 changed files with 837 additions and 73 deletions.
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ target/
!**/src/test/**/target/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea/
*.iws
*.iml
*.ipr
Expand Down
21 changes: 21 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,30 @@
<!-- Dependency Versions -->
<lib.assertj.version>3.24.2</lib.assertj.version>
<lib.junit-jupiter.version>5.9.3</lib.junit-jupiter.version>
<lib.maven-artifact.version>3.9.3</lib.maven-artifact.version>
<lib.packageurl-java.version>1.4.1</lib.packageurl-java.version>
<lib.semver4j.version>3.1.0</lib.semver4j.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>${lib.maven-artifact.version}</version>
</dependency>

<dependency>
<groupId>com.github.package-url</groupId>
<artifactId>packageurl-java</artifactId>
<version>${lib.packageurl-java.version}</version>
</dependency>

<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>semver4j</artifactId>
<version>${lib.semver4j.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
179 changes: 179 additions & 0 deletions src/main/java/io/github/nscuro/versatile/ComponentVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* This file is part of Dependency-Track.
*
* 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
*
* http://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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package io.github.nscuro.versatile;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* <p>
* Simple object to track the parts of a version number. The parts are contained
* in a List such that version 1.2.3 will be stored as: <code>versionParts[0] = 1;
* versionParts[1] = 2;
* versionParts[2] = 3;
* </code></p>
* <p>
* Note, the parser contained in this class expects the version numbers to be
* separated by periods. If a different separator is used the parser will likely
* fail.</p>
*
* @author Jeremy Long
*
* Ported from DependencyVersion in Dependency-Check v5.2.1
*/
public class ComponentVersion implements Iterable<String>, Comparable<ComponentVersion> {

/**
* A list of the version parts.
*/
private List<String> versionParts;

/**
* Constructor for a empty DependencyVersion.
*/
public ComponentVersion() {
}

/**
* Constructor for a DependencyVersion that will parse a version string.
* <b>Note</b>, this should only be used when the version passed in is
* already known to be a well formatted version number. Otherwise,
* DependencyVersionUtil.parseVersion() should be used instead.
*
* @param version the well formatted version number to parse
*/
public ComponentVersion(String version) {
parseVersion(version);
}

/**
* Parses a version string into its sub parts: major, minor, revision,
* build, etc. <b>Note</b>, this should only be used to parse something that
* is already known to be a version number.
*
* @param version the version string to parse
*/
public final void parseVersion(String version) {
versionParts = new ArrayList<>();
if (version != null) {
// https://github.com/DependencyTrack/dependency-track/issues/1374
// handle deb versions
String lcVersion = version.toLowerCase();
final Pattern debrx = Pattern.compile("^([0-9]+:)?(.*)(-[^-]+ubuntu[^-]+)$");
final Matcher debmatcher = debrx.matcher(lcVersion);
if (debmatcher.matches()) {
lcVersion = debmatcher.group(2);
}

final Pattern rx = Pattern.compile("(\\d+[a-z]{1,3}$|[a-z]{1,3}[_-]?\\d+|\\d+|(rc|release|snapshot|beta|alpha)$)",
Pattern.CASE_INSENSITIVE);
final Matcher matcher = rx.matcher(lcVersion);
while (matcher.find()) {
versionParts.add(matcher.group());
}
if (versionParts.isEmpty()) {
versionParts.add(version);
}
}
}

/**
* Get the value of versionParts.
*
* @return the value of versionParts
*/
public List<String> getVersionParts() {
return versionParts;
}

/**
* Set the value of versionParts.
*
* @param versionParts new value of versionParts
*/
public void setVersionParts(List<String> versionParts) {
this.versionParts = versionParts;
}

/**
* Retrieves an iterator for the version parts.
*
* @return an iterator for the version parts
*/
@Override
public Iterator<String> iterator() {
return versionParts.iterator();
}

/**
* Reconstructs the version string from the split version parts.
*
* @return a string representing the version.
*/
@Override
public String toString() {
return String.join(".", versionParts);
}

@Override
public int compareTo(ComponentVersion version) {
if (version == null) {
return 1;
}
final List<String> left = this.getVersionParts();
final List<String> right = version.getVersionParts();
final int max = left.size() < right.size() ? left.size() : right.size();

for (int i = 0; i < max; i++) {
final String lStr = left.get(i);
final String rStr = right.get(i);
if (lStr.equals(rStr)) {
continue;
}
try {
final int l = Integer.parseInt(lStr);
final int r = Integer.parseInt(rStr);
if (l < r) {
return -1;
} else if (l > r) {
return 1;
}
} catch (NumberFormatException ex) {
final int comp = left.get(i).compareTo(right.get(i));
if (comp < 0) {
return -1;
} else if (comp > 0) {
return 1;
}
}
}
// Modified from original by Steve Springett
// Account for comparisons where one version may be 1.0.0 and another may be 1.0.0.0.
if (left.size() == max && right.size() == left.size()+1 && right.get(right.size()-1).equals("0")) {
return 0;
} else if (right.size() == max && left.size() == right.size()+1 && left.get(left.size()-1).equals("0")) {
return 0;
} else {
return Integer.compare(left.size(), right.size());
}
}
}
91 changes: 85 additions & 6 deletions src/main/java/io/github/nscuro/versatile/Constraint.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,36 @@
package io.github.nscuro.versatile;

import io.github.nscuro.versatile.version.Version;

import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;

public record Constraint(Comparator comparator, String version) {
public class Constraint implements Comparable<Constraint> {

private final VersioningScheme scheme;
private final Comparator comparator;
private final Version version;

Constraint(final VersioningScheme scheme, final Comparator comparator, final Version version) {
if (scheme == null) {
throw new VersException("scheme must not be null");
}
if (comparator == null) {
throw new VersException("comparator must not be null");
}
if (comparator == Comparator.WILDCARD && version != null) {
throw new VersException("comparator %s is not allowed with version".formatted(comparator));
} else if (comparator != Comparator.WILDCARD && version == null) {
throw new VersException("comparator %s is not allowed without version".formatted(comparator));
}
this.scheme = scheme;
this.comparator = comparator;
this.version = version;
}

public static Constraint parse(final String constraintStr) {
static Constraint parse(final VersioningScheme scheme, final String constraintStr) {
final Comparator comparator;
if (constraintStr.startsWith("<=")) {
comparator = Comparator.LESS_THAN_OR_EQUAL;
Expand All @@ -22,12 +46,35 @@ public static Constraint parse(final String constraintStr) {
comparator = Comparator.EQUAL;
}

final String version = constraintStr.replaceFirst("^" + Pattern.quote(comparator.operator()), "");
if (version.isBlank()) {
throw new VersException();
final String versionStr = constraintStr.replaceFirst("^" + Pattern.quote(comparator.operator()), "").trim();
if (versionStr.isBlank()) {
throw new VersException("comparator %s is not allowed without version".formatted(comparator));
}

return new Constraint(comparator, maybeUrlDecode(version));
final Version version = Version.forScheme(scheme, maybeUrlDecode(versionStr));

return new Constraint(scheme, comparator, version);
}

boolean matches(final Version version) {
if (version == null) {
throw new VersException("version must not be null");
}
if (this.scheme != version.scheme()) {
throw new VersException("cannot evaluate constraint of scheme %s against version of scheme %s"
.formatted(this.scheme, version.scheme()));
}

final int comparisonResult = this.version.compareTo(version);
return switch (comparator) {
case LESS_THAN -> comparisonResult < 0;
case LESS_THAN_OR_EQUAL -> comparisonResult <= 0;
case GREATER_THAN_OR_EQUAL -> comparisonResult >= 0;
case GREATER_THAN -> comparisonResult > 0;
case EQUAL -> comparisonResult == 0;
case NOT_EQUAL -> comparisonResult != 0;
case WILDCARD -> true;
};
}

private static String maybeUrlDecode(final String version) {
Expand All @@ -38,4 +85,36 @@ private static String maybeUrlDecode(final String version) {
return version;
}

@Override
public int compareTo(final Constraint other) {
return this.version.compareTo(other.version);
}

public VersioningScheme scheme() {
return scheme;
}

public Comparator comparator() {
return comparator;
}

public Version version() {
return version;
}

@Override
public String toString() {
if (comparator == Comparator.WILDCARD) {
// Wildcard cannot have a version.
return Comparator.WILDCARD.operator();
}

if (comparator == Comparator.EQUAL) {
// Operator is omitted for equality.
return URLEncoder.encode(version().toString(), StandardCharsets.UTF_8);
}

return comparator.operator() + URLEncoder.encode(version.toString(), StandardCharsets.UTF_8);
}

}
40 changes: 40 additions & 0 deletions src/main/java/io/github/nscuro/versatile/PairwiseIterator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.github.nscuro.versatile;

import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

class PairwiseIterator<T> implements Iterator<Map.Entry<T, T>> {

private final Iterator<T> delegate;
private T current;
private T next;

PairwiseIterator(final Iterable<T> iterable) {
this.delegate = iterable.iterator();
}

@Override
public boolean hasNext() {
return delegate.hasNext();
}

@Override
public Map.Entry<T, T> next() {
if (!delegate.hasNext()) {
throw new NoSuchElementException();
}

if (current == null) {
current = delegate.next();
}
if (delegate.hasNext()) {
next = delegate.next();
}

final var item = Map.entry(current, next);
current = next;
return item;
}

}
Loading

0 comments on commit ad6f899

Please sign in to comment.