Skip to content

Commit

Permalink
Move MavenVersion and MavenVersionRange from XABT.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpobst committed Feb 15, 2024
1 parent 9bc20be commit f938d56
Show file tree
Hide file tree
Showing 6 changed files with 484 additions and 4 deletions.
4 changes: 0 additions & 4 deletions src/Java.Interop.Tools.Maven/Extensions/MavenNetExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ namespace Java.Interop.Tools.Maven.Extensions;

static class MavenNetExtensions
{
public static bool HasValue ([NotNullWhen (true)] this string? str) => !string.IsNullOrEmpty (str);

public static string OrEmpty (this string? str) => str ?? string.Empty;

public static string GetInheritedProperty (this ResolvedDependency dependency, ResolvedProject project, Func<ResolvedDependency, string?> property)
{
// Check our <dependencyManagement> section
Expand Down
84 changes: 84 additions & 0 deletions src/Java.Interop.Tools.Maven/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Diagnostics.CodeAnalysis;

namespace Java.Interop.Tools.Maven.Extensions;

static class StringExtensions
{
/// <summary>
/// Shortcut for !string.IsNullOrWhiteSpace (s)
/// </summary>
public static bool HasValue ([NotNullWhen (true)] this string? s) => !string.IsNullOrWhiteSpace (s);

/// <summary>
/// Shortcut for s ?? string.Empty
/// </summary>
public static string OrEmpty (this string? str) => str ?? string.Empty;

/// <summary>
/// Removes the first subset of a delimited string. ("127.0.0.1" -> "0.0.1")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? ChompFirst (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.IndexOf (separator);

if (index < 0)
return string.Empty;

return s.Substring (index + 1);
}

/// <summary>
/// Removes the final subset of a delimited string. ("127.0.0.1" -> "127.0.0")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? ChompLast (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.LastIndexOf (separator);

if (index < 0)
return string.Empty;

return s.Substring (0, index);
}

/// <summary>
/// Returns the first subset of a delimited string. ("127.0.0.1" -> "127")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? FirstSubset (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.IndexOf (separator);

if (index < 0)
return s;

return s.Substring (0, index);
}

/// <summary>
/// Returns the final subset of a delimited string. ("127.0.0.1" -> "1")
/// </summary>
[return: NotNullIfNotNull (nameof (s))]
public static string? LastSubset (this string? s, char separator)
{
if (!s.HasValue ())
return s;

var index = s.LastIndexOf (separator);

if (index < 0)
return s;

return s.Substring (index + 1);
}
}
140 changes: 140 additions & 0 deletions src/Java.Interop.Tools.Maven/Models/MavenVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;
using Java.Interop.Tools.Maven.Extensions;

namespace Java.Interop.Tools.Maven.Models;

// https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN8855
public class MavenVersion : IComparable, IComparable<MavenVersion>, IEquatable<MavenVersion>
{
public string? Major { get; private set; }
public string? Minor { get; private set; }
public string? Patch { get; private set; }
public string RawVersion { get; private set; }
public bool IsValid { get; private set; } = true;

MavenVersion (string rawVersion) => RawVersion = rawVersion;

public static MavenVersion Parse (string version)
{
var mv = new MavenVersion (version);

if (!version.HasValue ()) {
mv.IsValid = false;
return mv;
}

// We're going to parse through this assuming it's a valid Maven version
mv.Major = version.FirstSubset ('.');
version = version.ChompFirst ('.');

if (!TryParsePart (mv.Major, out var _, out var _))
mv.IsValid = false;

if (!version.HasValue ())
return mv;

mv.Minor = version.FirstSubset ('.');
version = version.ChompFirst ('.');

if (!TryParsePart (mv.Minor, out var _, out var _))
mv.IsValid = false;

if (!version.HasValue ())
return mv;

mv.Patch = version.FirstSubset ('.');
version = version.ChompFirst ('.');

if (!TryParsePart (mv.Patch, out var _, out var _))
mv.IsValid = false;

if (!version.HasValue ())
return mv;

// If there's something left, this is a nonstandard Maven version and all bets are off
mv.IsValid = false;

return mv;
}

public int CompareTo (object obj)
{
return CompareTo (obj as MavenVersion);
}

public int CompareTo (MavenVersion? other)
{
if (other is null)
return 1;

// If either instance is nonstandard, Maven does a simple string compare
if (!IsValid || !other.IsValid)
return string.Compare (RawVersion, other.RawVersion);

var major_compare = ComparePart (Major ?? "0", other.Major ?? "0");

if (major_compare != 0)
return major_compare;

var minor_compare = ComparePart (Minor ?? "0", other.Minor ?? "0");

if (minor_compare != 0)
return minor_compare;

return ComparePart (Patch ?? "0", other.Patch ?? "0");
}

public bool Equals (MavenVersion other)
{
return CompareTo (other) == 0;
}

int ComparePart (string a, string b)
{
// Check if they're the same string
if (a == b)
return 0;

// Don't need to check the return because this shouldn't be called if IsValid = false
TryParsePart (a, out var a_version, out var a_qualifier);
TryParsePart (b, out var b_version, out var b_qualifier);

// If neither have a qualifier, treat them like numbers
if (a_qualifier is null && b_qualifier is null)
return a_version.CompareTo (b_version);

// If the numeric versions are different, just use those
if (a_version != b_version)
return a_version.CompareTo (b_version);

// Identical versions with different qualifier fields are compared by using basic string comparison.
if (a_qualifier is not null && b_qualifier is not null)
return a_qualifier.CompareTo (b_qualifier);

// All versions with a qualifier are older than the same version without a qualifier (release version).
if (a_qualifier is not null)
return -1;

return 1;
}

static bool TryParsePart (string part, out int version, out string? qualifier)
{
// These can look like:
// 1
// 1-anything
var version_string = part.FirstSubset ('-');
qualifier = null;

// The first piece must be a number
if (!int.TryParse (version_string, out version))
return false;

part = part.ChompFirst ('-');

if (part.HasValue ())
qualifier = part;

return true;
}
}
127 changes: 127 additions & 0 deletions src/Java.Interop.Tools.Maven/Models/MavenVersionRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.Collections.Generic;
using Java.Interop.Tools.Maven.Extensions;

