// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.conflict.pair.tags;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.table.DefaultTableModel;
import org.openstreetmap.josm.command.conflict.TagConflictResolveCommand;
import org.openstreetmap.josm.data.conflict.Conflict;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.gui.conflict.pair.MergeDecisionType;
/**
* This is the {@link javax.swing.table.TableModel} used in the tables of the {@link TagMerger}.
*
* The model can {@link #populate(OsmPrimitive, OsmPrimitive)} itself from the conflicts
* in the tag sets of two {@link OsmPrimitive}s. Internally, it keeps a list of {@link TagMergeItem}s.
*
* {@link #decide(int, MergeDecisionType)} and {@link #decide(int[], MergeDecisionType)} can be used
* to remember a merge decision for a specific row in the model.
*
* The model notifies {@link PropertyChangeListener}s about updates of the number of
* undecided tags (see {@link #PROP_NUM_UNDECIDED_TAGS}).
*
*/
public class TagMergeModel extends DefaultTableModel {
public static final String PROP_NUM_UNDECIDED_TAGS = TagMergeModel.class.getName() + ".numUndecidedTags";
/** the list of tag merge items */
private final transient List<TagMergeItem> tagMergeItems;
/** the property change listeners */
private final transient Set<PropertyChangeListener> listeners;
private int numUndecidedTags;
/**
* Constructs a new {@code TagMergeModel}.
*/
public TagMergeModel() {
tagMergeItems = new ArrayList<>();
listeners = new HashSet<>();
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
if (listener == null) return;
if (listeners.contains(listener)) return;
listeners.add(listener);
}
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
synchronized (listeners) {
if (listener == null) return;
if (!listeners.contains(listener)) return;
listeners.remove(listener);
}
}
/**
* notifies {@link PropertyChangeListener}s about an update of {@link TagMergeModel#PROP_NUM_UNDECIDED_TAGS}
* @param oldValue the old value
* @param newValue the new value
*/
protected void fireNumUndecidedTagsChanged(int oldValue, int newValue) {
PropertyChangeEvent evt = new PropertyChangeEvent(this, PROP_NUM_UNDECIDED_TAGS, oldValue, newValue);
synchronized (listeners) {
for (PropertyChangeListener l : listeners) {
l.propertyChange(evt);
}
}
}
/**
* refreshes the number of undecided tag conflicts after an update in the list of
* {@link TagMergeItem}s. Notifies {@link PropertyChangeListener} if necessary.
*
*/
protected void refreshNumUndecidedTags() {
int newValue = 0;
for (TagMergeItem item: tagMergeItems) {
if (MergeDecisionType.UNDECIDED.equals(item.getMergeDecision())) {
newValue++;
}
}
int oldValue = numUndecidedTags;
numUndecidedTags = newValue;
fireNumUndecidedTagsChanged(oldValue, numUndecidedTags);
}
/**
* Populate the model with conflicts between the tag sets of the two
* {@link OsmPrimitive} <code>my</code> and <code>their</code>.
*
* @param my my primitive (i.e. the primitive from the local dataset)
* @param their their primitive (i.e. the primitive from the server dataset)
*
*/
public void populate(OsmPrimitive my, OsmPrimitive their) {
tagMergeItems.clear();
Set<String> keys = new HashSet<>();
keys.addAll(my.keySet());
keys.addAll(their.keySet());
for (String key : keys) {
String myValue = my.get(key);
String theirValue = their.get(key);
if (myValue == null || theirValue == null || !myValue.equals(theirValue)) {
tagMergeItems.add(
new TagMergeItem(key, my, their)
);
}
}
fireTableDataChanged();
refreshNumUndecidedTags();
}
/**
* add a {@link TagMergeItem} to the model
*
* @param item the item
*/
public void addItem(TagMergeItem item) {
if (item != null) {
tagMergeItems.add(item);
fireTableDataChanged();
refreshNumUndecidedTags();
}
}
protected void rememberDecision(int row, MergeDecisionType decision) {
TagMergeItem item = tagMergeItems.get(row);
item.decide(decision);
}
/**
* set the merge decision of the {@link TagMergeItem} in row <code>row</code>
* to <code>decision</code>.
*
* @param row the row
* @param decision the decision
*/
public void decide(int row, MergeDecisionType decision) {
rememberDecision(row, decision);
fireTableRowsUpdated(row, row);
refreshNumUndecidedTags();
}
/**
* set the merge decision of all {@link TagMergeItem} given by indices in <code>rows</code>
* to <code>decision</code>.
*
* @param rows the array of row indices
* @param decision the decision
*/
public void decide(int[] rows, MergeDecisionType decision) {
if (rows == null || rows.length == 0)
return;
for (int row : rows) {
rememberDecision(row, decision);
}
fireTableDataChanged();
refreshNumUndecidedTags();
}
@Override
public int getRowCount() {
return tagMergeItems == null ? 0 : tagMergeItems.size();
}
@Override
public Object getValueAt(int row, int column) {
// return the tagMergeItem for both columns. The cell
// renderer will dispatch on the column index and get
// the key or the value from the TagMergeItem
//
return tagMergeItems.get(row);
}
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
public TagConflictResolveCommand buildResolveCommand(Conflict<? extends OsmPrimitive> conflict) {
return new TagConflictResolveCommand(conflict, tagMergeItems);
}
public boolean isResolvedCompletely() {
for (TagMergeItem item: tagMergeItems) {
if (item.getMergeDecision().equals(MergeDecisionType.UNDECIDED))
return false;
}
return true;
}
public void decideRemaining(MergeDecisionType decision) {
for (TagMergeItem item: tagMergeItems) {
if (item.getMergeDecision().equals(MergeDecisionType.UNDECIDED))
item.decide(decision);
}
}
public int getNumResolvedConflicts() {
int n = 0;
for (TagMergeItem item: tagMergeItems) {
if (!item.getMergeDecision().equals(MergeDecisionType.UNDECIDED)) {
n++;
}
}
return n;
}
public int getFirstUndecided(int startIndex) {
for (int i = startIndex; i < tagMergeItems.size(); i++) {
if (tagMergeItems.get(i).getMergeDecision() == MergeDecisionType.UNDECIDED)
return i;
}
return -1;
}
}