com.metaeffekt.artifact.analysis.version.TokenVersionImpl Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2021-2024 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
*
* 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.
*/
package com.metaeffekt.artifact.analysis.version;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.version.token.VersionToken;
import com.metaeffekt.artifact.analysis.version.token.VersionTokenType;
import com.metaeffekt.artifact.analysis.version.token.VersionTokenizer;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.VersionComparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static org.metaeffekt.core.inventory.processor.model.Constants.ASTERISK;
/**
* A class representing a version of a software.
* The version is represented as a string. The string is tokenized into a list of {@link VersionToken}s.
* See {@link VersionTokenizer} for details on the tokenization and {@link TokenVersionImpl#compareTo(Version)} for
* details on the comparison of two versions.
*/
@Deprecated
public class TokenVersionImpl implements Version {
private final static Logger LOG = LoggerFactory.getLogger(TokenVersionImpl.class);
private final String version;
private final String update;
private final List tokenizedVersion;
private final List reducedTokenizedVersion;
TokenVersionImpl(String version) {
this.version = version;
this.update = null;
this.tokenizedVersion = VersionTokenizer.tokenize(this.version, null);
this.reducedTokenizedVersion = filterForComparableTokens(tokenizedVersion);
}
TokenVersionImpl(String version, String update) {
this.version = version;
this.update = update;
this.tokenizedVersion = VersionTokenizer.tokenize(this.version, this.update);
this.reducedTokenizedVersion = filterForComparableTokens(tokenizedVersion);
}
@Override
public String getVersion() {
return version;
}
@Override
public String getUpdate() {
return update;
}
/**
*
* This method compares the current version with the specified object for order.
* The concept of a version here is represented as a list of VersionTokens, where each VersionToken corresponds
* to a significant part of the versioning scheme (a portion of the version string that is separated by a dot, for example).
* It follows several steps for comparison:
*
*
*
* - If the version to compare with is not an instance of TokenVersionImpl, the compareTo method returns 1, effectively setting the current version as greater.
* - The list of VersionTokens is filtered to only include tokens that can be meaningfully compared by string or are marked as VERSION_MODIFIER.
* - The method ensures both versions have equal amount of comparable tokens by adding tokens of appropriate type to the one with lesser tokens.
* - Now, we start comparing tokens in sequence. If a comparison leads to a draw (0), the next set of tokens is compared.
* - If a token is of string type and its value is a single character, it is presumed to be a leading character and thus, is overlooked.
* - If both tokens are of VERSION_MODIFIER type, we use
VersionToken.VERSION_MODIFIER_COMPARATOR.compare
to weigh the tokens.
* - For the scenarios where one token is a VERSION_MODIFIER and the other is not, we choose to ignore VERSION_MODIFIER token and compare next tokens.
* - If tokens are neither VERSION_MODIFIERs, nor only comprised of letters, we resort to using
VersionComparator.INSTANCE.compare
.
* - In cases where exhaustive comparison of tokens still leads to a draw, the method repeats the process only for modifier tokens.
* - If one version carries additional modifier tokens, the other is presumed to carry neutral version modifier tokens for equivalency.
* - Notwithstanding all comparisons returning draws, the compareTo method would return zero, effectively setting both versions as equal.
*
*
* @param otherVersion The object to be compared.
* @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
* the specified object.
*/
@Override
public int compareTo(Version otherVersion) {
if (!(otherVersion instanceof TokenVersionImpl)) {
return 1;
}
final TokenVersionImpl other = (TokenVersionImpl) otherVersion;
final List reducedTokenizedVersion1 = new ArrayList<>(this.reducedTokenizedVersion);
final List reducedTokenizedVersion2 = new ArrayList<>(other.reducedTokenizedVersion);
modifyNumericVersionTokenLists(reducedTokenizedVersion1, reducedTokenizedVersion2);
int add1 = 0, add2 = 0;
for (int i = 0; Math.max(i + add1, i + add2) < Math.min(reducedTokenizedVersion1.size(), reducedTokenizedVersion2.size()); i++) {
final int index1 = i + add1;
final int index2 = i + add2;
final VersionToken thisToken = reducedTokenizedVersion1.get(index1 >= reducedTokenizedVersion1.size() ? reducedTokenizedVersion1.size() - 1 : index1);
final VersionToken otherToken = reducedTokenizedVersion2.get(index2 >= reducedTokenizedVersion2.size() ? reducedTokenizedVersion2.size() - 1 : index2);
// ignore leading letters
if (i == 0) {
if (thisToken.getType() == VersionTokenType.STRING && thisToken.getValue().length() == 1 && !thisToken.getValue().equals("\u0000")) {
add1 += 1;
i--;
continue;
} else if (otherToken.getType() == VersionTokenType.STRING && otherToken.getValue().length() == 1 && !otherToken.getValue().equals("\u0000")) {
add2 += 1;
i--;
continue;
}
}
boolean is1VersionModifier = thisToken.getType() == VersionTokenType.VERSION_MODIFIER;
boolean is2VersionModifier = otherToken.getType() == VersionTokenType.VERSION_MODIFIER;
final int comparisonResult;
if (is1VersionModifier && is2VersionModifier) {
comparisonResult = VersionToken.VERSION_MODIFIER_COMPARATOR.compare(thisToken, otherToken);
} else if (!is1VersionModifier && !is2VersionModifier) {
if (thisToken.isLettersOnly() || otherToken.isLettersOnly()) {
comparisonResult = thisToken.getValue().compareTo(otherToken.getValue());
} else {
comparisonResult = VersionComparator.INSTANCE.compare(thisToken.getValue(), otherToken.getValue());
}
} else {
if (is1VersionModifier) {
add1 += 1;
} else {
add2 += 1;
}
i--;
continue;
}
if (comparisonResult != 0) {
return comparisonResult;
}
}
final List thisVersionModifierTokens = filterForVersionModifierTokens(this.tokenizedVersion);
final List otherVersionModifierTokens = filterForVersionModifierTokens(other.tokenizedVersion);
for (int i = 0; i < Math.max(thisVersionModifierTokens.size(), otherVersionModifierTokens.size()); i++) {
final VersionToken thisToken = i < thisVersionModifierTokens.size() ? thisVersionModifierTokens.get(i) : VersionModifier.NEUTRAL_TOKEN;
final VersionToken otherToken = i < otherVersionModifierTokens.size() ? otherVersionModifierTokens.get(i) : VersionModifier.NEUTRAL_TOKEN;
final int comparisonResult = VersionToken.VERSION_MODIFIER_COMPARATOR.compare(thisToken, otherToken);
if (comparisonResult != 0) {
return comparisonResult;
}
}
return 0;
}
private void modifyNumericVersionTokenLists(List tokens1, List tokens2) {
final int size1WithoutModifier = (int) tokens1.stream().filter(token -> token.getType() != VersionTokenType.VERSION_MODIFIER).count();
final int size2WithoutModifier = (int) tokens2.stream().filter(token -> token.getType() != VersionTokenType.VERSION_MODIFIER).count();
if (size1WithoutModifier == size2WithoutModifier) {
return;
}
final int sizeDifference = Math.abs(size1WithoutModifier - size2WithoutModifier);
final List longerList = size1WithoutModifier > size2WithoutModifier ? tokens1 : tokens2;
final List shorterList = size1WithoutModifier < size2WithoutModifier ? tokens1 : tokens2;
for (int i = 0; i < sizeDifference; i++) {
final VersionToken lastToken = longerList.get(longerList.size() - 1).getType() == VersionTokenType.VERSION_MODIFIER ? longerList.get(longerList.size() - 2) : longerList.get(longerList.size() - 1);
final VersionToken newToken;
if (lastToken.getType() == VersionTokenType.NUMBER_OR_SEMVER) {
newToken = new VersionToken("0", VersionTokenType.NUMBER_OR_SEMVER);
} else if (lastToken.getType() == VersionTokenType.DATE) {
newToken = new VersionToken("00000000", VersionTokenType.DATE);
} else if (lastToken.getType() == VersionTokenType.STRING) {
newToken = new VersionToken("\u0000", VersionTokenType.STRING);
} else if (lastToken.getType() == VersionTokenType.VERSION_MODIFIER) {
newToken = new VersionToken(VersionModifier.NEUTRAL_TOKEN.getValue(), VersionTokenType.VERSION_MODIFIER);
} else {
LOG.warn("Could not determine type of last token in version token list. If a new type had been added to filterForNumericVersionTokens, make sure to add it in modifyNumericVersionTokenLists as well: {} {}", lastToken.getType(), lastToken);
continue;
}
shorterList.add(newToken);
}
}
private List filterForComparableTokens(List tokens) {
return tokens.stream()
.filter(token -> token.isComparableByString() || token.getType() == VersionTokenType.VERSION_MODIFIER)
.collect(Collectors.toList());
}
private List filterForVersionModifierTokens(List tokens) {
return tokens.stream()
.filter(token -> token.getType() == VersionTokenType.VERSION_MODIFIER)
.collect(Collectors.toList());
}
@Override
public String toString() {
return version + (StringUtils.hasText(update) ? " " + update : "") + " --> " + tokenizedVersion.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy