From 0b7e8dd8ca5fd9fd930b9036d3196ad959328f71 Mon Sep 17 00:00:00 2001
From: Greg DiCristofaro <gregd@basistech.com>
Date: Fri, 1 Sep 2023 12:24:21 -0400
Subject: [PATCH] updates

---
 .../sleuthkit/autopsy/apiupdate/APIDiff.java  | 273 +++++---
 .../autopsy/apiupdate/ApiChangeDTO.java       | 153 ----
 .../autopsy/apiupdate/CLIProcessor.java       |  43 +-
 .../apiupdate/ChangeOutputGenerator.java      | 662 ------------------
 .../org/sleuthkit/autopsy/apiupdate/Main.java | 109 +--
 .../autopsy/apiupdate/ModuleUpdates.java      |  42 +-
 .../apiupdate/PublicApiChangeType.java        |  24 +-
 7 files changed, 341 insertions(+), 965 deletions(-)
 delete mode 100644 release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ApiChangeDTO.java
 delete mode 100644 release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ChangeOutputGenerator.java

diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java
index 2f990d2a38..1affbd7c4a 100644
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java
@@ -4,20 +4,40 @@
  */
 package org.sleuthkit.autopsy.apiupdate;
 
+import com.google.common.collect.Comparators;
 import japicmp.cmp.JApiCmpArchive;
 import japicmp.cmp.JarArchiveComparator;
 import japicmp.cmp.JarArchiveComparatorOptions;
 import japicmp.config.Options;
+import japicmp.filter.BehaviorFilter;
+import japicmp.filter.ClassFilter;
+import japicmp.filter.FieldFilter;
+import japicmp.model.JApiAnnotation;
 import japicmp.model.JApiClass;
