@Override
  public synchronized R put(Integer key, R r) {
    String id = getIdOf(r);
    int n = getNumberOf(r);

    Index copy = copy();
    BuildReference<R> ref = createReference(r);
    BuildReference<R> old = copy.byId.put(id, ref);
    copy.byNumber.put(n, ref);
    index = copy;

    /*
       search relies on the fact that every object added via
       put() method be available in the xyzOnDisk index, so I'm adding them here
       however, this is awfully inefficient. I wonder if there's any better way to do this?
    */
    if (!idOnDisk.contains(id)) {
      ArrayList<String> a = new ArrayList<String>(idOnDisk);
      a.add(id);
      Collections.sort(a);
      idOnDisk = new SortedList<String>(a);
    }

    if (!numberOnDisk.contains(n)) {
      SortedIntList a = new SortedIntList(numberOnDisk);
      a.add(n);
      a.sort();
      numberOnDisk = a;
    }

    return unwrap(old);
  }
  private void loadIdOnDisk() {
    String[] buildDirs = dir.list(createDirectoryFilter());
    if (buildDirs == null) {
      // the job may have just been created
      buildDirs = EMPTY_STRING_ARRAY;
    }
    // wrap into ArrayList to enable mutation
    Arrays.sort(buildDirs);
    idOnDisk = new SortedList(new ArrayList<String>(Arrays.asList(buildDirs)));

    // TODO: should we check that shortcuts is a symlink?
    String[] shortcuts = dir.list();
    if (shortcuts == null) shortcuts = EMPTY_STRING_ARRAY;
    SortedIntList list = new SortedIntList(shortcuts.length / 2);
    for (String s : shortcuts) {
      try {
        list.add(Integer.parseInt(s));
      } catch (NumberFormatException e) {
        // this isn't a shortcut
      }
    }
    list.sort();
    numberOnDisk = list;
  }