Package org.openstreetmap.osmosis.set.v0_6

Source Code of org.openstreetmap.osmosis.set.v0_6.EntityMerger

// 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 java.util.logging.Logger;

import org.openstreetmap.osmosis.core.OsmosisRuntimeException;
import org.openstreetmap.osmosis.core.container.v0_6.BoundContainer;
import org.openstreetmap.osmosis.core.container.v0_6.EntityContainer;
import org.openstreetmap.osmosis.core.domain.v0_6.Bound;
import org.openstreetmap.osmosis.core.domain.v0_6.EntityType;
import org.openstreetmap.osmosis.core.merge.common.ConflictResolutionMethod;
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.SortedEntityPipeValidator;
import org.openstreetmap.osmosis.core.store.DataPostbox;
import org.openstreetmap.osmosis.core.task.v0_6.MultiSinkRunnableSource;
import org.openstreetmap.osmosis.core.task.v0_6.Sink;
import org.openstreetmap.osmosis.set.v0_6.impl.DataPostboxSink;


/**
* Merges two 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 EntityMerger implements MultiSinkRunnableSource {
 
  private static final Logger LOG = Logger.getLogger(EntityMerger.class.getName());

  private Sink sink;
  private DataPostbox<EntityContainer> postbox0;
  private SortedEntityPipeValidator sortedEntityValidator0;
  private DataPostbox<EntityContainer> postbox1;
  private SortedEntityPipeValidator sortedEntityValidator1;
  private ConflictResolutionMethod conflictResolutionMethod;
  private BoundRemovedAction boundRemovedAction;

  /**
   * 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.
   * @param boundRemovedAction
   *            The action to take if the merge operation removes
   *            a bound entity.
   */
  public EntityMerger(ConflictResolutionMethod conflictResolutionMethod, int inputBufferCapacity,
      BoundRemovedAction boundRemovedAction) {
   
    this.conflictResolutionMethod = conflictResolutionMethod;
   
    postbox0 = new DataPostbox<EntityContainer>(inputBufferCapacity);
    sortedEntityValidator0 = new SortedEntityPipeValidator();
    sortedEntityValidator0.setSink(new DataPostboxSink(postbox0));
   
    postbox1 = new DataPostbox<EntityContainer>(inputBufferCapacity);
    sortedEntityValidator1 = new SortedEntityPipeValidator();
    sortedEntityValidator1.setSink(new DataPostboxSink(postbox1));
  }
 
 
  /**
   * {@inheritDoc}
   */
  public Sink getSink(int instance) {
    // Determine which postbox should be written to.
    switch (instance) {
    case 0:
      return sortedEntityValidator0;
    case 1:
      return sortedEntityValidator1;
    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 setSink(Sink sink) {
    this.sink = sink;
  }
 
 
  /**
   * {@inheritDoc}
   */
  public void run() {
    try {
      EntityContainerComparator comparator;
      EntityContainer entityContainer0 = null;
      EntityContainer entityContainer1 = null;
     
      // Create a comparator for comparing two entities by type and identifier.
      comparator = new EntityContainerComparator(new EntityByTypeThenIdComparator());
     
      // 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();
      sink.initialize(Collections.<String, Object>emptyMap());
     
      // BEGIN bound special handling
     
      // If there is a bound, it's going to be the first object
      // in a properly sorted stream
      entityContainer0 = nextOrNull(postbox0);
      entityContainer1 = nextOrNull(postbox1);
         
      // There's only need for special processing if there actually is some data
      // on both streams - no data implies no bound
      if (entityContainer0 != null && entityContainer1 != null) {
        Bound bound0 = null;
        Bound bound1 = null;
       
        // If there are any bounds upstream, eat them up
        if (entityContainer0.getEntity().getType() == EntityType.Bound) {
          bound0 = (Bound) entityContainer0.getEntity();
          entityContainer0 = nextOrNull(postbox0);
        }
        if (entityContainer1.getEntity().getType() == EntityType.Bound) {
          bound1 = (Bound) entityContainer1.getEntity();
          entityContainer1 = nextOrNull(postbox1);
        }

        // Only post a bound downstream if both upstream sources had a bound.
        // (Otherwise there's either nothing to post or the posted bound is going
        // to be smaller than the actual data, which is bad)
        if (bound0 != null && bound1 != null) {
          sink.process(new BoundContainer(bound0.union(bound1)));
        } else if ((bound0 != null && bound1 == null)
            || (bound0 == null && bound1 != null)) {
          handleBoundRemoved(bound0 == null);
        }
      }
     
      // END bound special handling
     
      // We continue in the comparison loop while both sources still have data.
      while (
          (entityContainer0 != null || postbox0.hasNext())
          && (entityContainer1 != null || postbox1.hasNext())) {
        long comparisonResult;
       
        // Get the next input data where required.
        if (entityContainer0 == null) {
          entityContainer0 = postbox0.getNext();
        }
        if (entityContainer1 == null) {
          entityContainer1 = postbox1.getNext();
        }
       
        // Compare the two entities.
        comparisonResult = comparator.compare(entityContainer0, entityContainer1);
       
        if (comparisonResult < 0) {
          // Entity 0 doesn't exist on the other source and can be
          // sent straight through.
          sink.process(entityContainer0);
          entityContainer0 = null;
        } else if (comparisonResult > 0) {
          // Entity 1 doesn't exist on the other source and can be
          // sent straight through.
          sink.process(entityContainer1);
          entityContainer1 = null;
        } else {
          // The entity exists on both sources so we must resolve the conflict.
          if (conflictResolutionMethod.equals(ConflictResolutionMethod.Timestamp)) {
            int timestampComparisonResult;
           
            timestampComparisonResult =
              entityContainer0.getEntity().getTimestamp()
                .compareTo(entityContainer1.getEntity().getTimestamp());
           
            if (timestampComparisonResult < 0) {
              sink.process(entityContainer1);
            } else if (timestampComparisonResult > 0) {
              sink.process(entityContainer0);
            } else {
              // If both have identical timestamps, use the second source.
              sink.process(entityContainer1);
            }
           
          } else if (conflictResolutionMethod.equals(ConflictResolutionMethod.LatestSource)) {
            sink.process(entityContainer1);
          } else if (conflictResolutionMethod.equals(ConflictResolutionMethod.Version)) {
            int version0 = entityContainer0.getEntity().getVersion();
            int version1 = entityContainer1.getEntity().getVersion();
            if (version0 < version1) {
              sink.process(entityContainer1);
            } else if (version0 > version1) {
              sink.process(entityContainer0);
            } else {
              // If both have identical versions, use the second source.
              sink.process(entityContainer1);
            }

          } else {
            throw new OsmosisRuntimeException(
                "Conflict resolution method " + conflictResolutionMethod + " is not recognized.");
          }
         
          entityContainer0 = null;
          entityContainer1 = null;
        }
      }
     
      // Any remaining entities on either source can be sent straight through.
      while (entityContainer0 != null || postbox0.hasNext()) {
        if (entityContainer0 == null) {
          entityContainer0 = postbox0.getNext();
        }
        sink.process(entityContainer0);
        entityContainer0 = null;
      }
      while (entityContainer1 != null || postbox1.hasNext()) {
        if (entityContainer1 == null) {
          entityContainer1 = postbox1.getNext();
        }
        sink.process(entityContainer1);
        entityContainer1 = null;
      }
     
      sink.complete();
     
      postbox0.outputComplete();
      postbox1.outputComplete();
     
    } finally {
      sink.release();
     
      postbox0.outputRelease();
      postbox1.outputRelease();
    }
  }
   
  private void handleBoundRemoved(boolean source0BoundMissing) {
   
    if (boundRemovedAction == BoundRemovedAction.Ignore) {
      // Nothing to do
      return;
    }
   
    // Message for log or exception
    String missingSourceID, otherSourceID;
   
    if (source0BoundMissing) {
      missingSourceID = "0";
      otherSourceID = "1";
    } else {
      missingSourceID = "1";
      otherSourceID = "0";
    }
   
    String message = String.format(
        "Source %s of the merge task has an explicit bound set, but source %s has not. "
        + "Therefore the explicit bound has been removed from the merged stream.",
        missingSourceID, otherSourceID);
   
    // Now actually log or fail.
    if (boundRemovedAction == BoundRemovedAction.Warn) {
      LOG.warning(message);
    } else if (boundRemovedAction == BoundRemovedAction.Fail) {
      throw new OsmosisRuntimeException(message);
    }
  }


  private static EntityContainer nextOrNull(DataPostbox<EntityContainer> postbox) {

    if (postbox.hasNext()) {
      return postbox.getNext();
    }
   
    return null;
  }
}
TOP

Related Classes of org.openstreetmap.osmosis.set.v0_6.EntityMerger

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.