updates for single jar comparison

This commit is contained in:
Greg DiCristofaro 2023-09-05 11:43:45 -04:00
parent 40b9dc991b
commit 812563698e
5 changed files with 76 additions and 30 deletions

View File

@ -40,12 +40,17 @@ import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javassist.CtBehavior;
@ -53,11 +58,14 @@ import javassist.CtClass;
import javassist.CtField;
import javassist.CtMember;
import javassist.Modifier;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
/**
* Handles diffing the public API between two jar files.
*/
public class APIDiff {
private static final Logger LOGGER = Logger.getLogger(APIDiff.class.getName());
// filters to a jar or nbm file
private static final FileFilter JAR_FILTER
@ -67,19 +75,29 @@ public class APIDiff {
* Identifies common jar files between two directories. Only files listed in
* the directory are considered. This method does not recurse.
*
* @param prevDir The previous version directory.
* @param currDir The current version directory.
* @param prev The previous version directory.
* @param curr The current version directory.
* @return The jar file names.
*/
static List<String> getCommonJars(File prevDir, File currDir) {
Set<String> prevJars = getJars(prevDir);
Set<String> currJars = getJars(currDir);
static List<Pair<File, File>> getCommonJars(File prev, File curr) {
if (prev.isFile() && curr.isFile()) {
return Arrays.asList(Pair.of(prev, curr));
}
Set<String> commonJars = new HashSet<>(prevJars);
commonJars.retainAll(currJars);
Map<String, File> prevJars = getJars(prev);
Map<String, File> currJars = getJars(curr);
// TODO how to handle different
return commonJars.stream().sorted().collect(Collectors.toList());
List<Pair<File, File>> retMapping = new ArrayList<>();
for (String prevKey: (Iterable<String>) prevJars.keySet().stream().sorted(StringUtils::compareIgnoreCase)::iterator) {
File prevFile = prevJars.get(prevKey);
File curFile = currJars.get(prevKey);
if (prevFile != null && curFile != null) {
retMapping.add(Pair.of(prevFile, curFile));
}
}
return retMapping;
}
/**
@ -88,10 +106,10 @@ public class APIDiff {
* @param dir The directory.
* @return The jar file names.
*/
private static Set<String> getJars(File dir) {
return Stream.of(dir.listFiles(JAR_FILTER))
.map(f -> f.getName())
.collect(Collectors.toSet());
private static Map<String, File> getJars(File dir) {
File[] files = dir.isDirectory() ? dir.listFiles(JAR_FILTER) : new File[]{dir};
files = files == null ? new File[0] : files;
return Stream.of(files).collect(Collectors.toMap(f -> f.getName(), f -> f, (f1, f2) -> f1));
}
/**
@ -106,7 +124,8 @@ public class APIDiff {
private static Set<String> getPublicPackages(File jarFile) throws IOException, IllegalStateException {
String publicPackageStr = ManifestLoader.loadFromJar(jarFile).getValue("OpenIDE-Module-Public-Packages");
if (publicPackageStr == null) {
throw new IllegalStateException(MessageFormat.format("Manifest for {0} does not have key of 'OpenIDE-Module-Public-Packages'", jarFile.getAbsolutePath()));
LOGGER.log(Level.WARNING, MessageFormat.format("Manifest for {0} does not have key of 'OpenIDE-Module-Public-Packages'", jarFile.getAbsolutePath()));
return null;
} else {
return Stream.of(publicPackageStr.split(","))
.map(String::trim)
@ -140,6 +159,10 @@ public class APIDiff {
Set<String> prevPublicApiPackages = getPublicPackages(prevJar);
Set<String> curPublicApiPackages = getPublicPackages(curJar);
boolean filterToPublicPackages = (prevPublicApiPackages == null && curPublicApiPackages == null) ? false : true;
prevPublicApiPackages = prevPublicApiPackages == null ? Collections.emptySet() : prevPublicApiPackages;
curPublicApiPackages = curPublicApiPackages == null ? Collections.emptySet() : curPublicApiPackages;
Set<String> allPublicApiPackages = new HashSet<>();
allPublicApiPackages.addAll(prevPublicApiPackages);
allPublicApiPackages.addAll(curPublicApiPackages);
@ -161,7 +184,10 @@ public class APIDiff {
JarArchiveComparatorOptions comparatorOptions = new JarArchiveComparatorOptions();
// only classes in prev or current public api
if (filterToPublicPackages) {
comparatorOptions.getFilters().getExcludes().add((ClassFilter) (CtClass ctClass) -> !allPublicApiPackages.contains(ctClass.getPackageName()));
}
// only public classes
comparatorOptions.getFilters().getExcludes().add((ClassFilter) (CtClass ctClass) -> !Modifier.isPublic(ctClass.getModifiers()));
// only fields, methods that are public or protected and class is not final

View File

@ -189,14 +189,18 @@ public class CLIProcessor {
File prevVersFile = new File(prevVersPath);
File srcPathFile = new File(srcPath);
if (!curVersFile.isDirectory()) {
if (!curVersFile.exists()) {
throw new ParseException("No directory found at " + curVersFile.getAbsolutePath());
}
if (!prevVersFile.isDirectory()) {
if (!prevVersFile.exists()) {
throw new ParseException("No directory found at " + prevVersFile.getAbsolutePath());
}
if (curVersFile.isDirectory() != prevVersFile.isDirectory()) {
throw new ParseException("Current and previous paths must both be directories or files");
}
if (!srcPathFile.isDirectory()) {
throw new ParseException("No directory found at " + srcPathFile.getAbsolutePath());
}

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.apiupdate;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
@ -25,6 +26,7 @@ import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.tuple.Pair;
import org.sleuthkit.autopsy.apiupdate.APIDiff.ComparisonRecord;
import org.sleuthkit.autopsy.apiupdate.CLIProcessor.CLIArgs;
import org.sleuthkit.autopsy.apiupdate.ModuleUpdates.ModuleVersionNumbers;
@ -52,19 +54,26 @@ public class Main {
Map<String, ModuleVersionNumbers> newVersionNumMapping = new HashMap<>();
for (String commonJarFileName : APIDiff.getCommonJars(cliArgs.getPreviousVersPath(), cliArgs.getCurrentVersPath())) {
for (Pair<File, File> prevCurJars : APIDiff.getCommonJars(cliArgs.getPreviousVersPath(), cliArgs.getCurrentVersPath())) {
try {
ModuleVersionNumbers prevVersionNums = ModuleUpdates.getVersionsFromJar(cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile());
ModuleVersionNumbers prevVersionNums = ModuleUpdates.getVersionsFromJar(prevCurJars.getLeft());
ComparisonRecord record = APIDiff.getComparison(
cliArgs.getPreviousVersion(),
cliArgs.getCurrentVersion(),
cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile(),
cliArgs.getCurrentVersPath().toPath().resolve(commonJarFileName).toFile());
prevCurJars.getLeft(),
prevCurJars.getRight());
ModuleVersionNumbers projectedVersionNums = ModuleUpdates.getModuleVersionUpdate(prevVersionNums, record.getChangeType());
outputDiff(commonJarFileName, record, prevVersionNums, projectedVersionNums);
String jarFileName;
if (prevCurJars.getLeft().getName().equalsIgnoreCase(prevCurJars.getRight().getName())) {
jarFileName = prevCurJars.getLeft().getName();
} else {
jarFileName = MessageFormat.format("[previous: {0}, current: {1}]", prevCurJars.getLeft().getName(), prevCurJars.getRight().getName());
}
outputDiff(jarFileName, record, prevVersionNums, projectedVersionNums);
newVersionNumMapping.put(projectedVersionNums.getRelease().getModuleName(), projectedVersionNums);
} catch (IOException ex) {

View File

@ -110,7 +110,7 @@ public class ModuleUpdates {
*/
private static SemVer parseSemVer(String semVerStr, SemVer defaultSemVer, String resourceForLogging) {
if (StringUtils.isBlank(semVerStr)) {
LOGGER.log(Level.SEVERE, MessageFormat.format("Unable to parse semver for empty string in {0}", resourceForLogging));
LOGGER.log(Level.WARNING, MessageFormat.format("Unable to parse semver for empty string in {0}", resourceForLogging));
return defaultSemVer;
}
@ -127,7 +127,7 @@ public class ModuleUpdates {
LOGGER.log(Level.SEVERE, MessageFormat.format("Unable to parse semver string {0} for {1}", semVerStr, resourceForLogging), ex);
}
} else {
LOGGER.log(Level.SEVERE, MessageFormat.format("Unable to parse semver string {0} for {1}", semVerStr, resourceForLogging));
LOGGER.log(Level.WARNING, MessageFormat.format("Unable to parse semver string {0} for {1}", semVerStr, resourceForLogging));
}
return defaultSemVer;
@ -154,7 +154,7 @@ public class ModuleUpdates {
}
return new ReleaseVal(releaseName, releaseNum);
} else {
LOGGER.log(Level.SEVERE, MessageFormat.format("Unable to parse release version string {0} for {1}", releaseStr, resourceForLogging));
LOGGER.log(Level.WARNING, MessageFormat.format("Unable to parse release version string {0} for {1}", releaseStr, resourceForLogging));
}
return new ReleaseVal("", null);
@ -175,7 +175,7 @@ public class ModuleUpdates {
try {
return Integer.parseInt(str);
} catch (NullPointerException | NumberFormatException ex) {
LOGGER.log(Level.SEVERE, MessageFormat.format("Unable to parse version string {0} for {1}", str, resourceForLogging), ex);
LOGGER.log(Level.WARNING, MessageFormat.format("Unable to parse version string {0} for {1}", str, resourceForLogging), ex);
return defaultVal;
}
}
@ -193,8 +193,15 @@ public class ModuleUpdates {
SemVer specSemVer = parseSemVer(spec, DEFAULT_SEMVER,
MessageFormat.format("{0} in manifest for {1}", MANIFEST_SPEC_KEY, jarFile));
int implementation = tryParse(manifest.getValue(MANIFEST_IMPL_KEY), DEFAULT_VERS_VAL,
String implStr = manifest.getValue(MANIFEST_IMPL_KEY);
int implementation;
if (StringUtils.isBlank(implStr)) {
LOGGER.log(Level.WARNING, MessageFormat.format("No {0} for implementation found in {1}. Using default of {2}.", MANIFEST_IMPL_KEY, jarFile.getName(), DEFAULT_VERS_VAL));
implementation = DEFAULT_VERS_VAL;
} else {
implementation = tryParse(implStr, DEFAULT_VERS_VAL,
MessageFormat.format("{0} in manifest for {1}", MANIFEST_IMPL_KEY, jarFile));
}
ReleaseVal release = parseReleaseVers(manifest.getValue(MANIFEST_RELEASE_KEY),
MessageFormat.format("{0} in manifest for {1}", MANIFEST_RELEASE_KEY, jarFile));