/**
   * Retrieves the referenced element in the current XMIReader instance.
   *
   * <p>Note, null may be returned if it is a forward reference. If it is the case, it is added to
   * the forward reference.
   *
   * @param idRef the xmi id refers to an element which may be or may be not stored in XMIReader
   *     instance
   * @param elementName the xmi element name
   * @return the referenced element in the current XMIReader instance, or null if it is a forward
   *     reference
   * @throws SAXException if parent handler is null, or the parent element name or its associated
   *     object is null
   */
  private Object getReferenceElement(String idRef, String elementName) throws SAXException {
    // get the object from found elements
    Object obj = this.getCurrentXMIReader().getElement(idRef);

    // if it is not found, then add the forward reference
    if (obj == null) {
      XMIHandler handler = getParentHandler();

      // the parent handler is required
      if (handler == null) {
        throw new SAXException(
            "The handler for the parent element of " + elementName + " is null.");
      }

      // the parent element name is required
      if (handler.getLastProperty() == null) {
        throw new SAXException("The parent element name of " + elementName + " is null.");
      }

      // the object for the parent element is required
      if (handler.getLastRefObject() == null) {
        throw new SAXException(
            "The associated object for element " + handler.getLastProperty() + "  is null.");
      }

      // add the forward reference for the given xmi id
      this.getCurrentXMIReader()
          .putElementProperty(idRef, handler.getLastProperty(), handler.getLastRefObject());
    }

    return obj;
  }
  /**
   * This method is used to adjust the size of Diagram when the &lt;UML:Diagram&gt; element ended,
   * it will use the util method adjustGraphNodeSize in XMIConverterUtil class to adjust diagram
   * size. When the diagram doesn't contain any diagram element, the size should be configured empty
   * size, otherwise, the size will be calculated by the sub elements and the configured margin.
   *
   * @param uri the Namespace URI, or the empty string if the element has no Namespace URI or if
   *     Namespace processing is not being performed
   * @param localName the local name (without prefix), or the empty string if Namespace processing
   *     is not being performed
   * @param qName the qualified XML name (with prefix), or the empty string if qualified names are
   *     not available
   * @param chain the next chain of the chains of converters
   * @param reader the XMIReader instance firing this callback
   * @param handler the XMIHandler instance this converter added to
   * @throws SAXException if any error occurs in this method
   */
  public void endElement(
      String uri,
      String localName,
      String qName,
      XMIConverterChain chain,
      XMIReader reader,
      XMIHandler handler)
      throws SAXException {
    // Save the last ref object first, it maybe changed in later chain
    Object obj = handler.getLastRefObject();

    chain.endElement(uri, localName, qName, reader, handler);

    String elementName = ((qName != null) && (qName.trim().length() > 0)) ? qName : localName;

    if (DIAGRAM_QNAME.equals(elementName)) {
      Diagram diagram = (Diagram) obj;

      XMIConvertersUtil.adjustGraphNodeSize(diagram, emptyWidth, emptyHeight, margin);
    }
  }
  /**
   * This method implements the endElement(String,String,String) method in ContentHandler interface.
   *
   * <p>This component is a plugin of XMIReader component and this method is intended to be invoked
   * by XMIReader class.
   *
   * <p>This method receives notification of the end of an element. The diagram interchange related
   * elements will be processed by this method.
   *
   * @param uri the Namespace URI;
   * @param localName The local name (without prefix), or the empty string if Namespace processing
   *     is not being performed.
   * @param qName The qualified XML 1.0 name (with prefix), or the empty string if qualified names
   *     are not available
   * @throws SAXException Any SAX exception, possibly wrapping another exception, it may be caused
   *     by reflection, etc.
   */
  public void endElement(String uri, String localName, String qName) throws SAXException {
    String elementName = ((qName != null) && (qName.trim().length() != 0)) ? qName : localName;

    int dotIndex = elementName.indexOf(".");

    // if the element represents a uml model element and its parent element is a uml element
    // property,
    // then to set the property value for the parent uml element
    if (dotIndex < 0) {
      // get the object for the current element
      Object curElementObject = getLastRefObject();

      // make the parent element name and object on the top of the stack
      // it is called before getParentHandler() method because the handler for the
      // element and the handler for parent element may be the same
      pop();

      XMIHandler handler = getParentHandler();

      if (handler != null) {
        String parentElementName = handler.getLastProperty();
        // the parent element represents a uml model property
        if (parentElementName != null && parentElementName.indexOf(".") >= 0) {

          // update the uml model element property
          // for example:
          // <UML:GraphElement.semanticModel>
          //     <UML:SimpleSemanticModelElement xmi.id = '......'/>
          // </UML:GraphElement.semanticModel>
          // The current element is "UML:SimpleSemanticModelElement", the parent element is
          // "UML:GraphElement.semanticModel".
          // Here, the semanticModel property of the corresponding GraphElement instance is set.
          setElementProperty(handler.getLastRefObject(), parentElementName, curElementObject);
        }
      }
    } else {
      // get the property name
      // for example, get "semanticModel" from "UML:GraphElement.semanticModel"
      String propName = elementName.substring(dotIndex + 1);

      // If a XMI.field element ends, validate it and add the value to XMI.field values
      if (XMI_FIELD.equals(elementName)) {
        // Only zero or two sub XMI.field elements are allowed for each XMI.field element
        int count = (Integer) getLastRefObject();
        if (count != 0 && count != 2) {
          throw new SAXException(
              "One XMI.field element has "
                  + count
                  + " child XMI.field elements, instead of 0 or 2.");
        }

        // If the current XMI.field element is an inner XMI.field element
        // for example, <XMI.field><XMI.field>0.0</XMI.field></XMI.field>
        // The first XMI.field is an outer element while the inner XMI.field element is an
        // inner element
        if (xmiFieldValue != null) {
          this.xmiFieldValues.add(xmiFieldValue);
          this.xmiFieldValue = null;
        }

      } else if (DIMENSION_ELEMENTS.contains(propName)
          || POINT_ELEMENTS.contains(propName)
          || POINTS_ELEMENTS.contains(propName)) {
        // The elements that may have XMI.field sub-elements
        if (!this.xmiFieldValues.isEmpty()) {
          // Only two XMI.field sub-elements are allowed for the point or dimension elements
          if (!POINTS_ELEMENTS.contains(propName) && this.xmiFieldValues.size() != 2) {
            throw new SAXException(
                "The "
                    + elementName
                    + " element contains "
                    + this.xmiFieldValues.size()
                    + " XMI.field elements instead of two, only two is allowed.");
          }

          try {
            if (DIMENSION_ELEMENTS.contains(propName)) {
              // process the dimension element
              // for example
              // <UML:GraphNode.size>
              //     <XMI.field>80.0776</XMI.field>
              //     <XMI.field>15.0</XMI.field>
              // </UML:GraphNode.size>
              Dimension dimension = new Dimension();
              dimension.setWidth(Double.valueOf(this.xmiFieldValues.get(0).toString()));
              dimension.setHeight(Double.valueOf(this.xmiFieldValues.get(1).toString()));

              this.setElementProperty(getLastRefObject(), elementName, dimension);
            } else if (POINT_ELEMENTS.contains(propName)) {
              // process the point element
              // <UML:GraphElement.position>
              //     <XMI.field>2.0</XMI.field>
              //     <XMI.field>2.0</XMI.field>
              // </UML:GraphElement.position>
              Point point = new Point();
              point.setX(Double.valueOf(this.xmiFieldValues.get(0).toString()));
              point.setY(Double.valueOf(this.xmiFieldValues.get(1).toString()));

              this.setElementProperty(getLastRefObject(), elementName, point);
            } else {
              // process the point collect element
              // <UML:GraphEdge.waypoints>
              //   <XMI.field>
              //     <XMI.field>110.0</XMI.field>
              //     <XMI.field>205.0</XMI.field>
              //   </XMI.field>
              //   <XMI.field>
              //     <XMI.field>0.0</XMI.field>
              //     <XMI.field>0.0</XMI.field>
              //   </XMI.field>
              //   ......
              // </UML:GraphEdge.waypoints>
              int len = this.xmiFieldValues.size();

              // Add all the points
              for (int i = 0; i < len / 2; i++) {
                Point point = new Point();
                point.setX(Double.valueOf(this.xmiFieldValues.get(2 * i).toString()));
                point.setY(Double.valueOf(this.xmiFieldValues.get(2 * i + 1).toString()));

                this.setElementProperty(getLastRefObject(), elementName, point);
              }
            }
          } catch (NumberFormatException e) {
            throw new SAXException(
                "NumberFormatException occurs while converting string to double value.", e);
          }
        }
      }

      // make the parent element name and object because on the top of the stack
      // the name and object for the current element will be discarded
      pop();
    }
  }