public List<String> titlesForTag(Tag tag, int limit) {
   if (limit <= 0) {
     return Collections.emptyList();
   }
   final ArrayList<String> titles = new ArrayList<String>(limit);
   final boolean isNull = Tag.NULL.equals(tag);
   synchronized (myBooksByFile) {
     for (Book b : myBooksByFile.values()) {
       if (isNull ? b.tags().isEmpty() : b.tags().contains(tag)) {
         titles.add(b.getTitle());
         if (--limit == 0) {
           break;
         }
       }
     }
   }
   return titles;
 }
 public List<Book> booksForTag(Tag tag) {
   final boolean isNull = Tag.NULL.equals(tag);
   final LinkedList<Book> filtered = new LinkedList<Book>();
   for (Book b : books()) {
     final List<Tag> bookTags = b.tags();
     if (isNull && bookTags.isEmpty() || bookTags.contains(tag)) {
       filtered.add(b);
     }
   }
   return filtered;
 }
 public List<Tag> tags() {
   final Set<Tag> tags = new HashSet<Tag>();
   synchronized (myBooksByFile) {
     for (Book book : myBooksByFile.values()) {
       final List<Tag> bookTags = book.tags();
       if (bookTags.isEmpty()) {
         tags.add(Tag.NULL);
       } else {
         for (Tag t : bookTags) {
           for (; t != null; t = t.Parent) {
             tags.add(t);
           }
         }
       }
     }
   }
   return new ArrayList<Tag>(tags);
 }