/**
   * Convert an XPath value to an object in this object model. If the supplied value can be
   * converted to an object in this model, of the specified class, then the conversion should be
   * done and the resulting object returned. If the value cannot be converted, the method should
   * return null. Note that the supplied class might be a List, in which case the method should
   * inspect the contents of the Value to see whether they belong to this object model.
   *
   * @throws XPathException if the target class is explicitly associated with this object model, but
   *     the supplied value cannot be converted to the appropriate class
   */
  public Object convertXPathValueToObject(Value value, Class target, XPathContext context)
      throws XPathException {
    // We accept the object if (a) the target class is Node, Node[], or NodeList,
    // or (b) the supplied object is a node, or sequence of nodes, that wrap DOM nodes,
    // provided that the target class is Object or a collection class
    boolean requireDOM =
        (Node.class.isAssignableFrom(target)
            || (target == NodeList.class)
            || (target.isArray() && Node.class.isAssignableFrom(target.getComponentType())));

    // Note: we allow the declared type of the method argument to be a subclass of Node. If the
    // actual
    // node supplied is the wrong kind of node, this will result in a Java exception.

    boolean allowDOM =
        (target == Object.class
            || target.isAssignableFrom(ArrayList.class)
            || target.isAssignableFrom(HashSet.class)
            || (target.isArray() && target.getComponentType() == Object.class));
    if (!(requireDOM || allowDOM)) {
      return null;
    }
    List nodes = new ArrayList(20);

    SequenceIterator iter = value.iterate(context);
    while (true) {
      Item item = iter.next();
      if (item == null) {
        break;
      }
      if (item instanceof VirtualNode) {
        Object o = ((VirtualNode) item).getUnderlyingNode();
        if (o instanceof Node) {
          nodes.add(o);
        } else {
          if (requireDOM) {
            DynamicError err =
                new DynamicError(
                    "Extension function required class "
                        + target.getName()
                        + "; supplied value of class "
                        + item.getClass().getName()
                        + " could not be converted");
            throw err;
          }
          ;
        }
      } else if (requireDOM) {
        if (item instanceof NodeInfo) {
          nodes.add(NodeOverNodeInfo.wrap((NodeInfo) item));
        } else {
          DynamicError err =
              new DynamicError(
                  "Extension function required class "
                      + target.getName()
                      + "; supplied value of class "
                      + item.getClass().getName()
                      + " could not be converted");
          throw err;
        }
      } else {
        return null; // DOM Nodes are not actually required; let someone else try the conversion
      }
    }

    if (nodes.size() == 0 && !requireDOM) {
      return null; // empty sequence supplied - try a different mapping
    }
    if (Node.class.isAssignableFrom(target)) {
      if (nodes.size() != 1) {
        DynamicError err =
            new DynamicError(
                "Extension function requires a single DOM Node"
                    + "; supplied value contains "
                    + nodes.size()
                    + " nodes");
        throw err;
      }
      return nodes.get(0);
      // could fail if the node is of the wrong kind
    } else if (target == NodeList.class) {
      return new DOMNodeList(nodes);
    } else if (target.isArray() && target.getComponentType() == Node.class) {
      Node[] array = new Node[nodes.size()];
      nodes.toArray(array);
      return array;
    } else if (target.isAssignableFrom(ArrayList.class)) {
      return nodes;
    } else if (target.isAssignableFrom(HashSet.class)) {
      return new HashSet(nodes);
    } else {
      // after all this work, give up
      return null;
    }
  }