package spdxedit;
import com.google.common.base.Joiner;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.Window;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spdx.rdfparser.InvalidSPDXAnalysisException;
import org.spdx.rdfparser.SPDXDocumentFactory;
import org.spdx.rdfparser.SpdxDocumentContainer;
import org.spdx.rdfparser.model.SpdxDocument;
import org.spdx.rdfparser.model.SpdxPackage;
import org.spdx.tag.CommonCode;
import org.spdx.tools.TagToRDF;
import spdxedit.io.FileDataType;
import spdxedit.util.UiUtils;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
public class MainSceneController {
@FXML
private TreeView<Path> dirTree;
@FXML
private TextField txtDocumentName;
@FXML
private Button chooseDir;
@FXML
private Button btnAddPackage;
@FXML
private Button saveSpdx;
@FXML
private Button validateSpdx;
@FXML
private Button btnNewDocument;
@FXML
private ListView<SpdxPackage> addedPackagesUiList;
private SpdxDocument documentToEdit = null;
private static final Logger logger = LoggerFactory.getLogger(MainSceneController.class);
//A representation of a package in the pacage list
static class SpdxPackageListCell extends ListCell<SpdxPackage> {
public SpdxPackageListCell() {
super();
//A necessary hack to work around list view not selecting rows on click.
this.setOnMouseClicked(event -> getListView().getSelectionModel().select(this.getIndex()));
}
@Override
protected void updateItem(SpdxPackage item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
//TODO: Add graphic
setText(item.getName());
}
}
}
private FileChooser getSpdxFileChooser(Iterable<String> extenions) {
FileChooser chooser = new FileChooser();
boolean first = true;
for (String extension : extenions) {
FileChooser.ExtensionFilter spdxExtensionFilter = new FileChooser.ExtensionFilter(extension, "*." + extension);
chooser.getExtensionFilters().add(spdxExtensionFilter);
if (first){
chooser.setSelectedExtensionFilter(spdxExtensionFilter);
first = false;
}
}
return chooser;
}
@FXML
void initialize() {
assert dirTree != null : "fx:id=\"dirTree\" was not injected: check your FXML file 'MainScene.fxml'.";
assert chooseDir != null : "fx:id=\"chooseDir\" was not injected: check your FXML file 'MainScene.fxml'.";
assert btnAddPackage != null : "fx:id=\"btnAddPackage\" was not injected: check your FXML file 'MainScene.fxml'.";
assert btnNewDocument != null : "fx:id=\"btnNewDocument\" was not injected: check your FXML file 'MainScene.fxml'.";
assert saveSpdx != null : "fx:id=\"saveSpdx\" was not injected: check your FXML file 'MainScene.fxml'.";
assert validateSpdx != null : "fx:id=\"validateSpdx\" was not injected: check your FXML file 'MainScene.fxml'.";
assert txtDocumentName != null : "fx:id=\"txtDocumentName\" was not injected: check your FXML file 'MainScene.fxml'.";
assert addedPackagesUiList != null : "fx:id=\"addedPackagesUiList\" was not injected: check your FXML file 'MainScene.fxml'.";
txtDocumentName.textProperty().addListener((observable, oldValue, newValue) -> handleTxtDocumentNameChanged(newValue));
addedPackagesUiList.setCellFactory(param -> new SpdxPackageListCell());
}
private void enableAllButtons(){
this.btnAddPackage.setDisable(false);
this.validateSpdx.setDisable(false);
this.chooseDir.setDisable(false);
this.txtDocumentName.setDisable(false);
this.saveSpdx.setDisable(false);
}
private static Optional<Path> selectDirectory(Window parentWindow) {
Objects.requireNonNull(parentWindow);
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle(Main.APP_TITLE);
File result = chooser.showDialog(parentWindow);
if (result == null) {
return Optional.empty();
} else {
return Optional.of(Paths.get(result.getAbsolutePath()));
}
}
private static TreeItem<Path> getTreeForPath(final Path base) throws IOException {
final HashMap<Path, TreeItem<Path>> addedNodes = new HashMap<>();
TreeItem<Path> rootNode = new TreeItem<>(base);
rootNode.setExpanded(true);
addedNodes.put(base, rootNode);
Files.walk(base).forEachOrdered(path -> {
if (Objects.equals(path, base)) return;
TreeItem<Path> current = new TreeItem<>(path);
TreeItem<Path> parent = addedNodes.get(path.getParent());
parent.getChildren().add(current);
addedNodes.put(path, current);
});
return rootNode;
}
public void handleNewDocumentClicked(MouseEvent event){
TextInputDialog dialog = new TextInputDialog("http://url.example.com/spdx/builder");
((Stage)dialog.getDialogPane().getScene().getWindow()).getIcons().addAll(UiUtils.ICON_IMAGE_VIEW.getImage());
dialog.setTitle("New SPDX Document");
dialog.setHeaderText("Enter document namespace");
Optional<String> result = dialog.showAndWait();
while (result.isPresent() && !SpdxLogic.validateDocumentNamespace(result.orElse(""))){
dialog.setHeaderText("Invalid document namespace. Enter new document namespace.");
result = dialog.showAndWait();
}
if (result.isPresent()){
SpdxDocument newDocument = SpdxLogic.createEmptyDocument(result.get());
loadSpdxDocument(newDocument);
}
}
public void handleChooseDirectoryClicked(MouseEvent event) {
Optional<Path> chosenPath = selectDirectory(chooseDir.getParent().getScene().getWindow());
if (!chosenPath.isPresent()) return;
try {
dirTree.setRoot(getTreeForPath(chosenPath.get()));
} catch (IOException ioe) {
logger.error("Unable to access directory " + chosenPath.toString(), ioe);
}
}
public void handleSaveSpdxClicked(MouseEvent event) {
Optional<FileDataType> outputType = IoFileTypeSelectionDialog.getDataType("Save SPDX");
if (!outputType.isPresent()) return;
File targetFile = getSpdxFileChooser( outputType.get().getExtensions()).showSaveDialog(saveSpdx.getScene().getWindow());
if (targetFile == null) //Dialog cancelled
return;
try {
outputType.get().writeToFile(targetFile, documentToEdit);
} catch (IOException e) {
logger.error("Unable to write SPDX file", e);
}
}
public void handleAddPackageClicked(MouseEvent event) {
List<TreeItem<Path>> selectedNodes = dirTree.getSelectionModel().getSelectedItems();
assert (selectedNodes.size() <= 1);
Optional<Path> path = selectedNodes.size() > 0 ? Optional.of(selectedNodes.get(0).getValue()) : Optional.empty();
SpdxPackage newPackage = NewPackageDialog.createPackageWithPrompt(btnAddPackage.getScene().getWindow(), path, documentToEdit.getDocumentContainer());
addedPackagesUiList.getItems().add(newPackage);
if (addedPackagesUiList.getSelectionModel().getSelectedItem() == null) {
addedPackagesUiList.getSelectionModel().selectFirst();
}
}
private void loadSpdxDocument(SpdxDocument loadedDocument) {
this.documentToEdit = loadedDocument;
this.addedPackagesUiList.getItems().setAll(SpdxLogic.getSpdxPackagesInDocument(loadedDocument).collect(Collectors.toList()));
this.txtDocumentName.setText(loadedDocument.getName());
enableAllButtons();
}
public void handleLoadSpdxClicked(MouseEvent event) {
Optional<FileDataType> inputType = IoFileTypeSelectionDialog.getDataType("Load SPDX");
if (!inputType.isPresent())return;
File targetFile = getSpdxFileChooser(inputType.get().getExtensions()).showOpenDialog(saveSpdx.getScene().getWindow());
if (targetFile == null) return; //Cancelled
try {
SpdxDocument loadedDocument = inputType.get().readFromFile(targetFile);
loadSpdxDocument(loadedDocument);
} catch (InvalidSPDXAnalysisException isae) {
logger.error("Invalid SPDX load attempt", isae);
new Alert(Alert.AlertType.ERROR, "Invalid SPDX file " + targetFile.getAbsolutePath());
} catch (FileNotFoundException fnfe) {
logger.error("File not found: " + targetFile.getAbsolutePath());
new Alert(Alert.AlertType.ERROR, "File " + targetFile.getAbsolutePath() + " not found").showAndWait();
} catch (IOException ioe) {
logger.error("Error loading SPDX", ioe);
new Alert(Alert.AlertType.ERROR, "Error loading SPDX file " + targetFile.getAbsolutePath());
}
}
public void handlePackageListClicked(MouseEvent event) {
if (event.getClickCount() == 2) {
SpdxPackage toEdit = addedPackagesUiList.getSelectionModel().getSelectedItem();
List<SpdxPackage> otherPackages = SpdxLogic.getSpdxPackagesInDocument(documentToEdit)
.filter(spdxPackage -> !Objects.equals(spdxPackage, toEdit))
.collect(Collectors.toList());
PackageEditor.editPackage(toEdit, otherPackages, documentToEdit.getDocumentContainer(), addedPackagesUiList.getScene().getWindow());
}
}
public void handleValidateSpdxClicked(MouseEvent event) {
List<String> verificationResult = this.documentToEdit.verify();
Alert alert = new Alert(verificationResult.size() == 0 ? Alert.AlertType.INFORMATION : Alert.AlertType.WARNING);
alert.setTitle(Main.APP_TITLE);
if (verificationResult.size() == 0) {
alert.setHeaderText("Document valid.");
alert.setContentText("This SPDX document is valid.");
} else {
alert.setHeaderText("Document invalid.");
TextArea textArea = new TextArea(StringUtils.join(verificationResult, ",\n"));
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setPrefWidth(808);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane validationCongtent = new GridPane();
validationCongtent.setMaxWidth(Double.MAX_VALUE);
validationCongtent.add(new Label("Problems:"), 0, 0);
validationCongtent.add(textArea, 0, 1);
// Set expandable Exception into the dialog pane.
alert.getDialogPane().setContent(validationCongtent);
}
((Stage)alert.getDialogPane().getScene().getWindow()).getIcons().addAll(UiUtils.ICON_IMAGE_VIEW.getImage());
alert.showAndWait();
}
public void handleDirectoryTreeClicked(MouseEvent event) {
btnAddPackage.setDisable(dirTree.getSelectionModel().isEmpty());
}
@FXML
void handleTxtDocumentNameChanged(String newName) {
this.documentToEdit.setName(newName);
}
}