/** {@inheritDoc} */
  @Override
  public Object invoke(final Object proxy, final Method method, final Object[] args)
      throws Throwable {
    unwrapArgs(method.getParameterTypes(), args);
    if (!mixinHandlers.isEmpty()) {
      MethodSignature methodSignature = MethodSignature.forMethod(method);
      if (mixinHandlers.containsKey(methodSignature)) {
        return mixinHandlers.get(methodSignature).invoke(proxy, method, args);
      }
    }

    final InvocationHandler invocationHandler = handlers.get(MethodSignature.forMethod(method));
    if (invocationHandler != null) {
      try {
        return invocationHandler.invoke(proxy, method, args);
      } catch (XPathExpressionException e) {
        throw new XBPathException(e, method, "??");
      }
    }

    throw new IllegalArgumentException(
        "I don't known how to invoke method "
            + method
            + ". Did you forget to add a XB*-annotation or to register a mixin?");
  }
  private Map<MethodSignature, InvocationHandler> getDefaultInvokers(
      final Object defaultInvokerObject) {
    final ReflectionInvoker reflectionInvoker = new ReflectionInvoker(defaultInvokerObject);
    final Map<MethodSignature, InvocationHandler> invokers =
        new HashMap<MethodSignature, InvocationHandler>();
    for (Method m : DOMAccess.class.getMethods()) {
      if (m.getAnnotation(XBWrite.class) == null) {
        invokers.put(MethodSignature.forMethod(m), reflectionInvoker);
      }
    }

    invokers.put(MethodSignature.forVoidMethod("toString"), reflectionInvoker);
    invokers.put(MethodSignature.forSingleParam("equals", Object.class), reflectionInvoker);
    invokers.put(MethodSignature.forVoidMethod("hashCode"), reflectionInvoker);
    return invokers; // Collections.unmodifiableMap(invokers);
  }
  ProjectionInvocationHandler(
      final XBProjector projector,
      final Node node,
      final Class<?> projectionInterface,
      final Map<Class<?>, Object> mixins,
      final boolean toStringRendersXML,
      final boolean absentIsEmpty) {
    final Object defaultInvokerObject =
        DefaultDOMAccessInvoker.create(projectionInterface, node, projector, toStringRendersXML);
    final Map<MethodSignature, InvocationHandler> defaultInvocationHandlers =
        getDefaultInvokers(defaultInvokerObject);

    for (Entry<Class<?>, Object> e : mixins.entrySet()) {
      for (Method m : e.getKey().getMethods()) {
        mixinHandlers.put(
            MethodSignature.forMethod(m), new MixinInvoker(e.getValue(), projectionInterface));
      }
    }

    handlers.putAll(defaultInvocationHandlers);

    List<Class<?>> allSuperInterfaces =
        ReflectionHelper.findAllSuperInterfaces(projectionInterface);
    for (Class<?> i7e : allSuperInterfaces) {
      for (Method m : i7e.getDeclaredMethods()) {
        if (Modifier.isPrivate(m.getModifiers())) {
          // ignore private methods
          continue;
        }
        final MethodSignature methodSignature = MethodSignature.forMethod(m);
        if (ReflectionHelper.isDefaultMethod(m)) {
          handlers.put(methodSignature, DEFAULT_METHOD_INVOCATION_HANDLER);
          final XBOverride xbOverride = m.getAnnotation(XBOverride.class);
          if (xbOverride != null) {
            handlers.put(
                methodSignature.overridenBy(xbOverride.value()),
                new OverrideByDefaultMethodInvocationHandler(m));
          }
          continue;
        }
        if (defaultInvocationHandlers.containsKey(methodSignature)) {
          continue;
        }
        {
          final XBRead readAnnotation = m.getAnnotation(XBRead.class);
          if (readAnnotation != null) {
            handlers.put(
                methodSignature,
                new ReadInvocationHandler(
                    node, m, readAnnotation.value(), projector, absentIsEmpty));
            continue;
          }
        }
        {
          final XBUpdate updateAnnotation = m.getAnnotation(XBUpdate.class);
          if (updateAnnotation != null) {
            handlers.put(
                methodSignature,
                new UpdateInvocationHandler(node, m, updateAnnotation.value(), projector));
            continue;
          }
        }
        {
          final XBWrite writeAnnotation = m.getAnnotation(XBWrite.class);
          if (writeAnnotation != null) {
            handlers.put(
                methodSignature,
                new WriteInvocationHandler(node, m, writeAnnotation.value(), projector));
            continue;
          }
        }
        {
          final XBDelete delAnnotation = m.getAnnotation(XBDelete.class);
          if (delAnnotation != null) {
            handlers.put(
                methodSignature,
                new DeleteInvocationHandler(node, m, delAnnotation.value(), projector));
            continue;
          }
        }

        if (mixinHandlers.containsKey(methodSignature)) {
          continue;
        }

        throw new IllegalArgumentException(
            "I don't known how to handle method "
                + m
                + ". Did you forget to add a XB*-annotation or to register a mixin?");
      }
    }
  }