com.metaeffekt.artifact.terms.model.ScanResultPart 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.terms.model;
import org.metaeffekt.core.inventory.InventoryUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* Manages the scan result of a single scan target.
*/
public class ScanResultPart {
/**
* Matched {@link TermsMetaData} instances.
*/
private List matches = new ArrayList<>();
private List nameMatches = new ArrayList<>();
private List licenseMatches = new ArrayList<>();
/**
* Partially matched {@link TermsMetaData} instances.
*/
private List partialMatches = new ArrayList<>();
/**
* {@link TermsMetaData} instances that were explicitly excluded because of an excluded match.
*/
private List excludedMatches = new ArrayList<>();
/**
* All match items of the scan.
*/
private List matchItems = new ArrayList<>();
/**
* Filters the licenseMetaDataList. If a category is covered by a specific {@link TermsMetaData} instance all
* unspecific results are removed.
*/
public void filterUnspecific() {
Set coveredCategories = matches.stream().
filter(lmd -> !lmd.isUnspecific()).
map(TermsMetaData::getCategory).
collect(Collectors.toSet());
Set toBeRemoved = matches.stream().
filter(lmd -> lmd.isUnspecific()).
filter(lmd -> coveredCategories.contains(lmd.getCategory())).
collect(Collectors.toSet());
matches.removeAll(toBeRemoved);
}
/**
* Filters the licenseMetaDataList. If a license allows later versions of a given license, the version specific licenses
* are removed.
*/
public void filterSpecific() {
Set coveredCategories = matches.stream().
filter(lmd -> lmd.allowLaterVersions()).
map(TermsMetaData::getCanonicalName).
collect(Collectors.toSet());
Set toBeRemoved = matches.stream().
filter(lmd -> !lmd.allowLaterVersions()).
filter(lmd -> coveredCategories.contains(lmd.getCanonicalName() + " (or any later version)")).
collect(Collectors.toSet());
matches.removeAll(toBeRemoved);
}
/**
* Filters the licenseMetaDataList. Retains licenses with a specified name.
*/
public void filterUnnamed() {
Set coveredLicenses = matches.stream().
map(lmd -> lmd.getNamedEquivalence()).
filter(Objects::nonNull).
collect(Collectors.toSet());
Set toBeRemoved = matches.stream().
filter(lmd -> coveredLicenses.contains(lmd.getCanonicalName())).
collect(Collectors.toSet());
matches.removeAll(toBeRemoved);
}
public void filterConflicts(String category) {
Boolean licenseMatchCategory = false;
for (TermsMetaData licenseMatch : licenseMatches) {
if (licenseMatch.getCategory().equals(category)) licenseMatchCategory = true;
}
if (licenseMatchCategory == true) {
for (TermsMetaData nameMatch : nameMatches) {
if (nameMatch.getCategory().equals(category) && !(licenseMatches.contains(nameMatch))) {
matches.remove(nameMatch);
}
}
}
}
public List getMatchedTerms() {
return matches.stream().filter(lmd -> !excludedMatches.contains(lmd)).
map(TermsMetaData::getCanonicalName).collect(Collectors.toList());
}
public List getNameMatchedTerms() {
return nameMatches.stream().filter(lmd -> !excludedMatches.contains(lmd)).
map(TermsMetaData::getCanonicalName).collect(Collectors.toList());
}
public List getTextMatchedTerms() {
return licenseMatches.stream().filter(lmd -> !excludedMatches.contains(lmd)).
map(TermsMetaData::getCanonicalName).collect(Collectors.toList());
}
public List getPartialMatchedTerms() {
return partialMatches.stream().distinct().
map(TermsMetaData::getCanonicalName).sorted(String::compareTo).collect(Collectors.toList());
}
public List getExcludeMatchedLicenses() {
return excludedMatches.stream().distinct().
map(TermsMetaData::getCanonicalName).sorted(String::compareTo).collect(Collectors.toList());
}
public void cleanExcludedMatches() {
excludedMatches.clear();
}
public void cleanLicenseMatches() {
licenseMatches.clear();
}
private void addMatch(TermsMetaData licenseMetaData) {
if (!matches.contains(licenseMetaData)) {
matches.add(licenseMetaData);
}
}
public void addNameMatch(TermsMetaData licenseMetaData) {
if (!matches.contains(licenseMetaData)) {
matches.add(licenseMetaData);
}
if (!nameMatches.contains(licenseMetaData)) {
nameMatches.add(licenseMetaData);
}
}
public void addLicenseMatch(TermsMetaData licenseMetaData) {
if (!matches.contains(licenseMetaData)) {
matches.add(licenseMetaData);
}
if (!licenseMatches.contains(licenseMetaData)) {
licenseMatches.add(licenseMetaData);
}
}
public synchronized void merge(ScanResultPart other) {
for (TermsMetaData licenseMetaData : other.matches) {
addMatch(licenseMetaData);
}
for (TermsMetaData licenseMetaData : other.licenseMatches) {
addLicenseMatch(licenseMetaData);
}
for (TermsMetaData licenseMetaData : other.nameMatches) {
addNameMatch(licenseMetaData);
}
for (TermsMetaData licenseMetaData : other.partialMatches) {
addPartialMatch(licenseMetaData);
}
for (TermsMetaData licenseMetaData : other.excludedMatches) {
addExcludedMatch(licenseMetaData);
}
this.matchItems.addAll(other.getMatchItems());
}
public void filterIgnored() {
Set toBeRemoved = matches.stream().
filter(lmd -> lmd.ignore()).
collect(Collectors.toSet());
matches.removeAll(toBeRemoved);
}
public void process(NormalizationMetaData normalizationMetaData, boolean enableIgnore, boolean enableCombine) {
final Set toBeRemovedLmds = new HashSet<>();
//filter Name Match / EvidenceMatch Conflict
String[] categories = {"BSD"};
for (String cat : categories) {
filterConflicts(cat);
}
if (enableCombine) {
combine(normalizationMetaData, toBeRemovedLmds);
}
// filter those that are not an actual license
if (enableIgnore) {
filterIgnored();
}
if (enableCombine) {
combine(normalizationMetaData, toBeRemovedLmds);
}
// filter unnamed where named equivalent licenses are used
filterUnnamed();
if (enableCombine) {
combine(normalizationMetaData, toBeRemovedLmds);
}
// filter those covered by 'or any later version'
filterSpecific();
// filter those with unspecified version if a version was otherwise identified
filterUnspecific();
// FIXME: we may need to have as many passes as required to work down the chain
if (enableCombine) {
combine(normalizationMetaData, toBeRemovedLmds);
combine(normalizationMetaData, toBeRemovedLmds);
combine(normalizationMetaData, toBeRemovedLmds);
}
matches.removeAll(toBeRemovedLmds);
if (enableCombine) {
Map> map = buildCategoryMap(normalizationMetaData);
filterUnspecificFromCategoryMap(normalizationMetaData, map);
resolveSpecificInMultiLicenseExpressions(normalizationMetaData, map, false);
// in the second pass we also resolve the ANY license if it is unique
resolveSpecificInMultiLicenseExpressions(normalizationMetaData, map, true);
}
}
protected void resolveSpecificInMultiLicenseExpressions(NormalizationMetaData normalizationMetaData, Map> map, boolean resolveAny) {
final Set toBeRemovedLmds = new HashSet<>();
final Set toBeAddedLmds = new HashSet<>();
final Set noMarkerMatches = matches.stream().filter(lmd -> !lmd.isMarker()).collect(Collectors.toSet());
for (TermsMetaData lmd : matches) {
boolean replacement = false;
String canonicalName = lmd.getCanonicalName();
if (canonicalName.contains(" + ")) {
final List parts = InventoryUtils.tokenizeLicense(lmd.getCanonicalName(), true, false);
final List partLicenseMetaData = new ArrayList<>();
for (String part : parts) {
final TermsMetaData licenseMetaData = normalizationMetaData.getTermsMetaData(part);
if (licenseMetaData != null && licenseMetaData.isUnspecific()) {
final Set representatives = map.get(licenseMetaData.getCategory());
if (representatives != null && representatives.size() == 1) {
String representative = representatives.iterator().next();
canonicalName = canonicalName.replace(part + " +", representative + " +");
canonicalName = canonicalName.replace("+ " + part, "+ " + representative);
replacement = true;
// also add the representative (BSD alike --> BSD 3-Clause) to the list of parts covered
// by the (resulting) expression
TermsMetaData representativeLmd = normalizationMetaData.getTermsMetaData(representative);
if (representativeLmd != null) {
partLicenseMetaData.add(representativeLmd);
}
}
} else if (licenseMetaData == null && "ANY".equals(part)) {
if (noMarkerMatches.size() == 2) {
HashSet representativeSet = new HashSet<>(noMarkerMatches);
representativeSet.remove(lmd);
TermsMetaData representativeLmd = representativeSet.iterator().next();
String representative = representativeLmd.getCanonicalName();
canonicalName = canonicalName.replace(part + " +", representative + " +");
canonicalName = canonicalName.replace("+ " + part, "+ " + representative);
replacement = true;
partLicenseMetaData.remove(representativeLmd);
}
}
if (licenseMetaData != null) {
partLicenseMetaData.add(licenseMetaData);
}
}
if (replacement) {
toBeRemovedLmds.add(lmd);
toBeAddedLmds.add(findOrCreateLicenseMetaData(normalizationMetaData, true, canonicalName));
}
// remove those parts that are covered by the expression
toBeRemovedLmds.addAll(partLicenseMetaData);
}
}
matches.removeAll(toBeRemovedLmds);
matches.addAll(toBeAddedLmds);
removeLicenseOptionIfUnique(matches, normalizationMetaData);
}
private void removeLicenseOptionIfUnique(List licenseMetaDataList, NormalizationMetaData normalizationMetaData) {
// since the list may include duplicates we first filter unique names
final Set names = licenseMetaDataList.stream().filter(lmd -> !lmd.isMarker()).map(l -> l.getCanonicalName()).collect(Collectors.toSet());
// count the lmds with ' + ' (in the lack of another attribute that defines a license option)
long optionCount = names.stream().filter(n -> n.contains(" + ")).count();
// in case there is only one option, we remove the "License Option" lmd as it is redundant.
if (optionCount == 1) {
licenseMetaDataList.remove(normalizationMetaData.getTermsMetaData("Licensing Option"));
}
}
protected void filterUnspecificFromCategoryMap(NormalizationMetaData normalizationMetaData, Map> map) {
for (Set representatives : map.values()) {
if (representatives.size() > 1) {
for (String cn : representatives) {
TermsMetaData licenseMetaData = normalizationMetaData.getTermsMetaData(cn);
if (licenseMetaData.isUnspecific()) {
representatives.remove(cn);
break;
}
}
}
}
}
protected Map> buildCategoryMap(NormalizationMetaData normalizationMetaData) {
Map> map = new HashMap<>();
for (TermsMetaData lmd : matches) {
List parts = InventoryUtils.tokenizeLicense(lmd.getCanonicalName(), true, false);
for (String part : parts) {
TermsMetaData licenseMetaData = normalizationMetaData.getTermsMetaData(part);
if (licenseMetaData != null) {
map.computeIfAbsent(licenseMetaData.getCategory(), k -> new HashSet<>()).add(part);
}
}
}
return map;
}
public void combine(NormalizationMetaData normalizationMetaData, Set toBeRemoved) {
List toBeAdded = new ArrayList<>();
for (TermsMetaData lmd : matches) {
if (lmd.getCombinedWith() != null) {
for (Map.Entry combinerEntry : lmd.getCombinedWith().entrySet()) {
List combinerBaseLmds = mapLicenseMetaData(combinerEntry.getKey(), normalizationMetaData, false);
List combinerTargetLmds = mapLicenseMetaData(combinerEntry.getValue(), normalizationMetaData, true);
if (combinerBaseLmds.isEmpty()) {
throw new IllegalStateException("Cannot find base license meta data for combiner: " + combinerEntry);
}
if (matches.containsAll(combinerBaseLmds)) {
// mark the combiner base for removal; this entry is replaced by the target
toBeRemoved.addAll(combinerBaseLmds);
// remove this entry
toBeRemoved.add(lmd);
// add the target
toBeAdded.addAll(combinerTargetLmds);
// ensure the combiner target is no longer on toBeRemoved
toBeRemoved.removeAll(combinerTargetLmds);
}
}
}
}
// ensure that no duplicates are added, by filtering toBeAdded)
toBeAdded.removeAll(matches);
for (TermsMetaData toAdd : toBeAdded) {
if (!matches.contains(toAdd)) {
matches.add(toAdd);
}
}
}
protected List mapLicenseMetaData(String combiner, NormalizationMetaData normalizationMetaData, boolean allowCreate) {
List combinerTargetLmds = new ArrayList<>();
String[] targets = combiner.split(",");
for (String combinerTargetName : targets) {
TermsMetaData combinerTarget = findOrCreateLicenseMetaData(normalizationMetaData, allowCreate, combinerTargetName);
if (combinerTarget != null) {
combinerTargetLmds.add(combinerTarget);
}
}
return combinerTargetLmds;
}
private TermsMetaData findOrCreateLicenseMetaData(NormalizationMetaData normalizationMetaData, boolean allowCreate, String combinerTargetName) {
TermsMetaData combinerTarget = normalizationMetaData.getTermsMetaData(combinerTargetName.trim());
if (combinerTarget == null && allowCreate) {
combinerTarget = new TermsMetaData();
combinerTarget.setCanonicalName(combinerTargetName);
combinerTarget.setCategory(combinerTargetName);
}
return combinerTarget;
}
public void addPartialMatch(TermsMetaData licenseMetaData) {
partialMatches.add(licenseMetaData);
}
public void addExcludedMatch(TermsMetaData licenseMetaData) {
excludedMatches.add(licenseMetaData);
}
public void addMatchItem(MatchItem matchItem) {
this.matchItems.add(matchItem);
}
public List getMatchItems() {
return matchItems;
}
public void addMatchItems(List unmatchedMatchItems) {
matchItems.addAll(unmatchedMatchItems);
}
public List getMatches() {
return matches;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy