Package net.sf.xbus.protocol.records

Source Code of net.sf.xbus.protocol.records.RecordTypeDescriptionChecker

package net.sf.xbus.protocol.records;

import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.Vector;

import net.sf.xbus.base.core.Constants;
import net.sf.xbus.base.core.XException;
import net.sf.xbus.base.core.strings.CardinalityStrings;
import net.sf.xbus.base.xml.XDomSupport;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* <code>RecordTypeDescriptionChecker</code> checks the xml description of a
* record type interface. This description explains the structure of the used
* interface files.
*
* @author Stephan D�wel
*/
class RecordTypeDescriptionChecker
{

  /**
   * <code>instances</code> stores the instances of the checker by thread.
   */
  private static Hashtable instances = new Hashtable();

  /**
   * <code>getInstance</code> returns a
   * <code>RecordTypeDescriptionChecker</code> to work with.
   *
   * @return a checker instance
   */
  public static RecordTypeDescriptionChecker getInstance()
  { // Instances are stored by thread.
    RecordTypeDescriptionChecker instance = (RecordTypeDescriptionChecker) instances
        .get(Thread.currentThread().getName());
    if (instance == null)
    { // No checker initialised yet
      instance = new RecordTypeDescriptionChecker();
      instances.put(Thread.currentThread().getName(), instance);
    } // if (instance==null)
    return instance;
  } // getInstance()

  // ///////////////////////////////////////////////////
  // Checking the structrue description top level
  // ///////////////////////////////////////////////////

