Add method to get all images of an album to ImageProvider.
[Sone.git] / src / main / java / net / pterodactylus / sone / database / memory / MemoryDatabase.java
1 /*
2  * Sone - MemoryDatabase.java - Copyright © 2013 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.database.memory;
19
20 import static com.google.common.base.Optional.fromNullable;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.base.Predicates.not;
23 import static com.google.common.collect.FluentIterable.from;
24 import static java.util.Collections.emptyList;
25 import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER;
26
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Comparator;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.SortedSet;
37 import java.util.TreeSet;
38 import java.util.concurrent.locks.ReadWriteLock;
39 import java.util.concurrent.locks.ReentrantReadWriteLock;
40
41 import net.pterodactylus.sone.data.Album;
42 import net.pterodactylus.sone.data.Image;
43 import net.pterodactylus.sone.data.Post;
44 import net.pterodactylus.sone.data.PostReply;
45 import net.pterodactylus.sone.data.Reply;
46 import net.pterodactylus.sone.data.Sone;
47 import net.pterodactylus.sone.database.Database;
48 import net.pterodactylus.sone.database.DatabaseException;
49 import net.pterodactylus.sone.database.PostBuilder;
50 import net.pterodactylus.sone.database.PostDatabase;
51 import net.pterodactylus.sone.database.PostReplyBuilder;
52 import net.pterodactylus.sone.database.SoneBuilder;
53 import net.pterodactylus.util.config.Configuration;
54 import net.pterodactylus.util.config.ConfigurationException;
55
56 import com.google.common.base.Function;
57 import com.google.common.base.Optional;
58 import com.google.common.collect.ArrayListMultimap;
59 import com.google.common.collect.ListMultimap;
60 import com.google.common.collect.SortedSetMultimap;
61 import com.google.common.collect.TreeMultimap;
62 import com.google.common.util.concurrent.AbstractService;
63 import com.google.inject.Inject;
64
65 /**
66  * Memory-based {@link PostDatabase} implementation.
67  *
68  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
69  */
70 public class MemoryDatabase extends AbstractService implements Database {
71
72         /** The lock. */
73         private final ReadWriteLock lock = new ReentrantReadWriteLock();
74
75         /** The configuration. */
76         private final Configuration configuration;
77
78         private final Map<String, Sone> sones = new HashMap<String, Sone>();
79
80         /** All posts by their ID. */
81         private final Map<String, Post> allPosts = new HashMap<String, Post>();
82
83         /** All posts by their Sones. */
84         private final Map<String, Collection<Post>> sonePosts = new HashMap<String, Collection<Post>>();
85
86         /** All posts by their recipient. */
87         private final Map<String, Collection<Post>> recipientPosts = new HashMap<String, Collection<Post>>();
88
89         /** Whether posts are known. */
90         private final Set<String> knownPosts = new HashSet<String>();
91
92         /** All post replies by their ID. */
93         private final Map<String, PostReply> allPostReplies = new HashMap<String, PostReply>();
94
95         /** Replies sorted by Sone. */
96         private final SortedSetMultimap<String, PostReply> sonePostReplies = TreeMultimap.create(new Comparator<String>() {
97
98                 @Override
99                 public int compare(String leftString, String rightString) {
100                         return leftString.compareTo(rightString);
101                 }
102         }, PostReply.TIME_COMPARATOR);
103
104         /** Replies by post. */
105         private final Map<String, SortedSet<PostReply>> postReplies = new HashMap<String, SortedSet<PostReply>>();
106
107         /** Whether post replies are known. */
108         private final Set<String> knownPostReplies = new HashSet<String>();
109
110         private final Map<String, Album> allAlbums = new HashMap<String, Album>();
111         private final ListMultimap<String, String> albumChildren = ArrayListMultimap.create();
112         private final ListMultimap<String, String> albumImages = ArrayListMultimap.create();
113
114         private final Map<String, Image> allImages = new HashMap<String, Image>();
115
116         /**
117          * Creates a new memory database.
118          *
119          * @param configuration
120          *              The configuration for loading and saving elements
121          */
122         @Inject
123         public MemoryDatabase(Configuration configuration) {
124                 this.configuration = configuration;
125         }
126
127         //
128         // DATABASE METHODS
129         //
130
131         /**
132          * Saves the database.
133          *
134          * @throws DatabaseException
135          *              if an error occurs while saving
136          */
137         @Override
138         public void save() throws DatabaseException {
139                 saveKnownPosts();
140                 saveKnownPostReplies();
141         }
142
143         //
144         // SERVICE METHODS
145         //
146
147         /** {@inheritDocs} */
148         @Override
149         protected void doStart() {
150                 loadKnownPosts();
151                 loadKnownPostReplies();
152                 notifyStarted();
153         }
154
155         /** {@inheritDocs} */
156         @Override
157         protected void doStop() {
158                 try {
159                         save();
160                         notifyStopped();
161                 } catch (DatabaseException de1) {
162                         notifyFailed(de1);
163                 }
164         }
165
166         @Override
167         public Optional<Sone> getSone(String soneId) {
168                 lock.readLock().lock();
169                 try {
170                         return fromNullable(sones.get(soneId));
171                 } finally {
172                         lock.readLock().unlock();
173                 }
174         }
175
176         @Override
177         public Collection<Sone> getSones() {
178                 lock.readLock().lock();
179                 try {
180                         return Collections.unmodifiableCollection(sones.values());
181                 } finally {
182                         lock.readLock().unlock();
183                 }
184         }
185
186         @Override
187         public Collection<Sone> getLocalSones() {
188                 lock.readLock().lock();
189                 try {
190                         return from(getSones()).filter(LOCAL_SONE_FILTER).toSet();
191                 } finally {
192                         lock.readLock().unlock();
193                 }
194         }
195
196         @Override
197         public Collection<Sone> getRemoteSones() {
198                 lock.readLock().lock();
199                 try {
200                         return from(getSones()).filter(not(LOCAL_SONE_FILTER)).toSet();
201                 } finally {
202                         lock.readLock().unlock();
203                 }
204         }
205
206         @Override
207         public SoneBuilder newSoneBuilder() {
208                 return null;
209         }
210
211         //
212         // POSTPROVIDER METHODS
213         //
214
215         /** {@inheritDocs} */
216         @Override
217         public Optional<Post> getPost(String postId) {
218                 lock.readLock().lock();
219                 try {
220                         return fromNullable(allPosts.get(postId));
221                 } finally {
222                         lock.readLock().unlock();
223                 }
224         }
225
226         /** {@inheritDocs} */
227         @Override
228         public Collection<Post> getPosts(String soneId) {
229                 return new HashSet<Post>(getPostsFrom(soneId));
230         }
231
232         /** {@inheritDocs} */
233         @Override
234         public Collection<Post> getDirectedPosts(String recipientId) {
235                 lock.readLock().lock();
236                 try {
237                         Collection<Post> posts = recipientPosts.get(recipientId);
238                         return (posts == null) ? Collections.<Post>emptySet() : new HashSet<Post>(posts);
239                 } finally {
240                         lock.readLock().unlock();
241                 }
242         }
243
244         //
245         // POSTBUILDERFACTORY METHODS
246         //
247
248         /** {@inheritDocs} */
249         @Override
250         public PostBuilder newPostBuilder() {
251                 return new MemoryPostBuilder(this);
252         }
253
254         //
255         // POSTSTORE METHODS
256         //
257
258         /** {@inheritDocs} */
259         @Override
260         public void storePost(Post post) {
261                 checkNotNull(post, "post must not be null");
262                 lock.writeLock().lock();
263                 try {
264                         allPosts.put(post.getId(), post);
265                         getPostsFrom(post.getSone().getId()).add(post);
266                         if (post.getRecipientId().isPresent()) {
267                                 getPostsTo(post.getRecipientId().get()).add(post);
268                         }
269                 } finally {
270                         lock.writeLock().unlock();
271                 }
272         }
273
274         /** {@inheritDocs} */
275         @Override
276         public void removePost(Post post) {
277                 checkNotNull(post, "post must not be null");
278                 lock.writeLock().lock();
279                 try {
280                         allPosts.remove(post.getId());
281                         getPostsFrom(post.getSone().getId()).remove(post);
282                         if (post.getRecipientId().isPresent()) {
283                                 getPostsTo(post.getRecipientId().get()).remove(post);
284                         }
285                         post.getSone().removePost(post);
286                 } finally {
287                         lock.writeLock().unlock();
288                 }
289         }
290
291         /** {@inheritDocs} */
292         @Override
293         public void storePosts(Sone sone, Collection<Post> posts) throws IllegalArgumentException {
294                 checkNotNull(sone, "sone must not be null");
295                 /* verify that all posts are from the same Sone. */
296                 for (Post post : posts) {
297                         if (!sone.equals(post.getSone())) {
298                                 throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post));
299                         }
300                 }
301
302                 lock.writeLock().lock();
303                 try {
304                         /* remove all posts by the Sone. */
305                         getPostsFrom(sone.getId()).clear();
306                         for (Post post : posts) {
307                                 allPosts.remove(post.getId());
308                                 if (post.getRecipientId().isPresent()) {
309                                         getPostsTo(post.getRecipientId().get()).remove(post);
310                                 }
311                         }
312
313                         /* add new posts. */
314                         getPostsFrom(sone.getId()).addAll(posts);
315                         for (Post post : posts) {
316                                 allPosts.put(post.getId(), post);
317                                 if (post.getRecipientId().isPresent()) {
318                                         getPostsTo(post.getRecipientId().get()).add(post);
319                                 }
320                         }
321                 } finally {
322                         lock.writeLock().unlock();
323                 }
324         }
325
326         /** {@inheritDocs} */
327         @Override
328         public void removePosts(Sone sone) {
329                 checkNotNull(sone, "sone must not be null");
330                 lock.writeLock().lock();
331                 try {
332                         /* remove all posts by the Sone. */
333                         getPostsFrom(sone.getId()).clear();
334                         for (Post post : sone.getPosts()) {
335                                 allPosts.remove(post.getId());
336                                 if (post.getRecipientId().isPresent()) {
337                                         getPostsTo(post.getRecipientId().get()).remove(post);
338                                 }
339                         }
340                 } finally {
341                         lock.writeLock().unlock();
342                 }
343         }
344
345         //
346         // POSTREPLYPROVIDER METHODS
347         //
348
349         /** {@inheritDocs} */
350         @Override
351         public Optional<PostReply> getPostReply(String id) {
352                 lock.readLock().lock();
353                 try {
354                         return fromNullable(allPostReplies.get(id));
355                 } finally {
356                         lock.readLock().unlock();
357                 }
358         }
359
360         /** {@inheritDocs} */
361         @Override
362         public List<PostReply> getReplies(String postId) {
363                 lock.readLock().lock();
364                 try {
365                         if (!postReplies.containsKey(postId)) {
366                                 return emptyList();
367                         }
368                         return new ArrayList<PostReply>(postReplies.get(postId));
369                 } finally {
370                         lock.readLock().unlock();
371                 }
372         }
373
374         //
375         // POSTREPLYBUILDERFACTORY METHODS
376         //
377
378         /** {@inheritDocs} */
379         @Override
380         public PostReplyBuilder newPostReplyBuilder() {
381                 return new MemoryPostReplyBuilder(this, this);
382         }
383
384         //
385         // POSTREPLYSTORE METHODS
386         //
387
388         /** {@inheritDocs} */
389         @Override
390         public void storePostReply(PostReply postReply) {
391                 lock.writeLock().lock();
392                 try {
393                         allPostReplies.put(postReply.getId(), postReply);
394                         if (postReplies.containsKey(postReply.getPostId())) {
395                                 postReplies.get(postReply.getPostId()).add(postReply);
396                         } else {
397                                 TreeSet<PostReply> replies = new TreeSet<PostReply>(Reply.TIME_COMPARATOR);
398                                 replies.add(postReply);
399                                 postReplies.put(postReply.getPostId(), replies);
400                         }
401                 } finally {
402                         lock.writeLock().unlock();
403                 }
404         }
405
406         /** {@inheritDocs} */
407         @Override
408         public void storePostReplies(Sone sone, Collection<PostReply> postReplies) {
409                 checkNotNull(sone, "sone must not be null");
410                 /* verify that all posts are from the same Sone. */
411                 for (PostReply postReply : postReplies) {
412                         if (!sone.equals(postReply.getSone())) {
413                                 throw new IllegalArgumentException(String.format("PostReply from different Sone found: %s", postReply));
414                         }
415                 }
416
417                 lock.writeLock().lock();
418                 try {
419                         /* remove all post replies of the Sone. */
420                         for (PostReply postReply : getRepliesFrom(sone.getId())) {
421                                 removePostReply(postReply);
422                         }
423                         for (PostReply postReply : postReplies) {
424                                 allPostReplies.put(postReply.getId(), postReply);
425                                 sonePostReplies.put(postReply.getSone().getId(), postReply);
426                                 if (this.postReplies.containsKey(postReply.getPostId())) {
427                                         this.postReplies.get(postReply.getPostId()).add(postReply);
428                                 } else {
429                                         TreeSet<PostReply> replies = new TreeSet<PostReply>(Reply.TIME_COMPARATOR);
430                                         replies.add(postReply);
431                                         this.postReplies.put(postReply.getPostId(), replies);
432                                 }
433                         }
434                 } finally {
435                         lock.writeLock().unlock();
436                 }
437         }
438
439         /** {@inheritDocs} */
440         @Override
441         public void removePostReply(PostReply postReply) {
442                 lock.writeLock().lock();
443                 try {
444                         allPostReplies.remove(postReply.getId());
445                         if (postReplies.containsKey(postReply.getPostId())) {
446                                 postReplies.get(postReply.getPostId()).remove(postReply);
447                                 if (postReplies.get(postReply.getPostId()).isEmpty()) {
448                                         postReplies.remove(postReply.getPostId());
449                                 }
450                         }
451                 } finally {
452                         lock.writeLock().unlock();
453                 }
454         }
455
456         /** {@inheritDocs} */
457         @Override
458         public void removePostReplies(Sone sone) {
459                 checkNotNull(sone, "sone must not be null");
460
461                 lock.writeLock().lock();
462                 try {
463                         for (PostReply postReply : sone.getReplies()) {
464                                 removePostReply(postReply);
465                         }
466                 } finally {
467                         lock.writeLock().unlock();
468                 }
469         }
470
471         //
472         // ALBUMPROVDER METHODS
473         //
474
475         @Override
476         public Optional<Album> getAlbum(String albumId) {
477                 lock.readLock().lock();
478                 try {
479                         return fromNullable(allAlbums.get(albumId));
480                 } finally {
481                         lock.readLock().unlock();
482                 }
483         }
484
485         @Override
486         public List<Album> getAlbums(Album parent) {
487                 lock.readLock().lock();
488                 try {
489                         return from(albumChildren.get(parent.getId())).transformAndConcat(getAlbum()).toList();
490                 } finally {
491                         lock.readLock().unlock();
492                 }
493         }
494
495         //
496         // ALBUMSTORE METHODS
497         //
498
499         @Override
500         public void storeAlbum(Album album) {
501                 lock.writeLock().lock();
502                 try {
503                         allAlbums.put(album.getId(), album);
504                         albumChildren.put(album.getParent().getId(), album.getId());
505                 } finally {
506                         lock.writeLock().unlock();
507                 }
508         }
509
510         @Override
511         public void removeAlbum(Album album) {
512                 lock.writeLock().lock();
513                 try {
514                         allAlbums.remove(album.getId());
515                         albumChildren.remove(album.getParent().getId(), album.getId());
516                 } finally {
517                         lock.writeLock().unlock();
518                 }
519         }
520
521         //
522         // IMAGEPROVIDER METHODS
523         //
524
525         @Override
526         public Optional<Image> getImage(String imageId) {
527                 lock.readLock().lock();
528                 try {
529                         return fromNullable(allImages.get(imageId));
530                 } finally {
531                         lock.readLock().unlock();
532                 }
533         }
534
535         @Override
536         public List<Image> getImages(Album parent) {
537                 lock.readLock().lock();
538                 try {
539                         return from(albumImages.get(parent.getId())).transformAndConcat(getImage()).toList();
540                 } finally {
541                         lock.readLock().unlock();
542                 }
543         }
544
545         //
546         // IMAGESTORE METHODS
547         //
548
549         @Override
550         public void storeImage(Image image) {
551                 lock.writeLock().lock();
552                 try {
553                         allImages.put(image.getId(), image);
554                         albumImages.put(image.getAlbum().getId(), image.getId());
555                 } finally {
556                         lock.writeLock().unlock();
557                 }
558         }
559
560         @Override
561         public void removeImage(Image image) {
562                 lock.writeLock().lock();
563                 try {
564                         allImages.remove(image.getId());
565                         albumImages.remove(image.getAlbum().getId(), image.getId());
566                 } finally {
567                         lock.writeLock().unlock();
568                 }
569         }
570
571         //
572         // PACKAGE-PRIVATE METHODS
573         //
574
575         /**
576          * Returns whether the given post is known.
577          *
578          * @param post
579          *              The post
580          * @return {@code true} if the post is known, {@code false} otherwise
581          */
582         boolean isPostKnown(Post post) {
583                 lock.readLock().lock();
584                 try {
585                         return knownPosts.contains(post.getId());
586                 } finally {
587                         lock.readLock().unlock();
588                 }
589         }
590
591         /**
592          * Sets whether the given post is known.
593          *
594          * @param post
595          *              The post
596          * @param known
597          *              {@code true} if the post is known, {@code false} otherwise
598          */
599         void setPostKnown(Post post, boolean known) {
600                 lock.writeLock().lock();
601                 try {
602                         if (known) {
603                                 knownPosts.add(post.getId());
604                         } else {
605                                 knownPosts.remove(post.getId());
606                         }
607                 } finally {
608                         lock.writeLock().unlock();
609                 }
610         }
611
612         /**
613          * Returns whether the given post reply is known.
614          *
615          * @param postReply
616          *              The post reply
617          * @return {@code true} if the given post reply is known, {@code false}
618          *         otherwise
619          */
620         boolean isPostReplyKnown(PostReply postReply) {
621                 lock.readLock().lock();
622                 try {
623                         return knownPostReplies.contains(postReply.getId());
624                 } finally {
625                         lock.readLock().unlock();
626                 }
627         }
628
629         /**
630          * Sets whether the given post reply is known.
631          *
632          * @param postReply
633          *              The post reply
634          * @param known
635          *              {@code true} if the post reply is known, {@code false} otherwise
636          */
637         void setPostReplyKnown(PostReply postReply, boolean known) {
638                 lock.writeLock().lock();
639                 try {
640                         if (known) {
641                                 knownPostReplies.add(postReply.getId());
642                         } else {
643                                 knownPostReplies.remove(postReply.getId());
644                         }
645                 } finally {
646                         lock.writeLock().unlock();
647                 }
648         }
649
650         void moveUp(Album album) {
651                 lock.writeLock().lock();
652                 try {
653                         List<String> albums = albumChildren.get(album.getParent().getId());
654                         int currentIndex = albums.indexOf(album.getId());
655                         if (currentIndex == 0) {
656                                 return;
657                         }
658                         albums.remove(album.getId());
659                         albums.add(currentIndex - 1, album.getId());
660                 } finally {
661                         lock.writeLock().unlock();
662                 }
663         }
664
665         void moveDown(Album album) {
666                 lock.writeLock().lock();
667                 try {
668                         List<String> albums = albumChildren.get(album.getParent().getId());
669                         int currentIndex = albums.indexOf(album.getId());
670                         if (currentIndex == (albums.size() - 1)) {
671                                 return;
672                         }
673                         albums.remove(album.getId());
674                         albums.add(currentIndex + 1, album.getId());
675                 } finally {
676                         lock.writeLock().unlock();
677                 }
678         }
679
680         void moveUp(Image image) {
681                 lock.writeLock().lock();
682                 try {
683                         List<String> images = albumImages.get(image.getAlbum().getId());
684                         int currentIndex = images.indexOf(image.getId());
685                         if (currentIndex == 0) {
686                                 return;
687                         }
688                         images.remove(image.getId());
689                         images.add(currentIndex - 1, image.getId());
690                 } finally {
691                         lock.writeLock().unlock();
692                 }
693         }
694
695         void moveDown(Image image) {
696                 lock.writeLock().lock();
697                 try {
698                         List<String> images = albumChildren.get(image.getAlbum().getId());
699                         int currentIndex = images.indexOf(image.getId());
700                         if (currentIndex == (images.size() - 1)) {
701                                 return;
702                         }
703                         images.remove(image.getId());
704                         images.add(currentIndex + 1, image.getId());
705                 } finally {
706                         lock.writeLock().unlock();
707                 }
708         }
709
710         //
711         // PRIVATE METHODS
712         //
713
714         /**
715          * Gets all posts for the given Sone, creating a new collection if there is
716          * none yet.
717          *
718          * @param soneId
719          *              The ID of the Sone to get the posts for
720          * @return All posts
721          */
722         private Collection<Post> getPostsFrom(String soneId) {
723                 Collection<Post> posts = null;
724                 lock.readLock().lock();
725                 try {
726                         posts = sonePosts.get(soneId);
727                 } finally {
728                         lock.readLock().unlock();
729                 }
730                 if (posts != null) {
731                         return posts;
732                 }
733
734                 posts = new HashSet<Post>();
735                 lock.writeLock().lock();
736                 try {
737                         sonePosts.put(soneId, posts);
738                 } finally {
739                         lock.writeLock().unlock();
740                 }
741
742                 return posts;
743         }
744
745         /**
746          * Gets all posts that are directed the given Sone, creating a new collection
747          * if there is none yet.
748          *
749          * @param recipientId
750          *              The ID of the Sone to get the posts for
751          * @return All posts
752          */
753         private Collection<Post> getPostsTo(String recipientId) {
754                 Collection<Post> posts = null;
755                 lock.readLock().lock();
756                 try {
757                         posts = recipientPosts.get(recipientId);
758                 } finally {
759                         lock.readLock().unlock();
760                 }
761                 if (posts != null) {
762                         return posts;
763                 }
764
765                 posts = new HashSet<Post>();
766                 lock.writeLock().lock();
767                 try {
768                         recipientPosts.put(recipientId, posts);
769                 } finally {
770                         lock.writeLock().unlock();
771                 }
772
773                 return posts;
774         }
775
776         /** Loads the known posts. */
777         private void loadKnownPosts() {
778                 lock.writeLock().lock();
779                 try {
780                         int postCounter = 0;
781                         while (true) {
782                                 String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null);
783                                 if (knownPostId == null) {
784                                         break;
785                                 }
786                                 knownPosts.add(knownPostId);
787                         }
788                 } finally {
789                         lock.writeLock().unlock();
790                 }
791         }
792
793         /**
794          * Saves the known posts to the configuration.
795          *
796          * @throws DatabaseException
797          *              if a configuration error occurs
798          */
799         private void saveKnownPosts() throws DatabaseException {
800                 lock.readLock().lock();
801                 try {
802                         int postCounter = 0;
803                         for (String knownPostId : knownPosts) {
804                                 configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
805                         }
806                         configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
807                 } catch (ConfigurationException ce1) {
808                         throw new DatabaseException("Could not save database.", ce1);
809                 } finally {
810                         lock.readLock().unlock();
811                 }
812         }
813
814         /**
815          * Returns all replies by the given Sone.
816          *
817          * @param id
818          *              The ID of the Sone
819          * @return The post replies of the Sone, sorted by time (newest first)
820          */
821         private Collection<PostReply> getRepliesFrom(String id) {
822                 lock.readLock().lock();
823                 try {
824                         if (sonePostReplies.containsKey(id)) {
825                                 return Collections.unmodifiableCollection(sonePostReplies.get(id));
826                         }
827                         return Collections.emptySet();
828                 } finally {
829                         lock.readLock().unlock();
830                 }
831         }
832
833         /** Loads the known post replies. */
834         private void loadKnownPostReplies() {
835                 lock.writeLock().lock();
836                 try {
837                         int replyCounter = 0;
838                         while (true) {
839                                 String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null);
840                                 if (knownReplyId == null) {
841                                         break;
842                                 }
843                                 knownPostReplies.add(knownReplyId);
844                         }
845                 } finally {
846                         lock.writeLock().unlock();
847                 }
848         }
849
850         /**
851          * Saves the known post replies to the configuration.
852          *
853          * @throws DatabaseException
854          *              if a configuration error occurs
855          */
856         private void saveKnownPostReplies() throws DatabaseException {
857                 lock.readLock().lock();
858                 try {
859                         int replyCounter = 0;
860                         for (String knownReplyId : knownPostReplies) {
861                                 configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
862                         }
863                         configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
864                 } catch (ConfigurationException ce1) {
865                         throw new DatabaseException("Could not save database.", ce1);
866                 } finally {
867                         lock.readLock().unlock();
868                 }
869         }
870
871         private Function<String, Iterable<Album>> getAlbum() {
872                 return new Function<String, Iterable<Album>>() {
873                         @Override
874                         public Iterable<Album> apply(String input) {
875                                 return (input == null) ? Collections.<Album>emptyList() : getAlbum(input).asSet();
876                         }
877                 };
878         }
879
880         private Function<String, Iterable<Image>> getImage() {
881                 return new Function<String, Iterable<Image>>() {
882                         @Override
883                         public Iterable<Image> apply(String input) {
884                                 return (input == null) ? Collections.<Image>emptyList() : getImage(input).asSet();
885                         }
886                 };
887         }
888
889 }