com.metaeffekt.artifact.analysis.version.AllCategorizedPartsVersionImpl 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.curation.ConditionalCuratedVersionPartsExtractor;
import com.metaeffekt.artifact.analysis.version.curation.ConditionalCuratedVersionPartsExtractorCollection;
import com.metaeffekt.artifact.analysis.version.curation.ExtractedCuratedVersionParts;
import com.metaeffekt.artifact.analysis.version.curation.VersionContext;
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 com.metaeffekt.artifact.enrichment.matching.VulnerabilitiesFromCpeEnrichment;
import org.apache.commons.lang3.ObjectUtils;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import us.springett.parsers.cpe.Cpe;
import java.util.List;
import java.util.Optional;
import static com.metaeffekt.artifact.analysis.version.VersionModifier.NEUTRAL_TOKEN;
public class AllCategorizedPartsVersionImpl implements Version {
protected final String rawVersion;
protected final String rawUpdate;
protected final VersionToken specVersion;
protected final VersionToken semVersion;
protected final VersionToken buildVersion;
protected final VersionToken otherVersionPart;
protected final VersionToken versionModifier;
protected final VersionToken afterAllPart;
protected AllCategorizedPartsVersionImpl(String version, String update, VersionContext versionContext) {
this.rawVersion = version;
this.rawUpdate = update;
final List matchingExtractors = ConditionalCuratedVersionPartsExtractorCollection.findExtractors(VersionTokenizer.buildEffectiveVersionString(version, update), versionContext);
final Optional firstExtractor = matchingExtractors.stream().findFirst();
final Optional curatedResult = ConditionalCuratedVersionPartsExtractorCollection.applyFirstStep(firstExtractor, version, versionContext);
final boolean isCuratedResultPresent = curatedResult.isPresent();
final boolean curatedResultHasPreprocessedVersion = isCuratedResultPresent && curatedResult.get().isPreprocessorType();
final ExtractedCuratedVersionParts effectiveResult;
if (isCuratedResultPresent && !curatedResultHasPreprocessedVersion) {
effectiveResult = curatedResult.get();
} else {
final List tokens;
if (curatedResultHasPreprocessedVersion) {
tokens = VersionTokenizer.tokenize(curatedResult.get().getPreprocessedVersionToBeTokenized());
} else {
tokens = VersionTokenizer.tokenize(version, update);
}
effectiveResult = new ExtractedCuratedVersionParts();
// if there are more/equal to 3 string version parts (such as DUK-l09c10b187 --> l, c, b), then they are
// most likely not used as update parts, but as some kind of separators. In this case, we do not want to
// add them to the version parts.
boolean containsThreeOrMoreStringVersionParts = tokens.stream().filter(t -> t.getType() == VersionTokenType.STRING && t.isComparableByString()).count() >= 3;
for (int i = 0; i < tokens.size(); i++) {
final VersionToken token = tokens.get(i);
// skip single character strings at the beginning
if (i == 0 && token.getType() == VersionTokenType.STRING && token.getValue().length() == 1) {
continue;
}
if (token.getType() == VersionTokenType.VERSION_MODIFIER) {
if (effectiveResult.getVersionModifier() == null) {
effectiveResult.setVersionModifier(token);
}
} else if (token.isComparableByString() && !(containsThreeOrMoreStringVersionParts && token.getType() == VersionTokenType.STRING)) {
if (effectiveResult.getVersionModifier() != null && effectiveResult.getAfterAllPart() == null) {
effectiveResult.setAfterAllPart(token);
} else if (effectiveResult.getSpecVersion() == null) {
effectiveResult.setSpecVersion(token);
} else if (effectiveResult.getSemVersion() == null) {
effectiveResult.setSemVersion(token);
} else if (effectiveResult.getBuildVersion() == null) {
effectiveResult.setBuildVersion(token);
} else if (effectiveResult.getOtherVersionPart() == null) {
effectiveResult.setOtherVersionPart(token);
}
}
}
// potentially rearrange the version parts, push all parts one back if the other part is null
if (effectiveResult.getOtherVersionPart() == null && effectiveResult.getSpecVersion() != null
&& (effectiveResult.getSpecVersion().getType() == VersionTokenType.NUMBER_OR_SEMVER || effectiveResult.getSpecVersion().getType() == VersionTokenType.DATE)) {
effectiveResult.setOtherVersionPart(effectiveResult.getBuildVersion());
effectiveResult.setBuildVersion(effectiveResult.getSemVersion());
effectiveResult.setSemVersion(effectiveResult.getSpecVersion());
effectiveResult.setSpecVersion(null);
}
// check if build part is a string >= 8 chars and if so, move it to other part if other is null
if (effectiveResult.getOtherVersionPart() == null && effectiveResult.getBuildVersion() != null && effectiveResult.getBuildVersion().getValue().length() >= 8 && effectiveResult.getBuildVersion().getType() != VersionTokenType.NUMBER_OR_SEMVER) {
effectiveResult.setOtherVersionPart(effectiveResult.getBuildVersion());
effectiveResult.setBuildVersion(null);
}
// check if sem part is 4 dot separated parts long and if so, move the last part to the build part if build is null
if (effectiveResult.getBuildVersion() == null && effectiveResult.getSemVersion() != null && effectiveResult.getSemVersion().getValue().split("\\.").length == 4) {
final String[] parts = effectiveResult.getSemVersion().getValue().split("\\.");
effectiveResult.setBuildVersion(new VersionToken(parts[3], VersionTokenType.NUMBER_OR_SEMVER));
effectiveResult.setSemVersion(new VersionToken(org.apache.commons.lang3.StringUtils.join(parts, ".", 0, 3), VersionTokenType.NUMBER_OR_SEMVER));
}
// check if the current sem part does not contain "." and find if any other field after that contains a "." and if so, replace the sem part with it
if (effectiveResult.getSemVersion() != null && !effectiveResult.getSemVersion().getValue().contains(".")) {
if (effectiveResult.getBuildVersion() != null && effectiveResult.getBuildVersion().getValue().contains(".")) {
effectiveResult.setSemVersion(effectiveResult.getBuildVersion());
effectiveResult.setBuildVersion(null);
} else if (effectiveResult.getOtherVersionPart() != null && effectiveResult.getOtherVersionPart().getValue().contains(".")) {
effectiveResult.setSemVersion(effectiveResult.getOtherVersionPart());
effectiveResult.setOtherVersionPart(null);
}
}
}
for (ConditionalCuratedVersionPartsExtractor extractor : matchingExtractors) {
extractor.applySecondStep(effectiveResult);
}
this.specVersion = effectiveResult.getSpecVersion();
this.semVersion = effectiveResult.getSemVersion();
this.buildVersion = effectiveResult.getBuildVersion();
this.versionModifier = effectiveResult.getVersionModifier();
this.otherVersionPart = effectiveResult.getOtherVersionPart();
this.afterAllPart = effectiveResult.getAfterAllPart();
}
public AllCategorizedPartsVersionImpl(String version, VersionContext versionContext) {
this(version, null, versionContext);
}
@Override
public int compareTo(Version o) {
if (!(o instanceof AllCategorizedPartsVersionImpl)) {
return 1;
}
return compareTo((AllCategorizedPartsVersionImpl) o);
}
public int compareTo(AllCategorizedPartsVersionImpl o) {
if (o == null) {
return 1;
}
// compare part by part (spec, sem, build, other, modifier)
int result = compareByPart(specVersion, o.specVersion);
if (result == 0) {
result = compareByPart(semVersion, o.semVersion);
}
if (result == 0) {
result = compareByPart(buildVersion, o.buildVersion);
}
if (result == 0) {
result = compareByPart(otherVersionPart, o.otherVersionPart);
}
if (result == 0) {
result = compareByPart(versionModifier, o.versionModifier);
}
if (result == 0) {
result = compareByPart(afterAllPart, o.afterAllPart);
}
return result;
}
private int compareByPart(VersionToken o1, VersionToken o2) {
if (o1 == null && o2 == null) {
return 0;
} else if (o1 == null || o2 == null) {
// find the default value for the type that is non-null
final VersionToken nonNull = ObjectUtils.firstNonNull(o1, o2);
final VersionToken defaultValue;
switch (nonNull.getType()) {
case NUMBER_OR_SEMVER:
defaultValue = DEFAULT_NUMBER_OR_SEMVER;
break;
case DATE:
defaultValue = DEFAULT_DATE;
break;
case STRING:
defaultValue = DEFAULT_STRING;
break;
case VERSION_MODIFIER:
defaultValue = DEFAULT_VERSION_MODIFIER;
break;
default:
throw new IllegalStateException("Unknown version token type: " + nonNull.getType());
}
if (o1 == null) {
o1 = defaultValue;
} else {
o2 = defaultValue;
}
}
boolean is1VersionModifier = o1.getType() == VersionTokenType.VERSION_MODIFIER;
boolean is2VersionModifier = o2.getType() == VersionTokenType.VERSION_MODIFIER;
if (is1VersionModifier && is2VersionModifier) {
return VersionToken.VERSION_MODIFIER_COMPARATOR.compare(o1, o2);
} else if (!is1VersionModifier && !is2VersionModifier) {
if (o1.isLettersOnly() || o2.isLettersOnly()) {
return o1.getValue().compareTo(o2.getValue());
} else {
return VersionComparator.INSTANCE.compare(o1.getValue(), o2.getValue());
}
}
return is1VersionModifier ? 1 : -1;
}
@Override
public String getVersion() {
return rawVersion;
}
@Override
public String getUpdate() {
return rawUpdate;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Version && equals((Version) obj);
}
public VersionToken getSpecVersion() {
return specVersion;
}
public VersionToken getSemVersion() {
return semVersion;
}
public VersionToken getBuildVersion() {
return buildVersion;
}
public VersionToken getOtherVersionPart() {
return otherVersionPart;
}
public VersionToken getVersionModifier() {
return versionModifier;
}
public VersionToken getAfterAllPart() {
return afterAllPart;
}
/**
* Returns the {@link AllCategorizedPartsVersionImpl#toString()} part that only contains the pre-modifier parts
* such that:
* spec-sem [build-other]
* This code is used by the
* {@link VulnerabilitiesFromCpeEnrichment#deriveQueryVersion(Artifact, Cpe)}
* method.
*
* @return the pre-modifier part of the version
*/
public String toStringPreModifierPart() {
final StringBuilder sb = new StringBuilder();
if (nonNullAndNotEmpty(specVersion)) {
sb.append(specVersion);
}
if (nonNullAndNotEmpty(semVersion)) {
if (sb.length() > 0) {
sb.append("-");
}
sb.append(semVersion);
}
if (nonNullAndNotEmpty(buildVersion) || nonNullAndNotEmpty(otherVersionPart)) {
sb.append(" [");
if (nonNullAndNotEmpty(buildVersion)) {
sb.append(buildVersion);
}
if (nonNullAndNotEmpty(otherVersionPart)) {
if (nonNullAndNotEmpty(buildVersion)) {
sb.append("-");
}
sb.append(otherVersionPart);
}
sb.append("]");
}
return sb.toString().trim();
}
/**
* Returns the {@link AllCategorizedPartsVersionImpl#toString()} part that only contains the modifier and the
* after-all part such that:
* (version modifier) after-all
* This code is used by the
* {@link VulnerabilitiesFromCpeEnrichment#deriveQueryVersion(Artifact, Cpe)}
* method.
*
* @return the modifier part of the version
*/
public String toStringModifierPart() {
final StringBuilder sb = new StringBuilder();
if (nonNullAndNotEmpty(versionModifier) && versionModifier != NEUTRAL_TOKEN) {
sb.append("(").append(versionModifier).append(")");
}
if (nonNullAndNotEmpty(afterAllPart)) {
sb.append(" ").append(afterAllPart);
}
return sb.toString().trim();
}
@Override
public String toString() {
// return rawVersion + (rawUpdate != null ? ":" + rawUpdate : "") + " --> spec:" + specVersion + " sem:" + semVersion + " build:" + buildVersion + " other:" + otherVersionPart + " mod:" + versionModifier + " after-all:" + afterAllPart;
return (toStringPreModifierPart() + " " + toStringModifierPart()).trim();
}
private static boolean nonNullAndNotEmpty(VersionToken t) {
return t != null && StringUtils.hasText(t.getValue());
}
private final static VersionToken DEFAULT_NUMBER_OR_SEMVER = new VersionToken("0", VersionTokenType.NUMBER_OR_SEMVER);
private final static VersionToken DEFAULT_DATE = new VersionToken("00000000", VersionTokenType.DATE);
private final static VersionToken DEFAULT_STRING = new VersionToken("\u0000", VersionTokenType.STRING);
private final static VersionToken DEFAULT_VERSION_MODIFIER = new VersionToken(NEUTRAL_TOKEN.getValue(), VersionTokenType.VERSION_MODIFIER);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy