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    }