public void export(@Nonnull Workspace workspace, @Nonnull String contentDescription, boolean alertWhenEmpty) {
		WorkspaceResource primaryResource = workspace.getPrimaryResource();

		// Check if the user hasn't made any changes. Plenty of people have not understood that their changes weren't
		// saved for one reason or another (the amount of people seeing a red flash thinking that is fine is crazy)
		if (alertWhenEmpty) {
			boolean noChangesFound = exportConfig.getWarnNoChanges().getValue() && primaryResource.bundleStreamRecursive()
					.allMatch(b -> b.getDirtyKeys().isEmpty());
			if (noChangesFound) {
				Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Lang.get("dialog.file.nochanges"), ButtonType.YES, ButtonType.NO);
				alert.setTitle(Lang.get("dialog.title.nochanges"));
				Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
				stage.getIcons().add(Icons.getImage(Icons.LOGO));
				if (alert.showAndWait().orElse(ButtonType.NO) != ButtonType.YES)
					return;
			}
		}

		// Prompt a path for the user to write to.
		ObservableString lastWorkspaceExportDir = recentFilesConfig.getLastWorkspaceExportDirectory();
		File lastExportDir = lastWorkspaceExportDir.unboxingMap(File::new);
		File selectedPath;
		if (primaryResource instanceof WorkspaceDirectoryResource) {
			DirectoryChooser chooser = new DirectoryChooserBuilder()
					.setInitialDirectory(lastExportDir)
					.setTitle(Lang.get("dialog.file.export"))
					.build();
			selectedPath = chooser.showDialog(null);
		} else {
			FileChooserBuilder builder = new FileChooserBuilder()
					.setInitialDirectory(lastExportDir)
					.setTitle(Lang.get("dialog.file.export"));

			if (primaryResource instanceof WorkspaceFileResource primaryFileResource) {
				FileInfo backingFile = primaryFileResource.getFileInfo();

				// Use original file name as initial prompt name.
				String initialName = StringUtil.shortenPath(backingFile.getName());
				builder.setInitialFileName(initialName);

				// Mirror the original file type as an extension filter.
				String extension = IOUtil.getExtension(initialName);
				if (extension == null)
					extension = IOUtil.getExtensionFromFileInfo(backingFile);
				if (extension != null)
					builder.setFileExtensionFilter(extension.toUpperCase(), "*." + extension.toLowerCase());
			}

			FileChooser chooser = builder.build();
			selectedPath = chooser.showSaveDialog(null);
		}

		// Convert selected file to nio path, update last export directory.
		Path exportPath;
		if (selectedPath != null) {
			String parent = selectedPath.getParent();
			if (parent != null) lastWorkspaceExportDir.setValue(parent);
			exportPath = selectedPath.toPath();
		} else {
			// Selected path is null, meaning user closed out of file chooser.
			// Cancel export.
			return;
		}

		// Create export options from the resource type and export the workspace to the selected path.
		WorkspaceExporter exporter = createResourceExporter(primaryResource, exportPath);
		try {
			exporter.export(workspace);
			logger.info("Exported " + contentDescription + " to path '{}'", exportPath);
		} catch (IOException ex) {
			logger.error("Failed to export " + contentDescription + " to path '{}'", exportPath, ex);
			ErrorDialogs.show(
					Lang.getBinding("dialog.error.exportworkspace.title"),
					Lang.getBinding("dialog.error.exportworkspace.header"),
					Lang.getBinding("dialog.error.exportworkspace.content"),
					ex
			);
		}
	}
Export the given workspace.

@param workspace
		Workspace to export.
@param contentDescription
		Description string for what is within the workspace.
@param alertWhenEmpty
       {@code true} to warn users before exporting that no changes are present in the workspace.
       {@code false} to skip the warning.
recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java