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 }