001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.conflict.tags; 003 004import java.beans.PropertyChangeListener; 005import java.beans.PropertyChangeSupport; 006import java.util.ArrayList; 007import java.util.Collections; 008import java.util.Comparator; 009import java.util.HashMap; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014 015import javax.swing.table.DefaultTableModel; 016 017import org.openstreetmap.josm.data.osm.TagCollection; 018import org.openstreetmap.josm.gui.util.GuiHelper; 019import org.openstreetmap.josm.tools.CheckParameterUtil; 020 021public class TagConflictResolverModel extends DefaultTableModel { 022 static public final String NUM_CONFLICTS_PROP = TagConflictResolverModel.class.getName() + ".numConflicts"; 023 024 private TagCollection tags; 025 private List<String> displayedKeys; 026 private Set<String> keysWithConflicts; 027 private Map<String, MultiValueResolutionDecision> decisions; 028 private int numConflicts; 029 private PropertyChangeSupport support; 030 private boolean showTagsWithConflictsOnly = false; 031 private boolean showTagsWithMultiValuesOnly = false; 032 033 /** 034 * Constructs a new {@code TagConflictResolverModel}. 035 */ 036 public TagConflictResolverModel() { 037 numConflicts = 0; 038 support = new PropertyChangeSupport(this); 039 } 040 041 public void addPropertyChangeListener(PropertyChangeListener listener) { 042 support.addPropertyChangeListener(listener); 043 } 044 045 public void removePropertyChangeListener(PropertyChangeListener listener) { 046 support.removePropertyChangeListener(listener); 047 } 048 049 protected void setNumConflicts(int numConflicts) { 050 int oldValue = this.numConflicts; 051 this.numConflicts = numConflicts; 052 if (oldValue != this.numConflicts) { 053 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts); 054 } 055 } 056 057 protected void refreshNumConflicts() { 058 int count = 0; 059 for (MultiValueResolutionDecision d : decisions.values()) { 060 if (!d.isDecided()) { 061 count++; 062 } 063 } 064 setNumConflicts(count); 065 } 066 067 protected void sort() { 068 Collections.sort( 069 displayedKeys, 070 new Comparator<String>() { 071 @Override 072 public int compare(String key1, String key2) { 073 if (decisions.get(key1).isDecided() && ! decisions.get(key2).isDecided()) 074 return 1; 075 else if (!decisions.get(key1).isDecided() && decisions.get(key2).isDecided()) 076 return -1; 077 return key1.compareTo(key2); 078 } 079 } 080 ); 081 } 082 083 /** 084 * initializes the model from the current tags 085 * 086 */ 087 protected void rebuild() { 088 if (tags == null) return; 089 for(String key: tags.getKeys()) { 090 MultiValueResolutionDecision decision = new MultiValueResolutionDecision(tags.getTagsFor(key)); 091 if (decisions.get(key) == null) { 092 decisions.put(key,decision); 093 } 094 } 095 displayedKeys.clear(); 096 Set<String> keys = tags.getKeys(); 097 if (showTagsWithConflictsOnly) { 098 keys.retainAll(keysWithConflicts); 099 if (showTagsWithMultiValuesOnly) { 100 Set<String> keysWithMultiValues = new HashSet<String>(); 101 for (String key: keys) { 102 if (decisions.get(key).canKeepAll()) { 103 keysWithMultiValues.add(key); 104 } 105 } 106 keys.retainAll(keysWithMultiValues); 107 } 108 for (String key: tags.getKeys()) { 109 if (!decisions.get(key).isDecided() && !keys.contains(key)) { 110 keys.add(key); 111 } 112 } 113 } 114 displayedKeys.addAll(keys); 115 refreshNumConflicts(); 116 sort(); 117 GuiHelper.runInEDTAndWait(new Runnable() { 118 @Override public void run() { 119 fireTableDataChanged(); 120 } 121 }); 122 } 123 124 /** 125 * Populates the model with the tags for which conflicts are to be resolved. 126 * 127 * @param tags the tag collection with the tags. Must not be null. 128 * @param keysWithConflicts the set of tag keys with conflicts 129 * @throws IllegalArgumentException thrown if tags is null 130 */ 131 public void populate(TagCollection tags, Set<String> keysWithConflicts) { 132 CheckParameterUtil.ensureParameterNotNull(tags, "tags"); 133 this.tags = tags; 134 displayedKeys = new ArrayList<String>(); 135 this.keysWithConflicts = keysWithConflicts == null ? new HashSet<String>() : keysWithConflicts; 136 decisions = new HashMap<String, MultiValueResolutionDecision>(); 137 rebuild(); 138 } 139 140 @Override 141 public int getRowCount() { 142 if (displayedKeys == null) return 0; 143 return displayedKeys.size(); 144 } 145 146 @Override 147 public Object getValueAt(int row, int column) { 148 return decisions.get(displayedKeys.get(row)); 149 } 150 151 @Override 152 public boolean isCellEditable(int row, int column) { 153 return column == 2; 154 } 155 156 @Override 157 public void setValueAt(Object value, int row, int column) { 158 MultiValueResolutionDecision decision = decisions.get(displayedKeys.get(row)); 159 if (value instanceof String) { 160 decision.keepOne((String)value); 161 } else if (value instanceof MultiValueDecisionType) { 162 MultiValueDecisionType type = (MultiValueDecisionType)value; 163 switch(type) { 164 case KEEP_NONE: 165 decision.keepNone(); 166 break; 167 case KEEP_ALL: 168 decision.keepAll(); 169 break; 170 } 171 } 172 GuiHelper.runInEDTAndWait(new Runnable() { 173 @Override public void run() { 174 fireTableDataChanged(); 175 } 176 }); 177 refreshNumConflicts(); 178 } 179 180 /** 181 * Replies true if each {@link MultiValueResolutionDecision} is decided. 182 * 183 * @return true if each {@link MultiValueResolutionDecision} is decided; false 184 * otherwise 185 */ 186 public boolean isResolvedCompletely() { 187 return numConflicts == 0; 188 } 189 190 public int getNumConflicts() { 191 return numConflicts; 192 } 193 194 public int getNumDecisions() { 195 return decisions == null ? 0 : decisions.size(); 196 } 197 198 //TODO Should this method work with all decisions or only with displayed decisions? For MergeNodes it should be 199 //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes 200 public TagCollection getResolution() { 201 TagCollection tc = new TagCollection(); 202 for (String key: displayedKeys) { 203 tc.add(decisions.get(key).getResolution()); 204 } 205 return tc; 206 } 207 208 public TagCollection getAllResolutions() { 209 TagCollection tc = new TagCollection(); 210 for (MultiValueResolutionDecision value: decisions.values()) { 211 tc.add(value.getResolution()); 212 } 213 return tc; 214 } 215 216 public MultiValueResolutionDecision getDecision(int row) { 217 return decisions.get(displayedKeys.get(row)); 218 } 219 220 /** 221 * Sets whether all tags or only tags with conflicts are displayed 222 * 223 * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed 224 */ 225 public void setShowTagsWithConflictsOnly(boolean showTagsWithConflictsOnly) { 226 this.showTagsWithConflictsOnly = showTagsWithConflictsOnly; 227 rebuild(); 228 } 229 230 /** 231 * Sets whether all conflicts or only conflicts with multiple values are displayed 232 * 233 * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed 234 */ 235 public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) { 236 this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly; 237 rebuild(); 238 } 239 240 /** 241 * Prepare the default decisions for the current model 242 * 243 */ 244 public void prepareDefaultTagDecisions() { 245 for (MultiValueResolutionDecision decision: decisions.values()) { 246 List<String> values = decision.getValues(); 247 values.remove(""); 248 if (values.size() == 1) { 249 decision.keepOne(values.get(0)); 250 } else { 251 decision.keepAll(); 252 } 253 } 254 rebuild(); 255 } 256 257}