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.awt.Component; 007import java.awt.Dialog; 008import java.io.IOException; 009 010import javax.swing.JTree; 011import javax.swing.SwingUtilities; 012import javax.swing.event.TreeExpansionEvent; 013import javax.swing.event.TreeWillExpandListener; 014import javax.swing.tree.ExpandVetoException; 015import javax.swing.tree.TreePath; 016 017import org.openstreetmap.josm.data.osm.DataSet; 018import org.openstreetmap.josm.data.osm.DataSetMerger; 019import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 020import org.openstreetmap.josm.data.osm.Relation; 021import org.openstreetmap.josm.gui.MainApplication; 022import org.openstreetmap.josm.gui.PleaseWaitRunnable; 023import org.openstreetmap.josm.gui.progress.ProgressMonitor; 024import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor; 025import org.openstreetmap.josm.io.OsmApi; 026import org.openstreetmap.josm.io.OsmServerObjectReader; 027import org.openstreetmap.josm.io.OsmTransferException; 028import org.openstreetmap.josm.tools.Logging; 029import org.xml.sax.SAXException; 030 031/** 032 * This is a {@link JTree} rendering the hierarchical structure of {@link Relation}s. 033 * 034 * @see RelationTreeModel 035 */ 036public class RelationTree extends JTree { 037 /** 038 * builds the UI 039 */ 040 protected void build() { 041 setRootVisible(false); 042 setShowsRootHandles(true); 043 setCellRenderer(new RelationTreeCellRenderer()); 044 addTreeWillExpandListener(new LazyRelationLoader()); 045 } 046 047 /** 048 * constructor 049 */ 050 public RelationTree() { 051 super(); 052 build(); 053 } 054 055 /** 056 * constructor 057 * @param model the tree model 058 */ 059 public RelationTree(RelationTreeModel model) { 060 super(model); 061 build(); 062 } 063 064 /** 065 * replies the parent dialog this tree is embedded in. 066 * 067 * @return the parent dialog; null, if there is no parent dialog 068 */ 069 protected Dialog getParentDialog() { 070 Component c = this; 071 while (c != null && !(c instanceof Dialog)) { 072 c = c.getParent(); 073 } 074 return (Dialog) c; 075 } 076 077 /** 078 * An adapter for TreeWillExpand-events. If a node is to be expanded which is 079 * not loaded yet this will trigger asynchronous loading of the respective 080 * relation. 081 * 082 */ 083 class LazyRelationLoader implements TreeWillExpandListener { 084 085 @Override 086 public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { 087 // do nothing 088 } 089 090 @Override 091 public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { 092 TreePath path = event.getPath(); 093 Relation parent = (Relation) event.getPath().getLastPathComponent(); 094 if (!parent.isIncomplete() || parent.isNew()) 095 // we don't load complete or new relations 096 return; 097 // launch the download task 098 MainApplication.worker.submit(new RelationLoader(getParentDialog(), parent, path)); 099 } 100 } 101 102 /** 103 * Asynchronous download task for a specific relation 104 * 105 */ 106 class RelationLoader extends PleaseWaitRunnable { 107 private boolean canceled; 108 private Exception lastException; 109 private final Relation relation; 110 private DataSet ds; 111 private final TreePath path; 112 113 RelationLoader(Dialog dialog, Relation relation, TreePath path) { 114 super( 115 tr("Load relation"), 116 new PleaseWaitProgressMonitor( 117 dialog 118 ), 119 false /* don't ignore exceptions */ 120 ); 121 this.relation = relation; 122 this.path = path; 123 } 124 125 @Override 126 protected void cancel() { 127 OsmApi.getOsmApi().cancel(); 128 this.canceled = true; 129 } 130 131 @Override 132 protected void finish() { 133 if (canceled) 134 return; 135 if (lastException != null) { 136 Logging.error(lastException); 137 return; 138 } 139 DataSet editData = MainApplication.getLayerManager().getEditDataSet(); 140 DataSetMerger visitor = new DataSetMerger(editData, ds); 141 visitor.merge(); 142 if (!visitor.getConflicts().isEmpty()) { 143 editData.getConflicts().add(visitor.getConflicts()); 144 } 145 final RelationTreeModel model = (RelationTreeModel) getModel(); 146 SwingUtilities.invokeLater(() -> model.refreshNode(path)); 147 } 148 149 @Override 150 protected void realRun() throws SAXException, IOException, OsmTransferException { 151 try { 152 OsmServerObjectReader reader = new OsmServerObjectReader(relation.getId(), OsmPrimitiveType.from(relation), true); 153 ds = reader.parseOsm(progressMonitor 154 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 155 } catch (OsmTransferException e) { 156 if (canceled) { 157 Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString())); 158 return; 159 } 160 this.lastException = e; 161 } 162 } 163 } 164}