at.ac.tuwien.qse.sepm.gui.controller.impl.OrganizerImpl.java Source code

Java tutorial

Introduction

Here is the source code for at.ac.tuwien.qse.sepm.gui.controller.impl.OrganizerImpl.java

Source

package at.ac.tuwien.qse.sepm.gui.controller.impl;

/*
 * Copyright (c) 2015 Lukas Eibensteiner
 * Copyright (c) 2015 Kristoffer Kleine
 * Copyright (c) 2015 Branko Majic
 * Copyright (c) 2015 Enri Miho
 * Copyright (c) 2015 David Peherstorfer
 * Copyright (c) 2015 Marian Stoschitzky
 * Copyright (c) 2015 Christoph Wasylewski
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
 * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

import at.ac.tuwien.qse.sepm.entities.*;
import at.ac.tuwien.qse.sepm.gui.control.FilterControl;
import at.ac.tuwien.qse.sepm.gui.control.FilterGroup;
import at.ac.tuwien.qse.sepm.gui.controller.Inspector;
import at.ac.tuwien.qse.sepm.gui.controller.Organizer;
import at.ac.tuwien.qse.sepm.gui.dialogs.ErrorDialog;
import at.ac.tuwien.qse.sepm.gui.dialogs.InfoDialog;
import at.ac.tuwien.qse.sepm.gui.util.BufferedBatchOperation;
import at.ac.tuwien.qse.sepm.service.*;
import at.ac.tuwien.qse.sepm.service.impl.Aggregator;
import at.ac.tuwien.qse.sepm.service.impl.PhotoFilter;
import at.ac.tuwien.qse.sepm.service.impl.PhotoPathFilter;
import at.ac.tuwien.qse.sepm.service.impl.PhotoSet;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon;
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

public class OrganizerImpl extends Refresher implements Organizer {

    private static final Logger LOGGER = LogManager.getLogger(OrganizerImpl.class);
    private final DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy MMM");

    @Autowired
    private PhotographerService photographerService;
    @Autowired
    private ClusterService clusterService;
    @Autowired
    private Inspector<Photo> inspectorController;
    @Autowired
    private TagService tagService;
    @Autowired
    private WorkspaceService workspaceService;
    @Autowired
    private PhotoService photoService;

    @FXML
    private BorderPane root;

    @FXML
    private FilterGroup<Rating> ratingFilter;
    @FXML
    private FilterGroup<Tag> tagFilter;
    @FXML
    private FilterGroup<Photographer> photographerFilter;
    @FXML
    private FilterGroup<Journey> journeyFilter;
    @FXML
    private FilterGroup<Place> placeFilter;

    @FXML
    private ToggleButton listTab;
    @FXML
    private ToggleButton folderTab;
    @FXML
    private Node listTabContent;
    @FXML
    private Node folderTabContent;

    @FXML
    private TreeView<String> folderTree;

    private final PhotoSet allPhotos = new PhotoSet();
    private final PhotoSet acceptedPhotos = new PhotoSet();

    private PhotoFilter usedFilter = new PhotoFilter();
    private PhotoFilter photoFilter = usedFilter;
    private PhotoFilter folderFilter = new PhotoPathFilter();
    private Runnable filterChangeCallback;
    private boolean suppressChangeEvent = false;

    private BufferedBatchOperation<Photo> addOperation;
    private BufferedBatchOperation<Path> deleteOperation;

    @Autowired
    public void setScheduler(ScheduledExecutorService scheduler) {
        addOperation = new BufferedBatchOperation<>(photos -> {
            Platform.runLater(this::buildTreeView);
        }, scheduler);

        deleteOperation = new BufferedBatchOperation<>(paths -> {
            Platform.runLater(this::buildTreeView);
        }, scheduler);
    }

    @Override
    public void setFilterChangeAction(Runnable callback) {
        LOGGER.debug("setting usedFilter change action");
        filterChangeCallback = callback;
    }

    @Override
    public void reset() {
        allPhotos.clear();
        acceptedPhotos.clear();
        markDirty();
    }

    @Override
    public boolean accept(Photo photo) {
        remove(photo);
        allPhotos.add(photo);
        if (usedFilter.test(photo)) {
            acceptedPhotos.add(photo);
            markDirty();
            return true;
        }
        markDirty();
        return false;
    }

    @Override
    public void remove(Photo photo) {
        allPhotos.remove(photo);
        acceptedPhotos.remove(photo);
        markDirty();
    }

    @Override
    public void setWorldMapPlace(Place place) {
        placeFilter.excludeAll();
        placeFilter.include(place);
        placeFilter.expand();
        filterViewClicked();

        Platform.runLater(this::handlePlacesChange);
    }

    @Override
    protected void refresh() {
        LOGGER.debug("refreshing filter");
        refreshRatings();
        refreshTags();
        refreshJourneys();
        refreshPlaces();
        refreshPhotographers();
    }

    @FXML
    private void initialize() {

        initializeFilesTree();

        folderTree.setOnMouseClicked(event -> handleFolderChange());

        listTabContent.visibleProperty().bind(listTab.selectedProperty());
        folderTabContent.visibleProperty().bind(folderTab.selectedProperty());
        listTab.setOnAction(event -> filterViewClicked());
        folderTab.setOnAction(event -> folderViewClicked());

        ratingFilter.setOnUpdate(this::handleRatingsChange);
        tagFilter.setOnUpdate(this::handleCategoriesChange);
        photographerFilter.setOnUpdate(this::handlePhotographersChange);
        journeyFilter.setOnUpdate(this::handleJourneysChange);
        placeFilter.setOnUpdate(this::handlePlacesChange);

        refresh();
        listTab.fire();

        tagService.subscribeTagChanged((p) -> refreshTags());
        clusterService.subscribeJourneyChanged((p) -> refreshJourneys());
        clusterService.subscribePlaceChanged((p) -> refreshPlaces());
        photographerService.subscribeChanged((p) -> refreshPhotographers());
        photoService.subscribeCreate(addOperation::add);
        photoService.subscribeDelete(deleteOperation::add);

        start(1, TimeUnit.SECONDS);
    }

    private void initializeFilesTree() {
        folderTree.setOnMouseClicked(event -> handleFolderChange());
        folderTree.setCellFactory(treeView -> {
            HBox hbox = new HBox();
            hbox.setMaxWidth(200);
            hbox.setPrefWidth(200);
            hbox.setSpacing(7);

            FontAwesomeIconView openFolderIcon = new FontAwesomeIconView(FontAwesomeIcon.FOLDER_OPEN_ALT);
            openFolderIcon.setTranslateY(7);
            FontAwesomeIconView closedFolderIcon = new FontAwesomeIconView(FontAwesomeIcon.FOLDER_ALT);
            closedFolderIcon.setTranslateY(7);

            Label dirName = new Label();
            dirName.setMaxWidth(150);

            FontAwesomeIconView removeIcon = new FontAwesomeIconView(FontAwesomeIcon.REMOVE);

            Tooltip deleteToolTip = new Tooltip();
            deleteToolTip.setText("Verzeichnis aus Workspace entfernen");

            Button button = new Button(null, removeIcon);
            button.setTooltip(deleteToolTip);
            button.setTranslateX(8);

            return new TreeCell<String>() {
                @Override
                public void updateItem(String item, boolean empty) {
                    super.updateItem(item, empty);
                    if (item == null || empty) {
                        setGraphic(null);
                        setText(null);
                    } else if (getTreeItem() instanceof FilePathTreeItem) {
                        hbox.getChildren().clear();
                        dirName.setText(item);
                        if (getTreeItem().isExpanded()) {
                            hbox.getChildren().add(openFolderIcon);
                        } else {
                            hbox.getChildren().add(closedFolderIcon);
                        }
                        hbox.getChildren().add(dirName);
                        TreeItem<String> treeItem = getTreeItem();
                        TreeItem<String> parent = treeItem != null ? treeItem.getParent() : null;
                        if (parent != null && parent.equals(folderTree.getRoot())) {
                            String path = ((FilePathTreeItem) getTreeItem()).getFullPath();
                            button.setOnAction(event -> handleDeleteDirectory(Paths.get(path)));
                            hbox.getChildren().add(button);
                        }
                        setGraphic(hbox);
                    }
                }
            };
        });
    }

    private void handleDeleteDirectory(Path directory) {
        if (directory != null) {
            try {
                workspaceService.removeDirectory(directory);
            } catch (ServiceException ex) {
                LOGGER.error("Could not delete directory");
            }
        }
    }

    // This Method let's the User switch to the usedFilter-view
    private void filterViewClicked() {
        listTab.setSelected(true);
        folderTab.setSelected(false);
        usedFilter = photoFilter;
        handleFilterChange();
    }

    // This Method let's the User switch to the folder-view
    private void folderViewClicked() {
        listTab.setSelected(false);
        folderTab.setSelected(true);
        usedFilter = new PhotoPathFilter();
        buildTreeView();
        handleFilterChange();
    }

    private void buildTreeView() {
        Collection<Path> workspaceDirectories;
        try {
            workspaceDirectories = workspaceService.getDirectories();
            LOGGER.debug("found directories {}", workspaceDirectories);
        } catch (ServiceException ex) {
            workspaceDirectories = new LinkedList<>();
        }

        FilePathTreeItem root = new FilePathTreeItem(Paths.get("workspace"));
        root.setExpanded(true);

        folderTree.setRoot(root);
        folderTree.setShowRoot(false);

        for (Path dirPath : workspaceDirectories) {
            findFiles(dirPath.toFile(), root);
        }
    }

    private void findFiles(File dir, FilePathTreeItem parent) {
        FilePathTreeItem rootNode = new FilePathTreeItem(dir.toPath());
        rootNode.setExpanded(true);
        File[] files = dir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                findFiles(file, rootNode);
            }
        }
        if (parent == null) {
            folderTree.setRoot(rootNode);
        } else {
            parent.getChildren().add(rootNode);
        }
    }

    private void handleFilterChange() {
        LOGGER.debug("usedFilter changed");
        if (filterChangeCallback == null || suppressChangeEvent)
            return;
        filterChangeCallback.run();
    }

    private void handleFolderChange() {
        LOGGER.debug("handle folder");
        FilePathTreeItem item = (FilePathTreeItem) folderTree.getSelectionModel().getSelectedItem();
        if (item != null) {
            if (usedFilter instanceof PhotoPathFilter)
                ((PhotoPathFilter) usedFilter).setIncludedPath(Paths.get(item.getFullPath()));
            handleFilterChange();
        }
    }

    private void handleRatingsChange() {
        LOGGER.debug("rating usedFilter changed");
        usedFilter.getRatingFilter().getIncluded().clear();
        usedFilter.getRatingFilter().getIncluded().addAll(ratingFilter.getIncludedValues());
        handleFilterChange();
    }

    private void handleCategoriesChange() {
        LOGGER.debug("category usedFilter changed");
        usedFilter.getTagFilter().getRequired().clear();
        usedFilter.getTagFilter().getRequired().addAll(tagFilter.getIncludedValues());
        handleFilterChange();
    }

    private void handlePhotographersChange() {
        LOGGER.debug("photographer usedFilter changed");
        usedFilter.getPhotographerFilter().getIncluded().clear();
        usedFilter.getPhotographerFilter().getIncluded().addAll(photographerFilter.getIncludedValues());
        handleFilterChange();
    }

    private void handleJourneysChange() {
        LOGGER.debug("journey usedFilter changed");
        usedFilter.getJourneyFilter().getIncluded().clear();
        usedFilter.getJourneyFilter().getIncluded().addAll(journeyFilter.getIncludedValues());
        handleFilterChange();
    }

    private void handlePlacesChange() {
        LOGGER.debug("place usedFilter changed");
        usedFilter.getPlaceFilter().getIncluded().clear();
        usedFilter.getPlaceFilter().getIncluded().addAll(placeFilter.getIncludedValues());
        handleFilterChange();
    }

    private List<Rating> getAllRatings() {
        LOGGER.debug("fetching ratings");
        List<Rating> list = new LinkedList<Rating>();
        list.add(Rating.NONE);
        list.add(Rating.GOOD);
        list.add(Rating.NEUTRAL);
        list.add(Rating.BAD);
        LOGGER.debug("fetching ratings succeeded with {} items", list.size());
        return list;
    }

    private List<Tag> getAllTags() {
        LOGGER.debug("fetching categories");
        try {
            List<Tag> list = tagService.getAllTags();
            LOGGER.debug("fetching categories succeeded with {} items", list.size());
            list.sort((a, b) -> a.getName().compareTo(b.getName()));
            list.add(0, null);
            return list;
        } catch (ServiceException ex) {
            LOGGER.error("fetching categories failed", ex);
            ErrorDialog.show(root, "Fehler beim Laden", "Foto-Kategorien konnten nicht geladen werden.");
            return new ArrayList<>();
        }
    }

    private List<Photographer> getAllPhotographers() {
        LOGGER.debug("fetching photographers");
        try {
            List<Photographer> list = photographerService.readAll();
            LOGGER.debug("fetching photographers succeeded with {} items", list.size());
            list.add(0, null);
            return list;
        } catch (ServiceException ex) {
            LOGGER.error("fetching photographers failed", ex);
            ErrorDialog.show(root, "Fehler beim Laden", "Fotografen konnten nicht geladen werden.");
            return new ArrayList<>();
        }
    }

    private List<Journey> getAllJourneys() {
        LOGGER.debug("fetching journeys");
        try {
            List<Journey> list = clusterService.getAllJourneys();
            list.sort((a, b) -> a.getName().compareTo(b.getName()));
            list.add(0, null);
            return list;
        } catch (ServiceException ex) {
            LOGGER.error("fetching journeys failed", ex);
            InfoDialog dialog = new InfoDialog(root, "Fehler");
            dialog.setError(true);
            dialog.setHeaderText("Fehler beim Laden");
            dialog.setContentText("Reisen konnten nicht geladen werden.");
            dialog.showAndWait();
            return new ArrayList<>();
        }
    }

    private List<Place> getAllPlaces() {
        LOGGER.debug("fetching journeys");
        try {
            List<Place> list = clusterService.getAllPlaces();
            list.sort((a, b) -> {
                int c = a.getCountry().compareTo(b.getCountry());
                if (c != 0)
                    return c;
                return a.getCity().compareTo(b.getCity());
            });
            list.add(0, null);
            return list;
        } catch (ServiceException ex) {
            LOGGER.error("fetching journeys failed", ex);
            ErrorDialog.show(root, "Fehler beim Laden", "Reisen konnten nicht geladen werden.");
            return new ArrayList<>();
        }
    }

    private void refreshRatings() {
        List<Rating> ratings = getAllRatings();

        refreshFilter(acceptedPhotos.getRatings(), ratingFilter, ratings, "Keine Bewertung", value -> {
            switch (value) {
            case GOOD:
                return "Gut";
            case NEUTRAL:
                return "Neutral";
            case BAD:
                return "Schlecht";
            default:
                return "Keine Bewertung";
            }
        });

        photoFilter.getRatingFilter().getIncluded().addAll(ratings);
    }

    private void refreshTags() {
        List<Tag> tags = getAllTags();

        refreshFilterExcluded(acceptedPhotos.getTags(), tagFilter, tags, "Keine Kategorien", Tag::getName);
        photoFilter.getTagFilter().getRequired().clear();
    }

    private void refreshJourneys() {
        List<Journey> journeys = getAllJourneys();
        refreshFilter(acceptedPhotos.getJourneys(), journeyFilter, journeys, "Keine Reise", Journey::getName);

        photoFilter.getJourneyFilter().getIncluded().addAll(journeys);
    }

    private void refreshPlaces() {
        List<Place> places = getAllPlaces();
        refreshFilter(acceptedPhotos.getPlaces(), placeFilter, places, "Kein Ort",
                value -> value.getCountry() + ", " + value.getCity());

        photoFilter.getPlaceFilter().getIncluded().addAll(places);
    }

    private void refreshPhotographers() {
        List<Photographer> photographers = getAllPhotographers();
        refreshFilter(acceptedPhotos.getPhotographers(), photographerFilter, photographers, "Kein Fotograf",
                Photographer::getName);
        photoFilter.getPhotographerFilter().getIncluded().addAll(photographers);
    }

    private <T> void refreshFilter(Aggregator<T> aggregator, FilterGroup<T> filter, Iterable<T> values,
            String defaultLabel, Function<T, String> converter) {
        Platform.runLater(() -> {
            // NOTE: Remember the values that were excluded before the refresh and exclude them.
            // That way the filter stays the same and new values are included automatically.
            Set<T> excluded = filter.getExcludedValues();
            filter.getItems().clear();
            values.forEach(p -> {
                FilterControl<T> item = new FilterControl<>();
                item.setValue(p);
                item.setConverter(val -> {
                    if (val == null) {
                        return defaultLabel;
                    }
                    return converter.apply(val);
                });
                item.setIncluded(!excluded.contains(p));
                item.setCount(aggregator.getCount(p));
                filter.getItems().add(item);
            });
        });
    }

    private <T> void refreshFilterExcluded(Aggregator<T> aggregator, FilterGroup<T> filter, Iterable<T> values,
            String defaultLabel, Function<T, String> converter) {
        Platform.runLater(() -> {
            // NOTE: Remember the values that were excluded before the refresh and exclude them.
            // That way the filter stays the same and new values are included automatically.
            Set<T> included = filter.getIncludedValues();
            filter.getItems().clear();
            values.forEach(p -> {
                FilterControl<T> item = new FilterControl<>();
                item.setValue(p);
                item.setConverter(val -> {
                    if (val == null) {
                        return defaultLabel;
                    }
                    return converter.apply(val);
                });
                item.setIncluded(included.contains(p));
                item.setCount(aggregator.getCount(p));
                filter.getItems().add(item);
            });
        });
    }

    private void handlePhotoAdded(Photo photo) {
        addOperation.add(photo);
    }
}