package bluej.groupwork.ui;

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

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
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.layout.HBox;
import javafx.scene.layout.VBox;

import bluej.Config;
import bluej.groupwork.actions.UpdateAction;
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.TeamViewFilter;
import bluej.groupwork.TeamworkCommand;
import bluej.groupwork.TeamworkCommandResult;
import bluej.groupwork.UpdateFilter;
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 for showing files to be updated | | @author Bruce Quig | @author Davin McCall | @author Amjad Altadmri | @OnThread(Tag.FXPlatform) public class UpdateFilesFrame extends FXCustomizedDialog<Void>{ private CheckBox includeLayoutCheckbox; private ActivityIndicator progressBar; private UpdateAction updateAction; private Button updateButton; private UpdateWorker updateWorker; private Project project; private Repository repository; private ObservableList<UpdateStatus> updateListModel; private Set<TeamStatusInfo> changedLayoutFiles = new HashSet<>(); private Set<File> forcedLayoutFiles = new HashSet<>(); private static UpdateStatus noFilesToUpdate = new UpdateStatus(Config.getString("team.noupdatefiles")); private static UpdateStatus needUpdate = new UpdateStatus(Config.getString("team.pullNeeded")); private boolean includeLayout = true; private boolean pullWithNoChanges = false; private final boolean isDVCS;
| Constructor for UpdateFilesFrame. | public UpdateFilesFrame(Project project) { super(null, "team.update.title", "team-update-files"); this.project = project; updateAction.useButton(project, updateButton); isDVCS = project.getTeamSettingsController().isDVCS(); buildUI(); } @Override protected Node wrapButtonBar(Node original) { updateAction = new UpdateAction(this); updateButton = new Button(); updateButton.requestFocus(); progressBar = new ActivityIndicator(); progressBar.setRunning(false); HBox updateButtonPane = new HBox(); JavaFXUtil.addStyleClass(updateButtonPane, "button-hbox"); updateButtonPane.getChildren().addAll(progressBar, updateButton, original); return updateButtonPane; }
| Create the user-interface for the error display dialog. | private void buildUI() { VBox mainPane = new VBox(); JavaFXUtil.addStyleClass(mainPane, "main-pane"); updateListModel = FXCollections.observableArrayList(); Label updateFilesLabel = new Label(Config.getString("team.update.files")); ListView<UpdateStatus> updateFiles = new ListView<>(updateListModel); if (isDVCS) { updateFiles.setCellFactory(param -> new FileRendererCell(project, true)); } else { updateFiles.setCellFactory(param -> new FileRendererCell(project)); } updateFiles.setEditable(false); ScrollPane updateFileScrollPane = new ScrollPane(updateFiles); updateFileScrollPane.setFitToWidth(true); updateFileScrollPane.setFitToHeight(true); includeLayoutCheckbox = new CheckBox(Config.getString("team.update.includelayout")); includeLayoutCheckbox.setDisable(true); includeLayoutCheckbox.setOnAction(event -> { CheckBox layoutCheck = (CheckBox)event.getSource(); includeLayout = layoutCheck.isSelected(); resetForcedFiles(); if (includeLayout) { addModifiedLayouts(); } else { removeModifiedLayouts(); } }); mainPane.getChildren().addAll(updateFilesLabel, updateFileScrollPane, includeLayoutCheckbox); getDialogPane().setContent(mainPane); prepareButtonPane(); }
| Create the button panel with a close button | @return Pane the buttonPanel | private void prepareButtonPane() { getDialogPane().getButtonTypes().add(ButtonType.CANCEL); this.setOnCloseRequest(event -> { if (updateWorker != null) { updateWorker.abort(); } if (updateAction != null) { updateAction.cancel(); } close(); }); } public void setVisible(boolean visible) { if (visible) { show(); updateAction.setEnabled(false); includeLayoutCheckbox.setSelected(false); includeLayoutCheckbox.setDisable(true); changedLayoutFiles.clear(); forcedLayoutFiles.clear(); updateListModel.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(this.asWindow(), msgFinal); } } startProgress(); updateWorker = new UpdateWorker(); updateWorker.start(); } else { hide(); } } else { hide(); } } private void removeModifiedLayouts() { updateListModel.removeIf(updateStatus -> changedLayoutFiles.stream().anyMatch(statusInfo -> updateStatus.infoStatus != null && statusInfo.getFile().equals(updateStatus.infoStatus.getFile()) ) ); if (updateListModel.isEmpty()) { if (pullWithNoChanges) { updateListModel.add(needUpdate); } else { updateListModel.add(noFilesToUpdate); updateAction.setEnabled(false); } } }
| Add the modified layouts to the displayed list of files to be updated. Should only be | called if there is at least one modified layout file. | private void addModifiedLayouts() { updateListModel.remove(noFilesToUpdate); updateListModel.remove(needUpdate); updateAction.setEnabled(true); for (TeamStatusInfo statusInfo : changedLayoutFiles) { updateListModel.add(new UpdateStatus(statusInfo)); } }
| Start the activity indicator. | public void startProgress() { progressBar.setRunning(true); }
| Stop the activity indicator. Call from any thread. | public void stopProgress() { progressBar.setRunning(false); } public void disableLayoutCheck() { includeLayoutCheckbox.setDisable(true); }
| Re-set the forced files in the update action. This needs to be | done when the "include layout" option is toggled. |*/ private void resetForcedFiles() { Set<File> forcedFiles = new HashSet<>(forcedLayoutFiles); if (includeLayout) { forcedFiles.addAll(changedLayoutFiles.stream().map(TeamStatusInfo::getFile).collect(Collectors.toSet())); | |} | |updateAction.setFilesToForceUpdate(forcedFiles); | |} | |/** | Worker to do the actual status check (to populate commit dialog) in the background, to | avoid blocking the UI. | class UpdateWorker extends FXWorker implements StatusListener { List<TeamStatusInfo> response; TeamworkCommand command; TeamworkCommandResult result; private boolean aborted; private StatusHandle statusHandle; @OnThread(Tag.FXPlatform) public UpdateWorker() { super(); response = new ArrayList<>(); FileFilter filter = project.getTeamSettingsController().getFileFilter(true, !isDVCS); command = repository.getStatus(this, filter, true); }
| (non-Javadoc) | @see bluej.groupwork.StatusListener#gotStatus(bluej.groupwork.TeamStatusInfo) | @OnThread(Tag.Any) public void gotStatus(TeamStatusInfo info) { response.add(info); }
| (non-Javadoc) | @see bluej.groupwork.StatusListener#statusComplete(bluej.groupwork.CommitHandle) | @OnThread(Tag.Worker) public void statusComplete(StatusHandle statusHandle) { pullWithNoChanges = statusHandle.pullNeeded(); this.statusHandle = statusHandle; } @OnThread(Tag.Worker) public Object construct() { result = command.getResult(); return response; } public void abort() { command.cancel(); aborted = true; } @OnThread(Tag.FXPlatform) public void finished() { stopProgress(); if (! aborted) { if (result.isError()) { UpdateFilesFrame.this.dialogThenHide(() -> TeamUtils.handleServerResponseFX(result, UpdateFilesFrame.this.asWindow())); } else { Set<File> filesToUpdate = new HashSet<>(); Set<File> conflicts = new HashSet<>(); Set<File> modifiedLayoutFiles = new HashSet<>(); List<TeamStatusInfo> info = response; getUpdateFileSet(info, filesToUpdate, conflicts, modifiedLayoutFiles); if (conflicts.size() != 0) { 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 += " (and more - check status)"; } String filesListFinal = filesList; UpdateFilesFrame.this.dialogThenHide(() -> DialogManager.showMessageWithTextFX(UpdateFilesFrame.this.asWindow(), "team-unresolved-conflicts", filesListFinal)); return; } Set<File> updateFiles = new HashSet<>(); for (File file : filesToUpdate) { if (!filesToUpdate.contains(file.getParentFile())) { updateFiles.add(file); } } forcedLayoutFiles.removeIf(file -> filesToUpdate.contains(file.getParentFile())); updateAction.setStatusHandle(statusHandle); updateAction.setFilesToUpdate(updateFiles); resetForcedFiles(); if (includeLayout && ! changedLayoutFiles.isEmpty()) { for (TeamStatusInfo statusInfo : changedLayoutFiles) { updateListModel.add(new UpdateStatus(statusInfo)); } } if (updateListModel.isEmpty() && !pullWithNoChanges) { updateListModel.add(noFilesToUpdate); } else { if (isDVCS && pullWithNoChanges && updateListModel.isEmpty()) { updateListModel.add(needUpdate); } updateAction.setEnabled(true); } } } }
| Go through the status list, and figure out which files to update, and | which to force update. | | @param info The list of files with status (List of TeamStatusInfo) | @param filesToUpdate The set to store the files to update in | @param modifiedLayoutFiles The set to store the files to be force updated in | @param conflicts The set to store unresolved conflicts in | (any files in this set prevent update from occurring) | private void getUpdateFileSet(List<TeamStatusInfo> info, Set<File> filesToUpdate, Set<File> conflicts, Set<File> modifiedLayoutFiles) { if (isDVCS) { getUpdateFileSetDist(info, filesToUpdate, conflicts, modifiedLayoutFiles); } else { getUpdateFileSetNondist(info, filesToUpdate, conflicts, modifiedLayoutFiles); } if (! changedLayoutFiles.isEmpty()) { includeLayoutCheckbox.setDisable(false); includeLayoutCheckbox.setSelected(includeLayout); } }
| Go through file statuses and determine which files will be updated (non-distributed version control). | private void getUpdateFileSetNondist(List<TeamStatusInfo> info, Set<File> filesToUpdate, Set<File> conflicts, Set<File> modifiedLayoutFiles) { UpdateFilter filter = new UpdateFilter(); TeamViewFilter viewFilter = new TeamViewFilter(); for (TeamStatusInfo statusInfo : info) { Status status = statusInfo.getStatus(true); if (filter.accept(statusInfo)) { if (!BlueJPackageFile.isPackageFileName(statusInfo.getFile().getName())) { updateListModel.add(new UpdateStatus(statusInfo)); filesToUpdate.add(statusInfo.getFile()); } else { if (!viewFilter.accept(statusInfo)) { } else if (filter.updateAlways(statusInfo)) { updateListModel.add(new UpdateStatus(statusInfo)); forcedLayoutFiles.add(statusInfo.getFile()); } else { modifiedLayoutFiles.add(statusInfo.getFile()); changedLayoutFiles.add(statusInfo); } } } else { boolean conflict; conflict = status == Status.UNRESOLVED; conflict |= status == Status.CONFLICT_ADD; conflict |= status == Status.CONFLICT_LMRD; if (conflict) { if (!BlueJPackageFile.isPackageFileName(statusInfo.getFile().getName())) { conflicts.add(statusInfo.getFile()); } else { modifiedLayoutFiles.add(statusInfo.getFile()); changedLayoutFiles.add(statusInfo); } } } } }
| Go through file statuses and determine which files will be updated (distributed version control). | private void getUpdateFileSetDist(List<TeamStatusInfo> info, Set<File> filesToUpdate, Set<File> conflicts, Set<File> modifiedLayoutFiles) { UpdateFilter filter = new UpdateFilter(); TeamViewFilter viewFilter = new TeamViewFilter(); for (TeamStatusInfo statusInfo : info) { Status status = statusInfo.getStatus(false); if (filter.acceptDist(status)) { if (! BlueJPackageFile.isPackageFileName(statusInfo.getFile().getName())) { updateListModel.add(new UpdateStatus(statusInfo)); filesToUpdate.add(statusInfo.getFile()); } else { if (! viewFilter.accept(statusInfo)) { } else if (status != Status.NEEDS_UPDATE && status != Status.NEEDS_MERGE) { updateListModel.add(new UpdateStatus(statusInfo)); forcedLayoutFiles.add(statusInfo.getFile()); } else { modifiedLayoutFiles.add(statusInfo.getFile()); changedLayoutFiles.add(statusInfo); } } } } } } }

.   UpdateFilesFrame
.   wrapButtonBar
.   buildUI
.   prepareButtonPane
.   setVisible
.   removeModifiedLayouts
.   addModifiedLayouts
.   startProgress
.   stopProgress
.   disableLayoutCheck

top, use, map, class UpdateWorker

.   UpdateWorker
.   gotStatus
.   statusComplete
.   construct
.   abort
.   finished
.   getUpdateFileSet
.   getUpdateFileSetNondist
.   getUpdateFileSetDist




613 neLoCode + 33 LoComm