IIndex.java

  1. /*
  2.  * Portions of this software was developed by employees of the National Institute
  3.  * of Standards and Technology (NIST), an agency of the Federal Government and is
  4.  * being made available as a public service. Pursuant to title 17 United States
  5.  * Code Section 105, works of NIST employees are not subject to copyright
  6.  * protection in the United States. This software may be subject to foreign
  7.  * copyright. Permission in the United States and in foreign countries, to the
  8.  * extent that NIST may hold copyright, to use, copy, modify, create derivative
  9.  * works, and distribute this software and its documentation without fee is hereby
  10.  * granted on a non-exclusive basis, provided that this notice and disclaimer
  11.  * of warranty appears in all copies.
  12.  *
  13.  * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
  14.  * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
  15.  * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
  16.  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
  17.  * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
  18.  * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
  19.  * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
  20.  * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
  21.  * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
  22.  * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
  23.  * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
  24.  * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
  25.  */

  26. package gov.nist.secauto.metaschema.core.model.constraint;

  27. import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
  28. import gov.nist.secauto.metaschema.core.metapath.InvalidTypeMetapathException;
  29. import gov.nist.secauto.metaschema.core.metapath.MetapathException;
  30. import gov.nist.secauto.metaschema.core.metapath.MetapathExpression;
  31. import gov.nist.secauto.metaschema.core.metapath.MetapathExpression.ResultType;
  32. import gov.nist.secauto.metaschema.core.metapath.function.library.FnData;
  33. import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
  34. import gov.nist.secauto.metaschema.core.util.CollectionUtil;
  35. import gov.nist.secauto.metaschema.core.util.ObjectUtils;

  36. import java.util.ArrayList;
  37. import java.util.List;
  38. import java.util.regex.Matcher;
  39. import java.util.regex.Pattern;
  40. import java.util.stream.Collectors;

  41. import edu.umd.cs.findbugs.annotations.NonNull;
  42. import edu.umd.cs.findbugs.annotations.Nullable;

  43. public interface IIndex {

  44.   /**
  45.    * Construct a new index using the provided key field components to generate
  46.    * keys.
  47.    *
  48.    * @param keyFields
  49.    *          the key field components to use to generate keys by default
  50.    * @return the new index
  51.    */
  52.   @NonNull
  53.   static IIndex newInstance(@NonNull List<? extends IKeyField> keyFields) {
  54.     return new DefaultIndex(keyFields);
  55.   }

  56.   /**
  57.    * Check if a key contains information other than {@code null} Strings.
  58.    *
  59.    * @param key
  60.    *          the key to check
  61.    * @return {@code true} if the series of key values contains only {@code null}
  62.    *         values, or {@code false} otherwise
  63.    */
  64.   static boolean isAllNulls(@NonNull Iterable<String> key) {
  65.     for (String value : key) {
  66.       if (value != null) {
  67.         return false; // NOPMD readability
  68.       }
  69.     }
  70.     return true;
  71.   }

  72.   /**
  73.    * Retrieve the key field components used to generate a key for this index.
  74.    *
  75.    * @return the key field components
  76.    */
  77.   @NonNull
  78.   List<IKeyField> getKeyFields();

  79.   /**
  80.    * Store the provided item in the index using the index's key field components
  81.    * to generate the key.
  82.    *
  83.    * @param item
  84.    *          the item to store in the index
  85.    * @param dynamicContext
  86.    *          the Metapath evaluation context
  87.    * @return the previous item stored in the index, or {@code null} otherwise
  88.    */
  89.   @Nullable
  90.   default INodeItem put(@NonNull INodeItem item, @NonNull DynamicContext dynamicContext) {
  91.     List<String> key = toKey(item, getKeyFields(), dynamicContext);
  92.     return put(item, key);
  93.   }

  94.   /**
  95.    * Store the provided item using the provided key.
  96.    *
  97.    * @param item
  98.    *          the item to store
  99.    * @param key
  100.    *          the key to store the item with
  101.    * @return the previous item stored in the index using the key, or {@code null}
  102.    *         otherwise
  103.    */
  104.   @Nullable
  105.   INodeItem put(@NonNull INodeItem item, @NonNull List<String> key);

  106.   /**
  107.    * Retrieve the item from the index that matches the key generated by evaluating
  108.    * the index's default key field components against the provided item.
  109.    *
  110.    * @param item
  111.    *          the item to store in the index
  112.    * @param dynamicContext
  113.    *          the Metapath evaluation context
  114.    * @return the previous item stored in the index, or {@code null} otherwise
  115.    */
  116.   @Nullable
  117.   default INodeItem get(@NonNull INodeItem item, @NonNull DynamicContext dynamicContext) {
  118.     List<String> key = toKey(item, getKeyFields(), dynamicContext);
  119.     return get(key);
  120.   }

  121.   /**
  122.    * Retrieve the item from the index that matches the provided key.
  123.    *
  124.    * @param key
  125.    *          the key to use for lookup
  126.    * @return the item with the matching key or {@code null} if no matching item
  127.    *         was found
  128.    */
  129.   INodeItem get(List<String> key);

  130.   /**
  131.    * Construct a key by evaluating the provided key field components against the
  132.    * provided item.
  133.    *
  134.    * @param item
  135.    *          the item to generate the key from
  136.    * @param keyFields
  137.    *          the key field components used to generate the key
  138.    * @param dynamicContext
  139.    *          the Metapath evaluation context
  140.    * @return a new key
  141.    */
  142.   @NonNull
  143.   static List<String> toKey(@NonNull INodeItem item, @NonNull List<? extends IKeyField> keyFields,
  144.       @NonNull DynamicContext dynamicContext) {
  145.     return CollectionUtil.unmodifiableList(
  146.         ObjectUtils.notNull(keyFields.stream()
  147.             .map(keyField -> {
  148.               assert keyField != null;
  149.               return buildKeyItem(item, keyField, dynamicContext);
  150.             })
  151.             .collect(Collectors.toCollection(ArrayList::new))));
  152.   }

  153.   /**
  154.    * Evaluates the provided key field component against the item to generate a key
  155.    * value.
  156.    *
  157.    * @param item
  158.    *          the item to generate the key value from
  159.    * @param keyField
  160.    *          the key field component used to generate the key value
  161.    * @param dynamicContext
  162.    *          the Metapath evaluation context
  163.    * @return the key value or {@code null} if the evaluation resulted in no value
  164.    */
  165.   @Nullable
  166.   private static String buildKeyItem(
  167.       @NonNull INodeItem item,
  168.       @NonNull IKeyField keyField,
  169.       @NonNull DynamicContext dynamicContext) {
  170.     MetapathExpression keyPath = keyField.getTarget();

  171.     INodeItem keyItem;
  172.     try {
  173.       keyItem = keyPath.evaluateAs(item, ResultType.NODE, dynamicContext);
  174.     } catch (InvalidTypeMetapathException ex) {
  175.       throw new MetapathException("Key path did not result in a single node", ex);
  176.     }

  177.     String keyValue = null;
  178.     if (keyItem != null) {
  179.       keyValue = FnData.fnDataItem(keyItem).asString();
  180.       Pattern pattern = keyField.getPattern();
  181.       if (pattern != null) {
  182.         keyValue = applyPattern(keyItem, keyValue, pattern);
  183.       }
  184.     } // empty key
  185.     return keyValue;
  186.   }

  187.   /**
  188.    * Apply the key value pattern, if configured, to generate the final key value.
  189.    *
  190.    * @param keyItem
  191.    *          the node item used to form the key field
  192.    * @param pattern
  193.    *          the key field pattern configuration from the constraint
  194.    * @param keyValue
  195.    *          the current key value
  196.    * @return the final key value
  197.    */
  198.   private static String applyPattern(@NonNull INodeItem keyItem, @NonNull String keyValue,
  199.       @NonNull Pattern pattern) {
  200.     Matcher matcher = pattern.matcher(keyValue);
  201.     if (!matcher.matches()) {
  202.       throw new MetapathException(
  203.           String.format("Key field declares the pattern '%s' which does not match the value '%s' of node '%s'",
  204.               pattern.pattern(), keyValue, keyItem.getMetapath()));
  205.     }

  206.     if (matcher.groupCount() != 1) {
  207.       throw new MetapathException(String.format(
  208.           "The first group was not a match for value '%s' of node '%s' for key field pattern '%s'",
  209.           keyValue, keyItem.getMetapath(), pattern.pattern()));
  210.     }
  211.     return matcher.group(1);
  212.   }
  213. }