001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.conflict.pair.tags; 003 004 import java.beans.PropertyChangeEvent; 005 import java.beans.PropertyChangeListener; 006 import java.util.ArrayList; 007 import java.util.HashSet; 008 import java.util.List; 009 import java.util.Set; 010 011 import javax.swing.table.DefaultTableModel; 012 013 import org.openstreetmap.josm.command.TagConflictResolveCommand; 014 import org.openstreetmap.josm.data.conflict.Conflict; 015 import org.openstreetmap.josm.data.osm.OsmPrimitive; 016 import org.openstreetmap.josm.gui.conflict.pair.MergeDecisionType; 017 018 /** 019 * This is the {@link TableModel} used in the tables of the {@link TagMerger}. 020 * 021 * The model can {@link #populate(OsmPrimitive, OsmPrimitive)} itself from the conflicts 022 * in the tag sets of two {@link OsmPrimitive}s. Internally, it keeps a list of {@link TagMergeItem}s. 023 * 024 * {@link #decide(int, MergeDecisionType)} and {@link #decide(int[], MergeDecisionType)} can be used 025 * to remember a merge decision for a specific row in the model. 026 * 027 * The model notifies {@link PropertyChangeListener}s about updates of the number of 028 * undecided tags (see {@link #PROP_NUM_UNDECIDED_TAGS}). 029 * 030 */ 031 public class TagMergeModel extends DefaultTableModel { 032 static public final String PROP_NUM_UNDECIDED_TAGS = TagMergeModel.class.getName() + ".numUndecidedTags"; 033 034 /** the list of tag merge items */ 035 private final List<TagMergeItem> tagMergeItems; 036 037 /** the property change listeners */ 038 private final List<PropertyChangeListener> listeners; 039 040 private int numUndecidedTags = 0; 041 042 public TagMergeModel() { 043 tagMergeItems = new ArrayList<TagMergeItem>(); 044 listeners = new ArrayList<PropertyChangeListener>(); 045 } 046 047 public void addPropertyChangeListener(PropertyChangeListener listener) { 048 synchronized(listeners) { 049 if (listener == null) return; 050 if (listeners.contains(listener)) return; 051 listeners.add(listener); 052 } 053 } 054 055 public void removePropertyChangeListener(PropertyChangeListener listener) { 056 synchronized(listeners) { 057 if (listener == null) return; 058 if (!listeners.contains(listener)) return; 059 listeners.remove(listener); 060 } 061 } 062 063 /** 064 * notifies {@link PropertyChangeListener}s about an update of {@link TagMergeModel#PROP_NUM_UNDECIDED_TAGS} 065 066 * @param oldValue the old value 067 * @param newValue the new value 068 */ 069 protected void fireNumUndecidedTagsChanged(int oldValue, int newValue) { 070 PropertyChangeEvent evt = new PropertyChangeEvent(this,PROP_NUM_UNDECIDED_TAGS,oldValue, newValue); 071 synchronized(listeners) { 072 for(PropertyChangeListener l : listeners) { 073 l.propertyChange(evt); 074 } 075 } 076 } 077 078 /** 079 * refreshes the number of undecided tag conflicts after an update in the list of 080 * {@link TagMergeItem}s. Notifies {@link PropertyChangeListener} if necessary. 081 * 082 */ 083 protected void refreshNumUndecidedTags() { 084 int newValue=0; 085 for(TagMergeItem item: tagMergeItems) { 086 if (MergeDecisionType.UNDECIDED.equals(item.getMergeDecision())) { 087 newValue++; 088 } 089 } 090 int oldValue = numUndecidedTags; 091 numUndecidedTags = newValue; 092 fireNumUndecidedTagsChanged(oldValue, numUndecidedTags); 093 094 } 095 096 /** 097 * Populate the model with conflicts between the tag sets of the two 098 * {@link OsmPrimitive} <code>my</code> and <code>their</code>. 099 * 100 * @param my my primitive (i.e. the primitive from the local dataset) 101 * @param their their primitive (i.e. the primitive from the server dataset) 102 * 103 */ 104 public void populate(OsmPrimitive my, OsmPrimitive their) { 105 tagMergeItems.clear(); 106 Set<String> keys = new HashSet<String>(); 107 keys.addAll(my.keySet()); 108 keys.addAll(their.keySet()); 109 for(String key : keys) { 110 String myValue = my.get(key); 111 String theirValue = their.get(key); 112 if (myValue == null || theirValue == null || ! myValue.equals(theirValue)) { 113 tagMergeItems.add( 114 new TagMergeItem(key, my, their) 115 ); 116 } 117 } 118 fireTableDataChanged(); 119 refreshNumUndecidedTags(); 120 } 121 122 /** 123 * add a {@link TagMergeItem} to the model 124 * 125 * @param item the item 126 */ 127 public void addItem(TagMergeItem item) { 128 if (item != null) { 129 tagMergeItems.add(item); 130 fireTableDataChanged(); 131 refreshNumUndecidedTags(); 132 } 133 } 134 135 protected void rememberDecision(int row, MergeDecisionType decision) { 136 TagMergeItem item = tagMergeItems.get(row); 137 item.decide(decision); 138 } 139 140 /** 141 * set the merge decision of the {@link TagMergeItem} in row <code>row</code> 142 * to <code>decision</code>. 143 * 144 * @param row the row 145 * @param decision the decision 146 */ 147 public void decide(int row, MergeDecisionType decision) { 148 rememberDecision(row, decision); 149 fireTableRowsUpdated(row, row); 150 refreshNumUndecidedTags(); 151 } 152 153 /** 154 * set the merge decision of all {@link TagMergeItem} given by indices in <code>rows</code> 155 * to <code>decision</code>. 156 * 157 * @param row the array of row indices 158 * @param decision the decision 159 */ 160 161 public void decide(int [] rows, MergeDecisionType decision) { 162 if (rows == null || rows.length == 0) 163 return; 164 for (int row : rows) { 165 rememberDecision(row, decision); 166 } 167 fireTableDataChanged(); 168 refreshNumUndecidedTags(); 169 } 170 171 @Override 172 public int getRowCount() { 173 return tagMergeItems == null ? 0 : tagMergeItems.size(); 174 } 175 176 @Override 177 public Object getValueAt(int row, int column) { 178 // return the tagMergeItem for both columns. The cell 179 // renderer will dispatch on the column index and get 180 // the key or the value from the TagMergeItem 181 // 182 return tagMergeItems.get(row); 183 } 184 185 @Override 186 public boolean isCellEditable(int row, int column) { 187 return false; 188 } 189 190 public TagConflictResolveCommand buildResolveCommand(Conflict<? extends OsmPrimitive> conflict) { 191 return new TagConflictResolveCommand(conflict, tagMergeItems); 192 } 193 194 public boolean isResolvedCompletely() { 195 for (TagMergeItem item: tagMergeItems) { 196 if (item.getMergeDecision().equals(MergeDecisionType.UNDECIDED)) 197 return false; 198 } 199 return true; 200 } 201 202 public int getNumResolvedConflicts() { 203 int n = 0; 204 for (TagMergeItem item: tagMergeItems) { 205 if (!item.getMergeDecision().equals(MergeDecisionType.UNDECIDED)) { 206 n++; 207 } 208 } 209 return n; 210 211 } 212 213 public int getFirstUndecided(int startIndex) { 214 for (int i=startIndex; i<tagMergeItems.size(); i++) { 215 if (tagMergeItems.get(i).getMergeDecision() == MergeDecisionType.UNDECIDED) 216 return i; 217 } 218 return -1; 219 } 220 }