Event.java

  1. /*
  2.  * Event
  3.  */
  4. package gov.usgs.earthquake.indexer;

  5. import gov.usgs.earthquake.product.ProductId;

  6. import java.math.BigDecimal;
  7. import java.util.ArrayList;
  8. import java.util.Date;
  9. import java.util.Collections;
  10. import java.util.Comparator;
  11. import java.util.HashMap;
  12. import java.util.HashSet;
  13. import java.util.Iterator;
  14. import java.util.List;
  15. import java.util.Map;
  16. import java.util.TreeSet;
  17. import java.util.logging.Level;
  18. import java.util.logging.Logger;

  19. /**
  20.  * An event is a group of products that are nearby in space and time.
  21.  *
  22.  * Which products appear in an event depend primarily on the
  23.  * ProductIndexQuery.ResultType that is used when retrieving an event from the
  24.  * index. Unless CURRENT is used, you may not get what you expect.
  25.  */
  26. public class Event implements Comparable<Event> {

  27.   /** Origin product type */
  28.   public static final String ORIGIN_PRODUCT_TYPE = "origin";
  29.   /** Associate product type */
  30.   public static final String ASSOCIATE_PRODUCT_TYPE = "associate";
  31.   /** Disassociate product type */
  32.   public static final String DISASSOCIATE_PRODUCT_TYPE = "disassociate";
  33.   /** Property for othereventsource */
  34.   public static final String OTHEREVENTSOURCE_PROPERTY = "othereventsource";
  35.   /** Property for othereventsourcecode */
  36.   public static final String OTHEREVENTSOURCECODE_PROPERTY = "othereventsourcecode";

  37.   /** An ID used by the ProductIndex. */
  38.   private Long indexId = null;

  39.   /** Products nearby in space and time. Keyed by type. */
  40.   private Map<String, List<ProductSummary>> products = new HashMap<String, List<ProductSummary>>();

  41.   /** Cached summary. */
  42.   private EventSummary eventSummary = null;

  43.   /**
  44.    * Default constructor.
  45.    *
  46.    * All fields are set to null, and the list of products is empty.
  47.    */
  48.   public Event() {
  49.   }

  50.   /**
  51.    * Construct an event with only an indexId. The products map will be empty.
  52.    *
  53.    * @param indexId the indexId to set.
  54.    */
  55.   public Event(final Long indexId) {
  56.     this.setIndexId(indexId);
  57.   }

  58.   /**
  59.    * Construct and event with an indexId and a list of products.
  60.    *
  61.    * @param indexId  the product index id.
  62.    * @param products the list of products.
  63.    */
  64.   public Event(final Long indexId, final Map<String, List<ProductSummary>> products) {
  65.     this.setIndexId(indexId);
  66.     this.setProducts(products);
  67.   }

  68.   /**
  69.    * Copy constructor for event.
  70.    *
  71.    * The products associated with this event are not cloned, but the list of
  72.    * products is.
  73.    *
  74.    * @param copy the event to clone.
  75.    */
  76.   public Event(final Event copy) {
  77.     this(copy.getIndexId(), copy.getAllProducts());
  78.   }

  79.   /**
  80.    * Get the index id.
  81.    *
  82.    * @return the indexId or null if one hasn't been assigned.
  83.    */
  84.   public Long getIndexId() {
  85.     return indexId;
  86.   }

  87.   /**
  88.    * Set the index id.
  89.    *
  90.    * @param indexId the indexId to set.
  91.    */
  92.   public void setIndexId(Long indexId) {
  93.     this.indexId = indexId;
  94.   }

  95.   /**
  96.    * Get all products associated with event, even if they are deleted.
  97.    *
  98.    * @return all products associated with event.
  99.    */
  100.   public Map<String, List<ProductSummary>> getAllProducts() {
  101.     return products;
  102.   }

  103.   /**
  104.    * Get the event products.
  105.    *
  106.    * Only returns products that have not been deleted or superseded. This method
  107.    * returns a copy of the underlying product map that has been filtered to remove
  108.    * deleted products.
  109.    *
  110.    * @return a map of event products.
  111.    * @see #getAllProducts()
  112.    */
  113.   public Map<String, List<ProductSummary>> getProducts() {
  114.     Map<String, List<ProductSummary>> notDeleted = new HashMap<String, List<ProductSummary>>();
  115.     Iterator<String> types = products.keySet().iterator();
  116.     while (types.hasNext()) {
  117.       String type = types.next();
  118.       List<ProductSummary> notDeletedProducts = getProducts(type);
  119.       if (notDeletedProducts.size() > 0) {
  120.         notDeleted.put(type, notDeletedProducts);
  121.       }
  122.     }
  123.     return notDeleted;
  124.   }

  125.   /**
  126.    * Set products.
  127.    *
  128.    * ProductSummaries are not cloned, but lists are.
  129.    *
  130.    * @param newProducts the products to set.
  131.    */
  132.   public void setProducts(final Map<String, List<ProductSummary>> newProducts) {
  133.     this.products.clear();
  134.     Iterator<String> iter = new TreeSet<String>(newProducts.keySet()).iterator();
  135.     while (iter.hasNext()) {
  136.       String type = iter.next();
  137.       this.products.put(type, new ArrayList<ProductSummary>(newProducts.get(type)));
  138.     }
  139.     eventSummary = null;
  140.   }

  141.   /**
  142.    * A convenience method for adding a product summary to an event object.
  143.    *
  144.    * Note: this method does not update any associated product index.
  145.    *
  146.    * @param summary the summary to add to this event.
  147.    */
  148.   public void addProduct(final ProductSummary summary) {
  149.     String type = summary.getId().getType();
  150.     List<ProductSummary> list = products.get(type);
  151.     if (list == null) {
  152.       list = new ArrayList<ProductSummary>();
  153.       products.put(type, list);
  154.     }
  155.     if (!list.contains(summary)) {
  156.       list.add(summary);
  157.     }
  158.     eventSummary = null;
  159.   }

  160.   /**
  161.    * A convenience method for removing a product summary from an event object.
  162.    *
  163.    * Note: this method does not update any associated product index.
  164.    *
  165.    * @param summary the summary to remove from this event.
  166.    */
  167.   public void removeProduct(final ProductSummary summary) {
  168.     String type = summary.getId().getType();
  169.     // find the list of products of this type
  170.     List<ProductSummary> list = products.get(type);
  171.     if (list != null) {
  172.       // remove the product from the list
  173.       list.remove(summary);
  174.       if (list.size() == 0) {
  175.         // if the list is now empty, remove the list
  176.         products.remove(type);
  177.       }
  178.     }
  179.     eventSummary = null;
  180.   }

  181.   /**
  182.    * Convenience method to get products of a given type.
  183.    *
  184.    * This method always returns a copy of the internal list, and may be empty.
  185.    * Only returns products that have not been deleted or superseded.
  186.    *
  187.    * @param type the product type.
  188.    * @return a list of products of that type, which may be empty.
  189.    */
  190.   public List<ProductSummary> getProducts(final String type) {
  191.     ArrayList<ProductSummary> typeProducts = new ArrayList<ProductSummary>();

  192.     if (products.containsKey(type)) {
  193.       // only return products that haven't been deleted
  194.       typeProducts.addAll(getWithoutDeleted(getWithoutSuperseded(products.get(type))));
  195.     }

  196.     return typeProducts;
  197.   }

  198.   /**
  199.    * Get all event products (including those that are deleted or superseded).
  200.    *
  201.    * @return a list of event products.
  202.    */
  203.   public List<ProductSummary> getAllProductList() {
  204.     List<ProductSummary> allProductList = new ArrayList<ProductSummary>();
  205.     Map<String, List<ProductSummary>> allProducts = getAllProducts();
  206.     Iterator<String> iter = allProducts.keySet().iterator();
  207.     while (iter.hasNext()) {
  208.       allProductList.addAll(allProducts.get(iter.next()));
  209.     }
  210.     return allProductList;
  211.   }

  212.   /**
  213.    * Get all event products that have not been deleted or superseded as a list.
  214.    *
  215.    * @return a list of event products.
  216.    */
  217.   public List<ProductSummary> getProductList() {
  218.     List<ProductSummary> productList = new ArrayList<ProductSummary>();
  219.     Map<String, List<ProductSummary>> notDeletedProducts = getProducts();
  220.     Iterator<String> iter = notDeletedProducts.keySet().iterator();
  221.     while (iter.hasNext()) {
  222.       productList.addAll(notDeletedProducts.get(iter.next()));
  223.     }
  224.     return productList;
  225.   }

  226.   /**
  227.    * Get preferred products of all types.
  228.    *
  229.    * This map will contain one product of each type, chosen by preferred weight.
  230.    *
  231.    * @return a map from product type to the preferred product of that type.
  232.    */
  233.   public Map<String, ProductSummary> getPreferredProducts() {
  234.     Map<String, ProductSummary> preferredProducts = new HashMap<String, ProductSummary>();

  235.     Map<String, List<ProductSummary>> notDeletedProducts = getProducts();
  236.     Iterator<String> types = notDeletedProducts.keySet().iterator();
  237.     while (types.hasNext()) {
  238.       String type = types.next();
  239.       preferredProducts.put(type, getPreferredProduct(notDeletedProducts.get(type)));
  240.     }

  241.     return preferredProducts;
  242.   }

  243.   /**
  244.    * Get the preferred product of a specific type.
  245.    *
  246.    * @param type type of product to get.
  247.    * @return most preferred product of that type, or null if no product of that
  248.    *         type is associated.
  249.    */
  250.   public ProductSummary getPreferredProduct(final String type) {
  251.     return getPreferredProduct(getProducts(type));
  252.   }

  253.   /**
  254.    * Get a map of all event ids associated with this event.
  255.    *
  256.    * Same as Event.getEventCodes(this.getAllProductList());
  257.    *
  258.    * @deprecated use {@link #getAllEventCodes(boolean)} instead.
  259.    * @return map of all event ids associated with this event.
  260.    */
  261.   @Deprecated
  262.   public Map<String, String> getEventCodes() {
  263.     return getEventCodes(this.getAllProductList());
  264.   }

  265.   /**
  266.    * Get a map of all event ids associated with this event.
  267.    *
  268.    * Map key is eventSource, Map value is eventSourceCode.
  269.    *
  270.    * @deprecated use {@link #getAllEventCodes(boolean)} instead.
  271.    * @param summaries the summaries list to extract event codes from.
  272.    * @return map of all event ids associated with this event.
  273.    */
  274.   @Deprecated
  275.   public static Map<String, String> getEventCodes(final List<ProductSummary> summaries) {
  276.     Map<String, String> eventIds = new HashMap<String, String>();
  277.     // order most preferred last,
  278.     // to minimize impact of multiple codes from same source
  279.     List<ProductSummary> sorted = getSortedMostPreferredFirst(getWithoutSuperseded(summaries));
  280.     Collections.reverse(sorted);
  281.     // done ordering
  282.     Iterator<ProductSummary> iter = sorted.iterator();
  283.     while (iter.hasNext()) {
  284.       ProductSummary product = iter.next();
  285.       String source = product.getEventSource();
  286.       String code = product.getEventSourceCode();
  287.       if (source != null && code != null) {
  288.         eventIds.put(source.toLowerCase(), code.toLowerCase());
  289.       }
  290.     }
  291.     return eventIds;
  292.   }

  293.   /**
  294.    * Get a map of all event ids associated with this event, recognizing that one
  295.    * source may have multiple codes (they broke the rules, but it happens).
  296.    *
  297.    * @param includeDeleted whether to include ids for sub events whose products
  298.    *                       have all been deleted.
  299.    * @return Map from source to a list of codes from that source.
  300.    */
  301.   public Map<String, List<String>> getAllEventCodes(final boolean includeDeleted) {
  302.     Map<String, List<String>> allEventCodes = new HashMap<String, List<String>>();

  303.     Map<String, Event> subEvents = getSubEvents();
  304.     Iterator<String> iter = subEvents.keySet().iterator();
  305.     while (iter.hasNext()) {
  306.       Event subEvent = subEvents.get(iter.next());
  307.       if (!includeDeleted && subEvent.isDeleted()) {
  308.         // check for non-deleted products that should
  309.         // keep the event code alive
  310.         List<ProductSummary> nonDeletedProducts = getWithoutDeleted(getWithoutSuperseded(subEvent.getAllProductList()));
  311.         if (nonDeletedProducts.size() == 0) {
  312.           // filter deleted events
  313.           continue;
  314.         }
  315.         // otherwise, event has active products;
  316.         // prevent same source associations
  317.       }

  318.       // add code to list for source
  319.       String source = subEvent.getSource();
  320.       String sourceCode = subEvent.getSourceCode();
  321.       List<String> sourceEventCodes = allEventCodes.get(source);
  322.       if (sourceEventCodes == null) {
  323.         // create list for source
  324.         sourceEventCodes = new ArrayList<String>();
  325.         allEventCodes.put(source, sourceEventCodes);
  326.       }
  327.       // keep list distinct
  328.       if (!sourceEventCodes.contains(sourceCode)) {
  329.         sourceEventCodes.add(sourceCode);
  330.       }
  331.     }

  332.     return allEventCodes;
  333.   }

  334.   /**
  335.    * Get a list of all the preferred products sorted based on their authoritative
  336.    * weights
  337.    *
  338.    * @return sorted list of ProductSummary objects
  339.    */
  340.   public List<ProductSummary> getPreferredProductsSorted() {
  341.     Map<String, ProductSummary> preferred = getPreferredProducts();

  342.     // Transform the preferred HashMap into a List so we can sort based on
  343.     // preferred weight
  344.     List<ProductSummary> productList = new ArrayList<ProductSummary>(preferred.values());

  345.     // Sort the list, then iterate through it until we find the specified
  346.     // property
  347.     Collections.sort(productList, new MostPreferredFirstComparator());
  348.     return productList;
  349.   }

  350.   /**
  351.    * Get the event id.
  352.    *
  353.    * The event id is the combination of event source and event source code.
  354.    *
  355.    * @return the event id, or null if either event source or event source code is
  356.    *         null.
  357.    * @see #getSource()
  358.    * @see #getSourceCode()
  359.    */
  360.   public String getEventId() {
  361.     ProductSummary product = getEventIdProduct();
  362.     if (product != null) {
  363.       return product.getEventId();
  364.     }
  365.     return null;
  366.   }

  367.   /**
  368.    * Get the preferred source for this event. If an origin product exists, it's
  369.    * value is used.
  370.    *
  371.    * @return Source from preferred product or null
  372.    */
  373.   public String getSource() {
  374.     ProductSummary product = getEventIdProduct();
  375.     if (product != null) {
  376.       return product.getEventSource();
  377.     }
  378.     return null;
  379.   }

  380.   /**
  381.    * Get the preferred source code for this event. If an origin product exists,
  382.    * it's value is used.
  383.    *
  384.    * @return Source code from preferred product or null
  385.    */
  386.   public String getSourceCode() {
  387.     ProductSummary product = getEventIdProduct();
  388.     if (product != null) {
  389.       return product.getEventSourceCode();
  390.     }
  391.     return null;
  392.   }

  393.   /**
  394.    * Get the product used for eventsource and eventsourcecode.
  395.    *
  396.    * Event ID comes from the preferred origin product.
  397.    *
  398.    * @return The most preferred product summary. This summary is used to determine
  399.    *         the eventsouce and eventsourcecode.
  400.    * @see #getPreferredOriginProduct()
  401.    */
  402.   protected ProductSummary getEventIdProduct() {
  403.     ProductSummary product = getPreferredOriginProduct();
  404.     if (product == null) {
  405.       product = getProductWithOriginProperties();
  406.     }
  407.     return product;
  408.   }

  409.   /**
  410.    * Get the most recent product with origin properties (id, lat, lon, time).
  411.    *
  412.    * <strong>NOTE</strong>: this product may have been superseded by a delete.
  413.    * When an event has not been deleted, this method should be consistent with
  414.    * {@link #getPreferredOriginProduct()}.
  415.    *
  416.    * Products are checked in the following order, sorted most preferred first
  417.    * within each group. The first matching product is returned:
  418.    * <ol>
  419.    * <li>"origin" products not superseded or deleted, that have origin
  420.    * properties</li>
  421.    * <li>"origin" products superseded by a delete, that have origin
  422.    * properties</li>
  423.    * <li>products not superseded or deleted, that have origin properties</li>
  424.    * <li>products superseded by a delete, that have origin properties</li>
  425.    * </ol>
  426.    *
  427.    * @return the most recent product with origin properties.
  428.    * @see #productHasOriginProperties(ProductSummary)
  429.    */
  430.   public ProductSummary getProductWithOriginProperties() {
  431.     Map<String, List<ProductSummary>> allProducts = getAllProducts();
  432.     List<ProductSummary> productsList = null;
  433.     ProductSummary preferredProduct = null;
  434.     Iterator<ProductSummary> iter = null;

  435.     productsList = allProducts.get(ORIGIN_PRODUCT_TYPE);
  436.     if (productsList != null) {
  437.       // "origin" products not superseded or deleted
  438.       productsList = getSortedMostPreferredFirst(
  439.           getWithoutDeleted(getWithoutSuperseded(allProducts.get(ORIGIN_PRODUCT_TYPE))));
  440.       iter = productsList.iterator();
  441.       while (iter.hasNext()) {
  442.         preferredProduct = iter.next();
  443.         if (productHasOriginProperties(preferredProduct)) {
  444.           return preferredProduct;
  445.         }
  446.       }

  447.       // "origin" products superseded by a delete
  448.       productsList = getSortedMostPreferredFirst(
  449.           getWithoutSuperseded(getWithoutDeleted(allProducts.get(ORIGIN_PRODUCT_TYPE))));
  450.       iter = productsList.iterator();
  451.       while (iter.hasNext()) {
  452.         preferredProduct = iter.next();
  453.         if (productHasOriginProperties(preferredProduct)) {
  454.           return preferredProduct;
  455.         }
  456.       }
  457.     }

  458.     // products not superseded or deleted
  459.     productsList = getSortedMostPreferredFirst(
  460.         getWithoutDeleted(getWithoutSuperseded(productTypeMapToList(allProducts))));
  461.     iter = productsList.iterator();
  462.     while (iter.hasNext()) {
  463.       preferredProduct = iter.next();
  464.       if (productHasOriginProperties(preferredProduct)) {
  465.         return preferredProduct;
  466.       }
  467.     }

  468.     // products superseded by a delete
  469.     productsList = getSortedMostPreferredFirst(
  470.         getWithoutSuperseded(getWithoutDeleted(productTypeMapToList(allProducts))));
  471.     iter = productsList.iterator();
  472.     while (iter.hasNext()) {
  473.       preferredProduct = iter.next();
  474.       if (productHasOriginProperties(preferredProduct)) {
  475.         return preferredProduct;
  476.       }
  477.     }

  478.     return null;
  479.   }

  480.   /**
  481.    * Get the most preferred origin-like product for this event.
  482.    *
  483.    * The event is considered deleted if the returned product is null, deleted, or
  484.    * does not have origin properties. Information about the event may still be
  485.    * available using {@link #getProductWithOriginProperties()}.
  486.    *
  487.    * Products are checked in the following order, sorted most preferred first
  488.    * within each group. The first matching product is returned:
  489.    * <ul>
  490.    * <li>If any "origin" products exist:
  491.    * <ol>
  492.    * <li>"origin" products not superseded or deleted, that have origin
  493.    * properties.</li>
  494.    * <li>"origin" products not superseded, that have an event id.</li>
  495.    * </ol>
  496.    * </li>
  497.    * <li>If no "origin" products exist:
  498.    * <ol>
  499.    * <li>products not superseded or deleted, that have origin properties.</li>
  500.    * <li>products not superseded, that have an event id.</li>
  501.    * </ol>
  502.    * </li>
  503.    * </ul>
  504.    *
  505.    * @return the most recent product with origin properties.
  506.    * @see #productHasOriginProperties(ProductSummary)
  507.    */
  508.   public ProductSummary getPreferredOriginProduct() {
  509.     Map<String, List<ProductSummary>> allProducts = getAllProducts();
  510.     List<ProductSummary> productsList = null;
  511.     ProductSummary preferredProduct = null;
  512.     Iterator<ProductSummary> iter = null;

  513.     productsList = allProducts.get(ORIGIN_PRODUCT_TYPE);
  514.     if (productsList != null) {
  515.       // "origin" products not superseded or deleted,
  516.       // that have origin properties
  517.       productsList = getSortedMostPreferredFirst(
  518.           getWithoutDeleted(getWithoutSuperseded(allProducts.get(ORIGIN_PRODUCT_TYPE))));
  519.       iter = productsList.iterator();
  520.       while (iter.hasNext()) {
  521.         preferredProduct = iter.next();
  522.         if (productHasOriginProperties(preferredProduct)) {
  523.           return preferredProduct;
  524.         }
  525.       }

  526.       // "origin" products not superseded,
  527.       // that have event id
  528.       productsList = getSortedMostPreferredFirst(getWithoutSuperseded(allProducts.get(ORIGIN_PRODUCT_TYPE)));
  529.       iter = productsList.iterator();
  530.       while (iter.hasNext()) {
  531.         preferredProduct = iter.next();
  532.         if (preferredProduct.getEventSource() != null && preferredProduct.getEventSourceCode() != null) {
  533.           return preferredProduct;
  534.         }
  535.       }

  536.       return null;
  537.     }

  538.     // products not superseded or deleted,
  539.     // that have origin properties
  540.     productsList = getSortedMostPreferredFirst(
  541.         getWithoutDeleted(getWithoutSuperseded(productTypeMapToList(allProducts))));
  542.     iter = productsList.iterator();
  543.     while (iter.hasNext()) {
  544.       preferredProduct = iter.next();
  545.       if (productHasOriginProperties(preferredProduct)) {
  546.         return preferredProduct;
  547.       }
  548.     }

  549.     // products not superseded,
  550.     // that have event id
  551.     productsList = getSortedMostPreferredFirst(getWithoutSuperseded(productTypeMapToList(allProducts)));
  552.     iter = productsList.iterator();
  553.     while (iter.hasNext()) {
  554.       preferredProduct = iter.next();
  555.       if (preferredProduct.getEventSource() != null && preferredProduct.getEventSourceCode() != null) {
  556.         return preferredProduct;
  557.       }
  558.     }

  559.     return null;
  560.   }

  561.   /**
  562.    * Check if a product can define an event (id, lat, lon, time).
  563.    *
  564.    * @param product product to check.
  565.    * @return true if product has id, lat, lon, and time properties.
  566.    */
  567.   public static boolean productHasOriginProperties(final ProductSummary product) {
  568.     return (product.getEventSource() != null && product.getEventSourceCode() != null
  569.         && product.getEventLatitude() != null && product.getEventLongitude() != null && product.getEventTime() != null);
  570.   }

  571.   /**
  572.    * Get the most preferred magnitude product for event.
  573.    *
  574.    * Currently calls {@link #getPreferredOriginProduct()}.
  575.    *
  576.    * @return the most preferred magnitude product for event.
  577.    */
  578.   public ProductSummary getPreferredMagnitudeProduct() {
  579.     return getPreferredOriginProduct();
  580.   }

  581.   /**
  582.    * Get the preferred time for this event. If an origin product exists, it's
  583.    * value is used.
  584.    *
  585.    * @return Time from preferred product or null
  586.    */
  587.   public Date getTime() {
  588.     ProductSummary preferred = getProductWithOriginProperties();
  589.     if (preferred != null) {
  590.       return preferred.getEventTime();
  591.     }
  592.     return null;
  593.   }

  594.   /**
  595.    * Get the preferred latitude for this event. If an origin product exists, it's
  596.    * value is used.
  597.    *
  598.    * @return Latitude from preferred product or null
  599.    */
  600.   public BigDecimal getLatitude() {
  601.     ProductSummary preferred = getProductWithOriginProperties();
  602.     if (preferred != null) {
  603.       return preferred.getEventLatitude();
  604.     }
  605.     return null;

  606.   }

  607.   /**
  608.    * Get the preferred longitude for this event. If an origin product exists, it's
  609.    * value is used.
  610.    *
  611.    * @return Longitude from preferred product or null
  612.    */
  613.   public BigDecimal getLongitude() {
  614.     ProductSummary preferred = getProductWithOriginProperties();
  615.     if (preferred != null) {
  616.       return preferred.getEventLongitude();
  617.     }
  618.     return null;

  619.   }

  620.   /**
  621.    * Event update time is most recent product update time.
  622.    *
  623.    * @return the most recent product update time.
  624.    */
  625.   public Date getUpdateTime() {
  626.     Date updateTime = null;
  627.     Date time = null;
  628.     Iterator<ProductSummary> iter = getAllProductList().iterator();
  629.     while (iter.hasNext()) {
  630.       time = iter.next().getId().getUpdateTime();
  631.       if (updateTime == null || time.after(updateTime)) {
  632.         time = updateTime;
  633.       }
  634.     }
  635.     return updateTime;
  636.   }

  637.   /**
  638.    * Get the preferred depth for this event. If an origin product exists, it's
  639.    * value is used.
  640.    *
  641.    * @return Depth from preferred product or null
  642.    */
  643.   public BigDecimal getDepth() {
  644.     ProductSummary preferred = getProductWithOriginProperties();
  645.     if (preferred != null) {
  646.       return preferred.getEventDepth();
  647.     }
  648.     return null;
  649.   }

  650.   /**
  651.    * Get the preferred magntitude for this event. If an origin product exists,
  652.    * it's value is used.
  653.    *
  654.    * @return magnitude from preferred product or null
  655.    */
  656.   public BigDecimal getMagnitude() {
  657.     ProductSummary preferred = getPreferredMagnitudeProduct();
  658.     if (preferred != null) {
  659.       return preferred.getEventMagnitude();
  660.     }
  661.     return null;
  662.   }

  663.   /**
  664.    * @return boolean if the preferred event is deleted
  665.    */
  666.   public boolean isDeleted() {
  667.     ProductSummary preferred = getPreferredOriginProduct();
  668.     if (preferred != null && !preferred.isDeleted() && Event.productHasOriginProperties(preferred)) {
  669.       // have "origin" type product, that isn't deleted,
  670.       // and has origin properties
  671.       return false;
  672.     }
  673.     // otherwise, deleted
  674.     return true;
  675.   }

  676.   /**
  677.    * Get the most preferred product from a list of products.
  678.    *
  679.    * @param all a list of products containing only one type of product.
  680.    * @return the product with the highest preferred weight, and if tied the most
  681.    *         recent update time wins.
  682.    */
  683.   public static ProductSummary getPreferredProduct(final List<ProductSummary> all) {
  684.     ProductSummary preferred = null;

  685.     Iterator<ProductSummary> iter = all.iterator();
  686.     while (iter.hasNext()) {
  687.       ProductSummary summary = iter.next();
  688.       if (preferred == null) {
  689.         preferred = summary;
  690.       } else {
  691.         long summaryWeight = summary.getPreferredWeight();
  692.         long preferredWeight = preferred.getPreferredWeight();
  693.         if (summaryWeight > preferredWeight || (summaryWeight == preferredWeight
  694.             && summary.getId().getUpdateTime().after(preferred.getId().getUpdateTime()))) {
  695.           preferred = summary;
  696.         }
  697.       }
  698.     }
  699.     return preferred;
  700.   }

  701.   /**
  702.    * Summarize this event into preferred values.
  703.    *
  704.    * NOTE: the event summary may include information from an origin product, even
  705.    * when the preferred origin for the event has been deleted. Use
  706.    * getPreferredOriginProduct() to check the preferred origin of the event.
  707.    *
  708.    * @return an event summary.
  709.    */
  710.   public EventSummary getEventSummary() {
  711.     if (eventSummary != null) {
  712.       return eventSummary;
  713.     }

  714.     EventSummary summary = new EventSummary();
  715.     summary.setIndexId(this.getIndexId());
  716.     summary.setDeleted(this.isDeleted());

  717.     ProductSummary eventIdProduct = this.getEventIdProduct();
  718.     if (eventIdProduct != null) {
  719.       summary.setSource(eventIdProduct.getEventSource());
  720.       summary.setSourceCode(eventIdProduct.getEventSourceCode());
  721.     }

  722.     ProductSummary originProduct = this.getProductWithOriginProperties();
  723.     if (originProduct != null) {
  724.       summary.setLatitude(originProduct.getEventLatitude());
  725.       summary.setLongitude(originProduct.getEventLongitude());
  726.       summary.setTime(originProduct.getEventTime());
  727.       summary.setDepth(originProduct.getEventDepth());
  728.     }

  729.     ProductSummary magnitudeProduct = this.getPreferredMagnitudeProduct();
  730.     if (magnitudeProduct != null) {
  731.       summary.setMagnitude(magnitudeProduct.getEventMagnitude());
  732.     }

  733.     // we may be able to avoid implementing this here, since the mapping
  734.     // interface will be driven by the PHP product index.
  735.     summary.getEventCodes().putAll(this.getEventCodes());

  736.     // cache summary
  737.     eventSummary = summary;

  738.     return summary;
  739.   }

  740.   /**
  741.    * Comparison class that compares two ProductSummary objects based on their
  742.    * preferred weight and update time.
  743.    *
  744.    */
  745.   static class MostPreferredFirstComparator implements Comparator<ProductSummary> {

  746.     @Override
  747.     public int compare(ProductSummary p1, ProductSummary p2) {
  748.       if (p1.getPreferredWeight() > p2.getPreferredWeight()) {
  749.         return -1;
  750.       } else if (p1.getPreferredWeight() < p2.getPreferredWeight()) {
  751.         return 1;
  752.       } else {
  753.         Date p1Update = p1.getUpdateTime();
  754.         Date p2Update = p2.getUpdateTime();
  755.         if (p1Update.after(p2Update)) {
  756.           return -1;
  757.         } else if (p2Update.after(p1Update)) {
  758.           return 1;
  759.         } else {
  760.           return 0;
  761.         }
  762.       }
  763.     }
  764.   }

  765.   @Override
  766.   public int compareTo(Event that) {
  767.     int r;

  768.     List<ProductSummary> thisProducts = this.getProductList();
  769.     List<ProductSummary> thatProducts = that.getProductList();
  770.     if ((r = (thatProducts.size() - thisProducts.size())) != 0) {
  771.       return r;
  772.     }

  773.     Iterator<ProductSummary> thisIter = thisProducts.iterator();
  774.     Iterator<ProductSummary> thatIter = thatProducts.iterator();
  775.     while (thisIter.hasNext() && thatIter.hasNext()) {
  776.       // just compare product ids for now
  777.       r = thisIter.next().getId().compareTo(thatIter.next().getId());
  778.       if (r != 0) {
  779.         return r;
  780.       }
  781.     }

  782.     return 0;
  783.   }

  784.   /**
  785.    * Find the most preferred product.
  786.    *
  787.    * If preferredType is not null, products of this type are favored over those
  788.    * not of this type.
  789.    *
  790.    * If preferredNotNullProperty is not null, products that have this property set
  791.    * are favored over those without this property set.
  792.    *
  793.    * @param products                 the list of products to search.
  794.    * @param preferredType            the preferred product type, if available.
  795.    * @param preferredNotNullProperty the preferred property name, if available.
  796.    * @return The most preferred product summary of the given type.
  797.    */
  798.   public static ProductSummary getMostPreferred(final List<ProductSummary> products, final String preferredType,
  799.       final String preferredNotNullProperty) {
  800.     ProductSummary mostPreferred = null;

  801.     Iterator<ProductSummary> iter = products.iterator();
  802.     while (iter.hasNext()) {
  803.       ProductSummary next = iter.next();

  804.       // ignore products that don't have the preferredNotNullProperty
  805.       if (preferredNotNullProperty != null && next.getProperties().get(preferredNotNullProperty) == null) {
  806.         continue;
  807.       }

  808.       if (mostPreferred == null) {
  809.         // first product is most preferred so far
  810.         mostPreferred = next;
  811.         continue;
  812.       }

  813.       if (preferredType != null) {
  814.         if (next.getType().equals(preferredType)) {
  815.           if (!mostPreferred.getType().equals(preferredType)) {
  816.             // prefer products of this type
  817.             mostPreferred = next;
  818.           }
  819.         } else if (mostPreferred.getType().equals(preferredType)) {
  820.           // already have preferred product of preferred type
  821.           continue;
  822.         }
  823.       }

  824.       if (next.getPreferredWeight() > mostPreferred.getPreferredWeight()) {
  825.         // higher preferred weight
  826.         mostPreferred = next;
  827.       } else if (next.getPreferredWeight() == mostPreferred.getPreferredWeight()
  828.           && next.getUpdateTime().after(mostPreferred.getUpdateTime())) {
  829.         // same preferred weight, newer update
  830.         mostPreferred = next;
  831.       }
  832.     }

  833.     return mostPreferred;
  834.   }

  835.   /**
  836.    * Remove deleted products from the list.
  837.    *
  838.    * @param products list of products to filter.
  839.    * @return copy of the products list with deleted products removed.
  840.    */
  841.   public static List<ProductSummary> getWithoutDeleted(final List<ProductSummary> products) {
  842.     List<ProductSummary> withoutDeleted = new ArrayList<ProductSummary>();

  843.     Iterator<ProductSummary> iter = products.iterator();
  844.     while (iter.hasNext()) {
  845.       ProductSummary next = iter.next();
  846.       if (!next.isDeleted()) {
  847.         withoutDeleted.add(next);
  848.       }
  849.     }

  850.     return withoutDeleted;
  851.   }

  852.   /**
  853.    * Remove deleted products from the list.
  854.    *
  855.    * @param products list of products to filter.
  856.    * @return copy of the products list with deleted products removed.
  857.    */
  858.   public static List<ProductSummary> getWithEventId(final List<ProductSummary> products) {
  859.     List<ProductSummary> withEventId = new ArrayList<ProductSummary>();

  860.     Iterator<ProductSummary> iter = products.iterator();
  861.     while (iter.hasNext()) {
  862.       ProductSummary next = iter.next();
  863.       if (next.getEventId() != null) {
  864.         withEventId.add(next);
  865.       }
  866.     }

  867.     return withEventId;
  868.   }

  869.   /**
  870.    * Remove old versions of products from the list.
  871.    *
  872.    * @param products list of products to filter.
  873.    * @return a copy of the products list with products of the same
  874.    *         source+type+code but with older updateTimes (superseded) removed.
  875.    */
  876.   public static List<ProductSummary> getWithoutSuperseded(final List<ProductSummary> products) {
  877.     // place product into latest, keyed by source+type+code,
  878.     // keeping only most recent update for each key
  879.     Map<String, ProductSummary> latest = new HashMap<String, ProductSummary>();
  880.     Iterator<ProductSummary> iter = products.iterator();
  881.     while (iter.hasNext()) {
  882.       ProductSummary summary = iter.next();
  883.       ProductId id = summary.getId();

  884.       // key is combination of source, type, and code
  885.       // since none of these may contain ":", it is used as a delimiter to
  886.       // prevent collisions.
  887.       String key = new StringBuffer(id.getSource()).append(":").append(id.getType()).append(":").append(id.getCode())
  888.           .toString();
  889.       if (!latest.containsKey(key)) {
  890.         // first product
  891.         latest.put(key, summary);
  892.       } else {
  893.         // keep latest product
  894.         ProductSummary other = latest.get(key);
  895.         if (other.getId().getUpdateTime().before(id.getUpdateTime())) {
  896.           latest.put(key, summary);
  897.         }
  898.       }
  899.     }

  900.     // those that are in the latest map have not been superseded
  901.     return new ArrayList<ProductSummary>(latest.values());
  902.   }

  903.   /**
  904.    * Sort a list of products, most preferred first.
  905.    *
  906.    * @param products the list of products to sort.
  907.    * @return a copy of the list sorted with most preferred first.
  908.    */
  909.   public static List<ProductSummary> getSortedMostPreferredFirst(final List<ProductSummary> products) {
  910.     List<ProductSummary> mostPreferredFirst = new ArrayList<ProductSummary>(products);
  911.     Collections.sort(mostPreferredFirst, new MostPreferredFirstComparator());
  912.     return mostPreferredFirst;
  913.   }

  914.   static List<ProductSummary> productTypeMapToList(final Map<String, List<ProductSummary>> products) {
  915.     List<ProductSummary> list = new ArrayList<ProductSummary>();

  916.     Iterator<String> iter = products.keySet().iterator();
  917.     while (iter.hasNext()) {
  918.       list.addAll(products.get(iter.next()));
  919.     }

  920.     return list;
  921.   }

  922.   static Map<String, List<ProductSummary>> productListToTypeMap(final List<ProductSummary> products) {
  923.     Map<String, List<ProductSummary>> typeMap = new HashMap<String, List<ProductSummary>>();

  924.     Iterator<ProductSummary> iter = products.iterator();
  925.     while (iter.hasNext()) {
  926.       ProductSummary product = iter.next();
  927.       List<ProductSummary> typeProducts = typeMap.get(product.getType());
  928.       if (typeProducts == null) {
  929.         typeProducts = new ArrayList<ProductSummary>();
  930.         typeMap.put(product.getType(), typeProducts);
  931.       }
  932.       typeProducts.add(product);
  933.     }

  934.     return typeMap;
  935.   }

  936.   /**
  937.    * Return a list of sub-events that make up this event.
  938.    *
  939.    * Event lines are drawn by eventid. Products that have no eventid are included
  940.    * with the sub event whose id is considered preferred.
  941.    *
  942.    * @return map from eventid to event object with products for that eventid.
  943.    */
  944.   public Map<String, Event> getSubEvents() {
  945.     // Map of sub-events keyed by product "eventId"
  946.     Map<String, Event> subEvents = new HashMap<String, Event>();

  947.     // Map of events by source_type_code
  948.     Map<String, Event> productEvents = new HashMap<String, Event>();

  949.     // this is the event that will have products without event id...
  950.     String preferredEventId = this.getEventId();
  951.     Event preferredSubEvent = new Event();
  952.     // put a placeholder with no products into the map for this purpose.
  953.     subEvents.put(preferredEventId, preferredSubEvent);

  954.     // List of all products associated to the current event
  955.     List<ProductSummary> allProducts = this.getAllProductList();

  956.     // handle products with a current version
  957.     HashSet<ProductSummary> withoutSuperseded = new HashSet<ProductSummary>(getWithoutSuperseded(allProducts));
  958.     Iterator<ProductSummary> products = withoutSuperseded.iterator();
  959.     while (products.hasNext()) {
  960.       ProductSummary product = products.next();
  961.       Event subEvent = null;

  962.       String subEventId = product.getEventId();
  963.       if (subEventId == null) {
  964.         // maybe try to find another version of product with id?
  965.         subEvent = preferredSubEvent;
  966.       } else {
  967.         subEvent = subEvents.get(subEventId);
  968.         if (subEvent == null) {
  969.           // first product for this sub event
  970.           subEvent = new Event();
  971.           subEvents.put(subEventId, subEvent);
  972.         }
  973.       }
  974.       subEvent.addProduct(product);

  975.       ProductId id = product.getId();
  976.       String key = id.getSource() + "_" + id.getType() + "_" + id.getCode();
  977.       productEvents.put(key, subEvent);
  978.     }

  979.     // handle superseded products
  980.     HashSet<ProductSummary> superseded = new HashSet<ProductSummary>(allProducts);
  981.     superseded.removeAll(withoutSuperseded);
  982.     products = superseded.iterator();
  983.     while (products.hasNext()) {
  984.       ProductSummary next = products.next();
  985.       ProductId id = next.getId();
  986.       String key = id.getSource() + "_" + id.getType() + "_" + id.getCode();
  987.       Event subEvent = productEvents.get(key);
  988.       subEvent.addProduct(next);
  989.     }

  990.     return subEvents;
  991.   }

  992.   /**
  993.    * Check if this event has an associate product for another given Event.
  994.    *
  995.    * @param otherEvent the other event.
  996.    * @return true if there is an associate product, false otherwise.
  997.    */
  998.   public boolean hasAssociateProduct(final Event otherEvent) {
  999.     if (otherEvent == null) {
  1000.       // cannot have an association to a null event...
  1001.       return false;
  1002.     }

  1003.     String otherEventSource = otherEvent.getSource();
  1004.     String otherEventSourceCode = otherEvent.getSourceCode();
  1005.     if (otherEventSource == null || otherEventSourceCode == null) {
  1006.       // same without source+code
  1007.       return false;
  1008.     }

  1009.     // search associate products
  1010.     Iterator<ProductSummary> iter = getProducts(ASSOCIATE_PRODUCT_TYPE).iterator();
  1011.     while (iter.hasNext()) {
  1012.       ProductSummary associate = iter.next();

  1013.       if (otherEventSource.equalsIgnoreCase(associate.getProperties().get(OTHEREVENTSOURCE_PROPERTY))
  1014.           && otherEventSourceCode.equalsIgnoreCase(associate.getProperties().get(OTHEREVENTSOURCECODE_PROPERTY))) {
  1015.         // associated
  1016.         return true;
  1017.       }
  1018.     }

  1019.     return false;
  1020.   }

  1021.   /**
  1022.    * Check if this event has an disassociate product for another given Event.
  1023.    *
  1024.    * @param otherEvent the other event.
  1025.    * @return true if there is an disassociate product, false otherwise.
  1026.    */
  1027.   public boolean hasDisassociateProduct(final Event otherEvent) {
  1028.     if (otherEvent == null) {
  1029.       // cannot have an disassociation to a null event...
  1030.       return false;
  1031.     }

  1032.     String otherEventSource = otherEvent.getSource();
  1033.     String otherEventSourceCode = otherEvent.getSourceCode();
  1034.     if (otherEventSource == null || otherEventSourceCode == null) {
  1035.       // same without source+code
  1036.       return false;
  1037.     }

  1038.     // search disassociate products
  1039.     Iterator<ProductSummary> iter = getProducts(DISASSOCIATE_PRODUCT_TYPE).iterator();
  1040.     while (iter.hasNext()) {
  1041.       ProductSummary associate = iter.next();

  1042.       if (otherEventSource.equalsIgnoreCase(associate.getProperties().get(OTHEREVENTSOURCE_PROPERTY))
  1043.           && otherEventSourceCode.equalsIgnoreCase(associate.getProperties().get(OTHEREVENTSOURCECODE_PROPERTY))) {
  1044.         // disassociated
  1045.         return true;
  1046.       }
  1047.     }

  1048.     return false;
  1049.   }

  1050.   /**
  1051.    * Same as isAssociated(that, new DefaultAssociator());
  1052.    *
  1053.    * @param that an event to test
  1054.    * @return boolean true if associated, false otherwise
  1055.    */
  1056.   public boolean isAssociated(final Event that) {
  1057.     return this.isAssociated(that, new DefaultAssociator());
  1058.   }

  1059.   /**
  1060.    * Check if an event is associated to this event.
  1061.    *
  1062.    * Reasons events may be considered disassociated:
  1063.    * <ol>
  1064.    * <li>Share a common EVENTSOURCE with different EVENTSOURCECODE.</li>
  1065.    * <li>Either has a disassociate product for the other.</li>
  1066.    * <li>Preferred location in space and time is NOT nearby, and no other reason
  1067.    * to associate.</li>
  1068.    * </ol>
  1069.    *
  1070.    * Reasons events may be considered associated:
  1071.    * <ol>
  1072.    * <li>Share a common EVENTID</li>
  1073.    * <li>Either has an associate product for the other.</li>
  1074.    * <li>Their preferred location in space and time is nearby.</li>
  1075.    * </ol>
  1076.    *
  1077.    * @param that       candidate event to test.
  1078.    * @param associator An associator to compare two events
  1079.    * @return true if associated, false otherwise.
  1080.    */
  1081.   public boolean isAssociated(final Event that, final Associator associator) {
  1082.     return associator.eventsAssociated(this, that);
  1083.   }

  1084.   /**
  1085.    * Depending on logger level, takes in summary data and appends to buffer
  1086.    *
  1087.    * @param logger logger object
  1088.    */
  1089.   public void log(final Logger logger) {
  1090.     if (logger.isLoggable(Level.FINE)) {
  1091.       EventSummary summary = this.getEventSummary();
  1092.       logger.fine(new StringBuffer("Event").append("indexid=").append(summary.getIndexId()).append(", eventid=")
  1093.           .append(summary.getId()).append(", latitude=").append(summary.getLatitude()).append(", longitude=")
  1094.           .append(summary.getLongitude()).append(", time=").append(summary.getTime()).append(", deleted=")
  1095.           .append(summary.isDeleted()).toString());

  1096.       if (logger.isLoggable(Level.FINER)) {
  1097.         StringBuffer buf = new StringBuffer("Products in event");
  1098.         List<ProductSummary> products = this.getAllProductList();
  1099.         Iterator<ProductSummary> iter = products.iterator();
  1100.         while (iter.hasNext()) {
  1101.           ProductSummary next = iter.next();
  1102.           buf.append("\n\tstatus=").append(next.getStatus()).append(", id=").append(next.getId().toString())
  1103.               .append(", eventid=").append(next.getEventId()).append(", latitude=").append(next.getEventLatitude())
  1104.               .append(", longitude=").append(next.getEventLongitude()).append(", time=").append(next.getEventTime());
  1105.         }
  1106.         logger.finer(buf.toString());
  1107.       }
  1108.     }
  1109.   }

  1110. }