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

View File

@ -189,13 +189,17 @@ public class CLIProcessor {
File prevVersFile = new File(prevVersPath); File prevVersFile = new File(prevVersPath);
File srcPathFile = new File(srcPath); File srcPathFile = new File(srcPath);
if (!curVersFile.isDirectory()) { if (!curVersFile.exists()) {
throw new ParseException("No directory found at " + curVersFile.getAbsolutePath()); throw new ParseException("No directory found at " + curVersFile.getAbsolutePath());
} }
if (!prevVersFile.isDirectory()) { if (!prevVersFile.exists()) {
throw new ParseException("No directory found at " + prevVersFile.getAbsolutePath()); 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()) { if (!srcPathFile.isDirectory()) {
throw new ParseException("No directory found at " + srcPathFile.getAbsolutePath()); throw new ParseException("No directory found at " + srcPathFile.getAbsolutePath());

View File

@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.apiupdate; package org.sleuthkit.autopsy.apiupdate;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.HashMap; import java.util.HashMap;
@ -25,6 +26,7 @@ import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.apache.commons.cli.ParseException; 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.APIDiff.ComparisonRecord;
import org.sleuthkit.autopsy.apiupdate.CLIProcessor.CLIArgs; import org.sleuthkit.autopsy.apiupdate.CLIProcessor.CLIArgs;
import org.sleuthkit.autopsy.apiupdate.ModuleUpdates.ModuleVersionNumbers; import org.sleuthkit.autopsy.apiupdate.ModuleUpdates.ModuleVersionNumbers;
@ -52,19 +54,26 @@ public class Main {
Map<String, ModuleVersionNumbers> newVersionNumMapping = new HashMap<>(); 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 { try {
ModuleVersionNumbers prevVersionNums = ModuleUpdates.getVersionsFromJar(cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile()); ModuleVersionNumbers prevVersionNums = ModuleUpdates.getVersionsFromJar(prevCurJars.getLeft());
ComparisonRecord record = APIDiff.getComparison( ComparisonRecord record = APIDiff.getComparison(
cliArgs.getPreviousVersion(), cliArgs.getPreviousVersion(),
cliArgs.getCurrentVersion(), cliArgs.getCurrentVersion(),
cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile(), prevCurJars.getLeft(),
cliArgs.getCurrentVersPath().toPath().resolve(commonJarFileName).toFile()); prevCurJars.getRight());
ModuleVersionNumbers projectedVersionNums = ModuleUpdates.getModuleVersionUpdate(prevVersionNums, record.getChangeType()); ModuleVersionNumbers projectedVersionNums = ModuleUpdates.getModuleVersionUpdate(prevVersionNums, record.getChangeType());
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(commonJarFileName, record, prevVersionNums, projectedVersionNums); outputDiff(jarFileName, record, prevVersionNums, projectedVersionNums);
newVersionNumMapping.put(projectedVersionNums.getRelease().getModuleName(), projectedVersionNums); newVersionNumMapping.put(projectedVersionNums.getRelease().getModuleName(), projectedVersionNums);
} catch (IOException ex) { } catch (IOException ex) {

View File

@ -110,7 +110,7 @@ public class ModuleUpdates {
*/ */
private static SemVer parseSemVer(String semVerStr, SemVer defaultSemVer, String resourceForLogging) { private static SemVer parseSemVer(String semVerStr, SemVer defaultSemVer, String resourceForLogging) {
if (StringUtils.isBlank(semVerStr)) { 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; 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); LOGGER.log(Level.SEVERE, MessageFormat.format("Unable to parse semver string {0} for {1}", semVerStr, resourceForLogging), ex);
} }
} else { } 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; return defaultSemVer;
@ -154,7 +154,7 @@ public class ModuleUpdates {
} }
return new ReleaseVal(releaseName, releaseNum); return new ReleaseVal(releaseName, releaseNum);
} else { } 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); return new ReleaseVal("", null);
@ -175,7 +175,7 @@ public class ModuleUpdates {
try { try {
return Integer.parseInt(str); return Integer.parseInt(str);
} catch (NullPointerException | NumberFormatException ex) { } 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; return defaultVal;
} }
} }
@ -193,8 +193,15 @@ public class ModuleUpdates {
SemVer specSemVer = parseSemVer(spec, DEFAULT_SEMVER, SemVer specSemVer = parseSemVer(spec, DEFAULT_SEMVER,
MessageFormat.format("{0} in manifest for {1}", MANIFEST_SPEC_KEY, jarFile)); 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);
MessageFormat.format("{0} in manifest for {1}", MANIFEST_IMPL_KEY, jarFile)); 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), ReleaseVal release = parseReleaseVers(manifest.getValue(MANIFEST_RELEASE_KEY),
MessageFormat.format("{0} in manifest for {1}", MANIFEST_RELEASE_KEY, jarFile)); MessageFormat.format("{0} in manifest for {1}", MANIFEST_RELEASE_KEY, jarFile));