/**
   * Process this instruction
   *
   * @param context the dynamic context of the transformation
   * @return a TailCall to be executed by the caller, always null for this instruction
   */
  public TailCall processLeavingTail(XPathContext context) throws XPathException {
    Controller controller = context.getController();
    SequenceReceiver out = context.getReceiver();
    int opt = options;
    int ann = annotation;

    // we may need to change the namespace prefix if the one we chose is
    // already in use with a different namespace URI: this is done behind the scenes
    // by the Outputter

    CharSequence value = expandChildren(context);
    if (schemaType != null) {
      // test whether the value actually conforms to the given type
      XPathException err =
          schemaType.validateContent(
              value,
              DummyNamespaceResolver.getInstance(),
              context.getConfiguration().getNameChecker());
      if (err != null) {
        ValidationException verr =
            new ValidationException(
                "Attribute value "
                    + Err.wrap(value, Err.VALUE)
                    + " does not the match the required type "
                    + schemaType.getDescription()
                    + ". "
                    + err.getMessage());
        verr.setErrorCode("XTTE1540");
        verr.setLocator(this);
        throw verr;
      }
    } else if (validationAction == Validation.STRICT || validationAction == Validation.LAX) {
      try {
        ann = controller.getConfiguration().validateAttribute(nameCode, value, validationAction);
      } catch (ValidationException e) {
        DynamicError err = DynamicError.makeDynamicError(e);
        String errorCode = e.getErrorCodeLocalPart();
        if (errorCode == null) {
          errorCode = (validationAction == Validation.STRICT ? "XTTE1510" : "XTTE1515");
        }
        err.setErrorCode(errorCode);
        err.setXPathContext(context);
        err.setLocator(this);
        err.setIsTypeError(true);
        throw err;
      }
    }
    try {
      out.attribute(nameCode, ann, value, locationId, opt);
    } catch (XPathException err) {
      throw dynamicError(this, err, context);
    }

    return null;
  }
  /** Enumerate the results of the expression */
  public SequenceIterator iterate(XPathContext context) throws XPathException {

    Controller controller = context.getController();

    Item arg2;
    try {
      arg2 = argument[2].evaluateItem(context);
    } catch (XPathException e) {
      if ("XPDY0002".equals(e.getErrorCodeLocalPart())) {
        dynamicError(
            "Cannot call the key() function when there is no context item", "XTDE1270", context);
        return null;
      } else if ("XPDY0050".equals(e.getErrorCodeLocalPart())) {
        dynamicError(
            "In the key() function,"
                + " the node supplied in the third argument (or the context node if absent)"
                + " must be in a tree whose root is a document node",
            "XTDE1270",
            context);
        return null;
      } else if ("XPTY0020".equals(e.getErrorCodeLocalPart())) {
        dynamicError(
            "Cannot call the key() function when the context item is an atomic value",
            "XTDE1270",
            context);
        return null;
      }
      throw e;
    }

    NodeInfo origin = (NodeInfo) arg2;
    NodeInfo root = origin.getRoot();
    if (root.getNodeKind() != Type.DOCUMENT) {
      dynamicError(
          "In the key() function,"
              + " the node supplied in the third argument (or the context node if absent)"
              + " must be in a tree whose root is a document node",
          "XTDE1270",
          context);
      return null;
    }
    DocumentInfo doc = (DocumentInfo) root;

    int fprint = keyFingerprint;
    if (fprint == -1) {
      String givenkeyname = argument[0].evaluateItem(context).getStringValue();
      try {
        fprint =
            controller
                    .getNamePool()
                    .allocateLexicalQName(
                        givenkeyname,
                        false,
                        nsContext,
                        controller.getConfiguration().getNameChecker())
                & NamePool.FP_MASK;
      } catch (XPathException err) {
        dynamicError("Invalid key name: " + err.getMessage(), "XTDE1260", context);
      }
      if (fprint == -1) {
        dynamicError("Key '" + givenkeyname + "' has not been defined", "XTDE1260", context);
        return null;
      }
    }

    //        if (internal) {
    //            System.err.println("Using key " + fprint + " on doc " + doc);
    //        }

    // If the second argument is a singleton, we evaluate the function
    // directly; otherwise we recurse to evaluate it once for each Item
    // in the sequence.

    Expression expression = argument[1];
    SequenceIterator allResults;
    if (Cardinality.allowsMany(expression.getCardinality())) {
      final XPathContext keyContext = context;
      final DocumentInfo document = doc;
      final KeyManager keyManager = controller.getKeyManager();
      MappingFunction map =
          new MappingFunction() {
            // Map a value to the sequence of nodes having that value as a key value
            public Object map(Item item) throws XPathException {
              return keyManager.selectByKey(
                  keyFingerprint, document, (AtomicValue) item, keyContext);
            }
          };

      SequenceIterator keys = argument[1].iterate(context);
      SequenceIterator allValues = new MappingIterator(keys, map);
      allResults = new DocumentOrderIterator(allValues, LocalOrderComparer.getInstance());
    } else {
      try {
        AtomicValue keyValue = (AtomicValue) argument[1].evaluateItem(context);
        if (keyValue == null) {
          return EmptyIterator.getInstance();
        }
        KeyManager keyManager = controller.getKeyManager();
        allResults = keyManager.selectByKey(fprint, doc, keyValue, context);
      } catch (XPathException e) {
        if (e.getLocator() == null) {
          e.setLocator(this);
        }
        throw e;
      }
    }
    if (origin == doc) {
      return allResults;
    }
    SubtreeFilter filter = new SubtreeFilter();
    filter.origin = origin;
    return new ItemMappingIterator(allResults, filter);
  }