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