001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.downloadtasks;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.GraphicsEnvironment;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.HashSet;
010import java.util.LinkedHashSet;
011import java.util.Set;
012import java.util.concurrent.CancellationException;
013import java.util.concurrent.ExecutionException;
014import java.util.concurrent.Future;
015import java.util.function.Consumer;
016
017import javax.swing.JOptionPane;
018import javax.swing.SwingUtilities;
019
020import org.openstreetmap.josm.gui.ExceptionDialogUtil;
021import org.openstreetmap.josm.gui.MainApplication;
022import org.openstreetmap.josm.gui.Notification;
023import org.openstreetmap.josm.gui.util.GuiHelper;
024import org.openstreetmap.josm.tools.ExceptionUtil;
025import org.openstreetmap.josm.tools.Logging;
026import org.openstreetmap.josm.tools.Utils;
027
028/**
029 * The post-download handler notifies user of potential errors that occurred.
030 * @since 2322
031 */
032public class PostDownloadHandler implements Runnable {
033    private final DownloadTask task;
034    private final Future<?> future;
035    private Consumer<Collection<Object>> errorReporter;
036
037    private static final Set<String> NO_DATA_ERROR_MESSAGES = new HashSet<>();
038
039    /**
040     * Creates a new {@link PostDownloadHandler}
041     * @param task the asynchronous download task
042     * @param future the future on which the completion of the download task can be synchronized
043     */
044    public PostDownloadHandler(DownloadTask task, Future<?> future) {
045        this.task = task;
046        this.future = future;
047    }
048
049    /**
050     * Creates a new {@link PostDownloadHandler} using a custom error reporter
051     * @param task the asynchronous download task
052     * @param future the future on which the completion of the download task can be synchronized
053     * @param errorReporter a callback to inform about the number errors happened during the download
054     *                      task
055     */
056    public PostDownloadHandler(DownloadTask task, Future<?> future, Consumer<Collection<Object>> errorReporter) {
057        this(task, future);
058        this.errorReporter = errorReporter;
059    }
060
061    /**
062     * Adds a new translated error message indicating that no data has been downloaded.
063     * @param message new translated error message indicating that no data has been downloaded.
064     * @return {@code true} if the message was not already known
065     * @since 15358
066     */
067    public static boolean addNoDataErrorMessage(String message) {
068        return NO_DATA_ERROR_MESSAGES.add(message);
069    }
070
071    /**
072     * Determines if a translated error message indicates that no data has been downloaded.
073     * @param message translated error message to check
074     * @return {@code true} if the message indicates that no data has been downloaded
075     * @since 15358
076     */
077    public static boolean isNoDataErrorMessage(Object message) {
078        return NO_DATA_ERROR_MESSAGES.contains(message);
079    }
080
081    @Override
082    public void run() {
083        // wait for downloads task to finish (by waiting for the future to return a value)
084        //
085        try {
086            future.get();
087        } catch (InterruptedException | ExecutionException | CancellationException e) {
088            Logging.error(e);
089            return;
090        }
091
092        // make sure errors are reported only once
093        //
094        Set<Object> errors = new LinkedHashSet<>(task.getErrorObjects());
095
096        if (this.errorReporter != null) {
097            GuiHelper.runInEDT(() -> errorReporter.accept(errors));
098        }
099
100        if (errors.isEmpty()) {
101            return;
102        }
103
104        // just one error object?
105        //
106        if (errors.size() == 1) {
107            final Object error = errors.iterator().next();
108            if (!GraphicsEnvironment.isHeadless()) {
109                SwingUtilities.invokeLater(() -> {
110                    if (error instanceof Exception) {
111                        ExceptionDialogUtil.explainException((Exception) error);
112                    } else if (isNoDataErrorMessage(error)) {
113                        new Notification(error.toString()).setIcon(JOptionPane.WARNING_MESSAGE).show();
114                    } else {
115                        JOptionPane.showMessageDialog(
116                                MainApplication.getMainFrame(),
117                                error.toString(),
118                                tr("Error during download"),
119                                JOptionPane.ERROR_MESSAGE);
120                    }
121                });
122            }
123            return;
124        }
125
126        // multiple error object? prepare a HTML list
127        //
128        if (!errors.isEmpty()) {
129            final Collection<String> items = new ArrayList<>();
130            for (Object error : errors) {
131                if (error instanceof String) {
132                    items.add((String) error);
133                } else if (error instanceof Exception) {
134                    items.add(ExceptionUtil.explainException((Exception) error));
135                }
136            }
137
138            if (!GraphicsEnvironment.isHeadless()) {
139                SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(
140                        MainApplication.getMainFrame(),
141                        "<html>"+Utils.joinAsHtmlUnorderedList(items)+"</html>",
142                        tr("Errors during download"),
143                        JOptionPane.ERROR_MESSAGE));
144            }
145        }
146    }
147}