+import japicmp.model.JApiConstructor;
+import japicmp.model.JApiField;
+import japicmp.model.JApiHasChangeStatus;
+import japicmp.model.JApiImplementedInterface;
+import japicmp.model.JApiMethod;
+import japicmp.model.JApiSuperclass;
+import japicmp.output.Filter;
+import japicmp.output.stdout.StdoutOutputGenerator;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import javassist.CtBehavior;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.CtMember;
+import javassist.Modifier;
 
 /**
  *
@@ -57,107 +77,186 @@ private static Set<String> getPublicPackages(File jarFile) throws IOException, I
         }
     }
 
-    static void getComparison(String prevVersion, String curVersion, File prevJar, File curJar) throws IOException {
-        JarArchiveComparatorOptions comparatorOptions = new JarArchiveComparatorOptions();
-        //comparatorOptions.setAccessModifier(AccessModifier.);
-        comparatorOptions.getIgnoreMissingClasses().setIgnoreAllMissingClasses(true);
-        JarArchiveComparator jarArchiveComparator = new JarArchiveComparator(comparatorOptions);
-        List<JApiClass> jApiClasses = jarArchiveComparator.compare(
-                new JApiCmpArchive(prevJar, prevVersion),
-                new JApiCmpArchive(curJar, curVersion)
-        );
+    // only fields, methods that are public or protected
+    static boolean excludeMember(CtMember member) {
+        return !Modifier.isPublic(member.getModifiers()) && !Modifier.isProtected(member.getModifiers());
+    }
 
+    static ComparisonRecord getComparison(String prevVersion, String curVersion, File prevJar, File curJar) throws IOException {
+        // scope only to previous or current public packages
         Set<String> prevPublicApiPackages = getPublicPackages(prevJar);
         Set<String> curPublicApiPackages = getPublicPackages(curJar);
 
-        // TODO handle diff in this list
         Set<String> allPublicApiPackages = new HashSet<>();
         allPublicApiPackages.addAll(prevPublicApiPackages);
         allPublicApiPackages.addAll(curPublicApiPackages);
-        jApiClasses = jApiClasses.stream()
-                .filter(cls -> allPublicApiPackages.contains(cls.getNewClass().or(cls.getOldClass()).get().getPackageName()))
-                .collect(Collectors.toList());
 
+        Set<String> onlyPrevApiPackages = new HashSet<>();
+        Set<String> onlyCurApiPackages = new HashSet<>();
+        Set<String> commonApiPackages = new HashSet<>();
+        for (String apiPackage : allPublicApiPackages) {
+            boolean inPrev = prevPublicApiPackages.contains(apiPackage);
+            boolean inCur = curPublicApiPackages.contains(apiPackage);
+            if (inPrev && !inCur) {
+                onlyPrevApiPackages.add(apiPackage);
+            } else if (!inPrev && inCur) {
+                onlyCurApiPackages.add(apiPackage);
+            } else {
+                commonApiPackages.add(apiPackage);
+            }
+        }
+        
+        JarArchiveComparatorOptions comparatorOptions = new JarArchiveComparatorOptions();
+        // only classes in prev or current public api
+        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
+        comparatorOptions.getFilters().getExcludes().add((FieldFilter) (CtField ctField) -> excludeMember(ctField));
+        comparatorOptions.getFilters().getExcludes().add((BehaviorFilter) (CtBehavior ctBehavior) -> excludeMember(ctBehavior));
+
+        comparatorOptions.getIgnoreMissingClasses().setIgnoreAllMissingClasses(true);
+
+        JarArchiveComparator jarArchiveComparator = new JarArchiveComparator(comparatorOptions);
+        List<JApiClass> jApiClasses = jarArchiveComparator.compare(
+                new JApiCmpArchive(prevJar, prevVersion),
+                new JApiCmpArchive(curJar, curVersion)
+        );
+
+        PublicApiChangeType changeType = getChangeType(jApiClasses);
+        
         Options options = Options.newDefault();
         options.setOutputOnlyModifications(true);
 
-        System.out.println("Comparing " + prevJar.getName());
-        ChangeOutputGenerator stdoutOutputGenerator = new ChangeOutputGenerator(options, jApiClasses);
-        String output = stdoutOutputGenerator.generate();
-        System.out.println(output);
+        StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses);
+        String humanReadableApiChange = stdoutOutputGenerator.generate();
+        return new ComparisonRecord(prevVersion, curVersion, prevJar, curJar, humanReadableApiChange, changeType, onlyPrevApiPackages, onlyCurApiPackages, commonApiPackages);
+    }
+
+    private static void updateToMax(AtomicReference<PublicApiChangeType> apiChangeRef, JApiHasChangeStatus tp) {
+        PublicApiChangeType apiChangeType;
+        switch (tp.getChangeStatus()) {
+            case UNCHANGED:
+                apiChangeType = PublicApiChangeType.NONE;
+                break;
+            case NEW:
+                apiChangeType = PublicApiChangeType.COMPATIBLE_CHANGE;
+                break;
+            case MODIFIED:
+            case REMOVED:
+            default:
+                apiChangeType = PublicApiChangeType.INCOMPATIBLE_CHANGE;
+                break;
+        };
+
+        final PublicApiChangeType finalApiChangeType = apiChangeType;
+        apiChangeRef.updateAndGet((refType) -> Comparators.max(refType, finalApiChangeType));
+    }
+
+     static PublicApiChangeType getChangeType(List<JApiClass> jApiClasses) {
+        AtomicReference<PublicApiChangeType> apiChange = new AtomicReference<PublicApiChangeType>(PublicApiChangeType.NONE);
+
+        Filter.filter(jApiClasses, new Filter.FilterVisitor() {
+            @Override
+            public void visit(Iterator<JApiClass> itrtr, JApiClass jac) {
+                updateToMax(apiChange, jac);
+            }
+
+            @Override
+            public void visit(Iterator<JApiMethod> itrtr, JApiMethod jam) {
+                updateToMax(apiChange, jam);
+            }
+
+            @Override
+            public void visit(Iterator<JApiConstructor> itrtr, JApiConstructor jac) {
+                updateToMax(apiChange, jac);
+            }
+
+            @Override
+            public void visit(Iterator<JApiImplementedInterface> itrtr, JApiImplementedInterface jaii) {
+                updateToMax(apiChange, jaii);
+            }
+
+            @Override
+            public void visit(Iterator<JApiField> itrtr, JApiField jaf) {
+                updateToMax(apiChange, jaf);
+            }
+
+            @Override
+            public void visit(Iterator<JApiAnnotation> itrtr, JApiAnnotation jaa) {
+                updateToMax(apiChange, jaa);
+            }
+
+            @Override
+            public void visit(JApiSuperclass jas) {
+                updateToMax(apiChange, jas);
+            }
+        });
+
+        return apiChange.get();
     }
 
-    private static void generateOutput(Options options, List<JApiClass> jApiClasses, JarArchiveComparator jarArchiveComparator) {
-//        for (JApiClass cls: jApiClasses) {
-//            cls.is
-//        }
-//        
+    public static class ComparisonRecord {
+
+        private final String prevVersion;
+        private final String curVersion;
+        private final File prevJar;
+        private final File curJar;
+        private final String humanReadableApiChange;
+        private final PublicApiChangeType changeType;
+        private final Set<String> onlyPrevApiPackages;
+        private final Set<String> onlyCurrApiPackages;
+        private final Set<String> commonApiPackages;
+
+        public ComparisonRecord(String prevVersion, String curVersion, File prevJar, File curJar, String humanReadableApiChange, PublicApiChangeType changeType, Set<String> onlyPrevApiPackages, Set<String> onlyCurrApiPackages, Set<String> commonApiPackages) {
+            this.prevVersion = prevVersion;
+            this.curVersion = curVersion;
+            this.prevJar = prevJar;
+            this.curJar = curJar;
+            this.humanReadableApiChange = humanReadableApiChange;
+            this.changeType = changeType;
+            this.onlyPrevApiPackages = onlyPrevApiPackages;
+            this.onlyCurrApiPackages = onlyCurrApiPackages;
+            this.commonApiPackages = commonApiPackages;
+        }
+
+        public String getPrevVersion() {
+            return prevVersion;
+        }
+
+        public String getCurVersion() {
+            return curVersion;
+        }
+
+        public File getPrevJar() {
+            return prevJar;
+        }
+
+        public File getCurJar() {
+            return curJar;
+        }
+
+        public String getHumanReadableApiChange() {
+            return humanReadableApiChange;
+        }
+
+        public PublicApiChangeType getChangeType() {
+            return changeType;
+        }
+
+        public Set<String> getOnlyPrevApiPackages() {
+            return onlyPrevApiPackages;
+        }
+
+        public Set<String> getOnlyCurrApiPackages() {
+            return onlyCurrApiPackages;
+        }
+
+        public Set<String> getCommonApiPackages() {
+            return commonApiPackages;
+        }
+        
         
-//        if (options.isSemanticVersioning()) {
-//            SemverOut semverOut = new SemverOut(options, jApiClasses);
-//            String output = semverOut.generate();
-//            System.out.println(output);
-//            return;
-//        }
-//        if (options.getXmlOutputFile().isPresent() || options.getHtmlOutputFile().isPresent()) {
-//            SemverOut semverOut = new SemverOut(options, jApiClasses);
-//            XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions();
-//            xmlOutputGeneratorOptions.setCreateSchemaFile(true);
-//            xmlOutputGeneratorOptions.setSemanticVersioningInformation(semverOut.generate());
-//            XmlOutputGenerator xmlGenerator = new XmlOutputGenerator(jApiClasses, options, xmlOutputGeneratorOptions);
-//            try (XmlOutput xmlOutput = xmlGenerator.generate()) {
-//                XmlOutputGenerator.writeToFiles(options, xmlOutput);
-//            } catch (Exception e) {
-//                throw new JApiCmpException(JApiCmpException.Reason.IoException, "Could not close output streams: " + e.getMessage(), e);
-//            }
-//        }
-//        StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses);
-//        String output = stdoutOutputGenerator.generate();
-//        System.out.println(output);
-
-//        if (options.isErrorOnBinaryIncompatibility()
-//                || options.isErrorOnSourceIncompatibility()
-//                || options.isErrorOnExclusionIncompatibility()
-//                || options.isErrorOnModifications()
-//                || options.isErrorOnSemanticIncompatibility()) {
-//            IncompatibleErrorOutput errorOutput = new IncompatibleErrorOutput(options, jApiClasses, jarArchiveComparator);
-//            errorOutput.generate();
-//        }
     }
 
-    
-    
-//    enum ChangeType { CHANGE, ADD, REMOVE }
-//    
-//    public class ClassChangeDTO {
-//        private final String packageStr;
-//        private final String fullyQualifiedClassName;
-//        private final ChangeType changeType;
-//        
-//        private final ClassDataDTO prevClassRecord;
-//        private final ClassDataDTO currClassRecord;
-//        
-//        
-//    }
-//    
-//    public class ClassDataDTO {
-//        private final String packageStr;
-//        private final String fullyQualifiedClassName;
-//        private final AccessModifier accessModifier;
-//    }
-//    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
 }
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ApiChangeDTO.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ApiChangeDTO.java
deleted file mode 100644
index 2d3588f6a7..0000000000
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ApiChangeDTO.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
- * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
- */
-package org.sleuthkit.autopsy.apiupdate;
-
-import japicmp.model.JApiChangeStatus;
-import java.util.Optional;
-
-/**
- *
- * @author gregd
- */
-public class ApiChangeDTO {
-    //NEW, REMOVED, UNCHANGED, MODIFIED
-
-    public interface PublicApiChangeable {
-
-        PublicApiChangeType getChangeType();
-    }
-
-    public static class ClassChangeDTO {
-
-        private final JApiChangeStatus changeStatus;
-        private final Optional<String> oldDeclaration;
-        private final Optional<Long> oldSerialId;
-        
-        private final Optional<String> newDeclaration;
-        private final Optional<Long> newSerialId;
-
-        public ClassChangeDTO(JApiChangeStatus changeStatus, Optional<String> oldDeclaration, Optional<Long> oldSerialId, Optional<String> newDeclaration, Optional<Long> newSerialId) {
-            this.changeStatus = changeStatus;
-            this.oldDeclaration = oldDeclaration;
-            this.oldSerialId = oldSerialId;
-            this.newDeclaration = newDeclaration;
-            this.newSerialId = newSerialId;
-        }
-
-        public JApiChangeStatus getChangeStatus() {
-            return changeStatus;
-        }
-
-        public Optional<String> getOldDeclaration() {
-            return oldDeclaration;
-        }
-
-        public Optional<Long> getOldSerialId() {
-            return oldSerialId;
-        }
-
-        public Optional<String> getNewDeclaration() {
-            return newDeclaration;
-        }
-
-        public Optional<Long> getNewSerialId() {
-            return newSerialId;
-        }
-        
-        
-    }
-
-//    public static class SuperclassChangeDTO {
-//
-//        private final Optional<String> oldFullyQualifiedClassName;
-//        private final Optional<String> newFullyQualifiedClassName;
-//
-//        public SuperclassChangeDTO(Optional<String> oldFullyQualifiedClassName, Optional<String> newFullyQualifiedClassName) {
-//            this.oldFullyQualifiedClassName = oldFullyQualifiedClassName;
-//            this.newFullyQualifiedClassName = newFullyQualifiedClassName;
-//        }
-//
-//        public Optional<String> getOldFullyQualifiedClassName() {
-//            return oldFullyQualifiedClassName;
-//        }
-//
-//        public Optional<String> getNewFullyQualifiedClassName() {
-//            return newFullyQualifiedClassName;
-//        }
-//
-//    }
-//
-//    public static class InterfaceChangeDTO {
-//
-//        private final JApiChangeStatus changeStatus;
-//        private final String fullyQualifiedName;
-//
-//        public InterfaceChangeDTO(JApiChangeStatus changeStatus, String fullyQualifiedName) {
-//            this.changeStatus = changeStatus;
-//            this.fullyQualifiedName = fullyQualifiedName;
-//        }
-//
-//        public JApiChangeStatus getChangeStatus() {
-//            return changeStatus;
-//        }
-//
-//        public String getFullyQualifiedName() {
-//            return fullyQualifiedName;
-//        }
-//
-//    }
-    public static class MethodChangeDTO {
-
-        private final JApiChangeStatus changeStatus;
-        private final Optional<String> oldMethodDeclaration;
-        private final Optional<String> newMethodDeclaration;
-
-        public MethodChangeDTO(JApiChangeStatus changeStatus, Optional<String> oldMethodDeclaration, Optional<String> newMethodDeclaration) {
-            this.changeStatus = changeStatus;
-            this.oldMethodDeclaration = oldMethodDeclaration;
-            this.newMethodDeclaration = newMethodDeclaration;
-        }
-
-        public JApiChangeStatus getChangeStatus() {
-            return changeStatus;
-        }
-
-        public Optional<String> getOldMethodDeclaration() {
-            return oldMethodDeclaration;
-        }
-
-        public Optional<String> getNewMethodDeclaration() {
-            return newMethodDeclaration;
-        }
-
-    }
-
-    public static class FieldChangeDTO {
-
-        private final JApiChangeStatus changeStatus;
-        private final Optional<String> oldFieldDeclaration;
-        private final Optional<String> newFieldDeclaration;
-
-        public FieldChangeDTO(JApiChangeStatus changeStatus, Optional<String> oldFieldDeclaration, Optional<String> newFieldDeclaration) {
-            this.changeStatus = changeStatus;
-            this.oldFieldDeclaration = oldFieldDeclaration;
-            this.newFieldDeclaration = newFieldDeclaration;
-        }
-
-        public JApiChangeStatus getChangeStatus() {
-            return changeStatus;
-        }
-
-        public Optional<String> getOldFieldDeclaration() {
-            return oldFieldDeclaration;
-        }
-
-        public Optional<String> getNewFieldDeclaration() {
-            return newFieldDeclaration;
-        }
-
-    }
-
-}
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/CLIProcessor.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/CLIProcessor.java
index 1b5597e235..ba09ebe11d 100644
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/CLIProcessor.java
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/CLIProcessor.java
@@ -5,6 +5,7 @@
 package org.sleuthkit.autopsy.apiupdate;
 
 import java.io.File;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -37,7 +38,7 @@ public class CLIProcessor {
             .hasArg(true)
             .longOpt("curr-path")
             .option("c")
-            .required(true)
+            .required(false)
             .build();
 
     static Option PREV_VERS_OPT = Option.builder()
@@ -46,7 +47,7 @@ public class CLIProcessor {
             .hasArg(true)
             .longOpt("prev-version")
             .option("pv")
-            .required(true)
+            .required(false)
             .build();
 
     static Option CUR_VERS_OPT = Option.builder()
@@ -55,7 +56,7 @@ public class CLIProcessor {
             .hasArg(true)
             .longOpt("curr-version")
             .option("cv")
-            .required(true)
+            .required(false)
             .build();
 
     static Option SRC_LOC_OPT = Option.builder()
@@ -67,15 +68,27 @@ public class CLIProcessor {
             .required(true)
             .build();
 
+    static Option UPDATE_OPT = Option.builder()
+            .desc("Update source code versions")
+            .hasArg(false)
+            .longOpt("update")
+            .option("u")
+            .required(false)
+            .build();
+
     static List<Option> ALL_OPTIONS = Arrays.asList(
             PREV_VERS_PATH_OPT,
             CUR_VERS_PATH_OPT,
             PREV_VERS_OPT,
             CUR_VERS_OPT,
-            SRC_LOC_OPT
+            SRC_LOC_OPT,
+            UPDATE_OPT
     );
 
     static Options CLI_OPTIONS = getCliOptions(ALL_OPTIONS);
+    private static final String DEFAULT_CURR_VERSION = "Current Version";
+    private static final String DEFAULT_PREV_VERSION = "Previous Version";
+    private static final String BUILD_REL_PATH = "build/cluster/modules";
 
     private static Options getCliOptions(List<Option> opts) {
         Options toRet = new Options();
@@ -112,15 +125,19 @@ static CLIArgs parseCli(String[] args) throws ParseException {
         CommandLine helpCmd = parser.parse(HELP_OPTIONS, args, true);
         boolean isHelp = helpCmd.hasOption(HELP_OPT);
         if (isHelp) {
-            return new CLIArgs(null, null, null, null, null, true);
+            return new CLIArgs(null, null, null, null, null, false, true);
         }
 
         CommandLine cmd = parser.parse(CLI_OPTIONS, args);
-        String curVers = cmd.getOptionValue(CUR_VERS_OPT);
-        String prevVers = cmd.getOptionValue(PREV_VERS_OPT);
-        String curVersPath = cmd.getOptionValue(CUR_VERS_PATH_OPT);
+        String curVers = cmd.hasOption(CUR_VERS_OPT) ? cmd.getOptionValue(CUR_VERS_OPT) : DEFAULT_CURR_VERSION;
+        String prevVers = cmd.hasOption(PREV_VERS_OPT) ? cmd.getOptionValue(PREV_VERS_OPT) : DEFAULT_PREV_VERSION;
+        String curVersPath = cmd.hasOption(CUR_VERS_PATH_OPT) 
+                ? cmd.getOptionValue(CUR_VERS_PATH_OPT) 
+                : Paths.get(cmd.getOptionValue(SRC_LOC_OPT), BUILD_REL_PATH).toString();
+        
         String prevVersPath = cmd.getOptionValue(PREV_VERS_PATH_OPT);
         String srcPath = cmd.getOptionValue(SRC_LOC_OPT);
+        boolean makeUpdate = cmd.hasOption(UPDATE_OPT);
         File curVersFile = new File(curVersPath);
         File prevVersFile = new File(prevVersPath);
         File srcPathFile = new File(srcPath);
@@ -137,7 +154,7 @@ static CLIArgs parseCli(String[] args) throws ParseException {
             throw new ParseException("No directory found at " + srcPathFile.getAbsolutePath());
         }
 
-        return new CLIArgs(curVers, prevVers, curVersFile, prevVersFile, srcPathFile, false);
+        return new CLIArgs(curVers, prevVers, curVersFile, prevVersFile, srcPathFile, makeUpdate, false);
     }
 
     public static class CLIArgs {
@@ -148,14 +165,16 @@ public static class CLIArgs {
         private final File previousVersPath;
         private final boolean isHelp;
         private final File srcPath;
+        private final boolean makeUpdate;
 
-        public CLIArgs(String currentVersion, String previousVersion, File currentVersPath, File previousVersPath, File srcPath, boolean isHelp) {
+        public CLIArgs(String currentVersion, String previousVersion, File currentVersPath, File previousVersPath, File srcPath, boolean makeUpdate, boolean isHelp) {
             this.currentVersion = currentVersion;
             this.previousVersion = previousVersion;
             this.currentVersPath = currentVersPath;
             this.previousVersPath = previousVersPath;
             this.srcPath = srcPath;
             this.isHelp = isHelp;
+            this.makeUpdate = makeUpdate;
         }
 
         public String getCurrentVersion() {
@@ -178,6 +197,10 @@ public boolean isIsHelp() {
             return isHelp;
         }
 
+        public boolean isMakeUpdate() {
+            return makeUpdate;
+        }
+
         public File getSrcPath() {
             return srcPath;
         }
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ChangeOutputGenerator.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ChangeOutputGenerator.java
deleted file mode 100644
index 15b8fe44ef..0000000000
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ChangeOutputGenerator.java
+++ /dev/null
@@ -1,662 +0,0 @@
-package org.sleuthkit.autopsy.apiupdate;
-
-import japicmp.cli.CliParser;
-import japicmp.config.Options;
-import japicmp.model.*;
-import japicmp.model.JApiAnnotationElementValue.Type;
-import static japicmp.model.JApiChangeStatus.MODIFIED;
-import static japicmp.model.JApiChangeStatus.NEW;
-import static japicmp.model.JApiChangeStatus.REMOVED;
-import static japicmp.model.JApiChangeStatus.UNCHANGED;
-import japicmp.output.OutputFilter;
-import japicmp.output.OutputGenerator;
-import javassist.bytecode.annotation.MemberValue;
-
-import java.util.Comparator;
-import java.util.List;
-
-import static japicmp.util.GenericTemplateHelper.haveGenericTemplateInterfacesChanges;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import javassist.CtClass;
-import org.apache.commons.lang3.StringUtils;
-import org.sleuthkit.autopsy.apiupdate.ApiChangeDTO.FieldChangeDTO;
-import org.sleuthkit.autopsy.apiupdate.ApiChangeDTO.InterfaceChangeDTO;
-import org.sleuthkit.autopsy.apiupdate.ApiChangeDTO.SuperclassChangeDTO;
-
-// taken from https://raw.githubusercontent.com/siom79/japicmp/master/japicmp/src/main/java/japicmp/output/stdout/StdoutOutputGenerator.java
-public class ChangeOutputGenerator extends OutputGenerator<ApiChangeDTO> {
-
-    static final String NO_CHANGES = "No changes.";
-    static final String WARNING = "WARNING";
-
-    public ChangeOutputGenerator(Options options, List<JApiClass> jApiClasses) {
-        super(options, jApiClasses);
-    }
-
-    @Override
-    public String generate() {
-        OutputFilter outputFilter = new OutputFilter(options);
-        outputFilter.filter(jApiClasses);
-        StringBuilder sb = new StringBuilder();
-        sb.append(options.getDifferenceDescription()).append('\n');
-        if (options.getIgnoreMissingClasses().isIgnoreAllMissingClasses()) {
-            sb.append(WARNING).append(": You are using the option '").append(CliParser.IGNORE_MISSING_CLASSES)
-                    .append("', i.e. superclasses and interfaces that could not be found on the classpath are ignored.")
-                    .append(" Hence changes caused by these superclasses and interfaces are not reflected in the output.\n");
-        } else if (options.getIgnoreMissingClasses().getIgnoreMissingClassRegularExpression().size() > 0) {
-            sb.append(WARNING).append(": You have ignored certain classes, i.e. superclasses and interfaces that could not ")
-                    .append("be found on the classpath are ignored. Hence changes caused by these superclasses and interfaces are not reflected in the output.\n");
-        }
-        if (jApiClasses.size() > 0) {
-            for (JApiClass jApiClass : jApiClasses) {
-                processClass(sb, jApiClass);
-                processConstructors(sb, jApiClass);
-                processMethods(sb, jApiClass);
-                processAnnotations(sb, jApiClass, 1);
-            }
-        } else {
-            sb.append(NO_CHANGES);
-        }
-        return sb.toString();
-    }
-
-    private void processAnnotations(StringBuilder sb, JApiHasAnnotations jApiClass, int numberofTabs) {
-        List<JApiAnnotation> annotations = jApiClass.getAnnotations();
-        for (JApiAnnotation jApiAnnotation : annotations) {
-            appendAnnotation(sb, signs(jApiAnnotation), jApiAnnotation, numberofTabs);
-            List<JApiAnnotationElement> elements = jApiAnnotation.getElements();
-            for (JApiAnnotationElement jApiAnnotationElement : elements) {
-                appendAnnotationElement(sb, signs(jApiAnnotationElement), jApiAnnotationElement, numberofTabs + 1);
-            }
-        }
-    }
-
-    private void processConstructors(StringBuilder sb, JApiClass jApiClass) {
-        List<JApiConstructor> constructors = jApiClass.getConstructors();
-        for (JApiConstructor jApiConstructor : constructors) {
-            appendBehavior(sb, signs(jApiConstructor), jApiConstructor, "CONSTRUCTOR:");
-            processAnnotations(sb, jApiConstructor, 2);
-            processExceptions(sb, jApiConstructor, 2);
-            processGenericTemplateChanges(sb, jApiConstructor, 2);
-        }
-    }
-
-    private void processMethods(StringBuilder sb, JApiClass jApiClass) {
-        List<JApiMethod> methods = jApiClass.getMethods();
-        for (JApiMethod jApiMethod : methods) {
-            appendBehavior(sb, signs(jApiMethod), jApiMethod, "METHOD:");
-            processAnnotations(sb, jApiMethod, 2);
-            processExceptions(sb, jApiMethod, 2);
-            processGenericTemplateChanges(sb, jApiMethod, 2);
-        }
-    }
-
-    private void processExceptions(StringBuilder sb, JApiBehavior jApiBehavior, int indent) {
-        for (JApiException exception : jApiBehavior.getExceptions()) {
-            appendException(sb, signs(exception), exception, indent);
-        }
-    }
-
-    private void appendException(StringBuilder sb, String signs, JApiException jApiException, int indent) {
-        sb.append(tabs(indent)).append(signs).append(" ").append(jApiException.getChangeStatus()).append(" EXCEPTION: ").append(jApiException.getName()).append("\n");
-    }
-
-    private void processClass(StringBuilder sb, JApiClass jApiClass) {
-        appendClass(sb, signs(jApiClass), jApiClass);
-    }
-
-    private void appendBehavior(StringBuilder sb, String signs, JApiBehavior jApiBehavior, String classMemberType) {
-        sb.append("\t").append(signs).append(" ").append(jApiBehavior.getChangeStatus()).append(" ").append(classMemberType).append(" ")
-                .append(accessModifierAsString(jApiBehavior)).append(abstractModifierAsString(jApiBehavior)).append(staticModifierAsString(jApiBehavior))
-                .append(finalModifierAsString(jApiBehavior)).append(syntheticModifierAsString(jApiBehavior)).append(bridgeModifierAsString(jApiBehavior))
-                .append(returnType(jApiBehavior)).append(jApiBehavior.getName()).append("(");
-        int paramCount = 0;
-        for (JApiParameter jApiParameter : jApiBehavior.getParameters()) {
-            if (paramCount > 0) {
-                sb.append(", ");
-            }
-            sb.append(jApiParameter.getType());
-            appendGenericTypes(sb, jApiParameter);
-            paramCount++;
-        }
-        sb.append(")\n");
-    }
-
-    private void appendGenericTypes(StringBuilder sb, JApiHasGenericTypes jApiHasGenericTypes) {
-        if (!jApiHasGenericTypes.getNewGenericTypes().isEmpty() || !jApiHasGenericTypes.getOldGenericTypes().isEmpty()) {
-            if (jApiHasGenericTypes instanceof JApiCompatibility) {
-                List<JApiCompatibilityChange> compatibilityChanges = ((JApiCompatibility) jApiHasGenericTypes).getCompatibilityChanges();
-                appendGenericTypesForCompatibilityChanges(sb, jApiHasGenericTypes, compatibilityChanges);
-            }
-        }
-    }
-
-    private void appendGenericTypesForCompatibilityChanges(StringBuilder sb, JApiHasGenericTypes jApiHasGenericTypes, List<JApiCompatibilityChange> compatibilityChanges) {
-        if (compatibilityChanges.contains(JApiCompatibilityChange.METHOD_PARAMETER_GENERICS_CHANGED)
-                || compatibilityChanges.contains(JApiCompatibilityChange.METHOD_RETURN_TYPE_GENERICS_CHANGED)
-                || compatibilityChanges.contains(JApiCompatibilityChange.FIELD_GENERICS_CHANGED)
-                || compatibilityChanges.contains(JApiCompatibilityChange.CLASS_GENERIC_TEMPLATE_GENERICS_CHANGED)) {
-            appendGenericTypes(sb, false, jApiHasGenericTypes.getNewGenericTypes());
-            appendGenericTypes(sb, true, jApiHasGenericTypes.getOldGenericTypes());
-        } else {
-            if (!jApiHasGenericTypes.getNewGenericTypes().isEmpty()) {
-                appendGenericTypes(sb, false, jApiHasGenericTypes.getNewGenericTypes());
-            }
-            if (!jApiHasGenericTypes.getOldGenericTypes().isEmpty()) {
-                appendGenericTypes(sb, false, jApiHasGenericTypes.getOldGenericTypes());
-            }
-        }
-    }
-
-    private void appendGenericTypes(StringBuilder sb, boolean withChangeInParenthesis, List<JApiGenericType> genericTypes) {
-        if (!genericTypes.isEmpty()) {
-            if (withChangeInParenthesis) {
-                sb.append("(<- ");
-            }
-            sb.append("<");
-            int count = 0;
-            for (JApiGenericType genericType : genericTypes) {
-                if (count > 0) {
-                    sb.append(",");
-                }
-                appendGenericType(sb, genericType);
-                if (!genericType.getGenericTypes().isEmpty()) {
-                    appendGenericTypes(sb, false, genericType.getGenericTypes());
-                }
-                count++;
-            }
-            sb.append(">");
-            if (withChangeInParenthesis) {
-                sb.append(")");
-            }
-        }
-    }
-
-    private void appendGenericType(StringBuilder sb, JApiGenericType jApiGenericType) {
-        if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.NONE) {
-            sb.append(jApiGenericType.getType());
-        } else if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.UNBOUNDED) {
-            sb.append("?");
-        } else if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.EXTENDS) {
-            sb.append("? extends ").append(jApiGenericType.getType());
-        } else if (jApiGenericType.getGenericWildCard() == JApiGenericType.JApiGenericWildCard.SUPER) {
-            sb.append("? super ").append(jApiGenericType.getType());
-        }
-    }
-
-    private String returnType(JApiBehavior jApiBehavior) {
-        StringBuilder sb = new StringBuilder();
-        if (jApiBehavior instanceof JApiMethod) {
-            JApiMethod method = (JApiMethod) jApiBehavior;
-            JApiReturnType jApiReturnType = method.getReturnType();
-            if (jApiReturnType.getChangeStatus() == JApiChangeStatus.UNCHANGED) {
-                sb.append(jApiReturnType.getNewReturnType());
-                appendGenericTypes(sb, jApiReturnType);
-                sb.append(" ");
-            } else if (jApiReturnType.getChangeStatus() == JApiChangeStatus.MODIFIED) {
-                sb.append(jApiReturnType.getNewReturnType());
-                appendGenericTypes(sb, jApiReturnType);
-                sb.append(" (<-");
-                sb.append(jApiReturnType.getOldReturnType());
-                appendGenericTypes(sb, jApiReturnType);
-                sb.append(") ");
-            } else if (jApiReturnType.getChangeStatus() == JApiChangeStatus.NEW) {
-                sb.append(jApiReturnType.getNewReturnType());
-                appendGenericTypes(sb, jApiReturnType);
-                sb.append(" ");
-            } else {
-                sb.append(jApiReturnType.getOldReturnType());
-                appendGenericTypes(sb, jApiReturnType);
-                sb.append(" ");
-            }
-        }
-        return sb.toString();
-    }
-
-    private void appendAnnotation(StringBuilder sb, String signs, JApiAnnotation jApiAnnotation, int numberOfTabs) {
-        sb.append(String.format("%s%s %s ANNOTATION: %s\n", tabs(numberOfTabs), signs, jApiAnnotation.getChangeStatus(), jApiAnnotation.getFullyQualifiedName()));
-    }
-
-    private void appendAnnotationElement(StringBuilder sb, String signs, JApiAnnotationElement jApiAnnotationElement, int numberOfTabs) {
-        sb.append(String.format("%s%s %s ELEMENT: %s=", tabs(numberOfTabs), signs, jApiAnnotationElement.getChangeStatus(), jApiAnnotationElement.getName()));
-        Optional<MemberValue> oldValue = jApiAnnotationElement.getOldValue();
-        Optional<MemberValue> newValue = jApiAnnotationElement.getNewValue();
-        if (oldValue.isPresent() && newValue.isPresent()) {
-            if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.UNCHANGED) {
-                sb.append(elementValueList2String(jApiAnnotationElement.getNewElementValues()));
-            } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.REMOVED) {
-                sb.append(String.format("%s (-)", elementValueList2String(jApiAnnotationElement.getOldElementValues())));
-            } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.NEW) {
-                sb.append(String.format("%s (+)", elementValueList2String(jApiAnnotationElement.getNewElementValues())));
-            } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.MODIFIED) {
-                sb.append(String.format("%s (<- %s)", elementValueList2String(jApiAnnotationElement.getNewElementValues()), elementValueList2String(jApiAnnotationElement.getOldElementValues())));
-            }
-        } else if (!oldValue.isPresent() && newValue.isPresent()) {
-            sb.append(String.format("%s (+)", elementValueList2String(jApiAnnotationElement.getNewElementValues())));
-        } else if (oldValue.isPresent() && !newValue.isPresent()) {
-            sb.append(String.format("%s (-)", elementValueList2String(jApiAnnotationElement.getOldElementValues())));
-        } else {
-            sb.append(" n.a.");
-        }
-        sb.append("\n");
-    }
-
-    private void appendClass(StringBuilder sb, String signs, JApiClass jApiClass) {
-        sb.append(signs).append(" ").append(jApiClass.getChangeStatus()).append(" ")
-                .append(processClassType(jApiClass)).append(": ")
-                .append(accessModifierAsString(jApiClass))
-                .append(abstractModifierAsString(jApiClass))
-                .append(staticModifierAsString(jApiClass))
-                .append(finalModifierAsString(jApiClass))
-                .append(syntheticModifierAsString(jApiClass))
-                .append(jApiClass.getFullyQualifiedName()).append(" ")
-                .append(javaObjectSerializationStatus(jApiClass)).append("\n");
-
-        processGenericTemplateChanges(sb, jApiClass, 1);
-        processInterfaceChanges(sb, jApiClass);
-        processSuperclassChanges(sb, jApiClass);
-        processFieldChanges(sb, jApiClass);
-    }
-
-    private String getClassString(JApiClass clz, boolean oldClass) {
-        // TODO serial version id
-        // TODO annotations
-        String accessModifier = str(get(clz.getAccessModifier(), oldClass).orElse(null));
-        String abstractModifier = str(get(clz.getAbstractModifier(), oldClass).orElse(null));
-        String staticModifier = str(get(clz.getStaticModifier(), oldClass).orElse(null));
-        String finalModifier = str(get(clz.getFinalModifier(), oldClass).orElse(null));
-        String syntheticModifier = str(get(clz.getSyntheticModifier(), oldClass).orElse(null));
-
-        String type = str(clz.getClassType(), oldClass);
-        String name = clz.getFullyQualifiedName();
-
-        String genericModifier = strGeneric(clz.getGenericTemplates(), oldClass);
-
-        String implementsModifier = strImplements(clz.getInterfaces(), oldClass);
-        String extendsModifier = str(clz.getSuperclass(), oldClass);
-
-        return Stream.of(
-                accessModifier,
-                abstractModifier,
-                staticModifier,
-                finalModifier,
-                syntheticModifier,
-                type,
-                name + (StringUtils.isBlank(genericModifier) ? "" : genericModifier),
-                implementsModifier,
-                extendsModifier)
-                .filter(StringUtils::isNotBlank)
-                .collect(Collectors.joining(" "));
-    }
-    
-    
-    private String str(JApiSuperclass supClz, boolean oldClass) {
-        Optional<CtClass> ctClass = convert(oldClass ? supClz.getOldSuperclass() : supClz.getNewSuperclass());
-        String supClassExt = ctClass.map(ctClz -> str(ctClz)).orElse(null);
-        return StringUtils.isBlank(supClassExt) ? null : "extends " + supClassExt;
-    }
-    
-    private String strImplements(List<JApiImplementedInterface> interfaces, boolean oldClass) {
-        if (interfaces.isEmpty()) {
-            return null;
-        }
-        
-        String interfaceStr = interfaces.stream()
-                .filter(i -> (oldClass && i.getChangeStatus() == NEW) || (!oldClass && i.getChangeStatus() == REMOVED))
-                .map(i -> str(i.getCtClass()))
-                .collect(Collectors.joining(", "));
-        
-        return StringUtils.isNotBlank(interfaceStr) ? "implements " + interfaceStr : null;
-    }
-
-    
-    private String str(CtClass ctClass) {
-        return ctClass.getName() + (StringUtils.isBlank(ctClass.getGenericSignature()) ? "" : "<" + ctClass.getGenericSignature() + ">");
-    }
-    
-    private String str(JApiClassType classType, boolean oldClass) {
-        return Optional.ofNullable(classType)
-                .flatMap(ct -> convert(oldClass ? ct.getOldTypeOptional() : ct.getNewTypeOptional()))
-                .map(ct -> ct.name().toLowerCase())
-                .orElse("class");
-    }
-
-    private String strGeneric(List<JApiGenericTemplate> templates, boolean oldClass) {
-        if (templates.isEmpty()) {
-            return null;
-        }
-
-        String genericParams = templates.stream()
-                .map(t -> convert(oldClass ? 
-                        str(t.getName(), convert(t.getOldTypeOptional()), t.getOldInterfaceTypes()) :
-                        str(t.getName(), convert(t.getNewTypeOptional()), t.getNewInterfaceTypes())
-                )
-                .filter(Optional::isPresent)
-                .map(t -> t.get())
-                .collect(Collectors.joining(", "));
-
-        return StringUtils.isBlank(genericParams)
-                ? null
-                : "<" + genericParams + ">";
-    }
-    
-    private String strGeneric(String name, Optional<String> mainType, List<JApiGenericType> genericType) {
-        
-    }
-
-    // jApiGenericTemplate.getName() + ":" + interfaceTypes.join( "&")
-//            
-//    private List<GenericTemplateChangeDTO> getGenericTemplateChanges(JApiHasGenericTemplates jApiHasGenericTemplates) {        
-//        if (!genericTemplates.isEmpty()) {
-//            sb.append(tabs(numberOfTabs)).append("GENERIC TEMPLATES: ");
-//            genericTemplates.sort(Comparator.comparing(JApiGenericTemplate::getName));
-//            int count = 0;
-//            for (JApiGenericTemplate jApiGenericTemplate : genericTemplates) {
-//                if (count > 0) {
-//                    sb.append(", ");
-//                }
-//                count++;
-//                sb.append(signs(jApiGenericTemplate));
-//                if (sb.charAt(sb.length() - 1) != ' ') {
-//                    sb.append(" ");
-//                }
-//                sb.append(jApiGenericTemplate.getName()).append(":");
-//                JApiChangeStatus changeStatus = jApiGenericTemplate.getChangeStatus();
-//                if (changeStatus == JApiChangeStatus.NEW || changeStatus == JApiChangeStatus.UNCHANGED) {
-//                    sb.append(jApiGenericTemplate.getNewType());
-//                    if (jApiGenericTemplate instanceof JApiCompatibility) {
-//                        appendGenericTypesForCompatibilityChanges(sb, jApiGenericTemplate, ((JApiCompatibility) jApiHasGenericTemplates).getCompatibilityChanges());
-//                    }
-//                } else if (changeStatus == JApiChangeStatus.REMOVED) {
-//                    sb.append(jApiGenericTemplate.getOldType());
-//                    if (jApiGenericTemplate instanceof JApiCompatibility) {
-//                        appendGenericTypesForCompatibilityChanges(sb, jApiGenericTemplate, ((JApiCompatibility) jApiHasGenericTemplates).getCompatibilityChanges());
-//                    }
-//                } else {
-//                    sb.append(jApiGenericTemplate.getNewType());
-//                    appendGenericTypes(sb, false, jApiGenericTemplate.getNewGenericTypes());
-//                    sb.append(" (<-").append(jApiGenericTemplate.getOldType());
-//                    appendGenericTypes(sb, false, jApiGenericTemplate.getOldGenericTypes());
-//                    sb.append(")");
-//                }
-//                if (!jApiGenericTemplate.getOldInterfaceTypes().isEmpty() || !jApiGenericTemplate.getNewInterfaceTypes().isEmpty()) {
-//                    if (haveGenericTemplateInterfacesChanges(jApiGenericTemplate.getOldInterfaceTypes(), jApiGenericTemplate.getNewInterfaceTypes())) {
-//                        appendGenericTemplatesInterfaces(sb, jApiGenericTemplate, false, true);
-//                        sb.append(" (<-");
-//                        appendGenericTemplatesInterfaces(sb, jApiGenericTemplate, true, false);
-//                        sb.append(")");
-//                    } else {
-//                        appendGenericTemplatesInterfaces(sb, jApiGenericTemplate, false, true);
-//                    }
-//                }
-//            }
-//            sb.append("\n");
-//        }
-//    }
-    private void appendGenericTemplatesInterfaces(StringBuilder sb, JApiGenericTemplate jApiGenericTemplate, boolean printOld, boolean printNew) {
-        if (printOld) {
-            for (JApiGenericType jApiGenericType : jApiGenericTemplate.getOldInterfaceTypes()) {
-                sb.append(" & ");
-                sb.append(jApiGenericType.getType());
-                appendGenericTypes(sb, false, jApiGenericType.getGenericTypes());
-            }
-        }
-        if (printNew) {
-            for (JApiGenericType jApiGenericType : jApiGenericTemplate.getNewInterfaceTypes()) {
-                sb.append(" & ");
-                sb.append(jApiGenericType.getType());
-                appendGenericTypes(sb, false, jApiGenericType.getGenericTypes());
-            }
-        }
-    }
-
-    
-    private FieldChangeDTO getFieldChange(JApiField field) {
-        return new FieldChangeDTO(
-                field.getChangeStatus(),
-                Optional.ofNullable(getFieldString(field, true)),
-                Optional.ofNullable(getFieldString(field, false)));
-    }
-
-    private String getFieldString(JApiField field, boolean oldField) {
-        String accessModifier = str(get(field.getAccessModifier(), oldField).orElse(null));
-        String staticModifier = str(get(field.getStaticModifier(), oldField).orElse(null));
-        String finalModifier = str(get(field.getFinalModifier(), oldField).orElse(null));
-        String syntheticModifier = str(get(field.getSyntheticModifier(), oldField).orElse(null));
-        String fieldType = strOpt(field.getType(), oldField).orElse(null);
-        String name = field.getName();
-
-        return Stream.of(accessModifier, staticModifier, finalModifier, syntheticModifier, fieldType, name)
-                .filter(StringUtils::isNotBlank)
-                .collect(Collectors.joining(" "));
-    }
-
-    private String str(AccessModifier modifier) {
-        if (modifier == null || modifier == AccessModifier.PACKAGE_PROTECTED) {
-            return null;
-        } else {
-            return modifier.name().toLowerCase();
-        }
-    }
-
-    private String str(StaticModifier modifier) {
-        return (modifier == StaticModifier.STATIC)
-                ? "static"
-                : null;
-    }
-
-    private String str(FinalModifier modifier) {
-        return (modifier == FinalModifier.FINAL)
-                ? "final"
-                : null;
-    }
-
-    private String str(SyntheticModifier modifier) {
-        return (modifier == SyntheticModifier.SYNTHETIC)
-                ? "synthetic"
-                : null;
-    }
-
-    private String str(AbstractModifier modifier) {
-        return (modifier == AbstractModifier.ABSTRACT)
-                ? "abstract"
-                : null;
-    }
-
-    private Optional<String> strOpt(JApiType tp, boolean oldField) {
-        return oldField ? convert(tp.getOldTypeOptional()) : convert(tp.getNewTypeOptional());
-    }
-
-    private <T> Optional<T> get(JApiModifier<T> modifier, boolean oldField) {
-        return oldField ? convert(modifier.getOldModifier()) : convert(modifier.getNewModifier());
-    }
-
-//    private Optional<SuperclassChangeDTO> getSuperClassChange(JApiSuperclass jApiSuperclass) {
-//        return jApiSuperclass.getChangeStatus() != JApiChangeStatus.UNCHANGED
-//                ? Optional.ofNullable(new SuperclassChangeDTO(
-//                        convert(jApiSuperclass.getOldSuperclassName()),
-//                        convert(jApiSuperclass.getNewSuperclassName())))
-//                : Optional.empty();
-//    }
-//
-//    private List<InterfaceChangeDTO> getInterfaceChanges(JApiClass jApiClass) {
-//        return jApiClass.getInterfaces().stream()
-//                .filter(intfc -> intfc.getChangeStatus() != JApiChangeStatus.UNCHANGED)
-//                .map(intfc -> new InterfaceChangeDTO(intfc.getChangeStatus(), intfc.getFullyQualifiedName()))
-//                .collect(Collectors.toList());
-//    }
-    private <T> Optional<T> convert(japicmp.util.Optional<T> apiOpt) {
-        T val = apiOpt.or((T) null);
-        return java.util.Optional.ofNullable(val);
-    }
-
-//
-//    private void processClassFileFormatVersionChanges(StringBuilder sb, JApiClass jApiClass) {
-//        JApiClassFileFormatVersion classFileFormatVersion = jApiClass.getClassFileFormatVersion();
-//        sb.append(tabs(1))
-//                .append(signs(classFileFormatVersion))
-//                .append(" CLASS FILE FORMAT VERSION: ");
-//        if (classFileFormatVersion.getMajorVersionNew() != -1 && classFileFormatVersion.getMinorVersionNew() != -1) {
-//            sb.append(classFileFormatVersion.getMajorVersionNew()).append(".").append(classFileFormatVersion.getMinorVersionNew());
-//        } else {
-//            sb.append("n.a.");
-//        }
-//        sb.append(" <- ");
-//        if (classFileFormatVersion.getMajorVersionOld() != -1 && classFileFormatVersion.getMinorVersionOld() != -1) {
-//            sb.append(classFileFormatVersion.getMajorVersionOld()).append(".").append(classFileFormatVersion.getMinorVersionOld());
-//        } else {
-//            sb.append("n.a.");
-//        }
-//        sb.append("\n");
-//    }
-//    private String processClassType(JApiClass jApiClass) {
-//        JApiClassType classType = jApiClass.getClassType();
-//        switch (classType.getChangeStatus()) {
-//            case NEW:
-//                return classType.getNewType();
-//            case REMOVED:
-//                return classType.getOldType();
-//            case MODIFIED:
-//                return classType.getNewType() + " (<- " + classType.getOldType() + ") ";
-//            case UNCHANGED:
-//                return classType.getOldType();
-//        }
-//        return "n.a.";
-//    }
-//
-//    // TODO do we need this
-//    private String getJavaObjectSerializationStatus(JApiClass jApiClass) {
-//        return jApiClass.getJavaObjectSerializationCompatible().getDescription();
-//    }
-//    private String elementValueList2String(List<JApiAnnotationElementValue> values) {
-//        StringBuilder sb = new StringBuilder();
-//        for (JApiAnnotationElementValue value : values) {
-//            if (sb.length() > 0) {
-//                sb.append(",");
-//            }
-//            if (value.getName().isPresent()) {
-//                sb.append(value.getName().get()).append("=");
-//            }
-//            if (value.getType() != Type.Array && value.getType() != Type.Annotation) {
-//                if (value.getType() == Type.Enum) {
-//                    sb.append(value.getFullyQualifiedName()).append(".").append(value.getValueString());
-//                } else {
-//                    sb.append(value.getValueString());
-//                }
-//            } else {
-//                if (value.getType() == Type.Array) {
-//                    sb.append("{").append(elementValueList2String(value.getValues())).append("}");
-//                } else if (value.getType() == Type.Annotation) {
-//                    sb.append("@").append(value.getFullyQualifiedName()).append("(").append(elementValueList2String(value.getValues())).append(")");
-//                }
-//            }
-//        }
-//        return sb.toString();
-//    }
-//
-//    private String tabs(int numberOfTabs) {
-//        if (numberOfTabs <= 0) {
-//            return "";
-//        } else if (numberOfTabs == 1) {
-//            return "\t";
-//        } else if (numberOfTabs == 2) {
-//            return "\t\t";
-//        } else {
-//            StringBuilder sb = new StringBuilder();
-//            for (int i = 0; i < numberOfTabs; i++) {
-//                sb.append("\t");
-//            }
-//            return sb.toString();
-//        }
-//    }
-//    private String signs(JApiHasChangeStatus hasChangeStatus) {
-//        JApiChangeStatus changeStatus = hasChangeStatus.getChangeStatus();
-//        String retVal = "???";
-//        switch (changeStatus) {
-//            case UNCHANGED:
-//                retVal = "===";
-//                break;
-//            case NEW:
-//                retVal = "+++";
-//                break;
-//            case REMOVED:
-//                retVal = "---";
-//                break;
-//            case MODIFIED:
-//                retVal = "***";
-//                break;
-//        }
-//        boolean binaryCompatible = true;
-//        boolean sourceCompatible = true;
-//        if (hasChangeStatus instanceof JApiCompatibility) {
-//            JApiCompatibility jApiCompatibility = (JApiCompatibility) hasChangeStatus;
-//            binaryCompatible = jApiCompatibility.isBinaryCompatible();
-//            sourceCompatible = jApiCompatibility.isSourceCompatible();
-//        }
-//        if (binaryCompatible) {
-//            if (sourceCompatible) {
-//                retVal += " ";
-//            } else {
-//                retVal += "*";
-//            }
-//        } else {
-//            retVal += "!";
-//        }
-//        return retVal;
-//    } 
-//
-//    private String bridgeModifierAsString(JApiHasBridgeModifier modifier) {
-//        JApiModifier<BridgeModifier> bridgeModifier = modifier.getBridgeModifier();
-//        return modifierAsString(bridgeModifier, BridgeModifier.NON_BRIDGE);
-//    }
-//    private <T> String modifierAsString(JApiModifier<T> modifier, T notPrintValue) {
-//        if (modifier.getOldModifier().isPresent() && modifier.getNewModifier().isPresent()) {
-//            if (modifier.getChangeStatus() == JApiChangeStatus.MODIFIED) {
-//                return modifier.getNewModifier().get() + " (<- " + modifier.getOldModifier().get() + ") ";
-//            } else if (modifier.getChangeStatus() == JApiChangeStatus.NEW) {
-//                if (modifier.getNewModifier().get() != notPrintValue) {
-//                    return modifier.getNewModifier().get() + "(+) ";
-//                }
-//            } else if (modifier.getChangeStatus() == JApiChangeStatus.REMOVED) {
-//                if (modifier.getOldModifier().get() != notPrintValue) {
-//                    return modifier.getOldModifier().get() + "(-) ";
-//                }
-//            } else {
-//                if (modifier.getNewModifier().get() != notPrintValue) {
-//                    return modifier.getNewModifier().get() + " ";
-//                }
-//            }
-//        } else if (modifier.getOldModifier().isPresent()) {
-//            if (modifier.getOldModifier().get() != notPrintValue) {
-//                return modifier.getOldModifier().get() + "(-) ";
-//            }
-//        } else if (modifier.getNewModifier().isPresent()) {
-//            if (modifier.getNewModifier().get() != notPrintValue) {
-//                return modifier.getNewModifier().get() + "(+) ";
-//            }
-//        }
-//        return "";
-//    }
-//    private String fieldTypeChangeAsString(JApiField field) {
-//        JApiType type = field.getType();
-//        if (type.getOldTypeOptional().isPresent() && type.getNewTypeOptional().isPresent()) {
-//            if (type.getChangeStatus() == JApiChangeStatus.MODIFIED) {
-//                return type.getNewTypeOptional().get() + " (<- " + type.getOldTypeOptional().get() + ")";
-//            } else if (type.getChangeStatus() == JApiChangeStatus.NEW) {
-//                return type.getNewTypeOptional().get() + "(+)";
-//            } else if (type.getChangeStatus() == JApiChangeStatus.REMOVED) {
-//                return type.getOldTypeOptional().get() + "(-)";
-//            } else {
-//                return type.getNewTypeOptional().get();
-//            }
-//        } else if (type.getOldTypeOptional().isPresent() && !type.getNewTypeOptional().isPresent()) {
-//            return type.getOldTypeOptional().get();
-//        } else if (!type.getOldTypeOptional().isPresent() && type.getNewTypeOptional().isPresent()) {
-//            return type.getNewTypeOptional().get();
-//        }
-//        return "n.a.";
-//    }
-
-}
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java
index ee0f93ce7f..48d5735ece 100644
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/Main.java
@@ -4,10 +4,15 @@
 package org.sleuthkit.autopsy.apiupdate;
 
 import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.apache.commons.cli.ParseException;
+import org.sleuthkit.autopsy.apiupdate.APIDiff.ComparisonRecord;
 import org.sleuthkit.autopsy.apiupdate.CLIProcessor.CLIArgs;
+import org.sleuthkit.autopsy.apiupdate.ModuleUpdates.ModuleVersionNumbers;
 
 /**
  *
@@ -15,6 +20,8 @@
  */
 public class Main {
 
+    private static final Logger LOGGER = Logger.getLogger(Main.class.getName());
+
     public static void main(String[] args) {
         args = "-c C:\\Users\\gregd\\Desktop\\apidiff\\new -p C:\\Users\\gregd\\Desktop\\apidiff\\old -cv 4.21.0 -pv 4.20.0 -s C:\\Users\\gregd\\Documents\\Source\\autopsy".split(" ");
         CLIArgs cliArgs;
@@ -29,67 +36,69 @@ public static void main(String[] args) {
             System.exit(-1);
             return;
         }
-        
-//        Map<String, ModuleVersionNumbers> versNums = Stream.of(
-//                new ModuleVersionNumbers(
-//                        "org.sleuthkit.autopsy.core", 
-//                        new ModuleUpdates.SemVer(1,2,3),
-//                        4,
-//                        new ReleaseVal("org.sleuthkit.autopsy.core", 5)),
-//                new ModuleVersionNumbers(
-//                        "org.sleuthkit.autopsy.corelibs", 
-//                        new ModuleUpdates.SemVer(6,7,8),
-//                        9,
-//                        new ReleaseVal("org.sleuthkit.autopsy.corelibs", 10)))
-//                .collect(Collectors.toMap(v -> v.getModuleName(), v -> v, (v1, v2) -> v1));
-//        
-//        ModuleUpdates.setVersions(cliArgs.getSrcPath(), versNums);
 
+        Map<String, ModuleVersionNumbers> newVersionNumMapping = new HashMap<>();
+        
         for (String commonJarFileName : APIDiff.getCommonJars(cliArgs.getPreviousVersPath(), cliArgs.getCurrentVersPath())) {
             try {
-//                ModuleVersionNumbers m = ModuleUpdates.getVersionsFromJar(cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile());
-//                System.out.println(MessageFormat.format("release: {0}, spec: {1}, implementation: {2}", m.getRelease().getFullReleaseStr(), m.getSpec().getSemVerStr(), m.getImplementation()));
-                APIDiff.getComparison(
-                        cliArgs.getPreviousVersion(), 
-                        cliArgs.getCurrentVersion(), 
+                ModuleVersionNumbers prevVersionNums = ModuleUpdates.getVersionsFromJar(cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile());
+
+                ComparisonRecord record = APIDiff.getComparison(
+                        cliArgs.getPreviousVersion(),
+                        cliArgs.getCurrentVersion(),
                         cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile(),
                         cliArgs.getCurrentVersPath().toPath().resolve(commonJarFileName).toFile());
+
+                ModuleVersionNumbers projectedVersionNums = ModuleUpdates.getModuleVersionUpdate(prevVersionNums, record.getChangeType());
+
+                outputDiff(commonJarFileName, record, prevVersionNums, projectedVersionNums);
+
+                newVersionNumMapping.put(commonJarFileName, projectedVersionNums);
             } catch (IOException ex) {
                 Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
             }
-            
-        }
-        
-        
-        
-//        for (String commonJarFileName : getCommonJars(cliArgs.getPreviousVersPath(), cliArgs.getCurrentVersPath())) {
-////            getComparison(
-////                    cliArgs.getPreviousVersion(),
-////                    cliArgs.getCurrentVersion(),
-////                    cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile(),
-////                    cliArgs.getCurrentVersPath().toPath().resolve(commonJarFileName).toFile());
-//            try {
-//                Set<String> pubPackages = getPublicPackages(cliArgs.getPreviousVersPath().toPath().resolve(commonJarFileName).toFile());
-//                System.out.println(pubPackages);
-//            } catch (IOException ex) {
-//                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
-//            } catch (IllegalStateException ex) {
-//                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
-//            }
-//        }
-
-    }
 
+        }
 
-    
-    
-    private static void mainRun() {
+        if (cliArgs.isMakeUpdate()) {
+            ModuleUpdates.setVersions(cliArgs.getSrcPath(), newVersionNumMapping);
+        }
 
-        // get public API diff's, for each jar
-        // limit to public packages
-        // one of the following:
-        // generate text output of difference
-        // update version numbers in manifest file/references accordingly
     }
 
+    private static void outputDiff(
+            String commonJarFileName,
+            ComparisonRecord record,
+            ModuleVersionNumbers prevVersionNums,
+            ModuleVersionNumbers projectedVersionNums
+    ) {
+        LOGGER.log(Level.INFO, MessageFormat.format("\n"
+                + "====================================\n"
+                + "DIFF FOR: {0}\n"
+                + "Public API Change Type: {1}\n"
+                + "Previous Version Numbers:\n"
+                + "  - release: {2}\n"
+                + "  - specification: {3}\n"
+                + "  - implementation: {4}\n"
+                + "Current Version Numbers:\n"
+                + "  - release: {5}\n"
+                + "  - specification: {6}\n"
+                + "  - implementation: {7}\n"
+                + "====================================\n"
+                + "Public API packages only in previous: {8}\n"
+                + "Public API packages only in current: {9}\n"
+                + "{10}\n\n",
+                commonJarFileName,
+                record.getChangeType(),
+                prevVersionNums.getRelease().getFullReleaseStr(),
+                prevVersionNums.getSpec().getSemVerStr(),
+                prevVersionNums.getImplementation(),
+                projectedVersionNums.getRelease().getFullReleaseStr(),
+                projectedVersionNums.getSpec().getSemVerStr(),
+                projectedVersionNums.getImplementation(),
+                record.getOnlyPrevApiPackages(),
+                record.getOnlyCurrApiPackages(),
+                record.getHumanReadableApiChange()
+        ));
+    }
 }
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java
index 7453523351..efc1c02ed0 100644
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/ModuleUpdates.java
@@ -155,7 +155,47 @@ public static ModuleVersionNumbers getVersionsFromJar(File jarFile) throws IOExc
         return new ModuleVersionNumbers(jarFile.getName(), specSemVer, implementation, release);
     }
 
-    private static void updateVersions() {
+    static ModuleVersionNumbers getModuleVersionUpdate(ModuleVersionNumbers prev, PublicApiChangeType apiChangeType) {
+        switch (apiChangeType) {
+            case NONE:
+                return new ModuleVersionNumbers(
+                        prev.getModuleName(), 
+                        prev.getSpec(), 
+                        prev.getImplementation() + 1, 
+                        prev.getRelease()
+                );
+            case COMPATIBLE_CHANGE:
+                return new ModuleVersionNumbers(
+                        prev.getModuleName(), 
+                        new SemVer(
+                                prev.getSpec().getMajor(), 
+                                prev.getSpec().getMinor() + 1, 
+                                prev.getSpec().getPatch()
+                        ),
+                        prev.getImplementation() + 1, 
+                        new ReleaseVal(
+                                prev.getRelease().getModuleName(), 
+                                prev.getRelease().getReleaseVersion()
+                        )
+                );
+            case INCOMPATIBLE_CHANGE:
+                return new ModuleVersionNumbers(
+                        prev.getModuleName(), 
+                        new SemVer(
+                                prev.getSpec().getMajor() + 1, 
+                                prev.getSpec().getMinor(), 
+                                prev.getSpec().getPatch()
+                        ),
+                        prev.getImplementation() + 1, 
+                        new ReleaseVal(
+                                prev.getRelease().getModuleName(), 
+                                prev.getRelease().getReleaseVersion() == null ? null : prev.getRelease().getReleaseVersion() + 1
+                        )
+                );
+            default:
+                throw new IllegalArgumentException("Unknown api change type: " + apiChangeType);
+        }
+        
 //        [specification major/minor/patch, implementation, release]
 //        assumed defaults???
 //        NON_COMPATIBLE:
diff --git a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/PublicApiChangeType.java b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/PublicApiChangeType.java
index d3379ac93e..011f7a59ca 100644
--- a/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/PublicApiChangeType.java
+++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/PublicApiChangeType.java
@@ -4,10 +4,30 @@
  */
 package org.sleuthkit.autopsy.apiupdate;
 
+import java.util.Comparator;
+import org.apache.commons.lang3.ObjectUtils;
+
 /**
  *
  * @author gregd
  */
-public enum PublicApiChangeType {
-    NONE, COMPATIBLE_CHANGE, INCOMPATIBLE_CHANGE;
+public enum PublicApiChangeType implements Comparator<PublicApiChangeType> {
+    NONE(0), COMPATIBLE_CHANGE(1), INCOMPATIBLE_CHANGE(2);
+
+    private int level;
+
+    PublicApiChangeType(int level) {
+        this.level = level;
+    }
+
+    public int getLevel() {
+        return level;
+    }
+
+    @Override
+    public int compare(PublicApiChangeType o1, PublicApiChangeType o2) {
+        o1 = ObjectUtils.defaultIfNull(o1, PublicApiChangeType.NONE);
+        o2 = ObjectUtils.defaultIfNull(o2, PublicApiChangeType.NONE);
+        return Integer.compare(o1.getLevel(), o2.getLevel());
+    }
 }
-- 
GitLab