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