/**
   * Cleanup a file after it has been successfully processed.
   *
   * <p>This can through both IOExceptions and runtime exceptions due to Preconditions failures.
   *
   * <p>According to the link below, Solaris (I assume POSIX/linux) does atomic rename but Windows
   * does not guarantee it. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4017593 To be truly
   * correct, I need to check the return value (will likely fail in unix if moving from one volume
   * to another instead of just within same volume)
   */
  synchronized void changeState(String tag, State oldState, State newState) throws IOException {
    DFOData data = table.get(tag);
    Preconditions.checkArgument(data != null, "Tag " + tag + " has no data");
    Preconditions.checkArgument(
        tag.equals(data.tag), "Data associated with tag didn't match tag " + tag);

    if (LOG.isDebugEnabled()) {
      LOG.debug("Change " + data.s + "/" + oldState + " to " + newState + " : " + tag);
    }

    // null allows any previous state.
    if (oldState == null) {
      oldState = data.s;
    }

    Preconditions.checkState(
        data.s == oldState, "Expected state to be " + oldState + " but was " + data.s);

    if (oldState == State.ERROR) {
      throw new IllegalStateException("Cannot move from error state");
    }

    // SENT is terminal state, no where to move, just delete it.
    if (newState == State.SENT) {
      getQueue(oldState).remove(tag);
      File sentFile = getFile(tag);
      data.s = newState;
      if (!sentFile.delete()) {
        LOG.error("Couldn't delete " + sentFile + " - can be safely manually deleted");
      }

      // TODO (jon) need to eventually age off sent files entry to not exhaust
      // memory

      return;
    }
    // move files to other directories to making state change durable.
    File orig = getFile(tag);
    File newf = new File(getDir(newState), tag);
    boolean success = orig.renameTo(newf);
    if (!success) {
      throw new IOException("Move  " + orig + " -> " + newf + "failed!");
    }

    // is successful, update queues.
    LOG.debug("old state is " + oldState);
    getQueue(oldState).remove(tag);
    BlockingQueue<String> q = getQueue(newState);
    if (q != null) {
      q.add(tag);
    }
    data.s = newState;
  }
 static DFOData recovered(String tag) {
   DFOData data = new DFOData(tag);
   data.s = State.LOGGED;
   return data;
 }