package bluej.groupwork.ui;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;

import bluej.Config;
import bluej.groupwork.CommitFilter;
import bluej.groupwork.Repository;
import bluej.groupwork.StatusHandle;
import bluej.groupwork.StatusListener;
import bluej.groupwork.TeamStatusInfo;
import bluej.groupwork.TeamStatusInfo.Status;
import bluej.groupwork.TeamUtils;
import bluej.groupwork.TeamworkCommand;
import bluej.groupwork.TeamworkCommandResult;
import bluej.groupwork.actions.CommitAction;
import bluej.pkgmgr.BlueJPackageFile;
import bluej.pkgmgr.Project;
import bluej.utility.DialogManager;
import bluej.utility.FXWorker;
import bluej.utility.javafx.FXCustomizedDialog;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.Utility;

import threadchecker.OnThread;
import threadchecker.Tag;


| A user interface to add commit comments. | @author Bruce Quig | @author Amjad Altadmri | @OnThread(Tag.FXPlatform) public class CommitCommentsFrame extends FXCustomizedDialog<Void> implements CommitAndPushInterface{ private Project project; private Repository repository; private CommitAction commitAction; private CommitWorker commitWorker; private ObservableList<TeamStatusInfo> commitListModel = FXCollections.observableArrayList(); private Set<TeamStatusInfo> changedLayoutFiles = new HashSet<>();
| The packages whose layout should be committed compulsorily | private Set<File> packagesToCommmit = new HashSet<>(); private final TextArea commitText = new TextArea(""); private final CheckBox includeLayout = new CheckBox(Config.getString("team.commit.includelayout")); private final ActivityIndicator progressBar = new ActivityIndicator(); public CommitCommentsFrame(Project project) { super(null, "team.commit.title", "team-commit-comments"); this.project = project; getDialogPane().setContent(makeMainPane()); prepareButtonPane(); }
private Pane makeMainPane() { ListView<TeamStatusInfo> commitFiles = new ListView<>(commitListModel); commitFiles.setPlaceholder(new Label(Config.getString("team.nocommitfiles"))); commitFiles.setCellFactory(param -> new TeamStatusInfoCell(project)); commitFiles.setDisable(true); ScrollPane fileScrollPane = new ScrollPane(commitFiles); fileScrollPane.setFitToWidth(true); fileScrollPane.setFitToHeight(true); VBox.setMargin(fileScrollPane, new Insets(0, 0, 20, 0)); commitText.setPrefRowCount(20); commitText.setPrefColumnCount(35); VBox.setMargin(commitText, new Insets(0, 0, 10, 0)); commitAction = new CommitAction(this); Button commitButton = new Button(); commitAction.useButton(project, commitButton); commitButton.requestFocus(); commitText.disableProperty().bind(Bindings.isEmpty(commitListModel)); commitAction.disabledProperty().bind(Bindings.or(commitText.disabledProperty(), commitText.textProperty().isEmpty())); progressBar.setRunning(false); includeLayout.setOnAction(event -> { CheckBox layoutCheck = (CheckBox) event.getSource(); if (layoutCheck.isSelected()) { addModifiedLayouts(); } else { removeModifiedLayouts(); } }); includeLayout.setDisable(true); HBox commitButtonPane = new HBox(); JavaFXUtil.addStyleClass(commitButtonPane, "button-hbox"); commitButtonPane.setAlignment(Pos.CENTER_RIGHT); commitButtonPane.getChildren().addAll(includeLayout, commitButton, progressBar); VBox mainPane = new VBox(); JavaFXUtil.addStyleClass(mainPane, "main-pane"); mainPane.getChildren().addAll(new Label(Config.getString("team.commit.files")), fileScrollPane, new Label(Config.getString("team.commit.comment")), commitText, commitButtonPane); VBox.setVgrow(fileScrollPane, Priority.ALWAYS); VBox.setVgrow(commitText, Priority.ALWAYS); return mainPane; }
| Prepare the button panel with a Resolve button and a close button | private void prepareButtonPane() { getDialogPane().getButtonTypes().setAll(ButtonType.CLOSE); this.setOnCloseRequest(event -> { if (commitWorker != null) { commitWorker.abort(); commitAction.cancel(); } }); } public void setVisible(boolean visible) { if (visible) { includeLayout.setSelected(false); includeLayout.setDisable(true); changedLayoutFiles.clear(); commitListModel.clear(); repository = project.getRepository(); if (repository != null) { try { project.saveAllEditors(); project.saveAll(); } catch (IOException ioe) { String msg = DialogManager.getMessage("team-error-saving-project"); if (msg != null) { msg = Utility.mergeStrings(msg, ioe.getLocalizedMessage()); String msgFinal = msg; DialogManager.showErrorTextFX(asWindow(), msgFinal); } } startProgress(); commitWorker = new CommitWorker(); commitWorker.start(); if (!isShowing()) { show(); } } else { hide(); } } else { hide(); } } public String getComment() { return commitText.getText(); } public void setComment(String newComment) { commitText.setText(newComment); } public void reset() { commitListModel.clear(); setComment(""); } private void removeModifiedLayouts() { for (TeamStatusInfo info : changedLayoutFiles) { if (!packagesToCommmit.contains(info.getFile().getParentFile())) { commitListModel.remove(info); } } } private void addModifiedLayouts() { Set<File> displayedLayouts = new HashSet<>(); for (TeamStatusInfo info : changedLayoutFiles) { File parentFile = info.getFile().getParentFile(); if (!displayedLayouts.contains(parentFile) && !packagesToCommmit.contains(parentFile)) { commitListModel.add(info); displayedLayouts.add(info.getFile().getParentFile()); } } }
| Get a list of the layout files to be committed | public Set getChangedLayoutFiles() { return changedLayoutFiles.stream().map(TeamStatusInfo::getFile).collect(Collectors.toSet()); }
| Remove a file from the list of changes layout files. | private void removeChangedLayoutFile(File file) { for (Iterator<TeamStatusInfo> it = changedLayoutFiles.iterator(); it.hasNext(); ) { TeamStatusInfo info = it.next(); if (info.getFile().equals(file)) { it.remove(); return; } } }
| Get a set of the layout files which have changed (with status info). | public Set getChangedLayoutInfo() { return changedLayoutFiles; } public boolean includeLayout() { return includeLayout != null && includeLayout.isSelected(); }
| Start the activity indicator. | @OnThread(Tag.FXPlatform) public void startProgress() { progressBar.setRunning(true); }
| Stop the activity indicator. Call from any thread. | @OnThread(Tag.Any) public void stopProgress() { JavaFXUtil.runNowOrLater(() -> progressBar.setRunning(false)); } public Project getProject() { return project; } private void setLayoutChanged(boolean hasChanged) { includeLayout.setDisable(!hasChanged); }
| Inner class to do the actual cvs status check to populate commit dialog | to ensure that the UI is not blocked during remote call | class CommitWorker extends FXWorker implements StatusListener { List<TeamStatusInfo> response; TeamworkCommand command; TeamworkCommandResult result; private boolean aborted; public CommitWorker() { super(); response = new ArrayList<>(); FileFilter filter = project.getTeamSettingsController().getFileFilter(true, true); command = repository.getStatus(this, filter, false); }
| | @see bluej.groupwork.StatusListener#gotStatus(bluej.groupwork.TeamStatusInfo) | @OnThread(Tag.Any) @Override public void gotStatus(TeamStatusInfo info) { response.add(info); }
| | @see bluej.groupwork.StatusListener#statusComplete(bluej.groupwork.CommitHandle) | @OnThread(Tag.Worker) @Override public void statusComplete(StatusHandle statusHandle) { commitAction.setStatusHandle(statusHandle); } @OnThread(Tag.Worker) @Override public Object construct() { result = command.getResult(); return response; } public void abort() { command.cancel(); aborted = true; } @Override public void finished() { stopProgress(); if (!aborted) { if (result.isError()) { CommitCommentsFrame.this.dialogThenHide(() -> TeamUtils.handleServerResponseFX(result, CommitCommentsFrame.this.asWindow())); } else if (response != null) { Set<File> filesToCommit = new HashSet<>(); Set<File> filesToAdd = new LinkedHashSet<>(); Set<File> filesToDelete = new HashSet<>(); Set<File> mergeConflicts = new HashSet<>(); Set<File> deleteConflicts = new HashSet<>(); Set<File> otherConflicts = new HashSet<>(); Set<File> needsMerge = new HashSet<>(); Set<File> modifiedLayoutFiles = new HashSet<>(); List<TeamStatusInfo> info = response; getCommitFileSets(info, filesToCommit, filesToAdd, filesToDelete, mergeConflicts, deleteConflicts, otherConflicts, needsMerge, modifiedLayoutFiles); if (!mergeConflicts.isEmpty() || !deleteConflicts.isEmpty() || !otherConflicts.isEmpty() || !needsMerge.isEmpty()) { handleConflicts(mergeConflicts, deleteConflicts, otherConflicts, needsMerge); return; } commitAction.setFiles(filesToCommit); commitAction.setNewFiles(filesToAdd); commitAction.setDeletedFiles(filesToDelete); } if (!commitListModel.isEmpty()) { commitText.requestFocus(); } } } private void handleConflicts(Set<File> mergeConflicts, Set<File> deleteConflicts, Set<File> otherConflicts, Set<File> needsMerge) { String dlgLabel; String filesList; if (!mergeConflicts.isEmpty()) { dlgLabel = "team-resolve-merge-conflicts"; filesList = buildConflictsList(mergeConflicts); } else if (!deleteConflicts.isEmpty()) { dlgLabel = "team-resolve-conflicts-delete"; filesList = buildConflictsList(deleteConflicts); } else if (!otherConflicts.isEmpty()) { dlgLabel = "team-update-first"; filesList = buildConflictsList(otherConflicts); } else { stopProgress(); CommitCommentsFrame.this.dialogThenHide(() -> DialogManager.showMessageFX(CommitCommentsFrame.this.asWindow(), "team-uptodate-failed")); return; } stopProgress(); CommitCommentsFrame.this.dialogThenHide(() -> DialogManager.showMessageWithTextFX(CommitCommentsFrame.this.asWindow(), dlgLabel, filesList)); }
| Buid a list of files, max out at 10 files. | @param conflicts | @return | private String buildConflictsList(Set<File> conflicts) { String filesList = ""; Iterator<File> i = conflicts.iterator(); for (int j = 0; j < 10 && i.hasNext(); j++) { File conflictFile = i.next(); filesList += " " + conflictFile.getName() + "\n"; } if (i.hasNext()) { filesList += " " + Config.getString("team.commit.moreFiles"); } return filesList; }
| Go through the status list, and figure out which files to commit, and | of those which are to be added (i.e. which aren't in the repository) and | which are to be removed. | | @param info The list of files with status (List of TeamStatusInfo) | @param filesToCommit The set to store the files to commit in | @param filesToAdd The set to store the files to be added in | @param filesToRemove The set to store the files to be removed in | @param mergeConflicts The set to store files with merge conflicts in. | @param deleteConflicts The set to store files with conflicts in, which | need to be resolved by first deleting the local file | @param otherConflicts The set to store files with "locally deleted" conflicts |* (locally deleted, remotely modified). * @param needsMerge The set of files which are updated locally as * well as in the repository (required merging). | @param modifiedLayoutFiles | private void getCommitFileSets(List<TeamStatusInfo> info, Set<File> filesToCommit, Set<File> filesToAdd, Set<File> filesToRemove, Set<File> mergeConflicts, Set<File> deleteConflicts, Set<File> otherConflicts, Set<File> needsMerge, Set<File> modifiedLayoutFiles) { CommitFilter filter = new CommitFilter(); Map<File,File> modifiedLayoutDirs = new HashMap<>(); for (TeamStatusInfo statusInfo : info) { File file = statusInfo.getFile(); boolean isPkgFile = BlueJPackageFile.isPackageFileName(file.getName()); Status status = statusInfo.getStatus(); if (filter.accept(statusInfo, true)) { if (!isPkgFile) { commitListModel.add(statusInfo); filesToCommit.add(file); } else if (status == Status.NEEDS_ADD || status == Status.DELETED || status == Status.CONFLICT_LDRM) { if (packagesToCommmit.add(statusInfo.getFile().getParentFile())) { commitListModel.add(statusInfo); File otherPkgFile = modifiedLayoutDirs.remove(file.getParentFile()); if (otherPkgFile != null) { removeChangedLayoutFile(otherPkgFile); filesToCommit.add(otherPkgFile); } } filesToCommit.add(statusInfo.getFile()); } else { File parentFile = file.getParentFile(); if (!packagesToCommmit.contains(parentFile)) { modifiedLayoutFiles.add(file); modifiedLayoutDirs.put(parentFile, file); changedLayoutFiles.add(statusInfo); } else { filesToCommit.add(file); } } if (status == Status.NEEDS_ADD) { filesToAdd.add(statusInfo.getFile()); } else if (status == Status.DELETED || status == Status.CONFLICT_LDRM) { filesToRemove.add(statusInfo.getFile()); } } else if (!isPkgFile) { if (status == Status.HAS_CONFLICTS) { mergeConflicts.add(statusInfo.getFile()); } if (status == Status.UNRESOLVED || status == Status.CONFLICT_ADD || status == Status.CONFLICT_LMRD) { deleteConflicts.add(statusInfo.getFile()); } if (status == Status.CONFLICT_LDRM) { otherConflicts.add(statusInfo.getFile()); } if (status == Status.NEEDS_MERGE) { needsMerge.add(statusInfo.getFile()); } } } setLayoutChanged(!changedLayoutFiles.isEmpty()); } } }

.   CommitCommentsFrame
.   makeMainPane
.   prepareButtonPane
.   setVisible
.   getComment
.   setComment
.   reset
.   removeModifiedLayouts
.   addModifiedLayouts
.   getChangedLayoutFiles
.   removeChangedLayoutFile
.   getChangedLayoutInfo
.   includeLayout
.   startProgress
.   stopProgress
.   getProject
.   setLayoutChanged

top, use, map, class CommitWorker

.   CommitWorker
.   gotStatus
.   statusComplete
.   construct
.   abort
.   finished
.   handleConflicts
.   buildConflictsList
.   getCommitFileSets




616 neLoCode + 31 LoComm