/**
  * Add a new message to the beginning i.e. send it as soon as possible (e.g. if we tried to send
  * it and failed); it is assumed to already be urgent.
  */
 public void addFirst(MessageItem item) {
   long id = item.getID();
   Items list;
   if (itemsByID == null) {
     itemsByID = new HashMap<Long, Items>();
     if (nonEmptyItemsWithID == null) nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
     list = new Items(id);
     nonEmptyItemsWithID.push(list);
     itemsByID.put(id, list);
   } else {
     list = itemsByID.get(id);
     if (list == null) {
       list = new Items(id);
       if (nonEmptyItemsWithID == null) nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
       nonEmptyItemsWithID.unshift(list);
       itemsByID.put(id, list);
     } else {
       if (list.items.isEmpty()) {
         assert (list.getParent() == emptyItemsWithID);
         // It already exists, so it has a valid time.
         // Which is probably in the past, so use Forward.
         moveFromEmptyToNonEmptyForward(list);
       } else assert (list.getParent() == nonEmptyItemsWithID);
     }
   }
   list.addFirst(item);
 }
 /**
  * Add urgent messages, then non-urgent messages. Add a load message if need to.
  *
  * @param size
  * @param minSize
  * @param maxSize
  * @param now
  * @param messages
  * @return
  */
 int addPriorityMessages(
     int size,
     int minSize,
     int maxSize,
     long now,
     ArrayList<MessageItem> messages,
     MutableBoolean incomplete) {
   synchronized (PeerMessageQueue.this) {
     // Urgent messages first.
     if (logMINOR) {
       int nonEmpty = nonEmptyItemsWithID == null ? 0 : nonEmptyItemsWithID.size();
       int empty = emptyItemsWithID == null ? 0 : emptyItemsWithID.size();
       int byID = itemsByID == null ? 0 : itemsByID.size();
       if (nonEmpty + empty < byID) {
         Logger.error(
             this,
             "Leaking itemsByID? non empty = "
                 + nonEmpty
                 + " empty = "
                 + empty
                 + " by ID = "
                 + byID
                 + " on "
                 + this);
       } else if (logDEBUG)
         Logger.debug(
             this,
             "Items: non empty "
                 + nonEmpty
                 + " empty "
                 + empty
                 + " by ID "
                 + byID
                 + " on "
                 + this);
     }
     moveToUrgent(now);
     clearOldNonUrgent(now);
     size = addUrgentMessages(size, minSize, maxSize, now, messages);
     if (size < 0) {
       size = -size;
       incomplete.value = true;
       return size;
     }
     // If no more urgent messages, try to add some non-urgent messages too.
     size = addNonUrgentMessages(size, minSize, maxSize, now, messages);
     if (size < 0) {
       size = -size;
       incomplete.value = true;
     }
     return size;
   }
 }
 private void addToEmptyBackward(Items list) {
   if (emptyItemsWithID == null) emptyItemsWithID = new DoublyLinkedListImpl<Items>();
   Enumeration<Items> it = emptyItemsWithID.reverseElements();
   while (it.hasMoreElements()) {
     Items compare = it.nextElement();
     if (compare.timeLastSent <= list.timeLastSent) {
       emptyItemsWithID.insertNext(compare, list);
       return;
     }
   }
   emptyItemsWithID.unshift(list);
 }
 private void addToNonEmptyForward(Items list) {
   if (nonEmptyItemsWithID == null) nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
   Enumeration<Items> it = nonEmptyItemsWithID.elements();
   while (it.hasMoreElements()) {
     Items compare = it.nextElement();
     if (compare.timeLastSent >= list.timeLastSent) {
       nonEmptyItemsWithID.insertPrev(compare, list);
       return;
     }
   }
   nonEmptyItemsWithID.unshift(list);
 }
 private void moveToUrgent(long now) {
   if (itemsNonUrgent == null) return;
   ListIterator<MessageItem> it = itemsNonUrgent.listIterator();
   int moved = 0;
   while (it.hasNext()) {
     MessageItem item = it.next();
     if (item.submitted + PacketSender.MAX_COALESCING_DELAY <= now) {
       // Move to urgent list
       long id = item.getID();
       Items list;
       if (itemsByID == null) {
         itemsByID = new HashMap<Long, Items>();
         if (nonEmptyItemsWithID == null)
           nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
         list = new Items(id);
         nonEmptyItemsWithID.push(list);
         itemsByID.put(id, list);
       } else {
         list = itemsByID.get(id);
         if (list == null) {
           list = new Items(id);
           if (nonEmptyItemsWithID == null)
             nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
           // In order to ensure fairness, we add it at the beginning.
           // addLast() is typically called by sendAsync().
           // If there are later items they are probably block transfers that are
           // already in progress; it is fairer to send the new item first.
           nonEmptyItemsWithID.unshift(list);
           itemsByID.put(id, list);
         } else {
           if (list.items.isEmpty()) {
             assert (list.getParent() == emptyItemsWithID);
             // It already exists, so it has a valid time.
             // Which is probably in the past, so use Forward.
             moveFromEmptyToNonEmptyForward(list);
           } else {
             assert (list.getParent() == nonEmptyItemsWithID);
           }
         }
       }
       list.addLast(item);
       it.remove();
       moved++;
     } else {
       if (logDEBUG && moved > 0)
         Logger.debug(this, "Moved " + moved + " items to urgent round-robin");
       return;
     }
   }
 }
 private void moveFromEmptyToNonEmptyForward(Items list) {
   // Presumably is in emptyItemsWithID
   assert (list.items.isEmpty());
   if (logMINOR) {
     if (list.getParent() == nonEmptyItemsWithID) {
       Logger.error(this, "Already in non-empty yet empty?!");
       return;
     }
   }
   if (emptyItemsWithID != null) emptyItemsWithID.remove(list);
   addToNonEmptyForward(list);
 }
 public void removeUIDs(Long[] list) {
   if (itemsByID == null) return;
   for (Long l : list) {
     Items items = itemsByID.get(l);
     if (items == null) continue;
     if (items.items.isEmpty()) {
       itemsByID.remove(l);
       assert (emptyItemsWithID != null);
       assert (items.getParent() == emptyItemsWithID);
       emptyItemsWithID.remove(items);
     }
   }
 }
 private void clearOldNonUrgent(long now) {
   int removed = 0;
   if (emptyItemsWithID == null) return;
   while (true) {
     if (emptyItemsWithID.isEmpty()) return;
     Items list = emptyItemsWithID.head();
     if (!list.items.isEmpty()) {
       // FIXME remove paranoia
       Logger.error(this, "List with items in emptyItemsWithID!!");
       emptyItemsWithID.remove(list);
       addToNonEmptyBackward(list);
       return;
     }
     if (list.timeLastSent == -1 || now - list.timeLastSent > FORGET_AFTER) {
       // FIXME: Urgh, what a braindead API! remove(Object) on a Map<Long, Items> !?!?!?!
       // Anyway we'd better check the return value!
       Items old = itemsByID.remove(list.id);
       if (old == null)
         Logger.error(this, "List was not in the items by ID tracker: " + list.id);
       else if (old != list)
         Logger.error(
             this,
             "Different list in the items by ID tracker: "
                 + old
                 + " not "
                 + list
                 + " for "
                 + list.id);
       emptyItemsWithID.remove(list);
       removed++;
     } else {
       if (logDEBUG && removed > 0)
         Logger.debug(this, "Removed " + removed + " old empty UID trackers");
       break;
     }
   }
 }
 public boolean removeMessage(MessageItem item) {
   long id = item.getID();
   Items list;
   if (itemsByID != null) {
     list = itemsByID.get(id);
     if (list != null) {
       if (list.remove(item)) {
         if (list.items.isEmpty()) {
           nonEmptyItemsWithID.remove(list);
           addToEmptyBackward(list);
         }
         return true;
       }
     }
   }
   if (itemsNonUrgent != null) return itemsNonUrgent.remove(item);
   else return false;
 }
 /**
  * Add messages to <code>messages</code> until there are no more messages to add or <code>size
  * </code> would exceed <code>maxSize</code>. If <code>size == maxSize</code>, a message in the
  * queue will be added even if it makes <code>size</code> exceed <code>maxSize</code>. If <code>
  * isUrgent</code> is set, only messages that are considered urgent are added.
  *
  * @param size the current size of <code>messages</code>
  * @param minSize the size when <code>messages</code> is empty
  * @param maxSize the maximum size of <code>messages</code>
  * @param now the current time
  * @param messages the list that messages will be added to
  * @param isUrgent <code>true</code> if only urgent messages should be added
  * @return the size of <code>messages</code>, multiplied by -1 if there were messages that
  *     didn't fit
  */
 private int addUrgentMessages(
     int size, int minSize, int maxSize, long now, ArrayList<MessageItem> messages) {
   assert (size >= 0);
   assert (minSize >= 0);
   assert (maxSize >= minSize);
   if (size < 0) size = -size; // FIXME remove extra paranoia
   int added = 0;
   while (true) {
     boolean addedNone = true;
     int lists = 0;
     if (nonEmptyItemsWithID == null) return size;
     lists += nonEmptyItemsWithID.size();
     Items list = nonEmptyItemsWithID.head();
     for (int i = 0; i < lists && list != null; i++) {
       if (list.items.isEmpty()) {
         // Should not happen, but check for it anyway since it keeps happening. :(
         Logger.error(this, "List is in nonEmptyItemsWithID yet it is empty?!: " + list);
         nonEmptyItemsWithID.remove(list);
         addToEmptyBackward(list);
         if (nonEmptyItemsWithID.isEmpty()) return size;
         list = nonEmptyItemsWithID.head();
         continue;
       }
       MessageItem item = list.items.getFirst();
       int thisSize = item.getLength();
       boolean oversize = false;
       if (size + 2 + thisSize > maxSize) {
         if (size == minSize) {
           // Won't fit regardless, send it on its own.
           oversize = true;
         } else {
           // Send what we have so far.
           if (logDEBUG && added != 0)
             Logger.debug(
                 this,
                 "Added "
                     + added
                     + " urgent messages, could add more but out of space at "
                     + size);
           return -size;
         }
       }
       size += 2 + thisSize;
       list.items.removeFirst();
       // Move to end of list.
       Items prev = list.getPrev();
       nonEmptyItemsWithID.remove(list);
       list.timeLastSent = now;
       if (!list.items.isEmpty()) {
         addToNonEmptyBackward(list);
       } else {
         addToEmptyBackward(list);
       }
       if (prev == null) list = nonEmptyItemsWithID.head();
       else list = prev.getNext();
       messages.add(item);
       added++;
       addedNone = false;
       if (oversize) {
         if (logDEBUG) Logger.debug(this, "Returning with oversize urgent message");
         return size;
       }
     }
     if (addedNone) {
       if (logDEBUG && added != 0)
         Logger.debug(
             this,
             "Added "
                 + added
                 + " urgent messages, size now "
                 + size
                 + " no more queued at this priority");
       return size;
     }
   }
 }
 private int addNonUrgentMessages(
     int size, int minSize, int maxSize, long now, ArrayList<MessageItem> messages) {
   assert (size >= 0);
   assert (minSize >= 0);
   assert (maxSize >= minSize);
   if (size < 0) size = -size; // FIXME remove extra paranoia
   if (itemsNonUrgent == null) return size;
   int added = 0;
   for (ListIterator<MessageItem> items = itemsNonUrgent.listIterator(); items.hasNext(); ) {
     MessageItem item = items.next();
     int thisSize = item.getLength();
     boolean oversize = false;
     if (size + 2 + thisSize > maxSize) {
       if (size == minSize) {
         // Won't fit regardless, send it on its own.
         oversize = true;
       } else {
         // Send what we have so far.
         if (logDEBUG && added != 0)
           Logger.debug(
               this,
               "Returning with "
                   + added
                   + " non-urgent messages (have more but they don't fit)");
         return -size;
       }
     }
     size += 2 + thisSize;
     items.remove();
     messages.add(item);
     if (itemsByID != null) {
       long id = item.getID();
       Items tracker = itemsByID.get(id);
       if (tracker != null) {
         tracker.timeLastSent = now;
         DoublyLinkedList<? super Items> parent = tracker.getParent();
         // Demote the corresponding tracker to maintain round-robin.
         if (tracker.items.isEmpty()) {
           if (emptyItemsWithID == null) emptyItemsWithID = new DoublyLinkedListImpl<Items>();
           if (parent == null) {
             Logger.error(this, "Tracker is in itemsByID but not in either list! (empty)");
           } else if (parent == emptyItemsWithID) {
             // Normal. Remove it so we can re-add it in the right place.
             emptyItemsWithID.remove(tracker);
           } else if (parent == nonEmptyItemsWithID) {
             Logger.error(this, "Tracker is in non empty items list when is empty");
             nonEmptyItemsWithID.remove(tracker);
           } else assert (false);
           addToEmptyBackward(tracker);
         } else {
           if (nonEmptyItemsWithID == null)
             nonEmptyItemsWithID = new DoublyLinkedListImpl<Items>();
           if (parent == null) {
             Logger.error(this, "Tracker is in itemsByID but not in either list! (non-empty)");
           } else if (parent == nonEmptyItemsWithID) {
             // Normal. Remove it so we can re-add it in the right place.
             nonEmptyItemsWithID.remove(tracker);
           } else if (parent == emptyItemsWithID) {
             Logger.error(this, "Tracker is in empty items list when is non-empty");
             emptyItemsWithID.remove(tracker);
           } else assert (false);
           addToNonEmptyBackward(tracker);
         }
       }
     }
     added++;
     if (oversize) {
       if (logDEBUG) Logger.debug(this, "Returning with non-urgent oversize message");
       return size;
     }
   }
   if (logDEBUG && added != 0)
     Logger.debug(this, "Returning with " + added + " non-urgent messages (all gone)");
   return size;
 }
 private void moveFromEmptyToNonEmptyBackward(Items list) {
   // Presumably is in emptyItemsWithID
   emptyItemsWithID.remove(list);
   addToNonEmptyBackward(list);
 }