/** Receive notification of the end of an element. */
  @Override
  public void endElement(String uri, String l, String q) {
    /*
     * 1. If current element is a String, update its value from the string buffer.
     * 2. Add the element to parent.
     */
    ElementInfo element = _stack.remove(_stack.size() - 1);
    _logger.fine("endElement " + element);
    if (element.type == null) {
      _logger.warning("Element " + element.name + " not created ");
      return;
    } else if (_chars.length() > 0) {
      try {
        injectProperty(element.data, String.class, _chars.toString(), null, null);
      } catch (Exception x) {
        if (!_lenient) {
          throw new BeanAssemblyException(
              "Failed to set characters to object " + element.type.getName(), x);
        } else {
          _logger.warning("Failed to set characters to parent " + element.data);
        }
      }
    }
    _chars.setLength(0);
    _logger.fine(
        "<<ElementInfo: "
            + element.type.getName()
            + " in "
            + element
            + "\n    @as is "
            + element.inst.get("@as")
            + "\n    @id is "
            + element.inst.get("@id"));

    if (List.class.isAssignableFrom(element.data.getClass()) && element.name.endsWith("...")) {
      List<?> list = (List<?>) element.data;
      Object array = Array.newInstance(element.type, list.size());
      for (int i = 0; i < list.size(); ++i) {
        Array.set(array, i, list.get(i));
      }
      element.data = array;
    }

    String id = element.inst.get("@id");
    if (id != null) {
      // locally stored object - not added to the parent
      _local.put(id, element);
    } else if (!_stack.isEmpty()) {
      // inject into the parent as a property
      ElementInfo parent = _stack.get(_stack.size() - 1);
      _logger.fine("Parent is " + parent.data.getClass().getName());
      try {
        String as = element.inst.get("@as");
        if (as != null) {
          injectProperty(
              parent.data,
              element.type,
              element.data,
              Strings.toCamelCase(as, '-', false),
              element.args.complete());
        } else {
          injectProperty(parent.data, element.type, element.data, null, element.args.complete());
        }
      } catch (Exception x) {
        if (!_lenient) {
          throw new BeanAssemblyException(
              "Failed to set value " + element.data + " to parent " + parent.data, x);
        } else {
          _logger.log(
              Level.WARNING,
              "Failed to set value " + element.data + " to parent " + parent.data,
              x);
        }
      }
    }
    _top = element.data;
  }
  /** Receive notification of the start of an element. */
  @Override
  public void startElement(String uri, String l, String q, Attributes a) {
    /*
     * 1. Load a class that matches the element name.
     * 2. If no class found, assume the element maps to a String.
     * 3. Otherwise, construct a new object of the class with element attributes.
     */
    _logger.fine(
        S.fine(_logger)
            ? "Consider element " + l + "\n             uri " + uri + "\n               q " + q
            : null);
    ElementInfo info = new ElementInfo();

    // Record java packages defined on this element as xmlns
    for (int i = 0; i < a.getLength(); ++i) {
      _logger.fine(
          S.fine(_logger)
              ? "            attr "
                  + a.getQName(i)
                  + "="
                  + a.getValue(i)
                  + "\n                 "
                  + a.getQName(i)
                  + ":"
                  + a.getURI(i)
              : null);
      if (a.getQName(i).startsWith("xmlns:") && a.getValue(i).startsWith("java://")) {
        info.pkgs.put(a.getQName(i).substring(6), a.getValue(i).substring(7));
      }
    }

    // Resolve the package name of this element, which could be empty (default package)
    int colon = q.indexOf(':');
    if (colon > 0) {
      String xmlns = q.substring(0, colon);
      // is it defined right here?
      info.jpkg = info.pkgs.get(xmlns);
      // find a matching namespace from ancesters
      if (info.jpkg == null && !_stack.isEmpty()) {
        for (int i = _stack.size() - 1; i >= 0; --i) {
          info.jpkg = _stack.get(i).pkgs.get(xmlns);
          if (info.jpkg != null) {
            break;
          }
        }
      }
    } else if (isPrimitiveType(q)) {
      info.jpkg = "java.lang";
    } else if (!_stack.isEmpty()) {
      info.jpkg = _stack.get(_stack.size() - 1).jpkg;
    } else {
      info.jpkg = _jpkg;
    }

    _logger.fine("to create element with package = " + info.jpkg);
    try {
      info.name =
          (info.jpkg != null) ? info.jpkg + '.' + Strings.toCamelCase(l) : Strings.toCamelCase(l);
      try {
        if (info.name.endsWith("...")) {
          // Array construction
          info.type = Class.forName(info.name.substring(0, info.name.length() - 3));
          info.data = new ArrayList<Object>();
        } else {
          // Non-array construction
          int size = a.getLength();
          TypedValueGroup arguments = new TypedValueGroup();
          for (int i = 0; i < size; ++i) {
            if (!a.getQName(i).startsWith("xmlns:") && !a.getQName(i).equals("xmlns")) {
              arguments.add(guessUntypedValue(a.getQName(i), a.getValue(i)));
            }
          }
          arguments.complete();
          _logger.fine(S.fine(_logger) ? "arguments=" + arguments : null);

          if (arguments.size() > 0) {
            if (arguments.size() == 1 && "java.lang".equals(info.jpkg)) {
              info.inst.put(
                  "@as",
                  Strings.toCamelCase(
                      arguments.get(0).name, '-', false)); // respect original spelling
              info.data = arguments.get(0).get(0).data;
              info.type = arguments.get(0).get(0).type;
            } else {
              Exception last = null;
              Object[] args = new Object[arguments.size()];
              while (arguments.load(args, 0)) {
                try {
                  _logger.fine(
                      S.fine(_logger)
                          ? "to create " + info.name + " with args: " + args.length + args(args)
                          : null);
                  info.data = _factory.create(info.name, args);
                  info.type = info.data.getClass();
                  break;
                } catch (InvocationTargetException x) {
                  throw x;
                } catch (Exception x) {
                  last = x;
                  _logger.fine(
                      "failure in creating " + info.name + ": probing for other constructors");
                }
              }

              if (info.data == null) {
                throw last;
              }
            }
          } else {
            _logger.fine("Create " + info.name + " with the default constructor");
            info.data = _factory.create(info.name);
            info.type = info.data.getClass();
          }
        }
      } catch (ClassNotFoundException x) {
        // no class by the element name is found, assumed String
        if (!_lenient) {
          throw new BeanAssemblyException("No class associated with element " + q);
        } else {
          _logger.log(Level.WARNING, "can't find class " + info.name, x);
        }
      }
      _stack.add(info);
      // _logger.fine(">>ElementInfo: " + info.type.getName() + " in " + info);
      // all other exceptions indicate mismatches between the beans and the XML schema
    } catch (Exception x) {
      if (!_lenient) {
        throw new BeanAssemblyException("Failed to assemble bean from element " + q, x);
      } else {
        _logger.log(Level.SEVERE, "can't create object for this element", x);
      }
    }
  }