content: /**
   * Portions Copyright 2001-2003 Sun Microsystems, Inc.
   * Portions Copyright 1999-2001 Language Technologies Institute,
   * Carnegie Mellon University.
   * All Rights Reserved.  Use is subject to license terms.
   * <p/>
   * See the file "license.terms" for information on usage and
   * redistribution of this file, and for a DISCLAIMER OF ALL
   * WARRANTIES.
   */
  package com.sun.speech.freetts;
  
  import java.io.PrintWriter;
  import java.util.StringTokenizer;
  
  /**
   * Represents a node in a Relation. Items can have shared contents but
   * each item has its own set of Daughters. The shared contents of an
   * item (represented by ItemContents) includes the feature set for the
   * item and the set of all relations that this item is contained in.
   * An item can be contained in a number of relations and as daughters
   * to other items. This class is used to keep track of all of these
   * relationships. There may be many instances of item that reference
   * the same shared ItemContents.
   */
  public class Item implements Dumpable {
      private Relation ownerRelation;
      private ItemContents contents;
      private Item parent;
      private Item daughter;
      private Item next;
      private Item prev;
  
  
      /**
       * Creates an item. The item is coupled to a particular
       * Relation. If shared contents is null a new sharedContents is
       * created.
       *
       * @param relation       the relation that owns this item
       * @param sharedContents the contents that is shared with others.
       *                       If null, a new sharedContents is created.
       */
      public Item(Relation relation, ItemContents sharedContents) {
          ownerRelation = relation;
          if (sharedContents != null) {
              contents = sharedContents;
          } else {
              contents = new ItemContents();
          }
          parent = null;
          daughter = null;
          next = null;
          prev = null;
  
  
          getSharedContents().addItemRelation(relation.getName(), this);
      }
  
      /**
       * Finds the item in the given relation that has the same shared
       * contents.
       *
       * @param relationName the relation of interest
       * @return the item as found in the given relation or null if not
       * found
       */
      public Item getItemAs(String relationName) {
          return getSharedContents().getItemRelation(relationName);
      }
  
  
      /**
       * Retrieves the owning Relation.
       *
       * @return the relation that owns this item
       */
      public Relation getOwnerRelation() {
          return ownerRelation;
      }
  
      /**
       * Retrieves the shared contents for this item.
       *
       * @return the shared item contents
       */
      public ItemContents getSharedContents() {
          return contents;
      }
  
      /**
       * Determines if this item has daughters.
       *
       * @return true if this item has daughters
       */
      public boolean hasDaughters() {
          return daughter != null;
      }
  
      /**
       * Retrieves the first daughter of this item.
       *
       * @return the first daughter or null if none
       */
      public Item getDaughter() {
          return daughter;
      }
  
      /**
       * Retrieves the Nth daughter of this item.
       *
       * @param which the index of the daughter to return
       * @return the Nth daughter or null if none at the given index
       */
      public Item getNthDaughter(int which) {
          Item d = daughter;
          int count = 0;
          while (count++ != which && d != null) {
              d = d.next;
          }
          return d;
      }
  
      /**
       * Retrieves the last daughter of this item.
       *
       * @return the last daughter or null if none at the given index
       */
      public Item getLastDaughter() {
          Item d = daughter;
          if (d == null) {
              return null;
          }
          while (d.next != null) {
              d = d.next;
          }
          return d;
      }
  
      /**
       * Adds the given item as a daughter to this item.
       *
       * @param item the new daughter
       */
      public Item addDaughter(Item item) {
          Item newItem;
          ItemContents contents;
  
          Item p = getLastDaughter();
  
          if (p != null) {
              newItem = p.appendItem(item);
          } else {
              if (item == null) {
                  contents = new ItemContents();
              } else {
                  contents = item.getSharedContents();
              }
              newItem = new Item(getOwnerRelation(), contents);
              newItem.parent = this;
              daughter = newItem;
          }
          return newItem;
      }
  
      /**
       * Creates a new Item, adds it as a daughter to this item
       * and returns the new item.
       *
       * @return the newly created item that was added as a daughter
       */
      public Item createDaughter() {
          return addDaughter(null);
      }
  
  
      /**
       * Returns the parent of this item.
       *
       * @return the parent of this item
       */
      public Item getParent() {
          Item n;
          for (n = this; n.prev != null; n = n.prev) {
              if (n == null) {
                  return null;
              }
          }
          return n.parent;
      }
  
  
      /**
       * Sets the parent of this item.
       *
       * @param parent the parent of this item
       */
      /*
      private void setParent(Item parent) {
  	this.parent = parent;
      }
      */
  
      /**
       * Returns the utterance associated with this item.
       *
       * @return the utterance that contains this item
       */
      public Utterance getUtterance() {
          return getOwnerRelation().getUtterance();
      }
  
      /**
       * Returns the feature set of this item.
       *
       * @return the feature set of this item
       */
      public FeatureSet getFeatures() {
          return getSharedContents().getFeatures();
      }
  
      /**
       * Dumps out this item to the given output stream.
       *
       * @param out where to send the output
       * @param pad the leading whitspace
       */
      public void dump(PrintWriter out, int pad, String title) {
          String itemName = title + ":" + toString();
          getFeatures().dump(out, pad, itemName);
          if (hasDaughters()) {
              Item daughter = getDaughter();
              while (daughter != null) {
                  daughter.dump(out, pad + 8, "d");
                  daughter = daughter.next;
              }
          }
      }
  
      /**
       * Finds the feature by following the given path.
       * Path is a string of
       * ":" or "." separated strings with the following interpretations:
       * <ul>
       * <li> n - next item
       * <li> p - previous item
       * <li> parent - the parent
       * <li> daughter - the daughter
       * <li> daughter1 - same as daughter
       * <li> daughtern - the last daughter
       * <li> R:relname - the item as found in the given relation 'relname'
       * </ul>
       * The last element of the path will be interpreted as a
       * voice/language specific feature function (if present) or an
       * item feature name. If the feature function exists it will be
       * called with the item specified by the path, otherwise, a
       * feature will be retrieved with the given name. If neither exist
       * than a String "0" is returned.
       *
       * @param pathAndFeature the path to follow
       */
      public Object findFeature(String pathAndFeature) {
          int lastDot;
          String feature;
          String path;
          Item item;
          FeatureProcessor fp;
  
          Voice voice = getOwnerRelation().getUtterance().getVoice();
          Object results = null;
  
  
          lastDot = pathAndFeature.lastIndexOf(".");
          // string can be of the form "p.feature" or just "feature"
  
          if (lastDot == -1) {
              feature = pathAndFeature;
              path = null;
          } else {
              feature = pathAndFeature.substring(lastDot + 1);
              path = pathAndFeature.substring(0, lastDot);
          }
  
  
          item = findItem(path);
          if (item != null) {
              fp = voice.getFeatureProcessor(feature);
  
              if (fp != null) {
                  try {
                      results = fp.process(item);
                  } catch (ProcessException pe) {
                      System.err.println("Trouble while processing " +
                              fp.toString());
                  }
              } else {
                  results = item.getFeatures().getObject(feature);
              }
          }
          results = (results == null)
                  ? "0"
                  : results;
  
          // System.out.println("FI " + pathAndFeature + " are " + results);
  
          return results;
      }
  
      /**
       * Finds the item specified by the given path.
       * <p/>
       * Path is a string of ":" or
       * "." separated strings with the following interpretations:
       * <ul>
       * <li> n - next item
       * <li> p - previous item
       * <li> parent - the parent
       * <li> daughter - the daughter
       * <li> daughter1 - same as daughter
       * <li> daughtern - the last daughter
       * <li> R:relname - the item as found in the given relation 'relname'
       * </ul>
       * If the given path takes us outside of the bounds of the item
       * graph, then list access exceptions will be thrown.
       *
       * @param path the path to follow
       * @return the item at the given path
       */
      public Item findItem(String path) {
          Item pitem = this;
          StringTokenizer tok;
  
          if (path == null) {
              return this;
          }
  
          tok = new StringTokenizer(path, ":.");
  
          while (pitem != null && tok.hasMoreTokens()) {
              String token = tok.nextToken();
              if (token.equals("n")) {
                  pitem = pitem.getNext();
              } else if (token.equals("p")) {
                  pitem = pitem.getPrevious();
              } else if (token.equals("nn")) {
                  pitem = pitem.getNext();
                  if (pitem != null) {
                      pitem = pitem.getNext();
                  }
              } else if (token.equals("pp")) {
                  pitem = pitem.getPrevious();
                  if (pitem != null) {
                      pitem = pitem.getPrevious();
                  }
              } else if (token.equals("parent")) {
                  pitem = pitem.getParent();
              } else if (token.equals("daughter") || token.equals("daughter1")) {
                  pitem = pitem.getDaughter();
              } else if (token.equals("daughtern")) {
                  pitem = pitem.getLastDaughter();
              } else if (token.equals("R")) {
                  String relationName = tok.nextToken();
                  pitem = pitem.getSharedContents().getItemRelation(relationName);
              } else {
                  System.out.println("findItem: bad feature " + token +
                          " in " + path);
              }
          }
          return pitem;
      }
  
  
      /**
       * Gets the next item in this list.
       *
       * @return the next item or null
       */
      public Item getNext() {
          return next;
      }
  
  
      /**
       * Gets the previous item in this list.
       *
       * @return the previous item or null
       */
      public Item getPrevious() {
          return prev;
      }
  
  
      /**
       * Appends an item in this list after this item.
       *
       * @param originalItem new item has shared contents with this
       *                     item (or * null)
       * @return the newly appended item
       */
      public Item appendItem(Item originalItem) {
          ItemContents contents;
          Item newItem;
  
          if (originalItem == null) {
              contents = null;
          } else {
              contents = originalItem.getSharedContents();
          }
  
          newItem = new Item(getOwnerRelation(), contents);
          newItem.next = this.next;
          if (this.next != null) {
              this.next.prev = newItem;
          }
  
          attach(newItem);
  
          if (this.ownerRelation.getTail() == this) {
              this.ownerRelation.setTail(newItem);
          }
          return newItem;
      }
  
      /**
       * Attaches/appends an item to this one.
       *
       * @param item the item to append
       */
      void attach(Item item) {
          this.next = item;
          item.prev = this;
      }
  
      /**
       * Prepends an item in this list before this item.
       *
       * @param originalItem new item has shared contents with this
       *                     item (or * null)
       * @return the newly appended item
       */
      public Item prependItem(Item originalItem) {
          ItemContents contents;
          Item newItem;
  
          if (originalItem == null) {
              contents = null;
          } else {
              contents = originalItem.getSharedContents();
          }
  
          newItem = new Item(getOwnerRelation(), contents);
          newItem.prev = this.prev;
          if (this.prev != null) {
              this.prev.next = newItem;
          }
          newItem.next = this;
          this.prev = newItem;
          if (this.parent != null) {
              this.parent.daughter = newItem;
              newItem.parent = this.parent;
              this.parent = null;
          }
          if (this.ownerRelation.getHead() == this) {
              this.ownerRelation.setHead(newItem);
          }
          return newItem;
      }
  
  
      // Inherited from object
      public String toString() {
          // if we have a feature called 'name' use that
          // otherwise fall back on the default.
          String name = getFeatures().getString("name");
          if (name == null) {
              name = "";
          }
          return name;
      }
  
      /**
       * Determines if the shared contents of the two items are the same.
       *
       * @param otherItem the item to compare
       * @return true if the shared contents are the same
       */
      public boolean equalsShared(Item otherItem) {
          if (otherItem == null) {
              return false;
          } else {
              return getSharedContents().equals(otherItem.getSharedContents());
          }
      }
  }
  