// 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.container.v0_6.ChangeContainer; import org.openstreetmap.osmosis.core.merge.common.ConflictResolutionMethod; import org.openstreetmap.osmosis.core.sort.v0_6.EntityByTypeThenIdThenVersionComparator; import org.openstreetmap.osmosis.core.sort.v0_6.EntityContainerComparator; import org.openstreetmap.osmosis.core.sort.v0_6.SortedHistoryChangePipeValidator; import org.openstreetmap.osmosis.core.store.DataPostbox; import org.openstreetmap.osmosis.core.task.v0_6.ChangeSink; import org.openstreetmap.osmosis.core.task.v0_6.MultiChangeSinkRunnableChangeSource; import org.openstreetmap.osmosis.set.v0_6.impl.DataPostboxChangeSink; /** * Merges two change sources into a single data set. Conflicting elements are * resolved by using either the latest timestamp (default) or always selecting * the second source. * * @author Brett Henderson */ public class ChangeMerger implements MultiChangeSinkRunnableChangeSource { private ChangeSink changeSink; private DataPostbox<ChangeContainer> postbox0; private SortedHistoryChangePipeValidator sortedChangeValidator0; private DataPostbox<ChangeContainer> postbox1; private SortedHistoryChangePipeValidator sortedChangeValidator1; private ConflictResolutionMethod conflictResolutionMethod; /** * Creates a new instance. * * @param conflictResolutionMethod * The method to used to resolve conflict when two sources * contain the same entity. * @param inputBufferCapacity * The size of the buffers to use for input sources. */ public ChangeMerger(ConflictResolutionMethod conflictResolutionMethod, int inputBufferCapacity) { this.conflictResolutionMethod = conflictResolutionMethod; postbox0 = new DataPostbox<ChangeContainer>(inputBufferCapacity); sortedChangeValidator0 = new SortedHistoryChangePipeValidator(); sortedChangeValidator0.setChangeSink(new DataPostboxChangeSink(postbox0)); postbox1 = new DataPostbox<ChangeContainer>(inputBufferCapacity); sortedChangeValidator1 = new SortedHistoryChangePipeValidator(); sortedChangeValidator1.setChangeSink(new DataPostboxChangeSink(postbox1)); } /** * {@inheritDoc} */ public ChangeSink getChangeSink(int instance) { // Determine which postbox should be written to. switch (instance) { case 0: return sortedChangeValidator0; case 1: return sortedChangeValidator1; default: throw new OsmosisRuntimeException("Sink instance " + instance + " is not valid."); } } /** * This implementation always returns 2. * * @return 2 */ public int getChangeSinkCount() { return 2; } /** * {@inheritDoc} */ public void setChangeSink(ChangeSink changeSink) { this.changeSink = changeSink; } /** * {@inheritDoc} */ public void run() { try { EntityContainerComparator comparator; ChangeContainer changeContainer0 = null; ChangeContainer changeContainer1 = null; // Create a comparator for comparing two entities by type and identifier. comparator = new EntityContainerComparator(new EntityByTypeThenIdThenVersionComparator()); // 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. postbox0.outputInitialize(); postbox1.outputInitialize(); changeSink.initialize(Collections.<String, Object>emptyMap()); // We continue in the comparison loop while both sources still have data. while ( (changeContainer0 != null || postbox0.hasNext()) && (changeContainer1 != null || postbox1.hasNext())) { long comparisonResult; // Get the next input data where required. if (changeContainer0 == null) { changeContainer0 = postbox0.getNext(); } if (changeContainer1 == null) { changeContainer1 = postbox1.getNext(); } // Compare the two entities. comparisonResult = comparator.compare(changeContainer0.getEntityContainer(), changeContainer1.getEntityContainer()); if (comparisonResult < 0) { // Entity 0 doesn't exist on the other source and can be // sent straight through. changeSink.process(changeContainer0); changeContainer0 = null; } else if (comparisonResult > 0) { // Entity 1 doesn't exist on the other source and can be // sent straight through. changeSink.process(changeContainer1); changeContainer1 = null; } else { // The entity exists on both sources so we must resolve the conflict. if (conflictResolutionMethod.equals(ConflictResolutionMethod.Timestamp)) { int timestampComparisonResult; timestampComparisonResult = changeContainer0.getEntityContainer().getEntity().getTimestamp() .compareTo(changeContainer1.getEntityContainer().getEntity().getTimestamp()); if (timestampComparisonResult < 0) { changeSink.process(changeContainer1); } else if (timestampComparisonResult > 0) { changeSink.process(changeContainer0); } else { // If both have identical timestamps, use the second source. changeSink.process(changeContainer1); } } else if (conflictResolutionMethod.equals(ConflictResolutionMethod.LatestSource)) { changeSink.process(changeContainer1); } else if (conflictResolutionMethod.equals(ConflictResolutionMethod.Version)) { int version0 = changeContainer0.getEntityContainer().getEntity().getVersion(); int version1 = changeContainer1.getEntityContainer().getEntity().getVersion(); if (version0 < version1) { changeSink.process(changeContainer1); } else if (version0 > version1) { changeSink.process(changeContainer0); } else { // If both have identical versions, use the second source. changeSink.process(changeContainer1); } } else { throw new OsmosisRuntimeException( "Conflict resolution method " + conflictResolutionMethod + " is not recognized."); } changeContainer0 = null; changeContainer1 = null; } } // Any remaining entities on either source can be sent straight through. while (changeContainer0 != null || postbox0.hasNext()) { if (changeContainer0 == null) { changeContainer0 = postbox0.getNext(); } changeSink.process(changeContainer0); changeContainer0 = null; } while (changeContainer1 != null || postbox1.hasNext()) { if (changeContainer1 == null) { changeContainer1 = postbox1.getNext(); } changeSink.process(changeContainer1); changeContainer1 = null; } changeSink.complete(); postbox0.outputComplete(); postbox1.outputComplete(); } finally { changeSink.close(); postbox0.outputRelease(); postbox1.outputRelease(); } } }