001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.util.ArrayList; 008import java.util.List; 009import java.util.Optional; 010 011import javax.swing.JOptionPane; 012import javax.swing.SwingUtilities; 013 014import org.openstreetmap.josm.data.osm.DataSet; 015import org.openstreetmap.josm.data.osm.DataSetMerger; 016import org.openstreetmap.josm.data.osm.Relation; 017import org.openstreetmap.josm.gui.MainApplication; 018import org.openstreetmap.josm.gui.PleaseWaitRunnable; 019import org.openstreetmap.josm.gui.layer.OsmDataLayer; 020import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor; 021import org.openstreetmap.josm.io.OsmApi; 022import org.openstreetmap.josm.io.OsmServerBackreferenceReader; 023import org.openstreetmap.josm.io.OsmTransferException; 024import org.openstreetmap.josm.tools.CheckParameterUtil; 025import org.openstreetmap.josm.tools.Logging; 026import org.xml.sax.SAXException; 027 028/** 029 * This is an asynchronous task for loading the parents of a given relation. 030 * 031 * Typical usage: 032 * <pre> 033 * final ParentRelationLoadingTask task = new ParentRelationLoadingTask( 034 * child, // the child relation 035 * MainApplication.getLayerManager().getEditLayer(), // the edit layer 036 * true, // load fully 037 * new PleaseWaitProgressMonitor() // a progress monitor 038 * ); 039 * task.setContinuation( 040 * new Runnable() { 041 * public void run() { 042 * if (task.isCanceled() || task.hasError()) 043 * return; 044 * List<Relation> parents = task.getParents(); 045 * // do something with the parent relations 046 * } 047 * ); 048 * 049 * // start the task 050 * MainApplication.worker.submit(task); 051 * </pre> 052 * 053 */ 054public class ParentRelationLoadingTask extends PleaseWaitRunnable { 055 private boolean canceled; 056 private Exception lastException; 057 private DataSet referrers; 058 private final boolean full; 059 private final OsmDataLayer layer; 060 private final Relation child; 061 private final List<Relation> parents; 062 private Runnable continuation; 063 064 /** 065 * Creates a new task for asynchronously downloading the parents of a child relation. 066 * 067 * @param child the child relation. Must not be null. Must have an id > 0. 068 * @param layer the OSM data layer. Must not be null. 069 * @param full if true, parent relations are fully downloaded (i.e. with their members) 070 * @param monitor the progress monitor to be used 071 * 072 * @throws IllegalArgumentException if child is null 073 * @throws IllegalArgumentException if layer is null 074 * @throws IllegalArgumentException if child.getId() == 0 075 */ 076 public ParentRelationLoadingTask(Relation child, OsmDataLayer layer, boolean full, PleaseWaitProgressMonitor monitor) { 077 super(tr("Download referring relations"), monitor, false /* don't ignore exception */); 078 CheckParameterUtil.ensure(child, "child", "id > 0", ch -> ch.getUniqueId() > 0); 079 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 080 if (!layer.isDownloadable()) { 081 throw new IllegalArgumentException("Non-downloadable layer: " + layer); 082 } 083 referrers = null; 084 this.layer = layer; 085 parents = new ArrayList<>(); 086 this.child = child; 087 this.full = full; 088 } 089 090 /** 091 * Set a continuation which is called upon the job finished. 092 * 093 * @param continuation the continuation 094 */ 095 public void setContinuation(Runnable continuation) { 096 this.continuation = continuation; 097 } 098 099 /** 100 * Replies true if this has been canceled by the user. 101 * 102 * @return true if this has been canceled by the user. 103 */ 104 public boolean isCanceled() { 105 return canceled; 106 } 107 108 /** 109 * Replies true if an exception has been caught during the execution of this task. 110 * 111 * @return true if an exception has been caught during the execution of this task. 112 */ 113 public boolean hasError() { 114 return lastException != null; 115 } 116 117 protected OsmDataLayer getLayer() { 118 return layer; 119 } 120 121 public List<Relation> getParents() { 122 return parents; 123 } 124 125 @Override 126 protected void cancel() { 127 canceled = true; 128 OsmApi.getOsmApi().cancel(); 129 } 130 131 protected void showLastException() { 132 JOptionPane.showMessageDialog( 133 MainApplication.getMainFrame(), 134 Optional.ofNullable(lastException.getMessage()).orElseGet(lastException::toString), 135 tr("Error"), 136 JOptionPane.ERROR_MESSAGE 137 ); 138 } 139 140 @Override 141 protected void finish() { 142 if (canceled) return; 143 if (lastException != null) { 144 showLastException(); 145 return; 146 } 147 parents.clear(); 148 for (Relation parent : referrers.getRelations()) { 149 parents.add((Relation) getLayer().data.getPrimitiveById(parent)); 150 } 151 if (continuation != null) { 152 continuation.run(); 153 } 154 } 155 156 @Override 157 protected void realRun() throws SAXException, IOException, OsmTransferException { 158 try { 159 progressMonitor.indeterminateSubTask(null); 160 OsmServerBackreferenceReader reader = new OsmServerBackreferenceReader(child, full); 161 referrers = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 162 if (referrers != null) { 163 final DataSetMerger visitor = new DataSetMerger(getLayer().getDataSet(), referrers); 164 visitor.merge(); 165 166 // copy the merged layer's data source info 167 getLayer().getDataSet().addDataSources(referrers.getDataSources()); 168 // FIXME: this is necessary because there are dialogs listening 169 // for DataChangeEvents which manipulate Swing components on this thread. 170 SwingUtilities.invokeLater(getLayer()::onPostDownloadFromServer); 171 172 if (visitor.getConflicts().isEmpty()) 173 return; 174 getLayer().getConflicts().add(visitor.getConflicts()); 175 JOptionPane.showMessageDialog( 176 MainApplication.getMainFrame(), 177 tr("There were {0} conflicts during import.", 178 visitor.getConflicts().size()), 179 tr("Warning"), 180 JOptionPane.WARNING_MESSAGE 181 ); 182 } 183 } catch (OsmTransferException e) { 184 if (canceled) { 185 Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString())); 186 return; 187 } 188 lastException = e; 189 } 190 } 191}