All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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: *

* *
    *
  1. If the version to compare with is not an instance of TokenVersionImpl, the compareTo method returns 1, effectively setting the current version as greater.
  2. *
  3. The list of VersionTokens is filtered to only include tokens that can be meaningfully compared by string or are marked as VERSION_MODIFIER.
  4. *
  5. The method ensures both versions have equal amount of comparable tokens by adding tokens of appropriate type to the one with lesser tokens.
  6. *
  7. Now, we start comparing tokens in sequence. If a comparison leads to a draw (0), the next set of tokens is compared.
  8. *
  9. 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.
  10. *
  11. If both tokens are of VERSION_MODIFIER type, we use VersionToken.VERSION_MODIFIER_COMPARATOR.compare to weigh the tokens.
  12. *
  13. 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.
  14. *
  15. If tokens are neither VERSION_MODIFIERs, nor only comprised of letters, we resort to using VersionComparator.INSTANCE.compare.
  16. *
  17. In cases where exhaustive comparison of tokens still leads to a draw, the method repeats the process only for modifier tokens.
  18. *
  19. If one version carries additional modifier tokens, the other is presumed to carry neutral version modifier tokens for equivalency.
  20. *
  21. Notwithstanding all comparisons returning draws, the compareTo method would return zero, effectively setting both versions as equal.
  22. *
* * @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