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