public boolean expand(Writer output, Serializable object, RuleOptions options)
      throws IOException, RuleException {
    boolean expanded = false;
    boolean prefixPrinted = false;
    boolean atLeastOneChildPrinted = false;

    if (object == null) {
      if (Debug.isDebug()) {
        throw new NullPointerException(); // disclose programming error
        // in debug..
      } else {
        return false;
      }
    } // end if

    if (!(object instanceof DbObject)) {
      return false;
    } // end if

    DbObject dbObject = (DbObject) object;
    MetaRelationship metaRelation = getMetaRelation(dbObject);
    MetaClass childrenMetaClass = getChildrenMetaClass();

    try {
      // get metaRelation from its string representation
      if (metaRelation == null)
        metaRelation = (MetaRelationship) getMetaField(dbObject, sConnector);

      boolean state[] = {prefixPrinted, atLeastOneChildPrinted};

      if (metaRelation instanceof MetaRelation1) {
        MetaRelation1 metaRelation1 = (MetaRelation1) metaRelation;
        expanded |=
            expandMetaRelation1(output, dbObject, metaRelation1, state, childrenMetaClass, options);
      } else if (metaRelation instanceof MetaRelationN) {
        expanded |=
            expandMetaRelationN(output, dbObject, metaRelation, state, childrenMetaClass, options);
      } else if (metaRelation instanceof MetaChoice) {
        MetaChoice choice = (MetaChoice) metaRelation;
        expanded |= expandMetaChoice(output, dbObject, choice, state, childrenMetaClass, options);
      } else {
        // TODO: throw 'meta-relationship not supported'
      }

      super.terminate(output, options);
    } catch (DbException ex) {
      String msg = ex.getMessage();
      throw new RuleException(msg);
    } catch (RuntimeException ex) {
      String conn = m_connector.getGUIName();
      String objectKind = dbObject.getMetaClass().getGUIName();
      String msg = InvalidConnectorRuleException.buildMessage(m_ruleName, conn, objectKind);
      throw new InvalidConnectorRuleException(msg);
    }

    return expanded;
  }
  private boolean expandMetaChoice(
      Writer output,
      DbObject object,
      MetaChoice choice,
      boolean state[],
      MetaClass childrenMetaClass,
      RuleOptions options)
      throws DbException, IOException, RuleException {

    boolean expanded = false;
    Object child = object.get(choice);

    if (child instanceof DbObject) {
      DbObject dbChild = (DbObject) child;
      MetaClass metaClass = dbChild.getMetaClass();
      String className = metaClass.getGUIName();
      boolean excluded = false;

      // check if child's class is excluded
      if (options != null) {
        excluded = options.isExcluded(className);

        // if not, check if parent's connection is excluded
        if (!excluded) {
          MetaClass metaClass2 = object.getMetaClass();
          String parentClassName = metaClass2.getGUIName();
          String connectionName = parentClassName + "." + choice.getGUIName();
          excluded = options.isExcluded(connectionName);
        } // end if
      } // end if

      if (!excluded) {
        if (childrenMetaClass.isAssignableFrom(metaClass)) {
          expanded |= expandChild(output, object, dbChild, 1, state, options);
        } // end if
      } // end if
    } // end if

    if ((!expanded) && (nullModifier != null)) {
      expanded |= nullModifier.expand(output, object, options);
    } // end if

    return expanded;
  } // end expandMetaChoice
  // add childrenMetaClass to filter only children of that class
  // if null, scan each child
  protected boolean expandMetaRelationN(
      Writer output,
      DbObject object,
      MetaRelationship metaRelation,
      boolean state[],
      MetaClass childrenMetaClass,
      RuleOptions options)
      throws DbException, IOException, RuleException {

    boolean expanded = false;
    DbEnumeration dbEnumChildren = null;
    DbRelationN relationN = (DbRelationN) object.get(metaRelation);
    if (childrenMetaClass == null) {
      dbEnumChildren = relationN.elements();
    } else {
      dbEnumChildren = relationN.elements(childrenMetaClass);
    }

    // a first pass to find out the number of children
    int nbChildren = 0;
    try {
      while (dbEnumChildren.hasMoreElements()) {
        dbEnumChildren.nextElement();
        nbChildren++;

        // Cancel everything if the user has decided to stop the
        // operation
        if (options != null) {
          Controller controller = options.m_controller;
          if (controller != null) {
            boolean can_continue = controller.checkPoint();
            if (!can_continue) {
              throw new RuleException(controller.getAbortedString());
            }
          }
        } // end if
      } // end while
    } finally {
      dbEnumChildren.close();
    }

    // cannot rollback to the first element, so recreate the enumeration
    if (childrenMetaClass == null) {
      dbEnumChildren = relationN.elements();
    } else {
      dbEnumChildren = relationN.elements(childrenMetaClass);
    }

    try {
      while (dbEnumChildren.hasMoreElements()) {
        DbObject child = (DbObject) dbEnumChildren.nextElement();
        MetaClass metaClass = child.getMetaClass();
        String className = metaClass.getGUIName();
        boolean excluded = false;

        // check if child's class is excluded
        if (options != null) {
          excluded = options.isExcluded(className);

          // if not, check if parent's connection is excluded
          if (!excluded) {
            metaClass = object.getMetaClass();
            String parentClassName = metaClass.getGUIName();
            String connectionName = parentClassName + "." + metaRelation.getGUIName();
            excluded = options.isExcluded(connectionName);
          } // end if
        } // end if

        if (!excluded) {
          expanded |= expandChild(output, object, child, nbChildren, state, options);
        } // end if
      } // end while
    } finally {
      dbEnumChildren.close();
    }

    boolean atLeastOneChildPrinted = state[1];

    if ((atLeastOneChildPrinted) && (suffixModifier != null)) {
      expanded |= suffixModifier.expand(output, object, options);
    } else if ((!atLeastOneChildPrinted) && (nullModifier != null)) {
      expanded |= nullModifier.expand(output, object, options);
    }

    return expanded;
  } // end expandMetaRelationN
  private boolean expandMetaRelation1(
      Writer output,
      DbObject object,
      MetaRelation1 metaRelation1,
      boolean state[],
      MetaClass childrenMetaClass,
      RuleOptions options)
      throws DbException, IOException, RuleException {

    boolean expanded = false;
    Object child;
    if (childrenMetaClass != null) {
      if (metaRelation1 == DbObject.fComposite) {
        child = object.getCompositeOfType(childrenMetaClass);
      } else {
        child = object.get(metaRelation1);
        if (child instanceof DbObject) {
          DbObject dbChild = (DbObject) child;
          MetaClass mc = dbChild.getMetaClass();
          if (mc != childrenMetaClass) {
            child = null;
          }
        }
      }
    } else {
      child = object.get(metaRelation1);
    } // end if

    if (child != null) {
      if (child instanceof DbObject) {
        DbObject dbChild = (DbObject) child;
        MetaClass metaClass = dbChild.getMetaClass();
        String className = metaClass.getGUIName();
        boolean excluded = false;

        // check if child's class is excluded
        if (options != null) {
          excluded = options.isExcluded(className);

          // if not, check if parent's connection is excluded
          if (!excluded) {
            metaClass = object.getMetaClass();
            String parentClassName = metaClass.getGUIName();
            String connectionName = parentClassName + "." + metaRelation1.getGUIName();
            excluded = options.isExcluded(connectionName);
          } // end if
        } // end if

        if (!excluded) {
          expanded |= expandChild(output, object, dbChild, 1, state, options);
        } // end if
      } // end if
    } // end if

    // if a SUF modifier has to be expanded
    if ((expanded) && (suffixModifier != null)) {
      suffixModifier.expand(output, object, options);
    }

    // if a NULL modifier has to be expanded
    if ((!expanded) && (nullModifier != null)) {
      expanded |= nullModifier.expand(output, object, options);
    }

    return expanded;
  } // end expandMetaRelation1