private Object handeRootElementReplacement(
     final Object proxy, final Method method, final Document document, final Object valueToSet) {
   int count = document.getDocumentElement() == null ? 0 : 1;
   if (valueToSet == null) {
     DOMHelper.setDocumentElement(document, null);
     return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count));
   }
   if (valueToSet instanceof Element) {
     Element clone = (Element) ((Element) valueToSet).cloneNode(true);
     document.adoptNode(clone);
     if (document.getDocumentElement() == null) {
       document.appendChild(clone);
       return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
     }
     document.replaceChild(document.getDocumentElement(), clone);
     return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
   }
   if (!(valueToSet instanceof DOMAccess)) {
     throw new IllegalArgumentException(
         "Method "
             + method
             + " was invoked as setter changing the document root element. Expected value type was a projection so I can determine a element name. But you provided a "
             + valueToSet);
   }
   DOMAccess projection = (DOMAccess) valueToSet;
   Element element = projection.getDOMBaseElement();
   assert element != null;
   DOMHelper.setDocumentElement(document, element);
   return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count));
 }
    /**
     * @param typeToSet
     * @param iterable
     * @param parentElement
     * @param duplexExpression
     * @param elementSelector
     */
    private int applyIterableSetOnElement(
        final Iterable<?> iterable,
        final Element parentElement,
        final DuplexExpression duplexExpression) {
      int changeCount = 0;
      for (Object o : iterable) {
        if (o == null) {
          continue;
        }
        if (!isStructureChangingValue(o)) {
          final Node newElement = duplexExpression.createChildWithPredicate(parentElement);
          final String asString =
              projector
                  .config()
                  .getStringRenderer()
                  .render(o.getClass(), o, duplexExpression.getExpressionFormatPattern());
          newElement.setTextContent(asString);
          ++changeCount;
          continue;
        }
        Element elementToAdd;

        if (o instanceof Node) {
          final Node n = (Node) o;
          elementToAdd =
              (Element)
                  (Node.DOCUMENT_NODE != n.getNodeType()
                      ? n
                      : n.getOwnerDocument() == null
                          ? null
                          : n.getOwnerDocument().getDocumentElement());
        } else {
          final DOMAccess p = (DOMAccess) o;
          elementToAdd = p.getDOMBaseElement();
        }
        if (elementToAdd == null) {
          continue;
        }

        Element clone = (Element) elementToAdd.cloneNode(true);
        Element childWithPredicate =
            (Element) duplexExpression.createChildWithPredicate(parentElement);
        final String elementName = childWithPredicate.getNodeName();
        if (!elementName.equals(clone.getNodeName())) {
          if (!"*".equals(elementName)) {
            clone = DOMHelper.renameElement(clone, elementName);
          }
        }
        DOMHelper.replaceElement(childWithPredicate, clone);
        ++changeCount;
      }
      return changeCount;
    }
    @Override
    public Object invokeXpathProjection(
        final InvocationContext invocationContext, final Object proxy, final Object[] args)
        throws Throwable {
      assert ReflectionHelper.hasParameters(method);
      final Node node = getNodeForMethod(method, args);
      //            final Document document = DOMHelper.getOwnerDocumentFor(node);
      //            final XPath xPath = projector.config().createXPath(document);
      final XPathExpression expression = invocationContext.getxPathExpression();

      final Object valueToSet = args[findIndexOfValue];
      //      final Class<?> typeToSet = method.getParameterTypes()[findIndexOfValue];
      //     final boolean isMultiValue = isMultiValue(typeToSet);
      NodeList nodes = (NodeList) expression.evaluate(node, XPathConstants.NODESET);
      final int count = nodes.getLength();
      for (int i = 0; i < count; ++i) {
        final Node n = nodes.item(i);
        if (n == null) {
          continue;
        }
        if (Node.ATTRIBUTE_NODE == n.getNodeType()) {
          Element e = ((Attr) n).getOwnerElement();
          if (e == null) {
            continue;
          }
          DOMHelper.setOrRemoveAttribute(
              e, n.getNodeName(), valueToSet == null ? null : valueToSet.toString());
          continue;
        }
        if (valueToSet instanceof Element) {
          if (!(n instanceof Element)) {
            throw new IllegalArgumentException(
                "XPath for element update need to select elements only");
          }
          DOMHelper.replaceElement((Element) n, (Element) ((Element) valueToSet).cloneNode(true));
          continue;
        }
        n.setTextContent(valueToSet == null ? null : valueToSet.toString());
      }
      return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count));
    }
    @Override
    protected final Object invokeProjection(
        final String resolvedXpath, final Object proxy, final Object[] args) throws Throwable {
      final XPath xPath = projector.config().createXPath(DOMHelper.getOwnerDocumentFor(node));

      if (!lastInvocationContext.isStillValid(resolvedXpath)) {
        final DuplexExpression duplexExpression =
            new DuplexXPathParser(projector.config().getUserDefinedNamespaceMapping())
                .compile(resolvedXpath);
        String strippedXPath = duplexExpression.getExpressionAsStringWithoutFormatPatterns();
        MethodParamVariableResolver resolver = null;
        if (duplexExpression.isUsingVariables()) {
          XPathVariableResolver peviousResolver = xPath.getXPathVariableResolver();
          resolver =
              new MethodParamVariableResolver(
                  method,
                  args,
                  duplexExpression,
                  projector.config().getStringRenderer(),
                  peviousResolver);
          xPath.setXPathVariableResolver(resolver);
        }
        final XPathExpression xPathExpression = xPath.compile(strippedXPath);
        final Class<?> targetComponentType = findTargetComponentType(method);
        lastInvocationContext =
            new InvocationContext(
                resolvedXpath,
                xPath,
                xPathExpression,
                duplexExpression,
                resolver,
                targetComponentType,
                projector);
      }
      lastInvocationContext.updateMethodArgs(args);
      return invokeXpathProjection(lastInvocationContext, proxy, args);
    }
    @Override
    public Object invokeProjection(
        final String resolvedXpath, final Object proxy, final Object[] args) throws Throwable {
      //   final String pathToElement = resolvedXpath.replaceAll("\\[@",
      // "[attribute::").replaceAll("/?@.*", "").replaceAll("\\[attribute::", "[@");
      lastInvocationContext.updateMethodArgs(args);
      final Document document = DOMHelper.getOwnerDocumentFor(node);
      assert document != null;
      final Object valueToSet = args[findIndexOfValue];
      final boolean isMultiValue = isMultiValue(method.getParameterTypes()[findIndexOfValue]);
      // ROOT element update
      if ("/*".equals(resolvedXpath)) { // Setting a new root element.
        if (isMultiValue) {
          throw new IllegalArgumentException(
              "Method "
                  + method
                  + " was invoked as setter changing the document root element, but tries to set multiple values.");
        }
        return handeRootElementReplacement(proxy, method, document, valueToSet);
      }
      final boolean wildCardTarget = resolvedXpath.endsWith("/*");
      try {
        if (!lastInvocationContext.isStillValid(resolvedXpath)) {
          final DuplexExpression duplexExpression =
              wildCardTarget
                  ? new DuplexXPathParser(projector.config().getUserDefinedNamespaceMapping())
                      .compile(resolvedXpath.substring(0, resolvedXpath.length() - 2))
                  : new DuplexXPathParser(projector.config().getUserDefinedNamespaceMapping())
                      .compile(resolvedXpath);
          MethodParamVariableResolver resolver = null;
          if (duplexExpression.isUsingVariables()) {
            resolver =
                new MethodParamVariableResolver(
                    method, args, duplexExpression, projector.config().getStringRenderer(), null);
            duplexExpression.setXPathVariableResolver(resolver);
          }
          Class<?> targetComponentType = findTargetComponentType(method);
          lastInvocationContext =
              new InvocationContext(
                  resolvedXpath,
                  null,
                  null,
                  duplexExpression,
                  resolver,
                  targetComponentType,
                  projector);
        }
        final DuplexExpression duplexExpression = lastInvocationContext.getDuplexExpression();
        if (duplexExpression.getExpressionType().isMustEvalAsString()) {
          throw new XBPathException("Unwriteable xpath selector used ", method, resolvedXpath);
        }
        // MULTIVALUE
        if (isMultiValue) {
          if (duplexExpression.getExpressionType().equals(ExpressionType.ATTRIBUTE)) {
            throw new IllegalArgumentException(
                "Method "
                    + method
                    + " was invoked as setter changing some attribute, but was declared to set multiple values. I can not create multiple attributes for one path.");
          }
          final Iterable<?> iterable2Set =
              valueToSet == null
                  ? Collections.emptyList()
                  : (valueToSet.getClass().isArray())
                      ? ReflectionHelper.array2ObjectList(valueToSet)
                      : (Iterable<?>) valueToSet;
          if (wildCardTarget) {
            // TODO: check support of ParameterizedType e.g. Supplier
            final Element parentElement = (Element) duplexExpression.ensureExistence(node);
            DOMHelper.removeAllChildren(parentElement);
            int count = 0;
            for (Object o : iterable2Set) {
              if (o == null) {
                continue;
              }
              ++count;
              if (o instanceof Node) {
                DOMHelper.appendClone(parentElement, (Node) o);
                continue;
              }
              if (o instanceof DOMAccess) {
                DOMHelper.appendClone(parentElement, ((DOMAccess) o).getDOMBaseElement());
                continue;
              }
              throw new XBPathException(
                  "When using a wildcard target, the type to set must be a DOM Node or another projection. Otherwise I can not determine the element name.",
                  method,
                  resolvedXpath);
            }
            return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count));
          }
          final Element parentElement = duplexExpression.ensureParentExistence(node);
          duplexExpression.deleteAllMatchingChildren(parentElement);
          int count = applyIterableSetOnElement(iterable2Set, parentElement, duplexExpression);
          return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(count));
        }

        // ATTRIBUTES
        if (duplexExpression.getExpressionType().equals(ExpressionType.ATTRIBUTE)) {
          if (wildCardTarget) {
            // TODO: This may never happen, right?
            throw new XBPathException(
                "Wildcards are not allowed when writing to an attribute. I need to know to which Element I should set the attribute",
                method,
                resolvedXpath);
          }
          Attr attribute = (Attr) duplexExpression.ensureExistence(node);
          if (valueToSet == null) {
            attribute.getOwnerElement().removeAttributeNode(attribute);
            return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
          }

          DOMHelper.setStringValue(attribute, valueToSet.toString());
          return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
        }

        if ((valueToSet instanceof Node) || (valueToSet instanceof DOMAccess)) {
          if (valueToSet instanceof Attr) {
            if (wildCardTarget) {
              throw new XBPathException(
                  "Wildcards are not allowed when writing an attribute. I need to know to which Element I should set the attribute",
                  method,
                  resolvedXpath);
            }
            Element parentNode = duplexExpression.ensureParentExistence(node);
            if (((Attr) valueToSet).getNamespaceURI() != null) {
              parentNode.setAttributeNodeNS((Attr) valueToSet);
              return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
            }
            parentNode.setAttributeNode((Attr) valueToSet);
            return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
          }
          final Element newNodeOrigin =
              valueToSet instanceof DOMAccess
                  ? ((DOMAccess) valueToSet).getDOMBaseElement()
                  : (Element) valueToSet;
          final Element newNode = (Element) newNodeOrigin.cloneNode(true);
          DOMHelper.ensureOwnership(document, newNode);
          if (wildCardTarget) {
            Element parentElement = (Element) duplexExpression.ensureExistence(node);
            DOMHelper.removeAllChildren(parentElement);
            parentElement.appendChild(newNode);
            return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
          }
          Element previousElement = (Element) duplexExpression.ensureExistence(node);

          DOMHelper.replaceElement(previousElement, newNode);
          return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
        }

        final Element elementToChange = (Element) duplexExpression.ensureExistence(node);
        if (valueToSet == null) {
          // TODO: This should depend on the parameter type?
          // If param type == String, no structural change might be expected.
          DOMHelper.removeAllChildren(elementToChange);
        } else {
          final String asString =
              projector
                  .config()
                  .getStringRenderer()
                  .render(
                      valueToSet.getClass(),
                      valueToSet,
                      duplexExpression.getExpressionFormatPattern());
          elementToChange.setTextContent(asString);
        }
        return getProxyReturnValueForMethod(proxy, method, Integer.valueOf(1));
      } catch (XBPathParsingException e) {
        throw new XBPathException(e, method, resolvedXpath);
      }
    }