// This software is released into the Public Domain. See copying.txt for details. package org.openstreetmap.osmosis.set.v0_6; import java.util.Collections; import org.openstreetmap.osmosis.core.OsmosisRuntimeException; import org.openstreetmap.osmosis.core.change.v0_6.impl.TimestampSetter; import org.openstreetmap.osmosis.core.container.v0_6.ChangeContainer; import org.openstreetmap.osmosis.core.container.v0_6.EntityContainer; import org.openstreetmap.osmosis.core.sort.v0_6.EntityByTypeThenIdComparator; import org.openstreetmap.osmosis.core.sort.v0_6.EntityContainerComparator; import org.openstreetmap.osmosis.core.store.DataPostbox; import org.openstreetmap.osmosis.core.task.common.ChangeAction; import org.openstreetmap.osmosis.core.task.v0_6.ChangeSink; import org.openstreetmap.osmosis.core.task.v0_6.MultiSinkRunnableChangeSource; import org.openstreetmap.osmosis.core.task.v0_6.Sink; import org.openstreetmap.osmosis.set.v0_6.impl.DataPostboxSink; /** * Compares two different data sources and produces a set of differences. * * @author Brett Henderson */ public class ChangeDeriver implements MultiSinkRunnableChangeSource { private ChangeSink changeSink; private DataPostbox<EntityContainer> fromPostbox; private DataPostboxSink fromSink; private DataPostbox<EntityContainer> toPostbox; private DataPostboxSink toSink; /** * Creates a new instance. * * @param inputBufferCapacity * The size of the buffers to use for input sources. */ public ChangeDeriver(int inputBufferCapacity) { fromPostbox = new DataPostbox<EntityContainer>(inputBufferCapacity); fromSink = new DataPostboxSink(fromPostbox); toPostbox = new DataPostbox<EntityContainer>(inputBufferCapacity); toSink = new DataPostboxSink(toPostbox); } /** * {@inheritDoc} */ public Sink getSink(int instance) { switch (instance) { case 0: return fromSink; case 1: return toSink; default: throw new OsmosisRuntimeException("Sink instance " + instance + " is not valid."); } } /** * This implementation always returns 2. * * @return 2 */ public int getSinkCount() { return 2; } /** * {@inheritDoc} */ public void setChangeSink(ChangeSink changeSink) { this.changeSink = changeSink; } /** * Processes the input sources and sends the changes to the change sink. */ public void run() { try { EntityContainerComparator comparator; EntityContainer fromEntityContainer = null; EntityContainer toEntityContainer = null; TimestampSetter timestampSetter; // Create a comparator for comparing two entities by type and identifier. comparator = new EntityContainerComparator(new EntityByTypeThenIdComparator()); // Create an object for setting the current timestamp on entities being deleted. timestampSetter = new TimestampSetter(); // We can't get meaningful data from the initialize data on the // input streams, so pass empty meta data to the sink and discard // the input meta data. fromPostbox.outputInitialize(); toPostbox.outputInitialize(); changeSink.initialize(Collections.<String, Object>emptyMap()); // We continue in the comparison loop while both sources still have data. while ( (fromEntityContainer != null || fromPostbox.hasNext()) && (toEntityContainer != null || toPostbox.hasNext())) { int comparisonResult; // Get the next input data where required. if (fromEntityContainer == null) { fromEntityContainer = fromPostbox.getNext(); } if (toEntityContainer == null) { toEntityContainer = toPostbox.getNext(); } // Compare the two sources. comparisonResult = comparator.compare(fromEntityContainer, toEntityContainer); if (comparisonResult < 0) { // The from entity doesn't exist on the to source therefore // has been deleted. We don't know when the entity was // deleted so set the delete time to the current time. changeSink.process( new ChangeContainer( timestampSetter.updateTimestamp(fromEntityContainer), ChangeAction.Delete)); fromEntityContainer = null; } else if (comparisonResult > 0) { // The to entity doesn't exist on the from source therefore has // been created. changeSink.process(new ChangeContainer(toEntityContainer, ChangeAction.Create)); toEntityContainer = null; } else { // The entity exists on both sources, therefore we must // compare // the entities directly. If there is a difference, the // entity has been modified. if (!fromEntityContainer.getEntity().equals(toEntityContainer.getEntity())) { changeSink.process(new ChangeContainer(toEntityContainer, ChangeAction.Modify)); } fromEntityContainer = null; toEntityContainer = null; } } // Any remaining "from" entities are deletes. while (fromEntityContainer != null || fromPostbox.hasNext()) { if (fromEntityContainer == null) { fromEntityContainer = fromPostbox.getNext(); } // The from entity doesn't exist on the to source therefore // has been deleted. We don't know when the entity was // deleted so set the delete time to the current time. changeSink.process( new ChangeContainer( timestampSetter.updateTimestamp(fromEntityContainer), ChangeAction.Delete)); fromEntityContainer = null; } // Any remaining "to" entities are creates. while (toEntityContainer != null || toPostbox.hasNext()) { if (toEntityContainer == null) { toEntityContainer = toPostbox.getNext(); } changeSink.process(new ChangeContainer(toEntityContainer, ChangeAction.Create)); toEntityContainer = null; } changeSink.complete(); fromPostbox.outputComplete(); toPostbox.outputComplete(); } finally { changeSink.close(); fromPostbox.outputRelease(); toPostbox.outputRelease(); } } }