synchronized int unlockReference(LockTable lset, Lockable ref, Object qualifier, Object group) {

    // look for locks matching our reference and qualifier.
    HashMap dl = (HashMap) groups.get(group);
    if (dl == null) return 0;

    Lock lockInGroup = lset.unlockReference(this, ref, qualifier, dl);
    if (lockInGroup == null) {
      return 0;
    }

    if (lockInGroup.getCount() == 1) {

      if (dl.isEmpty()) {
        groups.remove(group);
        saveGroup(dl);
        if ((callbackGroup != null) && group.equals(callbackGroup)) {
          nextLimitCall = limit;
        }
      }

      return 1;
    }

    // the lock item will be left in the group
    lockInGroup.count--;
    dl.put(lockInGroup, lockInGroup);
    return 1;
  }
  /**
   * Return a count of the number of locks held by this space. The argument bail indicates at which
   * point the counting should bail out and return the current count. This routine will bail if the
   * count is greater than bail. Thus this routine is intended to for deadlock code to find the
   * space with the fewest number of locks.
   */
  synchronized int deadlockCount(int bail) {

    int count = 0;

    for (Iterator it = groups.values().iterator(); it.hasNext(); ) {
      HashMap group = (HashMap) it.next();
      for (Iterator locks = group.keySet().iterator(); locks.hasNext(); ) {
        Lock lock = (Lock) locks.next();
        count += lock.getCount();
        if (count > bail) return count;
      }
    }
    return count;
  }
  /** Add a lock to a group. */
  protected synchronized void addLock(Object group, Lock lock) throws StandardException {

    Lock lockInGroup = null;

    HashMap dl = (HashMap) groups.get(group);
    if (dl == null) {
      dl = getGroupMap(group);
    } else if (lock.getCount() != 1) {
      lockInGroup = (Lock) dl.get(lock);
    }

    if (lockInGroup == null) {
      lockInGroup = lock.copy();
      dl.put(lockInGroup, lockInGroup);
    }
    lockInGroup.count++;

    if (inLimit) return;

    if (!group.equals(callbackGroup)) return;

    int groupSize = dl.size();

    if (groupSize > nextLimitCall) {

      inLimit = true;
      callback.reached(
          this,
          group,
          limit,
          new LockList(java.util.Collections.enumeration(dl.keySet())),
          groupSize);
      inLimit = false;

      // see when the next callback should occur, if the callback
      // failed to release a sufficent amount of locks then
      // delay until another "limit" locks are obtained.
      int newGroupSize = dl.size();
      if (newGroupSize < (limit / 2)) nextLimitCall = limit;
      else if (newGroupSize < (nextLimitCall / 2)) nextLimitCall -= limit;
      else nextLimitCall += limit;
    }
  }
  private void mergeGroups(HashMap from, HashMap into) {

    for (Iterator e = from.keySet().iterator(); e.hasNext(); ) {

      Object lock = e.next();

      Object lockI = into.get(lock);

      if (lockI == null) {
        // lock is only in from list
        into.put(lock, lock);
      } else {
        // merge the locks
        Lock fromL = (Lock) lock;
        Lock intoL = (Lock) lockI;

        intoL.count += fromL.getCount();
      }
    }
  }