// License: GPL. For details, see LICENSE file.
package org.openstreetmap.hot.sds;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import org.openstreetmap.josm.actions.upload.UploadHook;
import org.openstreetmap.josm.data.APIDataSet;
import org.openstreetmap.josm.data.osm.INode;
import org.openstreetmap.josm.data.osm.IPrimitive;
import org.openstreetmap.josm.data.osm.IRelation;
import org.openstreetmap.josm.data.osm.IWay;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
/**
* This upload hook does the following things:
*
* 1. Find out if there are any changes in the special tags that need to
* be uploaded to a different server.
* 2. Find out if any objects that did have special tags have now been
* deleted, resulting in tag deletions on the special server.
* 3. Find out if any objects carrying special tags have been newly created.
* 4. Also, if it is determined that an object modification consists exclusively
* of special tags, then skip uploading that object, by removing it from
* the apiDataSet.
*
* This upload hook stores its findings with the SeparateDataStorePlugin, and
* changes are sent to the SDS server only after the OSM upload has sucessfully
* completed. The UploadSuccessHook is responsible for that.
*/
public class DetermineSdsModificationsUploadHook implements UploadHook {
private SeparateDataStorePlugin plugin;
DetermineSdsModificationsUploadHook(SeparateDataStorePlugin plugin) {
this.plugin = plugin;
}
@Override
public boolean checkUpload(APIDataSet apiDataSet) {
ArrayList<OsmPrimitive> droplist = new ArrayList<>();
// check deleted primitives for special tags.
for (OsmPrimitive del : apiDataSet.getPrimitivesToDelete()) {
IPrimitive old = plugin.getOriginalPrimitive(del);
if (hasSpecialTags(old)) {
// request deletion of all tags for this object on special server.
plugin.enqueueForUpload(del, new HashMap<String, String>(), false);
}
}
// check modified primitives.
for (OsmPrimitive upd : apiDataSet.getPrimitivesToUpdate()) {
HashSet<String> allKeys = new HashSet<>();
boolean specialTags = false;
// process tags of new object
for (String key : upd.keySet()) {
allKeys.add(key);
if (!specialTags && isSpecialKey(key))
specialTags = true;
}
// process tags of old object
IPrimitive old = plugin.getOriginalPrimitive(upd);
if (old != null) {
for (String key : old.keySet()) {
allKeys.add(key);
if (!specialTags && isSpecialKey(key))
specialTags = true;
}
}
// if neither has special tags, done with this object.
if (!specialTags) continue;
// special tags are involved. find out what, exactly, has changed.
boolean changeInSpecialTags = false;
boolean changeInOtherTags = false;
for (String key : allKeys) {
if (old.get(key) == null || upd.get(key) == null || !old.get(key).equals(upd.get(key))) {
if (isSpecialKey(key)) changeInSpecialTags = true; else changeInOtherTags = true;
if (changeInSpecialTags && changeInOtherTags) break;
}
}
// change *only* in standard tags - done with this object.
if (!changeInSpecialTags) continue;
// assemble new set of special tags. might turn out to be empty.
HashMap<String, String> newSpecialTags = new HashMap<>();
for (String key : upd.keySet()) {
if (isSpecialKey(key)) newSpecialTags.put(key, upd.get(key));
}
boolean uploadToOsm = changeInOtherTags;
// not done yet: if no changes in standard tags, we need to find out if
// there were changes in the other properties (node: lat/lon, way/relation:
// member list). If the answer is no, then the object must be removed from
// JOSM's normal upload queue, else we would be uploading a non-edit.
if (!changeInOtherTags) {
switch(old.getType()) {
case NODE:
INode nold = (INode) old;
INode nupd = (INode) upd;
uploadToOsm = !(nold.getCoor().equals(nupd.getCoor()));
break;
case WAY:
IWay wold = (IWay) old;
IWay wupd = (IWay) upd;
if (wold.getNodesCount() != wupd.getNodesCount()) {
uploadToOsm = true;
break;
}
for (int i = 0; i < wold.getNodesCount(); i++) {
if (wold.getNodeId(i) != wupd.getNodeId(i)) {
uploadToOsm = true;
break;
}
}
break;
case RELATION:
IRelation rold = (IRelation) old;
IRelation rupd = (IRelation) upd;
if (rold.getMembersCount() != rupd.getMembersCount()) {
uploadToOsm = true;
break;
}
for (int i = 0; i < rold.getMembersCount(); i++) {
if (rold.getMemberType(i) != rupd.getMemberType(i) ||
rold.getMemberId(i) != rupd.getMemberId(i)) {
uploadToOsm = true;
break;
}
}
break;
default: throw new AssertionError("unexpected case: " + old.getType());
}
}
// request that new set of special tags be uploaded
plugin.enqueueForUpload(upd, newSpecialTags, !uploadToOsm);
// we cannot remove from getPrimitivesToUpdate, this would result in a
// ConcurrentModificationException.
if (!uploadToOsm) droplist.add(upd);
}
apiDataSet.getPrimitivesToUpdate().removeAll(droplist);
// check added primitives.
for (OsmPrimitive add : apiDataSet.getPrimitivesToAdd()) {
// assemble new set of special tags. might turn out to be empty.
HashMap<String, String> newSpecialTags = new HashMap<>();
for (String key : add.keySet()) {
if (isSpecialKey(key)) newSpecialTags.put(key, add.get(key));
}
if (!newSpecialTags.isEmpty()) plugin.enqueueForUpload(add, newSpecialTags, false);
}
// FIXME it is possible that the list of OSM edits is totally empty.
return true;
}
boolean hasSpecialTags(IPrimitive p) {
for (String key : p.keySet()) {
if (isSpecialKey(key)) return true;
}
return false;
}
boolean isSpecialKey(String key) {
return key.startsWith(plugin.getIgnorePrefix());
}
}