namespace Java.Interop.Tools.Maven.Models;

public class MavenVersionRange
{
public string? MinVersion { get; private set; }
public string? MaxVersion { get; private set; }
public bool IsMinInclusive { get; private set; } = true;
public bool IsMaxInclusive { get; private set; }
public bool HasLowerBound { get; private set; }
public bool HasUpperBound { get; private set; }

// Adapted from https://github.com/Redth/MavenNet/blob/master/MavenNet/MavenVersionRange.cs
// Original version uses NuGetVersion, which doesn't cover all "valid" Maven version cases
public static IEnumerable<MavenVersionRange> Parse (string range)
{
if (!range.HasValue ())
yield break;

// Do a pass over the range string to parse out version groups
// eg: (1.0],(1.1,]
var in_group = false;
var current_group = string.Empty;

foreach (var c in range) {
if (c == '(' || c == '[') {
current_group += c;
in_group = true;
} else if (c == ')' || c == ']' || (!in_group && c == ',')) {
// Don't add the , separating groups
if (in_group)
current_group += c;

in_group = false;

if (current_group.HasValue ())
yield return ParseSingle (current_group);

current_group = string.Empty;
} else {
current_group += c;
}
}

if (!string.IsNullOrEmpty (current_group))
yield return ParseSingle (current_group);
}

static MavenVersionRange ParseSingle (string range)
{
var mv = new MavenVersionRange ();

// Check for opening ( or [
if (range [0] == '(') {
mv.IsMinInclusive = false;
range = range.Substring (1);
} else if (range [0] == '[') {
range = range.Substring (1);
}

var last = range.Length - 1;

// Check for closing ) or ]
if (range [last] == ')') {
mv.IsMaxInclusive = false;
range = range.Substring (0, last);
} else if (range [last] == ']') {
mv.IsMaxInclusive = true;
range = range.Substring (0, last);
}

// Look for a single value
if (!range.Contains (",")) {
mv.MinVersion = range;
mv.HasLowerBound = true;

// Special case [1.0]
if (mv.IsMinInclusive && mv.IsMaxInclusive) {
mv.MaxVersion = range;
mv.HasUpperBound = true;
}

return mv;
}

// Split the 2 values (note either can be empty)
var lower = range.FirstSubset (',').Trim ();
var upper = range.LastSubset (',').Trim ();

if (lower.HasValue ()) {
mv.MinVersion = lower;
mv.HasLowerBound = true;
}

if (upper.HasValue ()) {
mv.MaxVersion = upper;
mv.HasUpperBound = true;
}

return mv;
}

public bool ContainsVersion (MavenVersion version)
{
if (HasLowerBound) {
var min_version = MavenVersion.Parse (MinVersion!);

if (IsMinInclusive && version.CompareTo (min_version) < 0)
return false;
else if (!IsMinInclusive && version.CompareTo (min_version) <= 0)
return false;
}

if (HasUpperBound) {
var max_version = MavenVersion.Parse (MaxVersion!);

if (IsMaxInclusive && version.CompareTo (max_version) > 0)
return false;
else if (!IsMaxInclusive && version.CompareTo (max_version) >= 0)
return false;
}

return true;
}
}
Loading

0 comments on commit f938d56

Please sign in to comment.