  /**
   * <code>checkHeaderSpecification</code> checks the consistency of the
   * file header desription.
   *
   * @param headerSpec
   *            the structure description for the header
   * @param groupsSpec
   *            the list of field group specifications to check group
   *            references
   * @param validated
   *            Is the xml already validated?
   * @throws XException -
   *             in case of a inconsistency
   */
  public void checkHeaderSpecification(Element headerSpec, Node groupsSpec,
      boolean validated) throws XException
  {
    if (!validated && headerSpec.getAttribute("Name").length() == 0)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "1");
    }
    // Now the single fields in the header
    checkFieldsAndGroupRefs(headerSpec, groupsSpec, null, validated);
  } // checkHeaderSpecification(Element headerSpec, Node groupsSpec, boolean

  // validated)

  /**
   * <code>checkLinesSpecification</code> checks the consistency of the
   * lines section. The lines section lists all record types, declares their
   * order in the file and specifies how to identify them in the file.
   * Furthermore is fills the <code>recordTypeNames</code> and
   * <code>existanceIndicators</code> arrays to prepare checking the
   * consistency + of the record type declarations and the specifications
   * afterwards.
   *
   * @param linesSpec
   *            the lines section of the structure description
   * @param validated
   *            Is the xml already validated?
   * @return an array with 2 elements, the first is the array of all record
   *         type names, the second is an array of existance indicators (<code>null</code>
   *         if record identification method is "TypeIdentifier")
   * @throws XException
   *             in case of an inconsistency
   */
  public Object[] checkLinesSpecification(Element linesSpec, boolean validated)
      throws XException
  {
    // In which order to find the records
    String recOrder = linesSpec.getAttribute("RecordOrder");

    if (!validated)
    { // Errors in the xml?
      if (recOrder.length() == 0)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "2");
      }
      if (!recOrder.equals("Arbitrary") && !recOrder.equals("Ordered")
          && !recOrder.equals("Structured"))
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "3");
      }
    } // if (!validated)

    // How to identify the records
    String recIdentMethod = linesSpec.getAttribute("RecordIdentification");

    if (!validated)
    { // Errors in the xml?
      if (recIdentMethod.length() == 0)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "4");
      }
      if (!recIdentMethod.equals("TypeIdentifier")
          && !recIdentMethod.equals("RecordOrder"))
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "5");
      }
    } // if (!validated)

    if (recOrder.equals("Arbitrary")
        && !recIdentMethod.equals("TypeIdentifier"))
    {
      // forbidden combination
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "6");
    }
    // the record type declarations or ...
    // ... the list of record groups, if <recOrder>="Structured"
    NodeList recordTypes = linesSpec.getChildNodes();
    int numOfRecordTypes = recordTypes.getLength();
    // only ok if <recOrder>!="Structured" because otherwise this computes
    // the number of record groups!!!!!!!!!

    if (recordTypes.getLength() == 0)
    {
      // Extension if no prior DTD validating
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "7");
    }
    // Loop will run over record types, but the identification of record
    // record groups above is important, as well.
    // If <recOrder>!="Structured", the parent node is just the Lines node.
    Node recordGroup = linesSpec;

    // Now some corrections to the setting so far if record groups must be
    // treated.
    if (recOrder.equals("Structured"))
    {
      if (!recIdentMethod.equals("TypeIdentifier")
          && recordTypes.getLength() > 1)
      {
        // Several record groups are only allowed if the record types
        // can
        // be identiifed by type identifiers.
        // Attention: <recordTypes> contains the record groups - not the
        // types!
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "8");
      }
      numOfRecordTypes = 0;
      // For counting the record types a loop over the record groups is
      // necessary.
      for (int i = 0; i < recordTypes.getLength(); i++)
      { // loop over record groups
        if (!recordTypes.item(i).getNodeName().equals("RecordGroup"))
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "9");
        }
        numOfRecordTypes += recordTypes.item(i).getChildNodes()
            .getLength();

        // the number of records in that group
      } // for (int i=0; i<recordTypes.getLength(); i++)

      recordGroup = recordTypes.item(0);
      // for loop over record groups
      recordTypes = recordGroup.getChildNodes();
      // the record types one level deeper now, thus really to record
      // types
    } // if (recOrder.equals("Structured"))

    TreeMap[] existanceIndicators = null;
    // for record types which's occurrence depends on a certain value in the
    // record before

    if (recIdentMethod.equals("TypeIdentifier"))
    { // type identification by type identifier is easy but needs some
      // values
      // where to find the identifier within the records
      String idPos = linesSpec.getAttribute("IdentifierPos");
      // Where does the identifier start in the record string?
      if (idPos.length() == 0)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "10");
      }
      String idLength = linesSpec.getAttribute("IdentifierLength");
      // How long is the type identifier?
      if (idLength.length() == 0)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "11");
      }
      try
      { // The position must be a positive integer.
        int num = Integer.parseInt(idPos);
        if (num < 0)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "12");
        }
        // The length as well must be positive integer.
        // Length 0 is permitted for having a simple handling of files
        // with
        // only one record type.
        num = Integer.parseInt(idLength);
        if (num < 0)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "13");
        }
      } // try
      catch (ClassCastException e)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
      } // catch
    } // then (recIdentMethod.equals("TypeIdentifier"))
    else
    // Without type identifiers everything gets more complicated ...
    // ... but the primary value are already set.
    { // Indicators for successors may be important.
      existanceIndicators = new TreeMap[numOfRecordTypes];
      for (int i = 0; i < existanceIndicators.length; i++)
        existanceIndicators[i] = null;
      // Will be filled when going through the single record type
      // declarations.
    } // else (recIdentMethod.equals("TypeIdentifier"))

    String[] recordTypeNames = new String[numOfRecordTypes];
    // Storing the names for consistency check gainst the record type
    // specification section.
    LinkedList typeIds = new LinkedList();
    LinkedList typeIdIntervals = new LinkedList();
    // Record type identifier may be single values or intervals.
    // They are stored to check their unique reference to a record type.
    int recordTypesCount = recordTypes.getLength();
    // the number of record types in the current record group or at all
    // (wihtout grouping) - at least if no bad nodes are included in a
    // errorprone specification
    int recordTypesBefore = 0;
    // number of record type declarations checked in other record groups
    // before - used as offset to datermine the position in the record type
    // array
    while (recordGroup != null)
    { // loop over record groups
      // - only one loop if <recOrder>!="Structured" because of missing
      // record groups
      for (int i = 0; i < recordTypesCount; i++)
      { // Loop over all record type declarations of the record group
        Node rType = recordTypes.item(i);

        // Extension if no DTD validating
        if (rType.getNodeType() != Node.ELEMENT_NODE
            || !rType.getNodeName().equals("RecordType"))
        {
          List params = new Vector();
          params.add(rType.getNodeName());
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "15", params);
        }
        // Check the consistency of this single record type declaration.
        // If its occurrence depends on values in the prececeeding
        // record
        // the information about this value is stored in
        // <dependenceOnPredecessor>.
        String[] dependenceOnPredecessor = checkRecordTypeDeclaration(
            rType, recordTypesCount, recordTypesBefore + i,
            recIdentMethod, recOrder, recordTypeNames, typeIds,
            typeIdIntervals, existanceIndicators);

        if (existanceIndicators != null
            && dependenceOnPredecessor != null)
        { // Existance records are expected in general and an
          // existance
          // indicator is defined for the last cheched record type.
          // To ensure that the identification via existance
          // indicators
          // is possible, the information about them is analysed the
          // other
          // way round. Originally the information was attached as
          // <dependenceOnPredecessor> to the dependent record type.
          // Now it will be attached as <existanceIndicators> to its
          // possible predecessors.
          String occurrences = ((Element) recordTypes.item(i))
              .getAttribute("Occurrences");
          // Cardinality of the record type.
          boolean isOptional = CardinalityStrings
              .isCardinalityInterval(occurrences, true)
              && CardinalityStrings.getCardinalityLow(
                  occurrences, true) == 0;
          for (int j = i - 1; j > -1 && isOptional; j++)
          { // Backwards loop to the beginning of the record types
            // starting at the predecessor of the examined record
            // type.
            // As long as a record type is optional, its predecessor
            // is
            // added to the list of possible predecessors for the
            // originally
            // checked record type.
            // Stops at the first record type because this one may
            // not be optional.

            // Now information attached to the predecessor.
            if (existanceIndicators[j] == null)
            { // The predecessor does not yet have identified
              // successor
              // which depend on an existnace indicator.
              existanceIndicators[j] = new TreeMap();
              existanceIndicators[j].put(
                  dependenceOnPredecessor[0],
                  dependenceOnPredecessor[1]);
            } // then (existanceIndicators[j]==null)
            else
            { // The predecessor already has information about
              // successors.
              // Existance indicator value for the existance
              // indicator
              // field stated in the examined record type, but
              // used for
              // another record type !?
              String value = (String) existanceIndicators[j]
                  .get(dependenceOnPredecessor[0]);
              if (value != null)
                // Existance indicator values in the same field
                // for different record types.
                if (value.length() != dependenceOnPredecessor[1]
                    .length())
                {
                  throw new XException(
                      Constants.LOCATION_INTERN,
                      Constants.LAYER_PROTOCOL,
                      Constants.PACKAGE_PROTOCOL_RECORDS,
                      "16");
                }
                else
                  // No other use of the existance indicator
                  // field yet.
                  // Store the new value.
                  existanceIndicators[j].put(
                      dependenceOnPredecessor[0],
                      dependenceOnPredecessor[1]);
            } // else (existanceIndicators[j]==null)
            occurrences = ((Element) recordTypes.item(j))
                .getAttribute("Occurrences");
            // Cardinality of the last checked record type
            isOptional = CardinalityStrings.isCardinalityInterval(
                occurrences, true)
                && CardinalityStrings.getCardinalityLow(
                    occurrences, true) == 0;
          } // for (int j=i-1; j>-1 && isOptional; j++)
        } // if (existanceIndicators!=null &&
        // dependenceOnPredecessor!=null)
      } // for (int i=0; i<recordTypes.getLength(); i++)

      recordTypesBefore += recordTypesCount;
      // Correct the number of checked record types in the so far
      // examined record groups

      if (recordGroup.getNodeName().equals("RecordGroup"))
      { // record groups really present
        recordGroup = recordGroup.getNextSibling();
        // Choose next record group
        if (recordGroup != null)
        { // there is a next record group
          recordTypes = recordGroup.getChildNodes();
          recordTypesCount = recordTypes.getLength();
          // Next check the record types in the new group
        } // if (recordGroup != null)
      } // if (recordGroup.getNodeName().equals("RecordGroup"))
      else
        // no real record group loop but record declarations directly
        // in lines section, terminate loop by setting ...
        recordGroup = null;
    } // while (recordGroup!=null) - record group loop

    // Check record type names for uniqueness.
    for (int i = 0; i < recordTypeNames.length; i++)
      for (int j = i + 1; j < recordTypeNames.length; j++)
        if (recordTypeNames[i].equals(recordTypeNames[j]))
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "17");
        }
    // Checks on the type identifier
    // First check their length
    // If the record type identification is not done by type identifiers,
    // the two type id lists are empty.
    int identifierLength = 0;
    if (typeIds.size() > 0)
      // Some single value id's
      identifierLength = ((String) typeIds.get(0)).length();
    else if (typeIdIntervals.size() > 0)
      // No single value id but some id intervals
      identifierLength = ((String[]) typeIdIntervals.get(0))[0].length();
    // All id's must have the same length.
    // First the single value id's
    for (int i = 1; i < typeIds.size(); i++)
      if (((String) typeIds.get(0)).length() != identifierLength)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "18");
      }
    // Second the id intervals
    for (int i = 1; i < typeIdIntervals.size(); i++)
      // Attention: two values!
      if (((String[]) typeIdIntervals.get(0))[0].length() != identifierLength
          || ((String[]) typeIdIntervals.get(0))[1].length() != identifierLength)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "18");
      }
    // Second, check the id uniqueness in sense of identification of only
    // one
    // record type
    if (!typeIdsAreUnique(typeIds, typeIdIntervals))
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "20");
    }

    // Return the found record type names and existance indicators.
    Object[] checkInfos = new Object[2];
    checkInfos[0] = recordTypeNames;
    checkInfos[1] = existanceIndicators;
    return checkInfos;
  } // checkLinesSpecification(Element linesSpec, boolean validated)

  /**
   * <code>checkRecordTypesSpecification</code> checks the section of record
   * type specifications for consistency. In particular it checks for
   * existance of the record types and fields named in the lines section.
   *
   * @param recordTypesSpec
   *            the record type specifications section
   * @param groupsSpec
   *            the list of field group specifications to check group
   *            references
   * @param checkInfos
   *            record type names and existance indicators found in the lines
   *            section
   * @param validated
   *            Is the xml already validated?
   * @throws XException
   *             in case of an inconsistency
   */
  public void checkRecordTypesSpecification(Node recordTypesSpec,
      Node groupsSpec, Object[] checkInfos, boolean validated)
      throws XException
  {
    try
    // for casting to XException
    {
      NodeList recordTypes = recordTypesSpec.getChildNodes();
      // Here we have no record groups!
      for (int i = 0; i < recordTypes.getLength(); i++)
      { // loop over record type specifications
        Element recordType = (Element) recordTypes.item(i);

        // Extension if no prior validating
        if (!recordType.getNodeName().equals("RecordTypeSpec"))
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "21");
        }
        String recTypeName = recordType.getAttribute("Name");
        // Extension if no prior validating
        if (recTypeName == null
            || !XDomSupport.isValidTagName(recTypeName))
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "22");
        }
        // Search record type name in the name array from
        // the record type (Lines) section.
        boolean nameSearched = true;
        // Still searching?
        int pos = 0;
        for (; pos < ((String[]) checkInfos[0]).length && nameSearched; pos++)
          // loop over record type names from the lines section
          if (recTypeName.equals(((String[]) checkInfos[0])[pos]))
          { // name found
            ((String[]) checkInfos[0])[pos] = null;
            // deleted for avoiding more hits
            nameSearched = false;
            // search loop ended for this record type
          } // if
        // (recTypeName.equals(((String[])checkInfos[0])[pos]))
        if (nameSearched)
        {
          // Record type not declared in lines section
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "23");
        }
        // Now the single fields in the record type
        TreeMap existanceIndicators = null;
        // With the found position in the checkInfos array
        // the correct existance indicator map is retrieved ...
        if (checkInfos[1] != null)
          // Because pos was augmented once to much in the for loop,
          // now
          // use pos-1.
          existanceIndicators = ((TreeMap[]) checkInfos[1])[pos - 1];
        // ... and feeded to field checking
        checkFieldsAndGroupRefs(recordType, groupsSpec,
            existanceIndicators, validated);
      } // for (int i=0; i<recordTypes.getLength(); i++)
    } // try
    catch (ClassCastException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
  } // checkRecordTypesSpecification(Node recordTypesSpec, Node groupsSpec,

  // Object[] checkInfos, boolean validated)

  /**
   * <code>checkTrailerSpecification</code> checks the consistency of the
   * file trailer desription.
   *
   * @param trailerSpec
   *            the structure description for the trailer
   * @param groupsSpec
   *            the list of field group specifications to check group
   *            references
   * @param validated
   *            Is the xml already validated?
   * @throws XException
   *             in case of a inconsistency
   */
  public void checkTrailerSpecification(Element trailerSpec, Node groupsSpec,
      boolean validated) throws XException
  {
    // Extension if no prior DTD validating
    if (trailerSpec.getAttribute("Name").length() == 0)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "1");
    }
    // Now the single fields in the header
    checkFieldsAndGroupRefs(trailerSpec, groupsSpec, null, validated);
  } // checkTrailerSpecification(Element trailerSpec, Node groupsSpec,

  // boolean validated)

  /**
   * <code>checkGroupsSpecification</code> checks the consistency of each
   * field group specification.
   *
   * @param groupsSpec
   *            the list of field group specifications
   * @param validated
   *            Is the xml already validated?
   * @throws XException
   *             in case of an inconsistency
   */
  public void checkGroupsSpecification(Node groupsSpec, boolean validated)
      throws XException
  {
    NodeList groups = groupsSpec.getChildNodes();

    if (!validated)
    { // Extension if no prior DTD validating
      try
      // for casting to XException
      {
        for (int i = 0; i < groups.getLength(); i++)
          // consistency check on each single group specification
          // (redundant to validating against DTD)
          checkGroupSpecification((Element) groups.item(i));
      } // try
      catch (ClassCastException e)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
      } // catch
    } // if (!validated)

    // Check for the uniqueness of group names
    for (int i = 0; i < groups.getLength() - 1; i++)
    {
      String groupName = ((Element) groups.item(i)).getAttribute("Name");
      for (int j = i + 1; j < groups.getLength(); j++)
        if (groupName.equals(((Element) groups.item(j))
            .getAttribute("Name")))
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "27");
        }
    } // for (int i=0; i<groups.getLength()-1; i++)
  } // checkGroupsSpecification(Node groupsSpec, boolean validated)

  // //////////////////////////////////////////////
  // Structrue checking on some deeper level
  // //////////////////////////////////////////////

  /**
   * <code>checkRecordTypeDeclaration</code> checks a record type
   * declaration for consistency. Furthermore is fills the
   * <code>recordTypeNames</code> and <code>existanceIndicators</code> to
   * check consistency with the record type specifications afterwards.
   *
   * @param recTypeDecl
   *            the DOM node of the declaration
   * @param recordTypesCount
   *            the number of record types in the actual record group or the
   *            whole file (wihtout grouping)
   * @param pos
   *            the position in the list of declarations (starts at 0)
   * @param recIdentMethod
   *            the method to identify record types
   * @param recOrder
   *            the order of records in the file
   * @param recordTypeNames
   *            array of record type names, filled at position
   *            <code>pos</code> right here
   * @param typeIds
   *            the list of single-value type id's, a found id will be added
   * @param typeIdIntervals
   *            the list of type id intervals, a found interval will be added
   * @param existanceIndicators
   *            array of existance indicator maps, potentially filled with
   *            self reference at position <code>pos</code> right here
   * @return field/value pair for an existance indicator in the record type
   * @throws XException
   *             in case of an inconsistency
   */
  public String[] checkRecordTypeDeclaration(Node recTypeDecl,
      int recordTypesCount, int pos, String recIdentMethod,
      String recOrder, String[] recordTypeNames, LinkedList typeIds,
      LinkedList typeIdIntervals, TreeMap[] existanceIndicators)
      throws XException
  {
    String[] precedingIndicator = null;
    // for returning exstistance indicator information for predecessors

    try
    // for casting to XException
    {
      if (recIdentMethod.equals("TypeIdentifier"))
      { // Record types are identified by an id
        String id = ((Element) recTypeDecl).getAttribute("Identifier");
        // a single-value id
        String[] idLoHi = new String[2];
        idLoHi[0] = ((Element) recTypeDecl)
            .getAttribute("IdentifierLow");
        idLoHi[1] = ((Element) recTypeDecl)
            .getAttribute("IdentifierHigh");
        // an id interval
        if (id.length() == 0)
        { // No single-value id
          if (idLoHi[0].length() == 0 || idLoHi[1].length() == 0)
          {
            // Something must be specified: single value or
            // interval.
            throw new XException(Constants.LOCATION_INTERN,
                Constants.LAYER_PROTOCOL,
                Constants.PACKAGE_PROTOCOL_RECORDS, "28");
          }
          typeIdIntervals.add(idLoHi);
        } // then (id.length()==0)
        else
        { // Single-value id found
          if (idLoHi[0].length() > 0 || idLoHi[1].length() > 0)
          {
            // Any interval specification conflicts.
            throw new XException(Constants.LOCATION_INTERN,
                Constants.LAYER_PROTOCOL,
                Constants.PACKAGE_PROTOCOL_RECORDS, "28");
          }
          typeIds.add(id);
        } // else (id.length()==0)
      } // if (recIdentMethod.equals("TypeIdentifier"))

      recordTypeNames[pos] = XDomSupport.getTrimedNodeText(recTypeDecl);
      // the record type name
      if (!XDomSupport.isValidTagName(recordTypeNames[pos]))
      {
        // It must be a legal xml tag name.
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "30");
      }
      // Now look for record identification features, in particular the
      // cardinality
      // and existance indicator
      if (recOrder.equals("Ordered") || recOrder.equals("Structured"))
      { // Records are listed in the record type order or in groups.
        // The cardinality must be specified.
        String occurrences = ((Element) recTypeDecl)
            .getAttribute("Occurrences");
        if (occurrences.length() == 0)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "31");
        }
        if (!CardinalityStrings.isCardinalityValid(occurrences))
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "32");
        }

        // The first record in a record group must have cardinality 1.
        if (recOrder.equals("Structured") && pos == 0)
        {
          try
          {
            int occurrNum = Integer.parseInt(occurrences);
            if (occurrNum != 1)
            {
              throw new XException(Constants.LOCATION_INTERN,
                  Constants.LAYER_PROTOCOL,
                  Constants.PACKAGE_PROTOCOL_RECORDS, "33");
            }
          } // try
          catch (NumberFormatException e)
          {
            throw new XException(Constants.LOCATION_INTERN,
                Constants.LAYER_PROTOCOL,
                Constants.PACKAGE_PROTOCOL_RECORDS, "33");
          } // catch
        } // if (recOrder.equals("Structured") && pos==0)

        // If the record type is not given by an id but the cardinality
        // is specified as an
        // interval and there are also other record types, there must be
        // an existance indicator
        // refering the preceding record.
        if (recordTypesCount > 1
            && !recIdentMethod.equals("TypeIdentifier")
            && CardinalityStrings.isCardinalityInterval(
                occurrences, true))
        {
          String existIndField = ((Element) recTypeDecl)
              .getAttribute("ExistIndicatorField");
          // Indicator field specified by name
          if (existIndField.length() == 0)
          {
            throw new XException(Constants.LOCATION_INTERN,
                Constants.LAYER_PROTOCOL,
                Constants.PACKAGE_PROTOCOL_RECORDS, "35");
          }
          String existIndValue = ((Element) recTypeDecl)
              .getAttribute("ExistIndicatorValue");
          if (existIndValue.length() == 0)
          {
            throw new XException(Constants.LOCATION_INTERN,
                Constants.LAYER_PROTOCOL,
                Constants.PACKAGE_PROTOCOL_RECORDS, "36");
          }
          precedingIndicator = new String[2];
          precedingIndicator[0] = existIndField;
          precedingIndicator[1] = existIndValue;
          // If the record type may occurr more than once, it may be
          // its
          // predecessor itself.
          if (CardinalityStrings
              .getCardinalityHigh(occurrences, true) > 1)
          {
            existanceIndicators[pos] = new TreeMap();
            existanceIndicators[pos].put(precedingIndicator[0],
                precedingIndicator[1]);
          } // if (getCardinalityHigh(occurrences,true)>1)
        } // if (!recIdentMethod.equals("TypeIdentifier") &&
        // isCardinalityInterval(occurrences,true))
      } // if (recOrder.equals("Ordered") ||
      // recOrder.equals("Structured"))
    } // try
    catch (ClassCastException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
    return precedingIndicator;
    // Group starting record type found?
  } // checkRecordTypeDeclaration(Node recTypeDecl, int pos, String

  // recIdentMethod, String recOrder, String[] recordTypeNames, LinkedList
  // typeIds, LinkedList typeIdIntervals, TreeMap[] existanceIndicators)

  /**
   * <code>checkFieldsAndGroupRefs</code> checks the fields and group
   * references within an record type (including header and trailer) for
   * consistency.
   *
   * @param parent
   *            the record type specification node
   * @param groupsSpec
   *            the list of field group specifications
   * @param existanceIndicators
   *            existance indicator map for the surrounding record type,
   *            references to fields will be marked
   * @param validated
   *            Is the xml already validated?
   * @throws XException
   *             in case of an inconsistency
   */
  private void checkFieldsAndGroupRefs(Node parent, Node groupsSpec,
      TreeMap existanceIndicators, boolean validated) throws XException
  {
    try
    // try-catch block for casting standard exceptions to XException
    {
      NodeList fieldSpecs = parent.getChildNodes();
      for (int i = 0; i < fieldSpecs.getLength(); i++)
      {
        Element fieldSpec = (Element) fieldSpecs.item(i);
        // Two alternatives: field or group
        if (fieldSpec.getNodeName().equals("Field"))
          checkFieldSpecification(fieldSpec, existanceIndicators,
              validated);
        else if (fieldSpec.getNodeName().equals("Group"))
          checkGroupReference(fieldSpec, groupsSpec,
              existanceIndicators);
        else
        // Extension if no prior DTD validating
        {
          List params = new Vector();
          params.add(fieldSpec.getNodeName());
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "38", params);
        }
      } // for (int i=0; i<fieldSpecs.getLength(); i++)
    } // try
    catch (ClassCastException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
  } // checkFieldsAndGroupRefs(Node parent, Node groupsSpec, TreeMap

  // existanceIndicators)

  /**
   * <code>checkGroupReference</code> checks the consistency of a reference
   * to a field group.
   *
   * @param groupRef
   *            the DOM node for the group reference
   * @param groupsSpec
   *            the list of field group specifications to check group
   *            references
   * @param existanceIndicators
   *            existance indicator map for the surrounding record type,
   *            references to group fields will be marked
   * @throws XException
   *             in case of an inconsistency
   */
  public void checkGroupReference(Element groupRef, Node groupsSpec,
      TreeMap existanceIndicators) throws XException
  {
    try
    {
      String groupName = groupRef.getAttribute("Name");

      // Extension if no prior DTD validating
      if (groupName.length() == 0)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "40");
      }
      // Search for the group specification.
      if (groupsSpec == null)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "41");
      }
      List groups = XDomSupport.getChildNodesByAttrValue(groupsSpec,
          "Name", groupName, "GroupSpec");
      if (groups == null || groups.size() == 0)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "42");
      }
      if (groups.size() > 1)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "43");
      }
      // Group specification found

      // some existance indicators to search for
      if (existanceIndicators != null)
      {
        NodeList fields = ((Node) groups.get(0)).getChildNodes();
        for (int i = 0; i < fields.getLength(); i++)
        {
          String name = ((Element) fields.item(i))
              .getAttribute("Name");
          String value = (String) existanceIndicators.get(name);
          if (value != null)
          {
            if (value.equals("Found"))
            {
              // Will always occurr if a record type contains more
              // than one instance of
              // a group. Thus, existance indicators in group
              // should not occurr.
              throw new XException(Constants.LOCATION_INTERN,
                  Constants.LAYER_PROTOCOL,
                  Constants.PACKAGE_PROTOCOL_RECORDS, "44");
            }
            int length = Integer
                .parseInt(((Element) fields.item(i))
                    .getAttribute("Length"));
            if (value.length() != length)
            {
              throw new XException(Constants.LOCATION_INTERN,
                  Constants.LAYER_PROTOCOL,
                  Constants.PACKAGE_PROTOCOL_RECORDS, "45");
            }
            // Mark as found.
            existanceIndicators.put(name, "Found");
          } // if (value!=null)
        } // for (int i=0; i<fields.getLength(); i++)
      } // if (existanceIndicators!=null)
    } // try
    catch (ClassCastException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    }
    catch (NumberFormatException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    }
  } // checkGroupReference(Element groupRef, Node groupsSpec, TreeMap

  // existanceIndicators)

  /**
   * <code>checkFieldSpecification</code> checks the consistency of a field
   * specification.
   *
   * @param fieldSpec
   *            the DOM node for the field specification
   * @param existanceIndicators
   *            existance indicator map for the surrounding record type,
   *            references to field will be marked
   * @param validated
   *            Is the xml already validated?
   * @throws XException
   *             in case of an inconsistency
   */
  private void checkFieldSpecification(Element fieldSpec,
      TreeMap existanceIndicators, boolean validated) throws XException
  {
    try
    { // Field name
      String name = fieldSpec.getAttribute("Name");
      // Field length
      String length = fieldSpec.getAttribute("Length");
      // Field format
      String format = fieldSpec.getAttribute("Format");

      if (!validated)
      { // Extension if no prior DTD validating
        if (name.length() == 0)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "48");
        }
        if (length.length() == 0)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "49");
        }
        if (format.length() == 0)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "50");
        }
      } // if (!validated)

      // For dates further format infoamtion is mandatory.
      if (format.equals("date")
          && fieldSpec.getAttribute("DateFormat").length() == 0)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "51");
      }

      // The length must be an strictly positive integer
      int lengthNum = Integer.parseInt(length);
      if (lengthNum < 1)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "52");
      }
      // Constant values must be bound to a value.
      if (format.equals("const"))
      {
        String value = fieldSpec.getAttribute("Value");
        if (value.length() == 0)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "53");
        }
        else if (value.length() != lengthNum)
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "54");
        }
      } // if (fieldSpec.getAttribute("Format").equals("const"))

      // some existance indicators to search for
      if (existanceIndicators != null)
      {
        String value = (String) existanceIndicators.get(name);
        if (value != null)
        { // existance indicator defined for this field
          if (value.equals("Found"))
          {
            // Already found for other field
            throw new XException(Constants.LOCATION_INTERN,
                Constants.LAYER_PROTOCOL,
                Constants.PACKAGE_PROTOCOL_RECORDS, "44");
          }
          if (value.length() != lengthNum)
          {
            throw new XException(Constants.LOCATION_INTERN,
                Constants.LAYER_PROTOCOL,
                Constants.PACKAGE_PROTOCOL_RECORDS, "45");
          }
          // Mark as found.
          existanceIndicators.put(name, "Found");
        } // if (value!=null)
      } // if (existanceIndicators!=null)
    } // try
    catch (NumberFormatException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
    catch (ClassCastException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
  } // checkFieldSpecification(Element fieldSpec, TreeMap

  // existanceIndicators, boolean validated)

  /**
   * <code>checkGroupSpecification</code> checks the consistency of a single
   * field group specification. Extension if no prior DTD validating because
   * otherwise completely redundant.
   *
   * @param groupSpec
   *            the group specification to check as DOM node
   * @throws XException
   *             in case of an inconsistency
   */
  public void checkGroupSpecification(Element groupSpec) throws XException
  {
    if (groupSpec.getAttribute("Name").length() == 0)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "59");
    }
    try
    // for casting to XException
    {
      NodeList fieldSpecs = groupSpec.getChildNodes();
      // fields in the group
      for (int i = 0; i < fieldSpecs.getLength(); i++)
      { // Loop over all group fields
        if (!((Element) fieldSpecs.item(i)).getNodeName().equals(
            "Field"))
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "60");
        }
      } // for (int i=0; i<fieldSpecs.getLength(); i++)
    } // try
    catch (ClassCastException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
  } // checkGroupSpecification(Node groupSpec)

  // ///////////////////////
  // Auxiliary functions
  // ///////////////////////

  /**
   * <code>typeIdsAreUnique</code> checks if each declared record type id
   * refers uniquely to only one record type.
   *
   * @param typeIds
   *            single-value id's as found in the record type declarations
   * @param typeIdIntervals
   *            intervals of id's (used as counters) as found in the record
   *            type declarations
   * @return <code>true</code> if all id's are unique, false if an id refers
   *         to two record types
   */
  public boolean typeIdsAreUnique(LinkedList typeIds,
      LinkedList typeIdIntervals)
  {
    boolean result = true;
    for (int i = 0; i < typeIds.size() && result; i++)
    { // Loop over all single-value id's
      for (int j = i + 1; j < typeIds.size() && result; j++)
        // Loop over rest of single-value id's
        result = !((String) typeIds.get(i)).equals((typeIds.get(j)));
      // Repeated id?
      for (int j = 0; j < typeIdIntervals.size() && result; j++)
        // Loop over all id intervals
        result = (((String) typeIds.get(i))
            .compareTo(((String[]) typeIdIntervals.get(j))[0]) < 0)
            || (((String) typeIds.get(i))
                .compareTo(((String[]) typeIdIntervals.get(j))[1]) > 0);
      // Or member of one of the intervals?
    } // for (int i=0; i<typeIds.size() && result; i++)
    for (int i = 0; i < typeIdIntervals.size() && result; i++)
      // Loop over all id intervals
      for (int j = i + 1; j < typeIdIntervals.size() && result; j++)
        // Loop over rest of intervals
        result = (((String[]) typeIdIntervals.get(i))[0]
            .compareTo(((String[]) typeIdIntervals.get(j))[1]) > 0)
            || (((String[]) typeIdIntervals.get(i))[1]
                .compareTo(((String[]) typeIdIntervals.get(j))[0]) < 0);
    // Do the two intervals intersect?
    return result;
  } // typeIdsAreUnique(LinkedList typeIds, LinkedList typeIdIntervals)

  /**
   * <code>computeRecordLength</code> calculates the string length for a
   * record type - including header and trailer.
   *
   * @param spec
   *            the record type specification
   * @param groupsSpec
   *            the list of group specifications
   * @return the total length including filler
   * @throws XException
   *             in case of a mal-specified length
   */
  protected static int computeRecordLength(Node spec, Node groupsSpec)
      throws XException
  {
    int fieldLengthSum = 0;
    // for the result
    NodeList fieldSpecs = spec.getChildNodes();
    try
    // to cast to XException
    {
      for (int i = 0; i < fieldSpecs.getLength(); i++)
      {
        Element fieldSpec = (Element) fieldSpecs.item(i);
        if (fieldSpec.getNodeName().equals("Field"))
          fieldLengthSum += Integer.parseInt(fieldSpec
              .getAttribute("Length"));
        else if (fieldSpec.getNodeName().equals("Group"))
          fieldLengthSum += computeGroupLength(fieldSpec, groupsSpec);
      } // for (int i=0; i<fieldSpecs.getLength(); i++)
    }
    catch (NumberFormatException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    }
    return fieldLengthSum;
  } // computeRecordLength(Element spec)

  /**
   * <code>computeGroupLength</code> calculates the string length for a
   * field group.
   *
   * @param groupRef
   *            a refernce to the group
   * @param groupsSpec
   *            the list of group specifications
   * @return the total length including filler
   * @throws XException
   *             in case of a mal-specified length or missing group
   *             specification
   */
  protected static int computeGroupLength(Element groupRef, Node groupsSpec)
      throws XException
  {
    int groupLengthSum = 0;
    // for the result
    NodeList fieldSpecs = getGroupSpec(groupRef.getAttribute("Name"),
        groupsSpec).getChildNodes();
    // the group specification
    try
    // for casting to XException
    {
      for (int i = 0; i < fieldSpecs.getLength(); i++)
      {
        Element fieldSpec = (Element) fieldSpecs.item(i);
        if (fieldSpec.getNodeName().equals("Field"))
          groupLengthSum += Integer.parseInt(fieldSpec
              .getAttribute("Length"));
        else
        {
          throw new XException(Constants.LOCATION_INTERN,
              Constants.LAYER_PROTOCOL,
              Constants.PACKAGE_PROTOCOL_RECORDS, "63");
        }
      } // for (int i=0; i<fieldSpecs.getLength(); i++)
    } // try
    catch (NumberFormatException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
    return groupLengthSum;
  } // computeGroupLength(Element groupRef, Node groupsSpec)

  /**
   * <code>getGroupSpec</code> selects the group specification node by the
   * group name.
   *
   * @param groupName
   *            the name of the field group
   * @param groupsSpec
   *            the list of group specifications
   * @return the group specification node
   * @throws XException
   *             in case that the group specification cannot be retrieved
   */
  protected static Node getGroupSpec(String groupName, Node groupsSpec)
      throws XException
  {
    Node result = null;
    try
    // for casting to XException
    {
      List groups = XDomSupport.getChildNodesByAttrValue(groupsSpec,
          "Name", groupName, "GroupSpec");
      if (groups.size() != 1)
      {
        throw new XException(Constants.LOCATION_INTERN,
            Constants.LAYER_PROTOCOL,
            Constants.PACKAGE_PROTOCOL_RECORDS, "65");
      }
      result = (Node) groups.get(0);
    } // try
    catch (ClassCastException e)
    {
      throw new XException(Constants.LOCATION_INTERN,
          Constants.LAYER_PROTOCOL,
          Constants.PACKAGE_PROTOCOL_RECORDS, "0", e);
    } // catch
    return result;
  } // getGroupSpec(String groupName, Node groupsSpec)

} // RecordTypeDescriptionChecker
TOP

Related Classes of net.sf.xbus.protocol.records.RecordTypeDescriptionChecker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.