Skip to content

Instantly share code, notes, and snippets.

@Geolykt
Created August 16, 2022 16:17
Show Gist options
  • Save Geolykt/eb7ad7245cdc289501c0a3542df40ba0 to your computer and use it in GitHub Desktop.
Save Geolykt/eb7ad7245cdc289501c0a3542df40ba0 to your computer and use it in GitHub Desktop.
package de.geolykt.mavenresolver.version;
@Deprecated // This class was created by mistake
public class SemverVersion {
public final long major;
public final int minor;
public final int patch;
public final String[] preReleaseIdentifiers;
public final String[] metadata;
public SemverVersion(long major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.preReleaseIdentifiers = new String[0];
this.metadata = new String[0];
}
public SemverVersion(long major, int minor, int patch, String[] prereleaseIdentifiers, String[] metadataIdentifiers) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.preReleaseIdentifiers = prereleaseIdentifiers;
this.metadata = metadataIdentifiers;
}
/**
* Parses a version string as a {@link SemverVersion} object. Will not necessarily fail even if the string
* does not represent a semver release. Will however not be able to parse outlandish version strings.
* Calling {@link SemverVersion#toString()} on the output may as such not return the inserted string.
*
* @param string The version string
* @return The parsed object
*/
public static SemverVersion parseLeniently(String string) {
try {
return parse200(string);
} catch (NotASemverVersionException ignore) {
}
if (string.charAt(0) == 'v') {
// It is common that people prefix releases with "v". While it isn't really a valid semver string we will consider it as one
try {
return parse200(string.substring(1));
} catch (NotASemverVersionException ignore) {
}
}
int hyphenIndex = string.indexOf('-');
if (hyphenIndex == -1) {
// I have seen some artifacts excluding the hyphen - we can reconstruct it rather easily however
String[] dotParts = string.split("\\.");
if (dotParts.length == 3) {
try {
int major = Integer.parseInt(dotParts[0]);
int minor = Integer.parseInt(dotParts[1]);
int lastDigitChar = -1;
for (int i = 0; i < dotParts[2].length(); i++) {
if (Character.isDigit(dotParts[2].codePointAt(i))) {
lastDigitChar = i;
} else {
break;
}
}
int patch = Integer.parseInt(dotParts[2], 0, lastDigitChar + 1, 10);
String prerelease = dotParts[2].substring(lastDigitChar + 1);
int plusCharIndex = prerelease.indexOf('+');
if (plusCharIndex != -1) {
prerelease = prerelease.substring(0, plusCharIndex);
}
return new SemverVersion(major, minor, patch, prerelease.split("\\."), new String[0]);
} catch (NumberFormatException ignored) {
}
} else if (dotParts.length == 2) {
// Maven versions could technically just consist of "MAJOR.MINOR"
try {
int major = Integer.parseInt(dotParts[0]);
int minor = Integer.parseInt(dotParts[1]);
return new SemverVersion(major, minor, 0);
} catch (NumberFormatException ignored) {
}
} else if (dotParts.length == 4) {
// Sometimes MAJOR.MINOR.PATCH.MICROPATCH is used
try {
int major = Integer.parseInt(dotParts[0]);
int minor = Integer.parseInt(dotParts[1]);
int patch = Integer.parseInt(dotParts[2]);
int plusIndex = dotParts[3].indexOf('+');
if (plusIndex == -1) {
return new SemverVersion(major, minor, patch, new String[] {dotParts[3]}, new String[0]);
} else {
return new SemverVersion(major, minor, patch, new String[] {dotParts[3].substring(0, plusIndex)}, dotParts[3].substring(plusIndex + 1).split("\\."));
}
} catch (NumberFormatException ignored) {
}
} else if (dotParts.length == 1) {
// DATE is used quite often too.
try {
// For the sake of simplicity, we assume that DATE is a long and corresponds to the "major" field of SemVer, even though the two standards are very incompatible with each other
return new SemverVersion(Long.parseLong(dotParts[0]), 0, 0);
} catch (NumberFormatException ignored) {
}
}
} else {
String core = string.substring(0, hyphenIndex);
String[] dotParts = core.split("\\.");
if (dotParts.length == 2) {
// Maven versions could consist of "MAJOR.MINOR-PRERELEASE"
try {
int major = Integer.parseInt(dotParts[0]);
int minor = Integer.parseInt(dotParts[1]);
return new SemverVersion(major, minor, 0, string.substring(hyphenIndex + 1).split("\\."), new String[0]);
} catch (NumberFormatException ignored) {
}
}
}
throw new IllegalArgumentException("Cannot parse version string \"" + string + "\".");
}
/**
* Parses a SemVer 2.0.0 (https://semver.org/spec/v2.0.0.html) version string as a {@link SemverVersion} object.
*
* @param string The semver string
* @return The parsed object
*/
public static SemverVersion parse200(String string) throws NotASemverVersionException {
int prereleaseHyphenIndex = string.indexOf('-');
int metadataSeperator = string.indexOf('+');
if (metadataSeperator != -1 && prereleaseHyphenIndex != -1 && metadataSeperator < prereleaseHyphenIndex) {
throw new NotASemverVersionException("Does not follow the MAJOR.MINOR.PATCH-PRERELEASE+METADATA scheme");
}
int majorSeperator = string.indexOf('.');
if (majorSeperator == -1) {
throw new NotASemverVersionException("Does not follow the X.Y.Z scheme (\"X.\" not found)");
}
int minorSeperator = string.indexOf('.', majorSeperator + 1);
if (minorSeperator == -1) {
throw new NotASemverVersionException("Does not follow the X.Y.Z scheme (\"Y.\" not found)");
}
int patchSeperator;
if (prereleaseHyphenIndex != -1) {
if (minorSeperator > prereleaseHyphenIndex) {
throw new NotASemverVersionException("Does not follow the MAJOR.MINOR.PATCH-PRERELEASE scheme (hypen is far too early)");
} else {
patchSeperator = prereleaseHyphenIndex;
}
} else if (metadataSeperator != -1) {
patchSeperator = metadataSeperator;
} else {
patchSeperator = string.length();
}
int major;
int minor;
int patch;
try {
major = Integer.parseInt(string, 0, majorSeperator, 10);
minor = Integer.parseInt(string, majorSeperator + 1, minorSeperator, 10);
patch = Integer.parseInt(string, minorSeperator + 1, patchSeperator, 10);
} catch (NumberFormatException nfe) {
throw new NotASemverVersionException("Does not follow the X.Y.Z scheme (Either X, Y or Z not an integer)", nfe);
}
if (patchSeperator == string.length()) {
return new SemverVersion(major, minor, patch);
}
String metadata;
String prerelease;
if (prereleaseHyphenIndex == -1) {
metadata = string.substring(metadataSeperator + 1);
prerelease = null;
} else if (metadataSeperator == -1) {
metadata = null;
prerelease = string.substring(prereleaseHyphenIndex + 1);
} else {
metadata = string.substring(metadataSeperator + 1);
prerelease = string.substring(prereleaseHyphenIndex + 1, metadataSeperator);
}
String[] metadataParts;
String[] prereleaseParts;
if (metadata == null) {
metadataParts = new String[0];
} else {
metadataParts = metadata.split("\\.");
}
if (prerelease == null) {
prereleaseParts = new String[0];
} else {
prereleaseParts = prerelease.split("\\.");
}
return new SemverVersion(major, minor, patch, prereleaseParts, metadataParts);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(major);
builder.append('.');
builder.append(minor);
builder.append('.');
builder.append(patch);
if (preReleaseIdentifiers.length != 0) {
builder.append('-');
for (String id : preReleaseIdentifiers) {
builder.append(id);
builder.append('.');
}
builder.setLength(builder.length() - 1); // Remove extraneous dot
}
if (metadata.length != 0) {
builder.append('+');
for (String id : metadata) {
builder.append(id);
builder.append('.');
}
builder.setLength(builder.length() - 1); // Remove extraneous dot
}
return builder.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment