/**
 * PartsCheck.java
 *
 * DOM Usage example from the O'Reilly _XML in a Nutshell_ book.
 *
 */

// we'll use the Apache Software Foundation's Xerces parser.
import org.apache.xerces.parsers.*;
import org.apache.xerces.framework.*;

// import the DOM interfaces
import org.w3c.dom.*;
import org.xml.sax.*;

// get the necessary java support classes
import java.io.*;
import java.util.*;

/**
 * This class is designed to check the parts list of an XML document that
 * represents a piece of furniture for validity.  It uses the DOM to
 * analyze the actual furniture description and then check it against the
 * parts list that is embedded in the document.
 */
public class PartsCheck {
  // contains the true part count, keyed by part number
  HashMap m_hmTruePartsList = new HashMap();

  /**
   * The main function that allows this class to be invoked from the command
   * line.  Check each document provided on the command line for validity.
   */
  public static void main(String[] args) {
    PartsCheck pc = new PartsCheck();

    try {
      for (int i = 0; i < args.length; i++) {
        pc.validatePartsList(args[i]);
      }
    } catch (Exception e) {
      System.err.println(e);
    }
  }

  /**
   * Empty constructor that allows this object to be instantiated.
   */
  public PartsCheck()
  {
  }

  /**
   * Given a system identifier for an XML document, this function compares
   * the actual parts used to the declared parts list within the document.  It
   * prints warnings to standard error if the lists don't agree.
   */
  public void validatePartsList(String strXMLSysID) throws IOException,
      SAXException
  {
    // create a new parser
    DOMParser dp = new DOMParser();

    // parse the document and get the DOM Document interface
    dp.parse(strXMLSysID);
    Document doc = dp.getDocument();

    // get an accurate parts list count
    countParts(doc.getDocumentElement(), 1);

    // compare it to the parts list in the document
    reconcilePartsList(doc);
  }

  /**
   * Updates the true parts list by adding the count to the current count
   * for the part number given.
   */
  private void recordPart(String strPartNum, int cCount)
  {
    if (!m_hmTruePartsList.containsKey(strPartNum)) {
      // this part isn't listed yet
      m_hmTruePartsList.put(strPartNum, new Integer(cCount));
    } else {
      // update the count
      Integer cUpdate = (Integer)m_hmTruePartsList.get(strPartNum);
      m_hmTruePartsList.put(strPartNum, new Integer(cUpdate.intValue() + cCount));
    }
  }

  /**
   * Counts the parts referenced by and below the given node.
   */
  private void countParts(Node nd, int cRepeat)
  {
    // start the local repeat count at 1
    int cLocalRepeat = 1;
    Node ndTemp;

    if ((ndTemp = nd.getAttributes().getNamedItem("repeat")) != null) {
      // this node specifies a repeat count for its children
      cLocalRepeat = Integer.parseInt(ndTemp.getNodeValue());
    }

    if ((ndTemp = nd.getAttributes().getNamedItem("part_num")) != null) {
      // start the count at 1
      int cCount = 1;
      String strPartNum = ndTemp.getNodeValue();

      if ((ndTemp = nd.getAttributes().getNamedItem("count")) != null) {
        // more than one part needed by this node
        cCount = Integer.parseInt(ndTemp.getNodeValue());
      }

      // multiply the local count by the repeat passed in from the parent
      cCount *= cRepeat;

      // add the new parts count to the total
      recordPart(strPartNum, cCount);
    }

    // now process the children
    NodeList nl = nd.getChildNodes();
    Node ndCur;

    for (int i = 0; i < nl.getLength(); i++) {
      ndCur = nl.item(i);

      if (ndCur.getNodeType() == Node.ELEMENT_NODE) {
        // recursively count the parts for the child, using the local repeat
        countParts(ndCur, cLocalRepeat);
      }
    }
  }

  /**
   * This method reconciles the true parts list against the list in the document.
   */
  private void reconcilePartsList(Document doc)
  {
    Iterator iReal = m_hmTruePartsList.keySet().iterator();

    String strPartNum;
    int cReal;
    Node ndCheck;

    // loop through all of the parts in the true parts list
    while (iReal.hasNext()) {
      strPartNum = (String)iReal.next();
      cReal = ((Integer)m_hmTruePartsList.get(strPartNum)).intValue();

      // find the part list element in the document
      ndCheck = doc.getElementById(strPartNum);

      if (ndCheck == null) {
        // this part isn't even listed!
        System.err.println("missing <part_name> element for part #" +
            strPartNum + " (count " + cReal + ")");
      } else {
        Node ndTemp;

        if ((ndTemp = ndCheck.getAttributes().getNamedItem("count")) != null) {
          int cCheck = Integer.parseInt(ndTemp.getNodeValue());

          if (cCheck != cReal) {
            // counts don't agree
            System.err.println("<part_name> element for part #" +
                strPartNum + " is incorrect:  true part count = " + cReal +
                " (count in document is " + cCheck + ")");
          }
        } else {
          // they didn't provide a count for this part!
          System.err.println("missing count attribute for part #" +
              strPartNum + " (count " + cReal + ")");
        }
      }
    }
  }
}