diff --git a/.gitignore b/.gitignore index ae1f3c5722..04f7218a7f 100755 --- a/.gitignore +++ b/.gitignore @@ -78,8 +78,6 @@ genfiles.properties !/netbeans-plat/15 /docs/doxygen-user/user-docs /docs/doxygen-dev/build-docs -/jdiff-javadocs/* -/jdiff-logs/* /gen_version.txt hs_err_pid*.log diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties index 44e71a4513..8ad7ccebe9 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties @@ -6,7 +6,7 @@ CTLicenseDialog.title=Add a License... CTLicenseDialog.licenseNumberLabel.text=License Number: CTLicenseDialog.licenseNumberTextField.text= CTLicenseDialog.cancelButton.text=Cancel -CTLicenseDialog.okButton.text=Ok +CTLicenseDialog.okButton.text=OK CTLicenseDialog.warningLabel.text= CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text= CTMalwareScannerOptionsPanel.countersResetLabel.text= diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED index d9876a8ed7..8b72302f47 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED @@ -6,7 +6,7 @@ CTLicenseDialog.title=Add a License... CTLicenseDialog.licenseNumberLabel.text=License Number: CTLicenseDialog.licenseNumberTextField.text= CTLicenseDialog.cancelButton.text=Cancel -CTLicenseDialog.okButton.text=Ok +CTLicenseDialog.okButton.text=OK CTLicenseDialog.warningLabel.text= CTLicenseDialog_verifyInput_licenseNumberError=Please enter a license number CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text= diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form index 2ea57d43a8..cfa4c7e818 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form @@ -23,7 +23,7 @@ - + @@ -44,6 +44,21 @@ + + + + + + + + + + + + + + + @@ -92,50 +107,50 @@ - - - - - - - - - + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java index 99fc749dd7..4510a34cbe 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java @@ -58,6 +58,11 @@ class CTLicenseDialog extends javax.swing.JDialog { verifyInput(); } }); + + // set ok button as primary button + this.getRootPane().setDefaultButton(okButton); + // request focus for entering license string + this.licenseNumberTextField.requestFocusInWindow(); } private void configureHintText() { @@ -99,11 +104,12 @@ class CTLicenseDialog extends javax.swing.JDialog { java.awt.GridBagConstraints gridBagConstraints; javax.swing.JLabel licenseNumberLabel = new javax.swing.JLabel(); + licenseNumberTextField = new javax.swing.JTextField(); warningLabel = new javax.swing.JLabel(); javax.swing.JPanel buttonPadding = new javax.swing.JPanel(); + javax.swing.JPanel buttonPanel = new javax.swing.JPanel(); okButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); - licenseNumberTextField = new javax.swing.JTextField(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); setTitle(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.title")); // NOI18N @@ -121,6 +127,16 @@ class CTLicenseDialog extends javax.swing.JDialog { gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5); getContentPane().add(licenseNumberLabel, gridBagConstraints); + licenseNumberTextField.setText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.text")); // NOI18N + licenseNumberTextField.setToolTipText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.toolTipText")); // NOI18N + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); + getContentPane().add(licenseNumberTextField, gridBagConstraints); + warningLabel.setForeground(java.awt.Color.RED); org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.warningLabel.text")); // NOI18N warningLabel.setMaximumSize(new java.awt.Dimension(419, 36)); @@ -151,6 +167,8 @@ class CTLicenseDialog extends javax.swing.JDialog { gridBagConstraints.weightx = 1.0; getContentPane().add(buttonPadding, gridBagConstraints); + buttonPanel.setLayout(new java.awt.GridBagLayout()); + org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.okButton.text")); // NOI18N okButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -158,11 +176,13 @@ class CTLicenseDialog extends javax.swing.JDialog { } }); gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 2; - gridBagConstraints.gridy = 3; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); - getContentPane().add(okButton, gridBagConstraints); + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 5, 10, 5); + buttonPanel.add(okButton, gridBagConstraints); org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.cancelButton.text")); // NOI18N cancelButton.addActionListener(new java.awt.event.ActionListener() { @@ -172,20 +192,18 @@ class CTLicenseDialog extends javax.swing.JDialog { }); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 3; - gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); - getContentPane().add(cancelButton, gridBagConstraints); - - licenseNumberTextField.setText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.text")); // NOI18N - licenseNumberTextField.setToolTipText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.toolTipText")); // NOI18N - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 0; - gridBagConstraints.gridy = 1; - gridBagConstraints.gridwidth = 3; + gridBagConstraints.gridy = 0; gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; - gridBagConstraints.insets = new java.awt.Insets(0, 5, 5, 5); - getContentPane().add(licenseNumberTextField, gridBagConstraints); + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.insets = new java.awt.Insets(0, 0, 10, 10); + buttonPanel.add(cancelButton, gridBagConstraints); + + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + gridBagConstraints.gridwidth = 2; + getContentPane().add(buttonPanel, gridBagConstraints); pack(); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java index 0bdb52b4fd..ac38a6d3e9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalFilesPanel.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.casemodule; import java.io.File; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -330,12 +331,7 @@ final class LocalFilesPanel extends javax.swing.JPanel { }//GEN-LAST:event_changeNameButtonActionPerformed private void deleteButonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteButonActionPerformed - int minIdx = this.fileList.getMinSelectionIndex(); - int maxIdx = this.fileList.getMaxSelectionIndex(); - - if (minIdx >= 0 && maxIdx >= minIdx) { - this.listModel.remove(minIdx, maxIdx); - } + this.listModel.remove(this.fileList.getSelectedIndices()); this.fileList.clearSelection(); enableNext = !this.listModel.getFiles().isEmpty(); @@ -516,16 +512,26 @@ final class LocalFilesPanel extends javax.swing.JPanel { } /** - * Removes files in the list starting at minIdx going to maxIdx. - * - * @param minIdx The minimum index of items to be removed. - * @param maxIdx The maximum index to be removed. + * Removes the selected indices. + * @param selectedIndices The selected indices. */ - void remove(int minIdx, int maxIdx) { - for (int i = maxIdx; i >= minIdx; i--) { - items.remove(i); + void remove(int[] selectedIndices) { + if (selectedIndices != null) { + Iterable sortedIndices = (Iterable) () -> Arrays.stream(selectedIndices) + .mapToObj(i -> i) + // reverse order to remove highest index to lowest index + .sorted((a,b) -> Integer.compare(b, a)) + .iterator(); + + int prevIdxMax = items.size() - 1; + for (Integer idx: sortedIndices) { + if (idx != null && idx >= 0 && idx < this.items.size()) { + this.items.remove((int) idx); + } + } + + this.fireContentsChanged(this, 0, prevIdxMax); } - this.fireContentsChanged(this, 0, items.size() - 1); } /** diff --git a/apidiff.py b/apidiff.py deleted file mode 100644 index 3b69d6f59a..0000000000 --- a/apidiff.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -Generates an api diff from one commit to another. This script relies on gitpython and similarly require git -installed on the system. This script also requires python 3. - -This script can be called as follows: - -python apidiff.py -r -o - -If the '-o' flag is not specified, this script will create a folder at apidiff_output in the same directory as the -script. For full list of options call: - -python apidiff.py -h -""" - -import os -import subprocess -import sys -import time -from pathlib import Path -from typing import Tuple, Iterator, List - -import argparse as argparse -from git import Repo, Blob, Tree - -""" -These are exit codes for jdiff: -return code 1 = error in jdiff -return code 100 = no changes -return code 101 = compatible changes -return code 102 = incompatible changes -""" -NO_CHANGES = 100 -COMPATIBLE = 101 -NON_COMPATIBLE = 102 -ERROR = 1 - - -def compare_xml(jdiff_path: str, root_dir: str, output_folder: str, oldapi_folder: str, - newapi_folder: str, api_file_name: str, log_path: str) -> int: - """ - Compares xml generated by jdiff using jdiff. - :param jdiff_path: Path to jdiff jar. - :param root_dir: directory for output . - :param output_folder: Folder for diff output. - :param oldapi_folder: Folder name of old api (i.e. release-4.10.2). - :param newapi_folder: Folder name of new api (i.e. release-4.10.2). - :param api_file_name: Name of xml file name (i.e. if output.xml, just 'output') - :param log_path: Path to log file. - :return: jdiff exit code. - """ - jdiff_parent = os.path.dirname(jdiff_path) - - null_file = fix_path(os.path.join(jdiff_parent, "lib", "Null.java")) - - # comments are expected in a specific place - make_dir(os.path.join(root_dir, - output_folder, - f"user_comments_for_{oldapi_folder}", - f"{api_file_name}_to_{newapi_folder}")) - - log = open(log_path, "w") - cmd = ["javadoc", - "-doclet", "jdiff.JDiff", - "-docletpath", fix_path(jdiff_path), - "-d", fix_path(output_folder), - "-oldapi", fix_path(os.path.join(oldapi_folder, api_file_name)), - "-newapi", fix_path(os.path.join(newapi_folder, api_file_name)), - "-script", - null_file] - - code = None - try: - jdiff = subprocess.Popen(cmd, stdout=log, stderr=log, cwd=root_dir) - jdiff.wait() - code = jdiff.returncode - except Exception as e: - log_and_print(log, f"Error executing javadoc: {str(e)}\nExiting...") - exit(1) - log.close() - - print(f"Compared XML for {oldapi_folder} {newapi_folder}") - if code == NO_CHANGES: - print(" No API changes") - elif code == COMPATIBLE: - print(" API Changes are backwards compatible") - elif code == NON_COMPATIBLE: - print(" API Changes are not backwards compatible") - else: - print(" *Error in XML, most likely an empty module") - sys.stdout.flush() - return code - - -def gen_xml(jdiff_path: str, output_path: str, log_output_path: str, src: str, packages: List[str]): - """ - Uses jdiff to generate an xml representation of the source code. - :param jdiff_path: Path to jdiff jar. - :param output_path: Path to output path of diff. - :param log_output_path: The log output path. - :param src: The path to the source code. - :param packages: The packages to process. - """ - make_dir(output_path) - - log = open_log_file(log_output_path) - log_and_print(log, f"Generating XML for: {src} outputting to: {output_path}") - cmd = ["javadoc", - "-doclet", "jdiff.JDiff", - "-docletpath", fix_path(jdiff_path), - "-apiname", fix_path(output_path), - "-sourcepath", fix_path(src)] - cmd = cmd + packages - try: - jdiff = subprocess.Popen(cmd, stdout=log, stderr=log) - jdiff.wait() - except Exception as e: - log_and_print(log, f"Error executing javadoc {str(e)}\nExiting...") - exit(1) - - log_and_print(log, f"Generated XML for: " + str(packages)) - log.close() - sys.stdout.flush() - - -def _list_paths(root_tree: Tree, src_folder, path: Path = None) -> Iterator[Tuple[str, Blob]]: - """ - Given the root path to serve as a prefix, walks the tree of a git commit returning all files and blobs. - Repurposed from: https://www.enricozini.org/blog/2019/debian/gitpython-list-all-files-in-a-git-commit/ - Args: - root_tree: The tree of the commit to walk. - src_folder: relative path in repo to source folder that will be copied. - path: The path to use as a prefix. - Returns: A tuple iterator where each tuple consists of the path as a string and a blob of the file. - """ - for blob in root_tree.blobs: - next_path = Path(path) / blob.name if path else blob.name - if Path(src_folder) in Path(next_path).parents: - ret_item = (next_path, blob) - yield ret_item - for tree in root_tree.trees: - next_path = Path(path) / tree.name if path else tree.name - yield from _list_paths(tree, src_folder, next_path) - - -def _get_tree(repo_path: str, commit_id: str) -> Tree: - """ - Retrieves the git tree that can be walked for files and file content at the specified commit. - Args: - repo_path: The path to the repo or a child directory of the repo. - commit_id: The commit id. - Returns: The tree. - """ - repo = Repo(repo_path, search_parent_directories=True) - commit = repo.commit(commit_id.strip()) - return commit.tree - - -def copy_commit_paths(repo_path, commit_id, src_folder, output_folder): - """ - Copies all files located within a repo in the folder 'src_folder' to 'output_folder'. - :param repo_path: The path to the repo. - :param commit_id: The commit id. - :param src_folder: The relative path in the repo to the source folder. - :param output_folder: The output folder where the source will be copied. - """ - tree = _get_tree(repo_path, commit_id) - for rel_path, blob in _list_paths(tree, src_folder): - output_path = os.path.join(output_folder, os.path.relpath(rel_path, src_folder)) - parent_folder = os.path.dirname(output_path) - make_dir(parent_folder) - output_file = open(output_path, 'w') - output_file.write(blob.data_stream.read().decode('utf-8')) - output_file.close() - - -def open_log_file(log_path): - """ - Opens a path to a lof file for appending. Creating directories and log file as necessary. - :param log_path: The path to the log file. - :return: The log file opened for writing. - """ - if not os.path.exists(log_path): - make_dir(os.path.dirname(log_path)) - Path(log_path).touch() - - return open(log_path, 'a+') - - -def fix_path(path): - """ - Generates a path that is escaped from cygwin paths if present. - :param path: Path (possibly including cygdrive). - :return: The normalized path. - """ - if "cygdrive" in path: - new_path = path[11:] - return "C:/" + new_path - else: - return path - - -def log_and_print(log, message): - """ - Creates a log entry and prints to stdout. - :param log: The log file object. - :param message: The string to be printed. - """ - time_stamp = time.strftime('%Y-%m-%d %H:%M:%S') - print(f"{time_stamp}: {message}") - log.write(f"{time_stamp}: {message}\n") - - -def make_dir(dir_path: str): - """ - Create the given directory, if it doesn't already exist. - :param dir_path: The path to the directory. - :return: True if created. - """ - try: - if not os.path.isdir(dir_path): - os.makedirs(dir_path) - if os.path.isdir(dir_path): - return True - return False - except IOError: - print("Exception thrown when creating directory: " + dir_path) - return False - - -def run_compare(output_path: str, jdiff_path: str, repo_path: str, src_rel_path: str, prev_commit_id: str, - latest_commit_id: str, packages: List[str]): - """ - Runs a comparison of the api between two different commits/branches/tags of the same repo generating a jdiff diff. - :param output_path: The output path for artifacts. - :param jdiff_path: The path to the jdiff jar. - :param repo_path: The path to the repo. - :param src_rel_path: The relative path in the repo to the source directory. - :param prev_commit_id: The previous commit/branch/tag id. - :param latest_commit_id: The latest commit/branch/tag id. - :param packages: The packages to be considered for the api diff. - """ - log_path = os.path.join(output_path, "messages.log") - output_file_name = "output" - diff_dir = "diff" - src_folder = "src" - - for commit_id in [prev_commit_id, latest_commit_id]: - src_copy = os.path.join(output_path, src_folder, commit_id) - copy_commit_paths(repo_path, commit_id, src_rel_path, src_copy) - gen_xml(jdiff_path, os.path.join(output_path, commit_id, output_file_name), log_path, src_copy, packages) - - # compare the two - compare_xml(jdiff_path, output_path, os.path.join(output_path, diff_dir), - prev_commit_id, latest_commit_id, output_file_name, log_path) - - -def main(): - parser = argparse.ArgumentParser(description="Generates a jdiff diff of the java api between two commits in a " - "repo.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument(dest='prev_commit', type=str, help=r'The git commit id/branch/tag to be used for the first ' - r'commit') - parser.add_argument(dest='latest_commit', type=str, help=r'The git commit id/branch/tag to be used for the latest ' - r'commit') - parser.add_argument('-r', '--repo', dest='repo_path', type=str, required=True, - help='The path to the repo. If not specified, path of script is used.') - - parser.add_argument('-o', '--output', dest='output_path', type=str, required=False, - help='The location for output of all artifacts. Defaults to an output folder in same directory' - 'as script') - parser.add_argument('-s', '--src', dest='src_rel_folder', type=str, required=False, default="bindings/java/src", - help='The relative path within the repo of the src folder.') - # list of packages can be specified like this: - # https://stackoverflow.com/questions/15753701/how-can-i-pass-a-list-as-a-command-line-argument-with-argparse - parser.add_argument('-p', '--packages', dest='packages', nargs='+', required=False, - default=["org.sleuthkit.datamodel"], help='The packages to consider in api diff.') - parser.add_argument('-j', '--jdiff', dest='jdiff_path', type=str, required=False, - help='The packages to consider in api diff.') - - args = parser.parse_args() - script_path = os.path.dirname(os.path.realpath(__file__)) - repo_path = args.repo_path if args.repo_path else script_path - output_path = args.output_path if args.output_path else os.path.join(script_path, "apidiff_output") - jdiff_path = args.jdiff_path if args.jdiff_path else os.path.join(script_path, - "thirdparty/jdiff/v-custom/jdiff.jar") - run_compare(output_path=output_path, - jdiff_path=jdiff_path, - repo_path=repo_path, - packages=args.packages, - src_rel_path=args.src_rel_folder, - prev_commit_id=args.prev_commit, - latest_commit_id=args.latest_commit) - - -main() diff --git a/release_scripts/APIUpdate/.gitignore b/release_scripts/APIUpdate/.gitignore new file mode 100644 index 0000000000..c41cc9e35e --- /dev/null +++ b/release_scripts/APIUpdate/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/release_scripts/APIUpdate/APIUpdate-1.0-jar-with-dependencies.jar b/release_scripts/APIUpdate/APIUpdate-1.0-jar-with-dependencies.jar new file mode 100644 index 0000000000..40cf7bf7cf Binary files /dev/null and b/release_scripts/APIUpdate/APIUpdate-1.0-jar-with-dependencies.jar differ diff --git a/release_scripts/APIUpdate/README.md b/release_scripts/APIUpdate/README.md new file mode 100644 index 0000000000..7e27d21089 --- /dev/null +++ b/release_scripts/APIUpdate/README.md @@ -0,0 +1,25 @@ +# APIDiff + +## Overview + +This code can be used to determine the public API changes between the previous version of Autopsy jars to current version of Autopsy jars. Based on those changes, this code can update version numbers (release, implementation, specification, dependency version). + +## Sample Usage & Procedure + +1. Before starting download the nbm jar files from the [previous release](https://github.com/sleuthkit/autopsy/releases/). The jar files will be located in the zip file or the installation directory (i.e. `C:\Program Files\Autopsy-x.xx.x`) at `autopsy/modules`. +2. Make sure you build the current source directory in order for the program to find the new compiled jars. +3. This code can be called from this directory with a command like: `java -jar APIUpdate-1.0-jar-with-dependencies.jar -p C:\path\to\prev\vers\jars\` to get api updates. + - You can specify the `-u` flag to make updates in source code making the command: `java -jar APIUpdate-1.0-jar-with-dependencies.jar -p C:\path\to\prev\vers\jars\ -u`. + - You can also add ` >C:\path\to\outputdiff.txt 2>&1` to output to a file. + +## Arguments + +``` +usage: APIUpdate + -c,--curr-path The path to the current version jar files + -cv,--curr-version The current version number + -p,--prev-path The path to the previous version jar files + -pv,--prev-version The previous version number + -s,--src-path The path to the root of the autopsy report + -u,--update Update source code versions +``` diff --git a/release_scripts/APIUpdate/nbactions.xml b/release_scripts/APIUpdate/nbactions.xml new file mode 100644 index 0000000000..0102967d7e --- /dev/null +++ b/release_scripts/APIUpdate/nbactions.xml @@ -0,0 +1,55 @@ + + + + run + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:3.1.0:exec + + + + ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} + --help + ${packageClassName} + java + + + + debug + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:3.1.0:exec + + + -agentlib:jdwp=transport=dt_socket,server=n,address=${jpda.address} + ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} + --help + ${packageClassName} + java + true + + + + profile + + jar + + + process-classes + org.codehaus.mojo:exec-maven-plugin:3.1.0:exec + + + + ${exec.vmArgs} -classpath %classpath ${exec.mainClass} ${exec.appArgs} + ${packageClassName} + java + --help + + + diff --git a/release_scripts/APIUpdate/pom.xml b/release_scripts/APIUpdate/pom.xml new file mode 100644 index 0000000000..ed65d6174c --- /dev/null +++ b/release_scripts/APIUpdate/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + org.sleuthkit.autopsy.classpathsimplication + APIUpdate + 1.0 + jar + + UTF-8 + 17 + 17 + org.sleuthkit.autopsy.apiupdate.Main + + + + com.github.siom79.japicmp + japicmp + 0.17.2 + + + + commons-cli + commons-cli + 1.5.0 + + + + + org.apache.commons + commons-lang3 + 3.13.0 + + + + + + + + maven-assembly-plugin + + + + ${exec.mainClass} + + + + jar-with-dependencies + + + ${project.basedir} + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file 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 new file mode 100644 index 0000000000..d4aae110c7 --- /dev/null +++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/APIDiff.java @@ -0,0 +1,368 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package 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.JApiClass; +import japicmp.model.JApiSemanticVersionLevel; +import static japicmp.model.JApiSemanticVersionLevel.MAJOR; +import static japicmp.model.JApiSemanticVersionLevel.PATCH; +import japicmp.output.semver.SemverOut; +import japicmp.output.stdout.StdoutOutputGenerator; +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.List; +import java.util.Map; +import java.util.Optional; +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; +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 + = (File f) -> f.isFile() && (f.getName().toLowerCase().endsWith(".jar") || f.getName().toLowerCase().endsWith(".nbm")); + + /** + * Identifies common jar files between two directories. Only files listed in + * the directory are considered. This method does not recurse. + * + * @param prev The previous version directory. + * @param curr The current version directory. + * @return The jar file names. + */ + static List> getCommonJars(File prev, File curr) { + if (prev.isFile() && curr.isFile()) { + return Arrays.asList(Pair.of(prev, curr)); + } + + Map prevJars = getJars(prev); + Map currJars = getJars(curr); + Set combined = new HashSet<>(prevJars.keySet()); + combined.addAll(currJars.keySet()); + + List> retMapping = new ArrayList<>(); + + for (String prevKey : (Iterable) combined.stream().sorted(StringUtils::compareIgnoreCase)::iterator) { + File prevFile = prevJars.get(prevKey); + File curFile = currJars.get(prevKey); + retMapping.add(Pair.of(prevFile, curFile)); + } + + return retMapping; + } + + /** + * Returns all jar files listed in directory (does not recurse). + * + * @param dir The directory. + * @return The jar file names. + */ + private static Map 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)); + } + + /** + * Uses manfest.mf specification of "OpenIDE-Module-Public-Packages" to + * determine public API packages. + * + * @param jarFile The jar file. + * @return The set of package names. + * @throws IOException + * @throws IllegalStateException + */ + private static Set getPublicPackages(File jarFile) throws IOException, IllegalStateException { + String publicPackageStr = ManifestLoader.loadFromJar(jarFile).getValue("OpenIDE-Module-Public-Packages"); + if (publicPackageStr == null) { + 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) + .map(str -> str.endsWith(".*") ? str.substring(0, str.length() - 2) : str) + .collect(Collectors.toSet()); + } + } + + /** + * Filter to identify non-public, non-protected members for exclusion. + * + * @param member The CtMember (field/method). + * @return True if should be excluded (private/package private). + */ + static boolean excludeMember(CtMember member) { + return !Modifier.isPublic(member.getModifiers()) && !Modifier.isProtected(member.getModifiers()); + } + + /** + * Compares two jar files. + * + * @param prevVersion The name of the previous version. + * @param curVersion The name of the current version. + * @param prevJar The previous version jar file. + * @param curJar The current version jar file. + * @return A record describing the comparison in public API. + * @throws IOException + */ + static ComparisonRecord getComparison(String prevVersion, String curVersion, File prevJar, File curJar) throws IOException { + // scope only to previous or current public packages if jars have public packages + Set prevPublicApiPackages = getPublicPackages(prevJar); + Set curPublicApiPackages = getPublicPackages(curJar); + + Set onlyPrevApiPackages = new HashSet<>(); + Set onlyCurApiPackages = new HashSet<>(); + Set commonApiPackages = new HashSet<>(); + + Optional> allPublicApiPackagesFilter = Optional.empty(); + if (prevPublicApiPackages != null && curPublicApiPackages != null) { + Set allPublicApiPackages = new HashSet<>(); + allPublicApiPackages.addAll(prevPublicApiPackages); + allPublicApiPackages.addAll(curPublicApiPackages); + allPublicApiPackagesFilter.of(allPublicApiPackages); + + 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); + } + } + } + + // get classes diff for public api changes + List jApiClasses = getClassDiff(true, allPublicApiPackagesFilter, prevJar, prevVersion, curJar, curVersion); + + Options options = Options.newDefault(); + options.setOldArchives(Arrays.asList(new JApiCmpArchive(prevJar, prevVersion))); + options.setNewArchives(Arrays.asList(new JApiCmpArchive(curJar, curVersion))); + options.setOutputOnlyModifications(true); + + PublicApiChangeType changeType = getChangeType(options, jApiClasses); + + // if the change type is none, check for any internal changes + if (changeType == PublicApiChangeType.NONE) { + List alljApiClasses = getClassDiff(false,Optional.empty(), prevJar, prevVersion, curJar, curVersion); + PublicApiChangeType allClassChangeType = getChangeType(options, jApiClasses); + changeType = allClassChangeType == PublicApiChangeType.NONE ? PublicApiChangeType.NONE : allClassChangeType.INTERNAL_CHANGE; + } + + StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses); + String humanReadableApiChange = stdoutOutputGenerator.generate(); + return new ComparisonRecord(prevVersion, curVersion, prevJar, curJar, humanReadableApiChange, changeType, onlyPrevApiPackages, onlyCurApiPackages, commonApiPackages); + } + + private static List getClassDiff(boolean filter, Optional> publicPackagesFilter, File prevJar, String prevVersion, File curJar, String curVersion) { + JarArchiveComparatorOptions comparatorOptions = new JarArchiveComparatorOptions(); + if (filter) { + // only classes in prev or current public api + if (publicPackagesFilter.isPresent()) { + final Set publicPackages = publicPackagesFilter.get(); + comparatorOptions.getFilters().getExcludes().add((ClassFilter) (CtClass ctClass) -> !publicPackages.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 jApiClasses = jarArchiveComparator.compare( + new JApiCmpArchive(prevJar, prevVersion), + new JApiCmpArchive(curJar, curVersion) + ); + return jApiClasses; + } + + /** + * Updates an atomic ref to the public api change type to the maximum change + * (where no change is min and incompatible change is max). + * + * @param apiChangeRef The atomic ref to a public api change type. + * @param versionLevel The semantic version level of the current change. + */ + private static void updateToMax(AtomicReference apiChangeRef, JApiSemanticVersionLevel versionLevel) { + PublicApiChangeType apiChangeType; + if (versionLevel == null) { + return; + } + + switch (versionLevel) { + case PATCH: + apiChangeType = PublicApiChangeType.INTERNAL_CHANGE; + break; + case MINOR: + apiChangeType = PublicApiChangeType.COMPATIBLE_CHANGE; + break; + case MAJOR: + apiChangeType = PublicApiChangeType.INCOMPATIBLE_CHANGE; + break; + default: + LOGGER.log(Level.WARNING, "Unknown sem ver type: " + versionLevel.name()); + apiChangeType = PublicApiChangeType.INCOMPATIBLE_CHANGE; + break; + } + + final PublicApiChangeType finalApiChangeType = apiChangeType; + apiChangeRef.updateAndGet((refType) -> Comparators.max(refType, finalApiChangeType)); + } + + /** + * Determines the public api change type for the given classes. + * + * @param options The options for output. + * @param jApiClasses The classes. + * @return The public API change type. + */ + static PublicApiChangeType getChangeType(Options options, List jApiClasses) { + AtomicReference apiChange = new AtomicReference<>(PublicApiChangeType.NONE); + new SemverOut(options, jApiClasses, (change, semanticVersionLevel) -> updateToMax(apiChange, semanticVersionLevel)).generate(); + return apiChange.get(); + } + + /** + * A record describing the public API comparison of a previous and current + * version. + */ + 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 onlyPrevApiPackages; + private final Set onlyCurrApiPackages; + private final Set commonApiPackages; + + public ComparisonRecord(String prevVersion, String curVersion, File prevJar, File curJar, String humanReadableApiChange, PublicApiChangeType changeType, Set onlyPrevApiPackages, Set onlyCurrApiPackages, Set 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; + } + + /** + * @return The previous version name. + */ + public String getPrevVersion() { + return prevVersion; + } + + /** + * @return The current version name. + */ + public String getCurVersion() { + return curVersion; + } + + /** + * @return The previous version jar file. + */ + public File getPrevJar() { + return prevJar; + } + + /** + * @return The current version jar file. + */ + public File getCurJar() { + return curJar; + } + + /** + * @return The human readable output describing the api changes. + */ + public String getHumanReadableApiChange() { + return humanReadableApiChange; + } + + /** + * @return The public api change type. + */ + public PublicApiChangeType getChangeType() { + return changeType; + } + + /** + * @return Names of packages only in previous public API. + */ + public Set getOnlyPrevApiPackages() { + return onlyPrevApiPackages; + } + + /** + * @return Names of packages only in current public API. + */ + public Set getOnlyCurrApiPackages() { + return onlyCurrApiPackages; + } + + /** + * @return Names of packages in common between previous and current + * public API. + */ + public Set getCommonApiPackages() { + return commonApiPackages; + } + + } + +} 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 new file mode 100644 index 0000000000..9198974036 --- /dev/null +++ b/release_scripts/APIUpdate/src/main/java/org/sleuthkit/autopsy/apiupdate/CLIProcessor.java @@ -0,0 +1,286 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.apiupdate; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * Processes CLI options. + */ +public class CLIProcessor { + + private static final Option PREV_VERS_PATH_OPT = Option.builder() + .argName("path") + .desc("The path to the previous version jar files") + .hasArg(true) + .longOpt("prev-path") + .option("p") + .required(true) + .build(); + + private static final Option CUR_VERS_PATH_OPT = Option.builder() + .argName("path") + .desc("The path to the current version jar files") + .hasArg(true) + .longOpt("curr-path") + .option("c") + .required(false) + .build(); + + private static final Option PREV_VERS_OPT = Option.builder() + .argName("version") + .desc("The previous version number") + .hasArg(true) + .longOpt("prev-version") + .option("pv") + .required(false) + .build(); + + private static final Option CUR_VERS_OPT = Option.builder() + .argName("version") + .desc("The current version number") + .hasArg(true) + .longOpt("curr-version") + .option("cv") + .required(false) + .build(); + + private static final Option SRC_LOC_OPT = Option.builder() + .argName("path") + .desc("The path to the root of the autopsy report") + .hasArg(true) + .longOpt("src-path") + .option("s") + .required(false) + .build(); + + private static final Option UPDATE_OPT = Option.builder() + .desc("Update source code versions") + .hasArg(false) + .longOpt("update") + .option("u") + .required(false) + .build(); + + private static final List