/**
   * Serialises out the String. There are special rules about where we can and can't split onto
   * Continue records.
   */
  public void serialize(ContinuableRecordOutput out) {
    int numberOfRichTextRuns = 0;
    int extendedDataSize = 0;
    if (isRichText() && field_4_format_runs != null) {
      numberOfRichTextRuns = field_4_format_runs.size();
    }
    if (isExtendedText() && field_5_ext_rst != null) {
      extendedDataSize = 4 + field_5_ext_rst.getDataSize();
    }

    // Serialise the bulk of the String
    // The writeString handles tricky continue stuff for us
    out.writeString(field_3_string, numberOfRichTextRuns, extendedDataSize);

    if (numberOfRichTextRuns > 0) {

      // This will ensure that a run does not split a continue
      for (int i = 0; i < numberOfRichTextRuns; i++) {
        if (out.getAvailableSpace() < 4) {
          out.writeContinue();
        }
        FormatRun r = field_4_format_runs.get(i);
        r.serialize(out);
      }
    }

    if (extendedDataSize > 0) {
      field_5_ext_rst.serialize(out);
    }
  }
  /**
   * return a character representation of the fields of this record
   *
   * @return String of output for biffviewer etc.
   */
  public String getDebugInfo() {
    StringBuffer buffer = new StringBuffer();

    buffer.append("[UNICODESTRING]\n");
    buffer
        .append("    .charcount       = ")
        .append(Integer.toHexString(getCharCount()))
        .append("\n");
    buffer
        .append("    .optionflags     = ")
        .append(Integer.toHexString(getOptionFlags()))
        .append("\n");
    buffer.append("    .string          = ").append(getString()).append("\n");
    if (field_4_format_runs != null) {
      for (int i = 0; i < field_4_format_runs.size(); i++) {
        FormatRun r = field_4_format_runs.get(i);
        buffer.append("      .format_run" + i + "          = ").append(r.toString()).append("\n");
      }
    }
    if (field_5_ext_rst != null) {
      buffer.append("    .field_5_ext_rst          = ").append("\n");
      buffer.append(field_5_ext_rst.toString()).append("\n");
    }
    buffer.append("[/UNICODESTRING]\n");
    return buffer.toString();
  }
  /**
   * construct a unicode string record and fill its fields, ID is ignored
   *
   * @param in the RecordInputstream to read the record from
   */
  public UnicodeString(RecordInputStream in) {
    field_1_charCount = in.readShort();
    field_2_optionflags = in.readByte();

    int runCount = 0;
    int extensionLength = 0;
    // Read the number of rich runs if rich text.
    if (isRichText()) {
      runCount = in.readShort();
    }
    // Read the size of extended data if present.
    if (isExtendedText()) {
      extensionLength = in.readInt();
    }

    boolean isCompressed = ((field_2_optionflags & 1) == 0);
    if (isCompressed) {
      field_3_string = in.readCompressedUnicode(getCharCount());
    } else {
      field_3_string = in.readUnicodeLEString(getCharCount());
    }

    if (isRichText() && (runCount > 0)) {
      field_4_format_runs = new ArrayList<FormatRun>(runCount);
      for (int i = 0; i < runCount; i++) {
        field_4_format_runs.add(new FormatRun(in));
      }
    }

    if (isExtendedText() && (extensionLength > 0)) {
      field_5_ext_rst = new ExtRst(new ContinuableRecordInput(in), extensionLength);
      if (field_5_ext_rst.getDataSize() + 4 != extensionLength) {
        System.err.println(
            "ExtRst was supposed to be "
                + extensionLength
                + " bytes long, but seems to actually be "
                + (field_5_ext_rst.getDataSize() + 4));
      }
    }
  }
  /**
   * Our handling of equals is inconsistent with compareTo. The trouble is because we don't truely
   * understand rich text fields yet it's difficult to make a sound comparison.
   *
   * @param o The object to compare.
   * @return true if the object is actually equal.
   */
  public boolean equals(Object o) {
    if (!(o instanceof UnicodeString)) {
      return false;
    }
    UnicodeString other = (UnicodeString) o;

    // OK lets do this in stages to return a quickly, first check the actual string
    boolean eq =
        ((field_1_charCount == other.field_1_charCount)
            && (field_2_optionflags == other.field_2_optionflags)
            && field_3_string.equals(other.field_3_string));
    if (!eq) return false;

    // OK string appears to be equal but now lets compare formatting runs
    if ((field_4_format_runs == null) && (other.field_4_format_runs == null))
      // Strings are equal, and there are not formatting runs.
      return true;
    if (((field_4_format_runs == null) && (other.field_4_format_runs != null))
        || (field_4_format_runs != null) && (other.field_4_format_runs == null))
      // Strings are equal, but one or the other has formatting runs
      return false;

    // Strings are equal, so now compare formatting runs.
    int size = field_4_format_runs.size();
    if (size != other.field_4_format_runs.size()) return false;

    for (int i = 0; i < size; i++) {
      FormatRun run1 = field_4_format_runs.get(i);
      FormatRun run2 = other.field_4_format_runs.get(i);

      if (!run1.equals(run2)) return false;
    }

    // Well the format runs are equal as well!, better check the ExtRst data
    if (field_5_ext_rst == null && other.field_5_ext_rst == null) {
      // Good
    } else if (field_5_ext_rst != null && other.field_5_ext_rst != null) {
      int extCmp = field_5_ext_rst.compareTo(other.field_5_ext_rst);
      if (extCmp == 0) {
        // Good
      } else {
        return false;
      }
    } else {
      return false;
    }

    // Phew!! After all of that we have finally worked out that the strings
    // are identical.
    return true;
  }
  public Object clone() {
    UnicodeString str = new UnicodeString();
    str.field_1_charCount = field_1_charCount;
    str.field_2_optionflags = field_2_optionflags;
    str.field_3_string = field_3_string;
    if (field_4_format_runs != null) {
      str.field_4_format_runs = new ArrayList<FormatRun>();
      for (FormatRun r : field_4_format_runs) {
        str.field_4_format_runs.add(new FormatRun(r._character, r._fontIndex));
      }
    }
    if (field_5_ext_rst != null) {
      str.field_5_ext_rst = field_5_ext_rst.clone();
    }

    return str;
  }
 protected ExtRst clone() {
   ExtRst ext = new ExtRst();
   ext.reserved = reserved;
   ext.formattingFontIndex = formattingFontIndex;
   ext.formattingOptions = formattingOptions;
   ext.numberOfRuns = numberOfRuns;
   ext.phoneticText = phoneticText;
   ext.phRuns = new PhRun[phRuns.length];
   for (int i = 0; i < ext.phRuns.length; i++) {
     ext.phRuns[i] =
         new PhRun(
             phRuns[i].phoneticTextFirstCharacterOffset,
             phRuns[i].realTextFirstCharacterOffset,
             phRuns[i].realTextLength);
   }
   return ext;
 }
  public int compareTo(UnicodeString str) {

    int result = getString().compareTo(str.getString());

    // As per the equals method lets do this in stages
    if (result != 0) return result;

    // OK string appears to be equal but now lets compare formatting runs
    if ((field_4_format_runs == null) && (str.field_4_format_runs == null))
      // Strings are equal, and there are no formatting runs.
      return 0;

    if ((field_4_format_runs == null) && (str.field_4_format_runs != null))
      // Strings are equal, but one or the other has formatting runs
      return 1;
    if ((field_4_format_runs != null) && (str.field_4_format_runs == null))
      // Strings are equal, but one or the other has formatting runs
      return -1;

    // Strings are equal, so now compare formatting runs.
    int size = field_4_format_runs.size();
    if (size != str.field_4_format_runs.size()) return size - str.field_4_format_runs.size();

    for (int i = 0; i < size; i++) {
      FormatRun run1 = field_4_format_runs.get(i);
      FormatRun run2 = str.field_4_format_runs.get(i);

      result = run1.compareTo(run2);
      if (result != 0) return result;
    }

    // Well the format runs are equal as well!, better check the ExtRst data
    if ((field_5_ext_rst == null) && (str.field_5_ext_rst == null)) return 0;
    if ((field_5_ext_rst == null) && (str.field_5_ext_rst != null)) return 1;
    if ((field_5_ext_rst != null) && (str.field_5_ext_rst == null)) return -1;

    result = field_5_ext_rst.compareTo(str.field_5_ext_rst);
    if (result != 0) return result;

    // Phew!! After all of that we have finally worked out that the strings
    // are identical.
    return 0;
  }