// This software is released into the Public Domain. See copying.txt for details.
package org.openstreetmap.osmosis.set.v0_6;
import java.util.HashMap;
import java.util.Map;
import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
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.sort.v0_6.SortedDeltaChangePipeValidator;
import org.openstreetmap.osmosis.core.sort.v0_6.SortedEntityPipeValidator;
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.MultiSinkMultiChangeSinkRunnableSource;
import org.openstreetmap.osmosis.core.task.v0_6.Sink;
import org.openstreetmap.osmosis.set.v0_6.impl.DataPostboxChangeSink;
import org.openstreetmap.osmosis.set.v0_6.impl.DataPostboxSink;
/**
* Applies a change set to an input source and produces an updated data set.
*
* @author Brett Henderson
*/
public class ChangeApplier implements MultiSinkMultiChangeSinkRunnableSource {
private Sink sink;
private DataPostbox<EntityContainer> basePostbox;
private SortedEntityPipeValidator sortedEntityValidator;
private DataPostbox<ChangeContainer> changePostbox;
private SortedDeltaChangePipeValidator sortedChangeValidator;
/**
* Creates a new instance.
*
* @param inputBufferCapacity
* The size of the buffers to use for input sources.
*/
public ChangeApplier(int inputBufferCapacity) {
basePostbox = new DataPostbox<EntityContainer>(inputBufferCapacity);
sortedEntityValidator = new SortedEntityPipeValidator();
sortedEntityValidator.setSink(new DataPostboxSink(basePostbox));
changePostbox = new DataPostbox<ChangeContainer>(inputBufferCapacity);
sortedChangeValidator = new SortedDeltaChangePipeValidator();
sortedChangeValidator.setChangeSink(new DataPostboxChangeSink(changePostbox));
}
/**
* {@inheritDoc}
*/
public Sink getSink(int instance) {
if (instance != 0) {
throw new OsmosisRuntimeException("Sink instance " + instance
+ " is not valid.");
}
return sortedEntityValidator;
}
/**
* This implementation always returns 1.
*
* @return 1
*/
public int getSinkCount() {
return 1;
}
/**
* {@inheritDoc}
*/
public ChangeSink getChangeSink(int instance) {
if (instance != 0) {
throw new OsmosisRuntimeException("Change sink instance " + instance
+ " is not valid.");
}
return sortedChangeValidator;
}
/**
* This implementation always returns 1.
*
* @return 1
*/
public int getChangeSinkCount() {
return 1;
}
/**
* {@inheritDoc}
*/
public void setSink(Sink sink) {
this.sink = sink;
}
/**
* Processes an entity that exists on the base source but not the change
* source.
*
* @param entityContainer
* The entity to be processed.
*/
private void processBaseOnlyEntity(EntityContainer entityContainer) {
// The base entity doesn't exist on the change source therefore we
// simply pass it through.
sink.process(entityContainer);
}
/**
* Processes a change for an entity that exists on the change source but not
* the base source.
*
* @param changeContainer
* The change to be processed.
*/
private void processChangeOnlyEntity(ChangeContainer changeContainer) {
// This entity doesn't exist in the "base" source therefore
// we would normally expect a create.
// But to cover cases where the change is being re-applied or it is a
// previously deleted item which will show as a modify we need to be
// lenient with error checking.
// It is also possible that a delete will come through for a
// previously deleted item which can be ignored.
if (changeContainer.getAction().equals(ChangeAction.Create)
|| changeContainer.getAction().equals(ChangeAction.Modify)) {
sink.process(changeContainer.getEntityContainer());
}
}
/**
* Processes a change for an entity that exists on both the base source and
* the change source.
*
* @param changeContainer
* The change to be processed.
*/
private void processBothSourceEntity(EntityContainer entityContainer, ChangeContainer changeContainer) {
// The same entity exists in both sources therefore we are
// expecting a modify or delete. However a create is possible if the
// data is being re-applied so we need to be lenient.
if (changeContainer.getAction().equals(ChangeAction.Create)
|| changeContainer.getAction().equals(ChangeAction.Modify)) {
sink.process(changeContainer.getEntityContainer());
}
}
/**
* Processes the input sources and sends the updated data stream to the
* sink.
*/
public void run() {
try {
EntityContainerComparator comparator;
EntityContainer base = null;
ChangeContainer change = null;
Map<String, Object> metaData;
// Create a comparator for comparing two entities by type and identifier.
comparator = new EntityContainerComparator(new EntityByTypeThenIdComparator());
// Initialise the pipeline with a combination of the metadata from
// both inputs. The change stream metadata will be applied second
// and will override any values with the same key.
metaData = new HashMap<String, Object>();
metaData.putAll(basePostbox.outputInitialize());
metaData.putAll(changePostbox.outputInitialize());
sink.initialize(metaData);
// We continue in the comparison loop while both sources still have data.
while ((base != null || basePostbox.hasNext()) && (change != null || changePostbox.hasNext())) {
int comparisonResult;
// Get the next input data where required.
if (base == null) {
base = basePostbox.getNext();
}
if (change == null) {
change = changePostbox.getNext();
}
// Compare the two sources.
comparisonResult = comparator.compare(base, change.getEntityContainer());
if (comparisonResult < 0) {
processBaseOnlyEntity(base);
base = null;
} else if (comparisonResult > 0) {
processChangeOnlyEntity(change);
change = null;
} else {
processBothSourceEntity(base, change);
base = null;
change = null;
}
}
// Any remaining "base" entities are unmodified.
while (base != null || basePostbox.hasNext()) {
if (base == null) {
base = basePostbox.getNext();
}
processBaseOnlyEntity(base);
base = null;
}
// Process any remaining "change" entities.
while (change != null || changePostbox.hasNext()) {
if (change == null) {
change = changePostbox.getNext();
}
processChangeOnlyEntity(change);
change = null;
}
sink.complete();
basePostbox.outputComplete();
changePostbox.outputComplete();
} finally {
sink.release();
basePostbox.outputRelease();
changePostbox.outputRelease();
}
}
}