SearchResponseParser.java

  1. package gov.usgs.earthquake.indexer;

  2. import java.math.BigDecimal;
  3. import java.net.URI;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. import java.util.Map;

  7. import gov.usgs.earthquake.distribution.FileProductStorage;
  8. import gov.usgs.earthquake.product.Product;
  9. import gov.usgs.earthquake.product.ProductId;
  10. import gov.usgs.earthquake.product.io.XmlProductHandler;
  11. import gov.usgs.util.XmlUtils;

  12. import org.xml.sax.Attributes;
  13. import org.xml.sax.SAXException;
  14. import org.xml.sax.helpers.DefaultHandler;

  15. /**
  16.  * Parser for SearchXML response.
  17.  */
  18. public class SearchResponseParser extends DefaultHandler {

  19.     private SearchResponse response = null;

  20.     private SearchQuery query = null;

  21.     private ProductSummary pSummary = null;
  22.     private EventSummary eSummary = null;

  23.     private Event event = null;

  24.     private boolean inQueryElement = false;
  25.     private boolean inErrorElement = false;

  26.     private FileProductStorage storage;
  27.     private SearchResponseXmlProductSource productHandler = null;

  28.     /**
  29.      * Constructor
  30.      * @param storage a FileProductStorage
  31.      */
  32.     public SearchResponseParser(final FileProductStorage storage) {
  33.         this.storage = storage;
  34.     }

  35.     /** @return SearchResponse */
  36.     public SearchResponse getSearchResponse() {
  37.         return response;
  38.     }

  39.     @Override
  40.     public void startElement(String uri, String localName, String qName,
  41.             Attributes attributes) throws SAXException {
  42.         if (productHandler != null) {
  43.             productHandler.startElement(uri, localName, qName, attributes);
  44.         } else if (SearchXML.INDEXER_XMLNS.equals(uri)) {
  45.             if (SearchXML.RESPONSE_ELEMENT.equals(localName)) {
  46.                 response = new SearchResponse();
  47.             } else if (SearchXML.RESULT_ELEMENT.equals(localName)) {
  48.                 if (response == null)
  49.                     throw new SAXException(
  50.                             "Unexpected result element without response element parent.");
  51.                 SearchMethod method = SearchMethod.fromXmlMethodName(XmlUtils
  52.                         .getAttribute(attributes, uri,
  53.                                 SearchXML.METHOD_ATTRIBUTE));
  54.                 query = SearchQuery.getSearchQuery(method,
  55.                         new ProductIndexQuery());
  56.                 // create results container now
  57.                 if (query instanceof EventDetailQuery) {
  58.                     ((EventDetailQuery) query)
  59.                             .setResult(new ArrayList<Event>());
  60.                 } else if (query instanceof EventsSummaryQuery) {
  61.                     ((EventsSummaryQuery) query)
  62.                             .setResult(new ArrayList<EventSummary>());
  63.                 } else if (query instanceof ProductDetailQuery) {
  64.                     ((ProductDetailQuery) query)
  65.                             .setResult(new ArrayList<Product>());
  66.                 } else if (query instanceof ProductsSummaryQuery) {
  67.                     ((ProductsSummaryQuery) query)
  68.                             .setResult(new ArrayList<ProductSummary>());
  69.                 }
  70.             } else if (SearchXML.QUERY_ELEMENT.equals(localName)) {
  71.                 if (query == null)
  72.                     throw new SAXException(
  73.                             "Unexpected query element without result element parent.");
  74.                 inQueryElement = true;
  75.                 ProductIndexQuery piQuery = query.getProductIndexQuery();

  76.                 // Update the ProductIndexQuery with each given attribute
  77.                 // Event Source Attribute
  78.                 String eventSource = XmlUtils.getAttribute(attributes, uri,
  79.                         SearchXML.EVENT_SOURCE_ATTRIBUTE);
  80.                 if (eventSource != null) {
  81.                     piQuery.setEventSource(eventSource);
  82.                 }
  83.                 // Event Source Code Attribute
  84.                 String eventSourceCode = XmlUtils.getAttribute(attributes, uri,
  85.                         SearchXML.EVENT_SOURCE_CODE_ATTRIBUTE);
  86.                 if (eventSourceCode != null) {
  87.                     piQuery.setEventSourceCode(eventSourceCode);
  88.                 }
  89.                 // Max Event Depth Attribute
  90.                 String maxEventDepth = XmlUtils.getAttribute(attributes, uri,
  91.                         SearchXML.MAX_EVENT_DEPTH_ATTRIBUTE);
  92.                 if (maxEventDepth != null) {
  93.                     piQuery.setMaxEventDepth(new BigDecimal(maxEventDepth));
  94.                 }
  95.                 // Max Event Latitude Attribute
  96.                 String maxEventLatitude = XmlUtils.getAttribute(attributes,
  97.                         uri, SearchXML.MAX_EVENT_LATITUDE_ATTRIBUTE);
  98.                 if (maxEventLatitude != null) {
  99.                     piQuery.setMaxEventLatitude(new BigDecimal(maxEventLatitude));
  100.                 }
  101.                 // Max Event Longitude Attribute
  102.                 String maxEventLongitude = XmlUtils.getAttribute(attributes,
  103.                         uri, SearchXML.MAX_EVENT_LONGITUDE_ATTRIBUTE);
  104.                 if (maxEventLongitude != null) {
  105.                     piQuery.setMaxEventLongitude(new BigDecimal(
  106.                             maxEventLongitude));
  107.                 }
  108.                 // Max Event Magnitude Attribute
  109.                 String maxEventMagnitude = XmlUtils.getAttribute(attributes,
  110.                         uri, SearchXML.MAX_EVENT_MAGNITUDE_ATTRIBUTE);
  111.                 if (maxEventMagnitude != null) {
  112.                     piQuery.setMaxEventMagnitude(new BigDecimal(
  113.                             maxEventMagnitude));
  114.                 }
  115.                 // Max Event Time Attribute
  116.                 String maxEventTime = XmlUtils.getAttribute(attributes, uri,
  117.                         SearchXML.MAX_EVENT_TIME_ATTRIBUTE);
  118.                 if (maxEventTime != null) {
  119.                     piQuery.setMaxEventTime(XmlUtils.getDate(maxEventTime));
  120.                 }
  121.                 // Max Product Update Time Attribute
  122.                 String maxProductUpdateTime = XmlUtils.getAttribute(attributes,
  123.                         uri, SearchXML.MAX_PRODUCT_UPDATE_TIME_ATTRIBUTE);
  124.                 if (maxProductUpdateTime != null) {
  125.                     piQuery.setMaxProductUpdateTime(XmlUtils
  126.                             .getDate(maxProductUpdateTime));
  127.                 }
  128.                 // Min Event Depth Attribute
  129.                 String minEventDepth = XmlUtils.getAttribute(attributes, uri,
  130.                         SearchXML.MIN_EVENT_DEPTH_ATTRIBUTE);
  131.                 if (minEventDepth != null) {
  132.                     piQuery.setMinEventDepth(new BigDecimal(minEventDepth));
  133.                 }
  134.                 // Min Event Latitude Attribute
  135.                 String minEventLatitude = XmlUtils.getAttribute(attributes,
  136.                         uri, SearchXML.MIN_EVENT_LATITUDE_ATTRIBUTE);
  137.                 if (minEventLatitude != null) {
  138.                     piQuery.setMinEventLatitude(new BigDecimal(minEventLatitude));
  139.                 }
  140.                 // Min Event Longitude Attribute
  141.                 String minEventLongitude = XmlUtils.getAttribute(attributes,
  142.                         uri, SearchXML.MIN_EVENT_LONGITUDE_ATTRIBUTE);
  143.                 if (minEventLongitude != null) {
  144.                     piQuery.setMinEventLongitude(new BigDecimal(
  145.                             minEventLongitude));
  146.                 }
  147.                 // Min Event Magnitude Attribute
  148.                 String minEventMagnitude = XmlUtils.getAttribute(attributes,
  149.                         uri, SearchXML.MIN_EVENT_MAGNITUDE_ATTRIBUTE);
  150.                 if (minEventMagnitude != null) {
  151.                     piQuery.setMinEventMagnitude(new BigDecimal(
  152.                             minEventMagnitude));
  153.                 }
  154.                 // Min Event Time Attribute
  155.                 String minEventTime = XmlUtils.getAttribute(attributes, uri,
  156.                         SearchXML.MIN_EVENT_TIME_ATTRIBUTE);
  157.                 if (minEventTime != null) {
  158.                     piQuery.setMinEventTime(XmlUtils.getDate(minEventTime));
  159.                 }
  160.                 // Min Product Update Time Attribute
  161.                 String minProductUpdateTime = XmlUtils.getAttribute(attributes,
  162.                         uri, SearchXML.MIN_PRODUCT_UPDATE_TIME_ATTRIBUTE);
  163.                 if (minProductUpdateTime != null) {
  164.                     piQuery.setMinProductUpdateTime(XmlUtils
  165.                             .getDate(minProductUpdateTime));
  166.                 }
  167.                 // Product Code Attribute
  168.                 String productCode = XmlUtils.getAttribute(attributes, uri,
  169.                         SearchXML.PRODUCT_CODE_ATTRIBUTE);
  170.                 if (productCode != null) {
  171.                     piQuery.setProductCode(productCode);
  172.                 }
  173.                 // Product Source Attribute
  174.                 String productSource = XmlUtils.getAttribute(attributes, uri,
  175.                         SearchXML.PRODUCT_SOURCE_ATTRIBUTE);
  176.                 if (productSource != null) {
  177.                     piQuery.setProductSource(productSource);
  178.                 }
  179.                 // Product Status Attribute
  180.                 String productStatus = XmlUtils.getAttribute(attributes, uri,
  181.                         SearchXML.PRODUCT_STATUS_ATTRIBUTE);
  182.                 if (productStatus != null) {
  183.                     piQuery.setProductStatus(productStatus);
  184.                 }
  185.                 // Product Type Attribute
  186.                 String productType = XmlUtils.getAttribute(attributes, uri,
  187.                         SearchXML.PRODUCT_TYPE_ATTRIBUTE);
  188.                 if (productType != null) {
  189.                     piQuery.setProductType(productType);
  190.                 }
  191.                 // Product Version Attribute
  192.                 String productVersion = XmlUtils.getAttribute(attributes, uri,
  193.                         SearchXML.PRODUCT_VERSION_ATTRIBUTE);
  194.                 if (productVersion != null) {
  195.                     piQuery.setProductVersion(productVersion);
  196.                 }

  197.                 // Set result type. At the moment we ony support the "current"
  198.                 // type.
  199.                 piQuery.setResultType(ProductIndexQuery.RESULT_TYPE_CURRENT);
  200.             } else if (SearchXML.PRODUCT_SUMMARY_ELEMENT.equals(localName)) {
  201.                 if (inQueryElement) {
  202.                     // This product summary is being used to pass ID information
  203.                     // for the query
  204.                     ProductIndexQuery piQuery = query.getProductIndexQuery();
  205.                     piQuery.getProductIds().add(
  206.                             ProductId.parse(XmlUtils.getAttribute(attributes,
  207.                                     uri, SearchXML.ID_ATTRIBUTE)));
  208.                 } else {
  209.                     // This is a more complete returned product summary
  210.                     pSummary = new ProductSummary();

  211.                     // Set the attributes of the ProductSummary
  212.                     // Depth attribute
  213.                     String depth = XmlUtils.getAttribute(attributes, uri,
  214.                             SearchXML.DEPTH_ATTRIBUTE);
  215.                     if (depth != null) {
  216.                         pSummary.setEventDepth(new BigDecimal(depth));
  217.                     }
  218.                     // Latitude attribute
  219.                     String latitude = XmlUtils.getAttribute(attributes, uri,
  220.                             SearchXML.LATITUDE_ATTRIBUTE);
  221.                     if (latitude != null) {
  222.                         pSummary.setEventLatitude(new BigDecimal(latitude));
  223.                     }
  224.                     // Longitude attribute
  225.                     String longitude = XmlUtils.getAttribute(attributes, uri,
  226.                             SearchXML.LONGITUDE_ATTRIBUTE);
  227.                     if (longitude != null) {
  228.                         pSummary.setEventLongitude(new BigDecimal(longitude));
  229.                     }
  230.                     // Magnitude attribute
  231.                     String magnitude = XmlUtils.getAttribute(attributes, uri,
  232.                             SearchXML.MAGNITUDE_ATTRIBUTE);
  233.                     if (magnitude != null) {
  234.                         pSummary.setEventMagnitude(new BigDecimal(magnitude));
  235.                     }
  236.                     // Event Source attribute
  237.                     String eventSource = XmlUtils.getAttribute(attributes, uri,
  238.                             SearchXML.EVENT_SOURCE_ATTRIBUTE);
  239.                     if (eventSource != null) {
  240.                         pSummary.setEventSource(eventSource);
  241.                     }
  242.                     // Event Source Code attribute
  243.                     String eventSourceCode = XmlUtils.getAttribute(attributes,
  244.                             uri, SearchXML.EVENT_SOURCE_CODE_ATTRIBUTE);
  245.                     if (eventSourceCode != null) {
  246.                         pSummary.setEventSourceCode(eventSourceCode);
  247.                     }
  248.                     // Time attribute
  249.                     String time = XmlUtils.getAttribute(attributes, uri,
  250.                             SearchXML.TIME_ATTRIBUTE);
  251.                     if (time != null) {
  252.                         pSummary.setEventTime(XmlUtils.getDate(time));
  253.                     }
  254.                     // ID attribute
  255.                     String id = XmlUtils.getAttribute(attributes, uri,
  256.                             SearchXML.ID_ATTRIBUTE);
  257.                     if (id != null) {
  258.                         pSummary.setId(ProductId.parse(id));
  259.                     }
  260.                     // Preferred Weight attribute
  261.                     String preferredWeight = XmlUtils.getAttribute(attributes,
  262.                             uri, SearchXML.PREFERRED_WEIGHT_ATTRIBUTE);
  263.                     if (preferredWeight != null) {
  264.                         pSummary.setPreferredWeight(Integer
  265.                                 .parseInt(preferredWeight));
  266.                     }
  267.                     // Status attribute
  268.                     String status = XmlUtils.getAttribute(attributes, uri,
  269.                             SearchXML.STATUS_ATTRIBUTE);
  270.                     if (status != null) {
  271.                         pSummary.setStatus(status);
  272.                     }
  273.                     // Version attribute
  274.                     String version = XmlUtils.getAttribute(attributes, uri,
  275.                             SearchXML.VERSION_ATTRIBUTE);
  276.                     if (version != null) {
  277.                         pSummary.setVersion(version);
  278.                     }
  279.                 }
  280.             } else if (SearchXML.EVENT_ELEMENT.equals(localName)) {
  281.                 event = new Event();
  282.                 // Nothing further needs to be done here as properties are set
  283.                 // by ProductSummaries for the event
  284.             } else if (SearchXML.EVENT_SUMMARY_ELEMENT.equals(localName)) {
  285.                 eSummary = new EventSummary();

  286.                 // Configure EventSummary attributes
  287.                 // Depth attribute
  288.                 String depth = XmlUtils.getAttribute(attributes, uri,
  289.                         SearchXML.DEPTH_ATTRIBUTE);
  290.                 if (depth != null) {
  291.                     eSummary.setDepth(new BigDecimal(depth));
  292.                 }
  293.                 // Latitude attribute
  294.                 String latitude = XmlUtils.getAttribute(attributes, uri,
  295.                         SearchXML.LATITUDE_ATTRIBUTE);
  296.                 if (latitude != null) {
  297.                     eSummary.setLatitude(new BigDecimal(latitude));
  298.                 }
  299.                 // Longitude attribute
  300.                 String longitude = XmlUtils.getAttribute(attributes, uri,
  301.                         SearchXML.LONGITUDE_ATTRIBUTE);
  302.                 if (longitude != null) {
  303.                     eSummary.setLongitude(new BigDecimal(longitude));
  304.                 }
  305.                 // Magnitude attribute
  306.                 String magnitude = XmlUtils.getAttribute(attributes, uri,
  307.                         SearchXML.MAGNITUDE_ATTRIBUTE);
  308.                 if (magnitude != null) {
  309.                     eSummary.setMagnitude(new BigDecimal(magnitude));
  310.                 }
  311.                 // Source attribute
  312.                 String source = XmlUtils.getAttribute(attributes, uri,
  313.                         SearchXML.SOURCE_ATTRIBUTE);
  314.                 if (source != null) {
  315.                     eSummary.setSource(source);
  316.                 }
  317.                 // Source code attribute
  318.                 String sourceCode = XmlUtils.getAttribute(attributes, uri,
  319.                         SearchXML.SOURCE_CODE_ATTRIBUTE);
  320.                 if (sourceCode != null) {
  321.                     eSummary.setSourceCode(sourceCode);
  322.                 }
  323.                 // Time attribute
  324.                 String time = XmlUtils.getAttribute(attributes, uri,
  325.                         SearchXML.TIME_ATTRIBUTE);
  326.                 if (time != null) {
  327.                     eSummary.setTime(XmlUtils.getDate(time));
  328.                 }
  329.             } else if (SearchXML.ERROR_ELEMENT.equals(localName)) {
  330.                 inErrorElement = true;
  331.             }
  332.         } else if (XmlProductHandler.PRODUCT_XML_NAMESPACE.equals(uri)) {
  333.             if (XmlProductHandler.PROPERTY_ELEMENT.equals(localName)) {
  334.                 if (pSummary != null) {
  335.                     // Currently working on a Product Summary
  336.                     pSummary.getProperties()
  337.                             .put(XmlUtils.getAttribute(attributes, uri,
  338.                                     XmlProductHandler.PROPERTY_ATTRIBUTE_NAME),
  339.                                     XmlUtils.getAttribute(
  340.                                             attributes,
  341.                                             uri,
  342.                                             XmlProductHandler.PROPERTY_ATTRIBUTE_VALUE));
  343.                 } else if (eSummary != null) {
  344.                     // Currently working on an Event Summary
  345.                     eSummary.getProperties()
  346.                             .put(XmlUtils.getAttribute(attributes, uri,
  347.                                     XmlProductHandler.PROPERTY_ATTRIBUTE_NAME),
  348.                                     XmlUtils.getAttribute(
  349.                                             attributes,
  350.                                             uri,
  351.                                             XmlProductHandler.PROPERTY_ATTRIBUTE_VALUE));
  352.                 } else if (inQueryElement) {
  353.                     // Currently working on a query
  354.                     // This is defined in the schema, but not in the class
  355.                     // for either query
  356.                 } else {
  357.                     throw new SAXException(
  358.                             "Property element without appropriate parent encountered.");
  359.                 }
  360.             } else if (XmlProductHandler.LINK_ELEMENT.equals(localName)) {
  361.                 if (pSummary != null) {
  362.                     // This link is occurring as part of a product summary
  363.                     String relation = XmlUtils.getAttribute(attributes, uri,
  364.                             XmlProductHandler.LINK_ATTRIBUTE_RELATION);
  365.                     Map<String, List<URI>> links = pSummary.getLinks();
  366.                     if (links.containsKey(relation))
  367.                         links.get(relation)
  368.                                 .add(URI.create(XmlUtils.getAttribute(
  369.                                         attributes, uri,
  370.                                         XmlProductHandler.LINK_ATTRIBUTE_HREF)));
  371.                     else {
  372.                         List<URI> newList = new ArrayList<URI>();
  373.                         newList.add(URI.create(XmlUtils.getAttribute(
  374.                                 attributes, uri,
  375.                                 XmlProductHandler.LINK_ATTRIBUTE_HREF)));
  376.                         links.put(relation, newList);
  377.                     }
  378.                 } else {
  379.                     throw new SAXException(
  380.                             "Link element without appropriate parent encountered.");
  381.                 }
  382.             } else if (XmlProductHandler.PRODUCT_ELEMENT.equals(localName)) {
  383.                 // We are starting to process a product and need to pass data
  384.                 // through
  385.                 // until that is complete.
  386.                 productHandler = new SearchResponseXmlProductSource(storage);
  387.                 productHandler.startElement(uri, localName, qName, attributes);
  388.             }
  389.         }
  390.     }

  391.     @Override
  392.     public void endElement(String uri, String localName, String qName)
  393.             throws SAXException {
  394.         if (productHandler != null) {
  395.             productHandler.endElement(uri, localName, qName);
  396.             if (XmlProductHandler.PRODUCT_ELEMENT.equals(localName)) {
  397.                 ProductDetailQuery pdQuery = (ProductDetailQuery) query;
  398.                 pdQuery.getResult().add(productHandler.getProduct());
  399.                 productHandler = null;
  400.             }
  401.         } else if (SearchXML.INDEXER_XMLNS.equals(uri)) {
  402.             if (SearchXML.RESPONSE_ELEMENT.equals(localName)) {
  403.                 // Do nothing, this is the end element of the document.
  404.             } else if (SearchXML.RESULT_ELEMENT.equals(localName)) {
  405.                 // One of the results has completed
  406.                 if (response == null)
  407.                     throw new SAXException(
  408.                             "result element found without response parent");
  409.                 else {
  410.                     response.addResult(query);
  411.                     query = null;
  412.                 }
  413.             } else if (SearchXML.QUERY_ELEMENT.equals(localName)) {
  414.                 // The query element of a result has completed
  415.                 // Because the ProductIndexQuery is part of the
  416.                 // query object controlled by the result element
  417.                 // nothing particular needs to be done other than
  418.                 // set the flag for being in the query element to false
  419.                 inQueryElement = false;
  420.             } else if (SearchXML.PRODUCT_SUMMARY_ELEMENT.equals(localName)) {
  421.                 if (inQueryElement) {
  422.                     // Nothing needs to be done because this was just used
  423.                     // to get the product ID into the list of IDs for the
  424.                     // query.
  425.                 } else {
  426.                     if (event != null) {
  427.                         // We're adding product summaries to events.
  428.                         Map<String, List<ProductSummary>> eventProducts = event
  429.                                 .getAllProducts();
  430.                         String productType = pSummary.getType();
  431.                         if (eventProducts.containsKey(productType)) {
  432.                             // Key exists, so just add the product summary to it
  433.                             eventProducts.get(productType).add(pSummary);
  434.                         } else {
  435.                             List<ProductSummary> newList = new ArrayList<ProductSummary>();
  436.                             newList.add(pSummary);
  437.                             eventProducts.put(productType, newList);
  438.                         }
  439.                     } else if (query != null
  440.                             && query.getType() == SearchMethod.PRODUCTS_SUMMARY) {
  441.                         // This was a product summary query and these are its
  442.                         // results
  443.                         ProductsSummaryQuery psQuery = (ProductsSummaryQuery) query;
  444.                         psQuery.getResult().add(pSummary);
  445.                     } else {
  446.                         throw new SAXException(
  447.                                 "productSummary element encountered without recognized parent");
  448.                     }
  449.                     pSummary = null;
  450.                 }
  451.             } else if (SearchXML.EVENT_ELEMENT.equals(localName)) {
  452.                 if (query != null
  453.                         && query.getType() == SearchMethod.EVENT_DETAIL) {
  454.                     // This was an event detail query and has opened properly
  455.                     EventDetailQuery edQuery = (EventDetailQuery) query;
  456.                     edQuery.getResult().add(event);
  457.                     event = null;
  458.                 } else {
  459.                     throw new SAXException(
  460.                             "event element encountered without recognized parent");
  461.                 }
  462.             } else if (SearchXML.EVENT_SUMMARY_ELEMENT.equals(localName)) {
  463.                 if (query != null
  464.                         && query.getType() == SearchMethod.EVENTS_SUMMARY) {
  465.                     // This was an event summary query and has opened properly
  466.                     EventsSummaryQuery esQuery = (EventsSummaryQuery) query;
  467.                     esQuery.getResult().add(eSummary);
  468.                     esQuery = null;
  469.                 } else {
  470.                     throw new SAXException(
  471.                             "eventSummary element encountered without recognized parent");
  472.                 }
  473.             } else if (SearchXML.ERROR_ELEMENT.equals(localName)) {
  474.                 inErrorElement = false;
  475.             }
  476.         } else if (XmlProductHandler.PRODUCT_XML_NAMESPACE.equals(uri)) {
  477.             if (XmlProductHandler.PROPERTY_ELEMENT.equals(localName)) {
  478.                 // Simple tag is handled when the start element is encountered
  479.             } else if (XmlProductHandler.LINK_ELEMENT.equals(localName)) {
  480.                 // Simple tag is handled when the start element is encountered
  481.             }
  482.         }
  483.     }

  484.     @Override
  485.     public void characters(char[] ch, int start, int length)
  486.             throws SAXException {
  487.         if (productHandler != null) {
  488.             // Pass through if product is being generated
  489.             productHandler.characters(ch, start, length);
  490.         } else if (inErrorElement) {
  491.             query.setError(new String(ch));
  492.         }
  493.     }

  494. }