Java tutorial
package spdxedit; import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import javafx.beans.property.ReadOnlyStringWrapper; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.geometry.Insets; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import javafx.stage.*; import javafx.util.StringConverter; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.CheckListView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spdx.rdfparser.InvalidSPDXAnalysisException; import org.spdx.rdfparser.SpdxDocumentContainer; import org.spdx.rdfparser.model.Relationship; import org.spdx.rdfparser.model.Relationship.RelationshipType; import org.spdx.rdfparser.model.SpdxFile; import org.spdx.rdfparser.model.SpdxFile.FileType; import org.spdx.rdfparser.model.SpdxPackage; import spdxedit.externalRef.ExternalRefListControl; import spdxedit.license.FileLicenseEditor; import spdxedit.license.LicenseEditControl; import spdxedit.license.SpdxWithoutExeption; import spdxedit.util.StringableWrapper; import spdxedit.util.UiUtils; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; public class PackageEditor { private static final Logger logger = LoggerFactory.getLogger(PackageEditor.class); @FXML private TitledPane tabFiles; @FXML private TreeTableView<SpdxFile> filesTable; @FXML private TreeTableColumn<SpdxFile, String> tblColumnFile; @FXML private TitledPane tabRelationships; @FXML private Button btnOk; @FXML private Button btnDeleteFileFromPackage; @FXML private Button btnAddFile; @FXML private Button btnCopyright; @FXML private Button btnFileLicense; /** * Package properties */ @FXML private TitledPane tabPackageProperties; @FXML private TitledPane tabExternalRefs; /*** * FILE INFORMATION REPRESENTATIONS ***/ @FXML private CheckListView<StringableWrapper<FileType>> chkListFileTypes; @FXML private CheckBox chkDataFile; @FXML private CheckBox chkTestCase; @FXML private CheckBox chkDocumentation; @FXML private CheckBox chkOptionalComponent; @FXML private CheckBox chkMetafile; @FXML private CheckBox chkBuildTool; @FXML private CheckBox chkExcludeFile; /** * PACKAGE RELATIONSHIP REPRESENTATIONS **/ @FXML private ListView<SpdxPackage> lstTargetPackages; @FXML private ChoiceBox<RelationshipType> chcNewRelationshipType; @FXML private Button btnAddRelationship; @FXML private Button btnRemoveRelationship; /** * PACKAGE LICENSE EDITOR */ @FXML private Tab tabPkgDeclaredLicense; @FXML private Tab tabPkgConcludedLicense; @FXML private TitledPane tabPkgLicenses; private LicenseEditControl pkgDeclaredLicenseEdit; private LicenseEditControl pkgConcludedLicenseEdit; @FXML private ListView<StringableWrapper<RelationshipType>> lstPackageRelationships; private static final StringConverter<RelationshipType> RELATIONSHIP_TYPE_STRING_CONVERTER = new StringConverter<RelationshipType>() { @Override public String toString(RelationshipType relationshipType) { return SpdxLogic.toString(relationshipType); } @Override public RelationshipType fromString(String string) { throw new UnsupportedOperationException("Shoudln't have to convert strings to relationship types"); } }; //The package being edited private SpdxPackage pkg; //The file currently being edited private SpdxFile currentFile; //The container of the document being edited. private SpdxDocumentContainer documentContainer; //Packages to which the edited package can have a relationship private List<SpdxPackage> otherPackages; @FXML private void initialize() { assert tabFiles != null : "fx:id=\"tabFiles\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert tabPackageProperties != null : "fx:id=\"tabPackageProperties\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert tabExternalRefs != null : "fx:id=\"tabExternalRefs\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert filesTable != null : "fx:id=\"filesTable\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert tblColumnFile != null : "fx:id=\"tblColumnFile\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chkListFileTypes != null : "fx:id=\"chkListFileTypes\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert tabRelationships != null : "fx:id=\"tabRelationships\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert btnOk != null : "fx:id=\"btnOk\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert btnDeleteFileFromPackage != null : "fx:id=\"btnDeleteFileFromPackage\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert btnAddFile != null : "fx:id=\"btnAddFile\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert btnCopyright != null : "fx:id=\"btnCopyright\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert btnFileLicense != null : "fx:id=\"btnFileLicense\" was not injected: check your FXML file 'PackageEditor.fxml'."; //File relationship checkboxes assert chkDataFile != null : "fx:id=\"chkDataFile\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chkTestCase != null : "fx:id=\"chkTestCase\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chkDocumentation != null : "fx:id=\"chkDocumentation\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chkOptionalComponent != null : "fx:id=\"chkOptionalComponent\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chkMetafile != null : "fx:id=\"chkMetafile\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chkBuildTool != null : "fx:id=\"chkBuildTool\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chkExcludeFile != null : "fx:id=\"chkExcludeFile\" was not injected: check your FXML file 'PackageEditor.fxml'."; //Initialise file relationship checkbox handling //TODO: Could make this easier by extending the CheckBox control? chkDataFile.selectedProperty().addListener((observable, oldValue, newValue) -> addOrRemoveFileRelationshipToPackage(RelationshipType.DATA_FILE_OF, newValue)); chkTestCase.selectedProperty().addListener((observable, oldValue, newValue) -> addOrRemoveFileRelationshipToPackage(RelationshipType.TEST_CASE_OF, newValue)); chkDocumentation.selectedProperty().addListener((observable, oldValue, newValue) -> addOrRemoveFileRelationshipToPackage(RelationshipType.DOCUMENTATION_OF, newValue)); chkOptionalComponent.selectedProperty() .addListener((observable, oldValue, newValue) -> addOrRemoveFileRelationshipToPackage( RelationshipType.OPTIONAL_COMPONENT_OF, newValue)); chkMetafile.selectedProperty().addListener((observable, oldValue, newValue) -> addOrRemoveFileRelationshipToPackage(RelationshipType.METAFILE_OF, newValue)); chkBuildTool.selectedProperty().addListener((observable, oldValue, newValue) -> addOrRemoveFileRelationshipToPackage(RelationshipType.BUILD_TOOL_OF, newValue)); chkExcludeFile.selectedProperty() .addListener((observable, oldValue, newValue) -> handleChkExcludeFileChange(newValue)); //Package relationship controls assert lstTargetPackages != null : "fx:id=\"lstTargetPackages\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert chcNewRelationshipType != null : "fx:id=\"chcNewRelationshipType\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert btnAddRelationship != null : "fx:id=\"btnAddRelationship\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert btnRemoveRelationship != null : "fx:id=\"btnRemoveRelationship\" was not injected: check your FXML file 'PackageEditor.fxml'."; //Initialize package relationship controls lstTargetPackages.getSelectionModel().selectedItemProperty() .addListener((observable1, oldValue1, newValue1) -> handleTargetPackageSelected(newValue1)); lstTargetPackages.setCellFactory(listView -> new MainSceneController.SpdxPackageListCell()); lstPackageRelationships.getSelectionModel().selectedItemProperty().addListener( (observable1, oldValue1, newValue1) -> btnRemoveRelationship.setDisable(newValue1 == null)); //Package relationship types chcNewRelationshipType.setConverter(RELATIONSHIP_TYPE_STRING_CONVERTER); chcNewRelationshipType.getItems() .setAll(Stream.of(RelationshipType.AMENDS, RelationshipType.DESCENDANT_OF, RelationshipType.DYNAMIC_LINK, RelationshipType.GENERATED_FROM, RelationshipType.GENERATES, RelationshipType.HAS_PREREQUISITE, RelationshipType.PREREQUISITE_FOR, RelationshipType.STATIC_LINK, RelationshipType.OTHER).collect(Collectors.toList())); chcNewRelationshipType.getSelectionModel().selectFirst(); assert (otherPackages != null); //Constructor finished executing lstTargetPackages.getItems().setAll(otherPackages); //Package license editor assert tabPkgLicenses != null : "fx:id=\"tabPkgLicenses\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert tabPkgDeclaredLicense != null : "fx:id=\"tabPkgDeclaredLicense\" was not injected: check your FXML file 'PackageEditor.fxml'."; assert tabPkgConcludedLicense != null : "fx:id=\"tabPkgConcludedLicense\" was not injected: check your FXML file 'PackageEditor.fxml'."; pkgConcludedLicenseEdit = new LicenseEditControl(documentContainer); pkgConcludedLicenseEdit.setInitialValue(pkg.getLicenseConcluded()); pkgConcludedLicenseEdit .setOnLicenseChange(license -> SpdxWithoutExeption.setLicenseConcluded(pkg, license)); tabPkgConcludedLicense.setContent(pkgConcludedLicenseEdit.getUi()); pkgDeclaredLicenseEdit = new LicenseEditControl(documentContainer); pkgDeclaredLicenseEdit.setInitialValue(SpdxWithoutExeption.getLicenseDeclared(pkg)); pkgDeclaredLicenseEdit.setOnLicenseChange(license -> SpdxWithoutExeption.setLicenseDeclared(pkg, license)); tabPkgDeclaredLicense.setContent(pkgDeclaredLicenseEdit.getUi()); //Initialize other elements tblColumnFile.setCellValueFactory( (TreeTableColumn.CellDataFeatures<SpdxFile, String> param) -> new ReadOnlyStringWrapper( param.getValue().getValue().getName())); //Load all file types into the file type list in order. chkListFileTypes.getItems().setAll(Stream.of(FileType.values()).sorted(Ordering.usingToString()) //Sort .map(fileType -> StringableWrapper.wrap(fileType, SpdxLogic::toString)) //Wrap so that the nice toString function gets used by the checkbox .collect(Collectors.toList())); chkListFileTypes.getCheckModel().getCheckedItems().addListener(this::handleFileTypeCheckedOrUnchecked); filesTable.getSelectionModel().selectedItemProperty() .addListener((observable, oldValue, newValue) -> handleFileSelected(newValue)); filesTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); filesTable.setShowRoot(false); AnchorPane externalRefControl = new ExternalRefListControl(pkg).getUi(); externalRefControl.setPadding(new Insets(12, 10, 5, 10)); tabExternalRefs.setContent(externalRefControl); externalRefControl.setManaged(true); new PackagePropertyEditor(pkg).initialize(tabPackageProperties); } /** * Opens the modal package editor for the provided package. * * @param pkg The package to edit. * @param relatablePackages Packages to which the edited package may optionally have defined relationships * @param parentWindow The parent window. */ public static void editPackage(final SpdxPackage pkg, final List<SpdxPackage> relatablePackages, SpdxDocumentContainer documentContainer, Window parentWindow) { final PackageEditor packageEditor = new PackageEditor(pkg, relatablePackages, documentContainer); final Stage dialogStage = new Stage(); dialogStage.setTitle("Edit SPDX Package: " + pkg.getName()); dialogStage.initStyle(StageStyle.DECORATED); dialogStage.initModality(Modality.APPLICATION_MODAL); dialogStage.setY(parentWindow.getX() + parentWindow.getWidth() / 2); dialogStage.setY(parentWindow.getY() + parentWindow.getHeight() / 2); dialogStage.setResizable(false); try { FXMLLoader loader = new FXMLLoader(NewPackageDialog.class.getResource("/PackageEditor.fxml")); loader.setController(packageEditor); Pane pane = loader.load(); Scene scene = new Scene(pane); dialogStage.setScene(scene); dialogStage.getIcons().clear(); dialogStage.getIcons().add(UiUtils.ICON_IMAGE_VIEW.getImage()); //Populate the file list on appearance dialogStage.setOnShown(event -> { try { final SpdxFile dummyfile = new SpdxFile(pkg.getName(), null, null, null, null, null, null, null, null, null, null, null, null); TreeItem<SpdxFile> root = new TreeItem<>(dummyfile); packageEditor.filesTable.setRoot(root); //Assume a package without is external //TODO: replace with external packages or whatever alternate mechanism in 2.1 packageEditor.btnAddFile.setDisable(pkg.getFiles().length == 0); root.getChildren() .setAll(Stream.of(pkg.getFiles()) .sorted(Comparator.comparing(file -> StringUtils.lowerCase(file.getName()))) //Sort by file name .map(TreeItem<SpdxFile>::new).collect(Collectors.toList())); } catch (InvalidSPDXAnalysisException e) { logger.error("Unable to get files for package " + pkg.getName(), e); } packageEditor.tabFiles.setExpanded(true); }); //Won't assign this event through FXML - don't want to propagate the stage beyond this point. packageEditor.btnOk.setOnMouseClicked(event -> dialogStage.close()); dialogStage.showAndWait(); } catch (IOException ioe) { throw new RuntimeException("Unable to load dialog", ioe); } } private PackageEditor(SpdxPackage pkg, List<SpdxPackage> relatablePackages, SpdxDocumentContainer documentContainer) { this.pkg = pkg; this.otherPackages = relatablePackages; this.documentContainer = documentContainer; } //Load the values for the file in all file editing contorls private void handleFileSelected(TreeItem<SpdxFile> newSelection) { btnDeleteFileFromPackage.setDisable(newSelection == null); if (newSelection == null) { currentFile = null; } else { //Set currentFile to null to make sure we don't accidentally edit the previous file currentFile = null; //Set the file type checkbox values to reflect this file's types chkListFileTypes.getCheckModel().clearChecks(); //If multiple selections, then disable everything but the delete button boolean multipleSelections = filesTable.getSelectionModel().getSelectedItems().size() > 1; chkListFileTypes.setDisable(multipleSelections); //Disable/enable usage checkboxes for multiple selection Stream.of(chkExcludeFile, chkBuildTool, chkMetafile, chkOptionalComponent, chkDocumentation, chkTestCase, chkDataFile).forEach(checkbox -> checkbox.setDisable(multipleSelections)); if (multipleSelections) return; //The element lookup by index seems to be broken on the CheckListView control, //so we'll have to provide the indices chkListFileTypes.getItems().forEach(item -> { if (ArrayUtils.contains(newSelection.getValue().getFileTypes(), item.getValue())) chkListFileTypes.getCheckModel().check(item); else chkListFileTypes.getCheckModel().clearCheck(item); }); //Reset the relationship checkboxes chkDataFile.setSelected(SpdxLogic .findRelationship(newSelection.getValue(), RelationshipType.DATA_FILE_OF, pkg).isPresent()); chkTestCase.setSelected(SpdxLogic .findRelationship(newSelection.getValue(), RelationshipType.TEST_CASE_OF, pkg).isPresent()); chkDocumentation.setSelected(SpdxLogic .findRelationship(newSelection.getValue(), RelationshipType.DOCUMENTATION_OF, pkg).isPresent()); chkMetafile.setSelected(SpdxLogic .findRelationship(newSelection.getValue(), RelationshipType.METAFILE_OF, pkg).isPresent()); chkOptionalComponent.setSelected( SpdxLogic.findRelationship(newSelection.getValue(), RelationshipType.OPTIONAL_COMPONENT_OF, pkg) .isPresent()); chkBuildTool.setSelected(SpdxLogic .findRelationship(newSelection.getValue(), RelationshipType.BUILD_TOOL_OF, pkg).isPresent()); chkExcludeFile.setSelected(SpdxLogic.isFileExcludedFromVerification(pkg, newSelection.getValue())); //Set the file relationship checkboxes Relationship[] relationships = newSelection.getValue().getRelationships(); currentFile = newSelection.getValue(); } } private void handleFileTypeCheckedOrUnchecked( ListChangeListener.Change<? extends StringableWrapper<FileType>> change) { if (currentFile == null) return; FileType[] newFileTypes = change.getList().stream().map(wrappedType -> wrappedType.getValue()) //Unwrap the stringable wrapper .toArray(size -> new FileType[size]); //Get array try { currentFile.setFileTypes(newFileTypes); } catch (InvalidSPDXAnalysisException e) { logger.error("Unable to update types of file " + currentFile.getName()); } } public void handleDeleteFileFromPackageClick(MouseEvent event) { List<TreeItem<SpdxFile>> itemsToRemove = ImmutableList .copyOf(filesTable.getSelectionModel().getSelectedItems()); List<Integer> selectedIndices = ImmutableList.copyOf(filesTable.getSelectionModel().getSelectedIndices()); filesTable.getSelectionModel().clearSelection(); filesTable.getRoot().getChildren().removeAll(itemsToRemove); SpdxLogic.removeFilesFromPackage(pkg, itemsToRemove.stream().map(TreeItem::getValue).collect(Collectors.toList())); } public void handleAddFileClick(MouseEvent event) { FileChooser chooser = new FileChooser(); chooser.setTitle("Add file"); File file = chooser.showOpenDialog(btnAddFile.getParent().getScene().getWindow()); if (file == null) //Dialog cancelled. return; Path path = Paths.get(file.getAbsolutePath()); SpdxLogic.addFileToPackage(pkg, path, file.toURI().toString()); } private void addOrRemoveFileRelationshipToPackage(RelationshipType relationshipType, boolean shouldExist) { if (currentFile != null) { SpdxLogic.setFileRelationshipToPackage(currentFile, pkg, relationshipType, shouldExist); } } public void handleBtnAddRelationshipClick(MouseEvent event) { assert (lstTargetPackages.getSelectionModel().getSelectedItems().size() > 0); assert (chcNewRelationshipType.getSelectionModel().getSelectedIndex() >= 0); SpdxPackage targetPackage = lstTargetPackages.getSelectionModel().getSelectedItem(); RelationshipType relationshipType = chcNewRelationshipType.getSelectionModel().getSelectedItem(); //TODO: remove existing relationship types from dropdown try { pkg.addRelationship(new Relationship(targetPackage, relationshipType, null)); lstPackageRelationships.getItems().add(StringableWrapper.wrap(relationshipType, SpdxLogic::toString)); } catch (InvalidSPDXAnalysisException e) { logger.error("Unable to add package relationship", e); new Alert(Alert.AlertType.ERROR, "Unable to add relationship").showAndWait(); } } public void handleBtnRemoveRelationshipClick(MouseEvent event) { assert (lstTargetPackages.getSelectionModel().getSelectedItems().size() > 0); assert (lstPackageRelationships.getSelectionModel().getSelectedItems().size() > 0); StringableWrapper<RelationshipType> wrappedRelationshipType = lstPackageRelationships.getSelectionModel() .getSelectedItem(); lstPackageRelationships.getItems().remove(wrappedRelationshipType); SpdxLogic.removeRelationship(pkg, wrappedRelationshipType.getValue(), lstTargetPackages.getSelectionModel().getSelectedItem()); } private void handleTargetPackageSelected(SpdxPackage pkg) { //Get the relationships the edited package has to the selected target package. List<StringableWrapper<RelationshipType>> relationshipTypes = Arrays.stream(this.pkg.getRelationships()) .filter(relationship -> Objects.equals(relationship.getRelatedSpdxElement(), pkg)) .map(Relationship::getRelationshipType) .map(relationshipType -> StringableWrapper.wrap(relationshipType, SpdxLogic::toString)) .collect(Collectors.toList()); lstPackageRelationships.getItems().setAll(relationshipTypes); btnAddRelationship.setDisable(pkg == null); } private void handleChkExcludeFileChange(boolean newValue) { if (currentFile != null) { if (newValue) { SpdxLogic.excludeFileFromVerification(pkg, currentFile); } else { SpdxLogic.unexcludeFileFromVerification(pkg, currentFile); } } } public void handleBtnCopyrightClick(MouseEvent event) { String oldCopyright = currentFile.getCopyrightText(); TextInputDialog dialog = new TextInputDialog(); dialog.setTitle("Copyright"); dialog.setHeaderText("Enter the copyright text"); ((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons() .addAll(UiUtils.ICON_IMAGE_VIEW.getImage()); Optional<String> newCopyrightText = dialog.showAndWait(); if (newCopyrightText.isPresent()) { currentFile.setCopyrightText(newCopyrightText.get()); } } public void handleBtnFileLicenseClick(MouseEvent event) { FileLicenseEditor.editConcludedLicense(this.currentFile, documentContainer); } }