Expose album moving methods in the album database.
[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         @Override
496         public void moveUp(Album album) {
497                 lock.writeLock().lock();
498                 try {
499                         List<String> albums = albumChildren.get(album.getParent().getId());
500                         int currentIndex = albums.indexOf(album.getId());
501                         if (currentIndex == 0) {
502                                 return;
503                         }
504                         albums.remove(album.getId());
505                         albums.add(currentIndex - 1, album.getId());
506                 } finally {
507                         lock.writeLock().unlock();
508                 }
509         }
510
511         @Override
512         public void moveDown(Album album) {
513                 lock.writeLock().lock();
514                 try {
515                         List<String> albums = albumChildren.get(album.getParent().getId());
516                         int currentIndex = albums.indexOf(album.getId());
517                         if (currentIndex == (albums.size() - 1)) {
518                                 return;
519                         }
520                         albums.remove(album.getId());
521                         albums.add(currentIndex + 1, album.getId());
522                 } finally {
523                         lock.writeLock().unlock();
524                 }
525         }
526
527         //
528         // ALBUMSTORE METHODS
529         //
530
531         @Override
532         public void storeAlbum(Album album) {
533                 lock.writeLock().lock();
534                 try {
535                         allAlbums.put(album.getId(), album);
536                         albumChildren.put(album.getParent().getId(), album.getId());
537                 } finally {
538                         lock.writeLock().unlock();
539                 }
540         }
541
542         @Override
543         public void removeAlbum(Album album) {
544                 lock.writeLock().lock();
545                 try {
546                         allAlbums.remove(album.getId());
547                         albumChildren.remove(album.getParent().getId(), album.getId());
548                 } finally {
549                         lock.writeLock().unlock();
550                 }
551         }
552
553         //
554         // IMAGEPROVIDER METHODS
555         //
556
557         @Override
558         public Optional<Image> getImage(String imageId) {
559                 lock.readLock().lock();
560                 try {
561                         return fromNullable(allImages.get(imageId));
562                 } finally {
563                         lock.readLock().unlock();
564                 }
565         }
566
567         @Override
568         public List<Image> getImages(Album parent) {
569                 lock.readLock().lock();
570                 try {
571                         return from(albumImages.get(parent.getId())).transformAndConcat(getImage()).toList();
572                 } finally {
573                         lock.readLock().unlock();
574                 }
575         }
576
577         @Override
578         public void moveUp(Image image) {
579                 lock.writeLock().lock();
580                 try {
581                         List<String> images = albumImages.get(image.getAlbum().getId());
582                         int currentIndex = images.indexOf(image.getId());
583                         if (currentIndex == 0) {
584                                 return;
585                         }
586                         images.remove(image.getId());
587                         images.add(currentIndex - 1, image.getId());
588                 } finally {
589                         lock.writeLock().unlock();
590                 }
591         }
592
593         @Override
594         public void moveDown(Image image) {
595                 lock.writeLock().lock();
596                 try {
597                         List<String> images = albumChildren.get(image.getAlbum().getId());
598                         int currentIndex = images.indexOf(image.getId());
599                         if (currentIndex == (images.size() - 1)) {
600                                 return;
601                         }
602                         images.remove(image.getId());
603                         images.add(currentIndex + 1, image.getId());
604                 } finally {
605                         lock.writeLock().unlock();
606                 }
607         }
608
609         //
610         // IMAGESTORE METHODS
611         //
612
613         @Override
614         public void storeImage(Image image) {
615                 lock.writeLock().lock();
616                 try {
617                         allImages.put(image.getId(), image);
618                         albumImages.put(image.getAlbum().getId(), image.getId());
619                 } finally {
620                         lock.writeLock().unlock();
621                 }
622         }
623
624         @Override
625         public void removeImage(Image image) {
626                 lock.writeLock().lock();
627                 try {
628                         allImages.remove(image.getId());
629                         albumImages.remove(image.getAlbum().getId(), image.getId());
630                 } finally {
631                         lock.writeLock().unlock();
632                 }
633         }
634
635         //
636         // PACKAGE-PRIVATE METHODS
637         //
638
639         /**
640          * Returns whether the given post is known.
641          *
642          * @param post
643          *              The post
644          * @return {@code true} if the post is known, {@code false} otherwise
645          */
646         boolean isPostKnown(Post post) {
647                 lock.readLock().lock();
648                 try {
649                         return knownPosts.contains(post.getId());
650                 } finally {
651                         lock.readLock().unlock();
652                 }
653         }
654
655         /**
656          * Sets whether the given post is known.
657          *
658          * @param post
659          *              The post
660          * @param known
661          *              {@code true} if the post is known, {@code false} otherwise
662          */
663         void setPostKnown(Post post, boolean known) {
664                 lock.writeLock().lock();
665                 try {
666                         if (known) {
667                                 knownPosts.add(post.getId());
668                         } else {
669                                 knownPosts.remove(post.getId());
670                         }
671                 } finally {
672                         lock.writeLock().unlock();
673                 }
674         }
675
676         /**
677          * Returns whether the given post reply is known.
678          *
679          * @param postReply
680          *              The post reply
681          * @return {@code true} if the given post reply is known, {@code false}
682          *         otherwise
683          */
684         boolean isPostReplyKnown(PostReply postReply) {
685                 lock.readLock().lock();
686                 try {
687                         return knownPostReplies.contains(postReply.getId());
688                 } finally {
689                         lock.readLock().unlock();
690                 }
691         }
692
693         /**
694          * Sets whether the given post reply is known.
695          *
696          * @param postReply
697          *              The post reply
698          * @param known
699          *              {@code true} if the post reply is known, {@code false} otherwise
700          */
701         void setPostReplyKnown(PostReply postReply, boolean known) {
702                 lock.writeLock().lock();
703                 try {
704                         if (known) {
705                                 knownPostReplies.add(postReply.getId());
706                         } else {
707                                 knownPostReplies.remove(postReply.getId());
708                         }
709                 } finally {
710                         lock.writeLock().unlock();
711                 }
712         }
713
714         //
715         // PRIVATE METHODS
716         //
717
718         /**
719          * Gets all posts for the given Sone, creating a new collection if there is
720          * none yet.
721          *
722          * @param soneId
723          *              The ID of the Sone to get the posts for
724          * @return All posts
725          */
726         private Collection<Post> getPostsFrom(String soneId) {
727                 Collection<Post> posts = null;
728                 lock.readLock().lock();
729                 try {
730                         posts = sonePosts.get(soneId);
731                 } finally {
732                         lock.readLock().unlock();
733                 }
734                 if (posts != null) {
735                         return posts;
736                 }
737
738                 posts = new HashSet<Post>();
739                 lock.writeLock().lock();
740                 try {
741                         sonePosts.put(soneId, posts);
742                 } finally {
743                         lock.writeLock().unlock();
744                 }
745
746                 return posts;
747         }
748
749         /**
750          * Gets all posts that are directed the given Sone, creating a new collection
751          * if there is none yet.
752          *
753          * @param recipientId
754          *              The ID of the Sone to get the posts for
755          * @return All posts
756          */
757         private Collection<Post> getPostsTo(String recipientId) {
758                 Collection<Post> posts = null;
759                 lock.readLock().lock();
760                 try {
761                         posts = recipientPosts.get(recipientId);
762                 } finally {
763                         lock.readLock().unlock();
764                 }
765                 if (posts != null) {
766                         return posts;
767                 }
768
769                 posts = new HashSet<Post>();
770                 lock.writeLock().lock();
771                 try {
772                         recipientPosts.put(recipientId, posts);
773                 } finally {
774                         lock.writeLock().unlock();
775                 }
776
777                 return posts;
778         }
779
780         /** Loads the known posts. */
781         private void loadKnownPosts() {
782                 lock.writeLock().lock();
783                 try {
784                         int postCounter = 0;
785                         while (true) {
786                                 String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null);
787                                 if (knownPostId == null) {
788                                         break;
789                                 }
790                                 knownPosts.add(knownPostId);
791                         }
792                 } finally {
793                         lock.writeLock().unlock();
794                 }
795         }
796
797         /**
798          * Saves the known posts to the configuration.
799          *
800          * @throws DatabaseException
801          *              if a configuration error occurs
802          */
803         private void saveKnownPosts() throws DatabaseException {
804                 lock.readLock().lock();
805                 try {
806                         int postCounter = 0;
807                         for (String knownPostId : knownPosts) {
808                                 configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
809                         }
810                         configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
811                 } catch (ConfigurationException ce1) {
812                         throw new DatabaseException("Could not save database.", ce1);
813                 } finally {
814                         lock.readLock().unlock();
815                 }
816         }
817
818         /**
819          * Returns all replies by the given Sone.
820          *
821          * @param id
822          *              The ID of the Sone
823          * @return The post replies of the Sone, sorted by time (newest first)
824          */
825         private Collection<PostReply> getRepliesFrom(String id) {
826                 lock.readLock().lock();
827                 try {
828                         if (sonePostReplies.containsKey(id)) {
829                                 return Collections.unmodifiableCollection(sonePostReplies.get(id));
830                         }
831                         return Collections.emptySet();
832                 } finally {
833                         lock.readLock().unlock();
834                 }
835         }
836
837         /** Loads the known post replies. */
838         private void loadKnownPostReplies() {
839                 lock.writeLock().lock();
840                 try {
841                         int replyCounter = 0;
842                         while (true) {
843                                 String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null);
844                                 if (knownReplyId == null) {
845                                         break;
846                                 }
847                                 knownPostReplies.add(knownReplyId);
848                         }
849                 } finally {
850                         lock.writeLock().unlock();
851                 }
852         }
853
854         /**
855          * Saves the known post replies to the configuration.
856          *
857          * @throws DatabaseException
858          *              if a configuration error occurs
859          */
860         private void saveKnownPostReplies() throws DatabaseException {
861                 lock.readLock().lock();
862                 try {
863                         int replyCounter = 0;
864                         for (String knownReplyId : knownPostReplies) {
865                                 configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
866                         }
867                         configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
868                 } catch (ConfigurationException ce1) {
869                         throw new DatabaseException("Could not save database.", ce1);
870                 } finally {
871                         lock.readLock().unlock();
872                 }
873         }
874
875         private Function<String, Iterable<Album>> getAlbum() {
876                 return new Function<String, Iterable<Album>>() {
877                         @Override
878                         public Iterable<Album> apply(String input) {
879                                 return (input == null) ? Collections.<Album>emptyList() : getAlbum(input).asSet();
880                         }
881                 };
882         }
883
884         private Function<String, Iterable<Image>> getImage() {
885                 return new Function<String, Iterable<Image>>() {
886                         @Override
887                         public Iterable<Image> apply(String input) {
888                                 return (input == null) ? Collections.<Image>emptyList() : getImage(input).asSet();
889                         }
890                 };
891         }
892
893 }