/**
   * Increases the capacity of and internally reorganizes this SymbolTable, in order to accommodate
   * and access its entries more efficiently. This method is called automatically when the number of
   * keys in the SymbolTable exceeds this hashtable's capacity and load factor.
   */
  protected void rehash() {

    int oldCapacity = fBuckets.length;
    SREntry[] oldTable = fBuckets;

    int newCapacity = oldCapacity * 2 + 1;
    SREntry[] newTable = new SREntry[newCapacity];

    fThreshold = (int) (newCapacity * fLoadFactor);
    fBuckets = newTable;
    fTableSize = fBuckets.length;

    for (int i = oldCapacity; i-- > 0; ) {
      for (SREntry old = oldTable[i]; old != null; ) {
        SREntry e = old;
        old = old.next;

        SREntryData data = (SREntryData) e.get();
        if (data != null) {
          int index = hash(data.characters, 0, data.characters.length) % newCapacity;
          if (newTable[index] != null) {
            newTable[index].prev = e;
          }
          e.next = newTable[index];
          e.prev = null;
          newTable[index] = e;
        } else {
          fCount--;
        }
      }
    }
  }
  /**
   * Adds the specified symbol to the symbol table and returns a reference to the unique symbol. If
   * the symbol already exists, the previous symbol reference is returned instead, in order
   * guarantee that symbol references remain unique.
   *
   * @param buffer The buffer containing the new symbol.
   * @param offset The offset into the buffer of the new symbol.
   * @param length The length of the new symbol in the buffer.
   */
  public String addSymbol(char[] buffer, int offset, int length) {
    clean();
    // search for identical symbol
    int bucket = hash(buffer, offset, length) % fTableSize;
    OUTER:
    for (SREntry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
      SREntryData data = (SREntryData) entry.get();
      if (data == null) {
        continue;
      }
      if (length == data.characters.length) {
        for (int i = 0; i < length; i++) {
          if (buffer[offset + i] != data.characters[i]) {
            continue OUTER;
          }
        }
        return data.symbol;
      }
    }

    if (fCount >= fThreshold) {
      // Rehash the table if the threshold is exceeded
      rehash();
      bucket = hash(buffer, offset, length) % fTableSize;
    }

    // add new entry
    String symbol = new String(buffer, offset, length).intern();
    SREntry entry =
        new SREntry(symbol, buffer, offset, length, fBuckets[bucket], bucket, fReferenceQueue);
    fBuckets[bucket] = entry;
    ++fCount;
    return symbol;
  } // addSymbol(char[],int,int):String
  /**
   * Adds the specified symbol to the symbol table and returns a reference to the unique symbol. If
   * the symbol already exists, the previous symbol reference is returned instead, in order
   * guarantee that symbol references remain unique.
   *
   * @param symbol The new symbol.
   */
  public String addSymbol(String symbol) {
    clean();
    // search for identical symbol
    int bucket = hash(symbol) % fTableSize;
    for (SREntry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
      SREntryData data = (SREntryData) entry.get();
      if (data == null) {
        continue;
      }
      if (data.symbol.equals(symbol)) {
        return data.symbol;
      }
    }

    if (fCount >= fThreshold) {
      // Rehash the table if the threshold is exceeded
      rehash();
      bucket = hash(symbol) % fTableSize;
    }

    // add new entry
    symbol = symbol.intern();
    SREntry entry = new SREntry(symbol, fBuckets[bucket], bucket, fReferenceQueue);
    fBuckets[bucket] = entry;
    ++fCount;
    return symbol;
  } // addSymbol(String):String
 private void initialize(SREntry next, int bucket) {
   this.next = next;
   if (next != null) {
     next.prev = this;
   }
   this.prev = null;
   this.bucket = bucket;
 }
  /**
   * Returns true if the symbol table already contains the specified symbol.
   *
   * @param buffer The buffer containing the symbol to look for.
   * @param offset The offset into the buffer.
   * @param length The length of the symbol in the buffer.
   */
  public boolean containsSymbol(char[] buffer, int offset, int length) {

    // search for identical symbol
    int bucket = hash(buffer, offset, length) % fTableSize;
    OUTER:
    for (SREntry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
      SREntryData data = (SREntryData) entry.get();
      if (data == null) {
        continue;
      }
      if (length == data.characters.length) {
        for (int i = 0; i < length; i++) {
          if (buffer[offset + i] != data.characters[i]) {
            continue OUTER;
          }
        }
        return true;
      }
    }

    return false;
  } // containsSymbol(char[],int,int):boolean
  /**
   * Returns true if the symbol table already contains the specified symbol.
   *
   * @param symbol The symbol to look for.
   */
  public boolean containsSymbol(String symbol) {

    // search for identical symbol
    int bucket = hash(symbol) % fTableSize;
    int length = symbol.length();
    OUTER:
    for (SREntry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
      SREntryData data = (SREntryData) entry.get();
      if (data == null) {
        continue;
      }
      if (length == data.characters.length) {
        for (int i = 0; i < length; i++) {
          if (symbol.charAt(i) != data.characters[i]) {
            continue OUTER;
          }
        }
        return true;
      }
    }

    return false;
  } // containsSymbol(String):boolean