source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/tags/TagConflictResolverModel.java@ 12660

Last change on this file since 12660 was 12660, checked in by michael2402, 7 years ago

See #14794: Add javadoc for gui/conflict/tags package.

  • Property svn:eol-style set to native
File size: 10.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.tags;
3
4import java.beans.PropertyChangeListener;
5import java.beans.PropertyChangeSupport;
6import java.util.ArrayList;
7import java.util.HashMap;
8import java.util.HashSet;
9import java.util.List;
10import java.util.Map;
11import java.util.Set;
12
13import javax.swing.table.DefaultTableModel;
14
15import org.openstreetmap.josm.data.osm.TagCollection;
16import org.openstreetmap.josm.gui.util.GuiHelper;
17import org.openstreetmap.josm.tools.CheckParameterUtil;
18
19/**
20 * This model holds the information about tags that are currently conflicting and the decision of the user regarding them.
21 */
22public class TagConflictResolverModel extends DefaultTableModel {
23 public static final String NUM_CONFLICTS_PROP = TagConflictResolverModel.class.getName() + ".numConflicts";
24
25 private transient TagCollection tags;
26 private List<String> displayedKeys;
27 private final Set<String> keysWithConflicts = new HashSet<>();
28 private transient Map<String, MultiValueResolutionDecision> decisions;
29 private int numConflicts;
30 private final PropertyChangeSupport support;
31 private boolean showTagsWithConflictsOnly;
32 private boolean showTagsWithMultiValuesOnly;
33
34 /**
35 * Constructs a new {@code TagConflictResolverModel}.
36 */
37 public TagConflictResolverModel() {
38 numConflicts = 0;
39 support = new PropertyChangeSupport(this);
40 }
41
42 public void addPropertyChangeListener(PropertyChangeListener listener) {
43 support.addPropertyChangeListener(listener);
44 }
45
46 public void removePropertyChangeListener(PropertyChangeListener listener) {
47 support.removePropertyChangeListener(listener);
48 }
49
50 protected void setNumConflicts(int numConflicts) {
51 int oldValue = this.numConflicts;
52 this.numConflicts = numConflicts;
53 if (oldValue != this.numConflicts) {
54 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts);
55 }
56 }
57
58 protected void refreshNumConflicts() {
59 setNumConflicts((int) decisions.values().stream().filter(d -> !d.isDecided()).count());
60 }
61
62 protected void sort() {
63 displayedKeys.sort((key1, key2) -> {
64 if (decisions.get(key1).isDecided() && !decisions.get(key2).isDecided())
65 return 1;
66 else if (!decisions.get(key1).isDecided() && decisions.get(key2).isDecided())
67 return -1;
68 return key1.compareTo(key2);
69 }
70 );
71 }
72
73 /**
74 * initializes the model from the current tags
75 *
76 */
77 public void rebuild() {
78 rebuild(true);
79 }
80
81 /**
82 * initializes the model from the current tags
83 * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
84 * @since 11626
85 */
86 void rebuild(boolean fireEvent) {
87 if (tags == null) return;
88 for (String key: tags.getKeys()) {
89 MultiValueResolutionDecision decision = new MultiValueResolutionDecision(tags.getTagsFor(key));
90 if (decisions.get(key) == null) {
91 decisions.put(key, decision);
92 }
93 }
94 displayedKeys.clear();
95 Set<String> keys = tags.getKeys();
96 if (showTagsWithConflictsOnly) {
97 keys.retainAll(keysWithConflicts);
98 if (showTagsWithMultiValuesOnly) {
99 Set<String> keysWithMultiValues = new HashSet<>();
100 for (String key: keys) {
101 if (decisions.get(key).canKeepAll()) {
102 keysWithMultiValues.add(key);
103 }
104 }
105 keys.retainAll(keysWithMultiValues);
106 }
107 for (String key: tags.getKeys()) {
108 if (!decisions.get(key).isDecided() && !keys.contains(key)) {
109 keys.add(key);
110 }
111 }
112 }
113 displayedKeys.addAll(keys);
114 refreshNumConflicts();
115 sort();
116 if (fireEvent) {
117 GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
118 }
119 }
120
121 /**
122 * Populates the model with the tags for which conflicts are to be resolved.
123 *
124 * @param tags the tag collection with the tags. Must not be null.
125 * @param keysWithConflicts the set of tag keys with conflicts
126 * @throws IllegalArgumentException if tags is null
127 */
128 public void populate(TagCollection tags, Set<String> keysWithConflicts) {
129 populate(tags, keysWithConflicts, true);
130 }
131
132 /**
133 * Populates the model with the tags for which conflicts are to be resolved.
134 *
135 * @param tags the tag collection with the tags. Must not be null.
136 * @param keysWithConflicts the set of tag keys with conflicts
137 * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
138 * @throws IllegalArgumentException if tags is null
139 * @since 11626
140 */
141 void populate(TagCollection tags, Set<String> keysWithConflicts, boolean fireEvent) {
142 CheckParameterUtil.ensureParameterNotNull(tags, "tags");
143 this.tags = tags;
144 displayedKeys = new ArrayList<>();
145 if (keysWithConflicts != null) {
146 this.keysWithConflicts.addAll(keysWithConflicts);
147 }
148 decisions = new HashMap<>();
149 rebuild(fireEvent);
150 }
151
152 /**
153 * Returns the OSM key at the given row.
154 * @param row The table row
155 * @return the OSM key at the given row.
156 * @since 6616
157 */
158 public final String getKey(int row) {
159 return displayedKeys.get(row);
160 }
161
162 @Override
163 public int getRowCount() {
164 if (displayedKeys == null) return 0;
165 return displayedKeys.size();
166 }
167
168 @Override
169 public Object getValueAt(int row, int column) {
170 return getDecision(row);
171 }
172
173 @Override
174 public boolean isCellEditable(int row, int column) {
175 return column == 2;
176 }
177
178 @Override
179 public void setValueAt(Object value, int row, int column) {
180 MultiValueResolutionDecision decision = getDecision(row);
181 if (value instanceof String) {
182 decision.keepOne((String) value);
183 } else if (value instanceof MultiValueDecisionType) {
184 MultiValueDecisionType type = (MultiValueDecisionType) value;
185 switch(type) {
186 case KEEP_NONE:
187 decision.keepNone();
188 break;
189 case KEEP_ALL:
190 decision.keepAll();
191 break;
192 case SUM_ALL_NUMERIC:
193 decision.sumAllNumeric();
194 break;
195 default: // Do nothing
196 }
197 }
198 GuiHelper.runInEDTAndWait(this::fireTableDataChanged);
199 refreshNumConflicts();
200 }
201
202 /**
203 * Replies true if each {@link MultiValueResolutionDecision} is decided.
204 *
205 * @return true if each {@link MultiValueResolutionDecision} is decided; false otherwise
206 */
207 public boolean isResolvedCompletely() {
208 return numConflicts == 0;
209 }
210
211 /**
212 * Gets the number of reamining conflicts.
213 * @return The number
214 */
215 public int getNumConflicts() {
216 return numConflicts;
217 }
218
219 /**
220 * Gets the number of decisions the user can take
221 * @return The number of decisions
222 */
223 public int getNumDecisions() {
224 return decisions == null ? 0 : decisions.size();
225 }
226
227 //TODO Should this method work with all decisions or only with displayed decisions? For MergeNodes it should be
228 //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes
229 public TagCollection getResolution() {
230 TagCollection tc = new TagCollection();
231 for (String key: displayedKeys) {
232 tc.add(decisions.get(key).getResolution());
233 }
234 return tc;
235 }
236
237 public TagCollection getAllResolutions() {
238 TagCollection tc = new TagCollection();
239 for (MultiValueResolutionDecision value: decisions.values()) {
240 tc.add(value.getResolution());
241 }
242 return tc;
243 }
244
245 /**
246 * Returns the conflict resolution decision at the given row.
247 * @param row The table row
248 * @return the conflict resolution decision at the given row.
249 */
250 public MultiValueResolutionDecision getDecision(int row) {
251 return decisions.get(getKey(row));
252 }
253
254 /**
255 * Sets whether all tags or only tags with conflicts are displayed
256 *
257 * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed
258 */
259 public void setShowTagsWithConflictsOnly(boolean showTagsWithConflictsOnly) {
260 this.showTagsWithConflictsOnly = showTagsWithConflictsOnly;
261 rebuild();
262 }
263
264 /**
265 * Sets whether all conflicts or only conflicts with multiple values are displayed
266 *
267 * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed
268 */
269 public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) {
270 this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly;
271 rebuild();
272 }
273
274 /**
275 * Prepare the default decisions for the current model
276 *
277 */
278 public void prepareDefaultTagDecisions() {
279 prepareDefaultTagDecisions(true);
280 }
281
282 /**
283 * Prepare the default decisions for the current model
284 * @param fireEvent {@code true} to call {@code fireTableDataChanged} (can be a slow operation)
285 * @since 11626
286 */
287 void prepareDefaultTagDecisions(boolean fireEvent) {
288 for (MultiValueResolutionDecision decision: decisions.values()) {
289 List<String> values = decision.getValues();
290 values.remove("");
291 if (values.size() == 1) {
292 // TODO: Do not suggest to keep the single value in order to avoid long highways to become tunnels+bridges+...
293 // (only if both primitives are tagged)
294 decision.keepOne(values.get(0));
295 }
296 // else: Do not suggest to keep all values in order to reduce the wrong usage of semicolon values, see #9104!
297 }
298 rebuild(fireEvent);
299 }
300
301 /**
302 * Returns the set of keys in conflict.
303 * @return the set of keys in conflict.
304 * @since 6616
305 */
306 public final Set<String> getKeysWithConflicts() {
307 return new HashSet<>(keysWithConflicts);
308 }
309}
Note: See TracBrowser for help on using the repository browser.