Remove some unused methods.
[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.unmodifiableCollection;
25 import static net.pterodactylus.sone.data.Reply.TIME_COMPARATOR;
26 import static net.pterodactylus.sone.data.Sone.LOCAL_SONE_FILTER;
27 import static net.pterodactylus.sone.data.Sone.toAllAlbums;
28 import static net.pterodactylus.sone.data.Sone.toAllImages;
29
30 import java.util.Collection;
31 import java.util.Comparator;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.concurrent.locks.ReadWriteLock;
38 import java.util.concurrent.locks.ReentrantReadWriteLock;
39
40 import net.pterodactylus.sone.data.Album;
41 import net.pterodactylus.sone.data.Image;
42 import net.pterodactylus.sone.data.Post;
43 import net.pterodactylus.sone.data.PostReply;
44 import net.pterodactylus.sone.data.Sone;
45 import net.pterodactylus.sone.data.impl.AlbumBuilderImpl;
46 import net.pterodactylus.sone.data.impl.ImageBuilderImpl;
47 import net.pterodactylus.sone.database.AlbumBuilder;
48 import net.pterodactylus.sone.database.Database;
49 import net.pterodactylus.sone.database.DatabaseException;
50 import net.pterodactylus.sone.database.ImageBuilder;
51 import net.pterodactylus.sone.database.PostBuilder;
52 import net.pterodactylus.sone.database.PostDatabase;
53 import net.pterodactylus.sone.database.PostReplyBuilder;
54 import net.pterodactylus.sone.database.SoneBuilder;
55 import net.pterodactylus.sone.database.SoneProvider;
56 import net.pterodactylus.util.config.Configuration;
57 import net.pterodactylus.util.config.ConfigurationException;
58
59 import com.google.common.base.Optional;
60 import com.google.common.base.Predicate;
61 import com.google.common.collect.HashMultimap;
62 import com.google.common.collect.Multimap;
63 import com.google.common.collect.SortedSetMultimap;
64 import com.google.common.collect.TreeMultimap;
65 import com.google.common.util.concurrent.AbstractService;
66 import com.google.inject.Inject;
67 import com.google.inject.Singleton;
68
69 /**
70  * Memory-based {@link PostDatabase} implementation.
71  *
72  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
73  */
74 @Singleton
75 public class MemoryDatabase extends AbstractService implements Database {
76
77         /** The lock. */
78         private final ReadWriteLock lock = new ReentrantReadWriteLock();
79
80         /** The Sone provider. */
81         private final SoneProvider soneProvider;
82
83         /** The configuration. */
84         private final Configuration configuration;
85         private final ConfigurationLoader configurationLoader;
86
87         private final Map<String, Sone> allSones = new HashMap<String, Sone>();
88
89         /** All posts by their ID. */
90         private final Map<String, Post> allPosts = new HashMap<String, Post>();
91
92         /** All posts by their Sones. */
93         private final Multimap<String, Post> sonePosts = HashMultimap.create();
94
95         /** Whether posts are known. */
96         private final Set<String> knownPosts = new HashSet<String>();
97
98         /** All post replies by their ID. */
99         private final Map<String, PostReply> allPostReplies = new HashMap<String, PostReply>();
100
101         /** Replies sorted by Sone. */
102         private final SortedSetMultimap<String, PostReply> sonePostReplies = TreeMultimap.create(new Comparator<String>() {
103
104                 @Override
105                 public int compare(String leftString, String rightString) {
106                         return leftString.compareTo(rightString);
107                 }
108         }, TIME_COMPARATOR);
109
110         /** Whether post replies are known. */
111         private final Set<String> knownPostReplies = new HashSet<String>();
112
113         private final Map<String, Album> allAlbums = new HashMap<String, Album>();
114         private final Multimap<String, Album> soneAlbums = HashMultimap.create();
115
116         private final Map<String, Image> allImages = new HashMap<String, Image>();
117         private final Multimap<String, Image> soneImages = HashMultimap.create();
118
119         private final MemoryBookmarkDatabase memoryBookmarkDatabase;
120
121         /**
122          * Creates a new memory database.
123          *
124          * @param soneProvider
125          *              The Sone provider
126          * @param configuration
127          *              The configuration for loading and saving elements
128          */
129         @Inject
130         public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) {
131                 this.soneProvider = soneProvider;
132                 this.configuration = configuration;
133                 this.configurationLoader = new ConfigurationLoader(configuration);
134                 memoryBookmarkDatabase =
135                                 new MemoryBookmarkDatabase(this, configurationLoader);
136         }
137
138         //
139         // DATABASE METHODS
140         //
141
142         /**
143          * Saves the database.
144          *
145          * @throws DatabaseException
146          *              if an error occurs while saving
147          */
148         @Override
149         public void save() throws DatabaseException {
150                 saveKnownPosts();
151                 saveKnownPostReplies();
152         }
153
154         //
155         // SERVICE METHODS
156         //
157
158         /** {@inheritDocs} */
159         @Override
160         protected void doStart() {
161                 memoryBookmarkDatabase.start();
162                 loadKnownPosts();
163                 loadKnownPostReplies();
164                 notifyStarted();
165         }
166
167         /** {@inheritDocs} */
168         @Override
169         protected void doStop() {
170                 try {
171                         memoryBookmarkDatabase.stop();
172                         save();
173                         notifyStopped();
174                 } catch (DatabaseException de1) {
175                         notifyFailed(de1);
176                 }
177         }
178
179         @Override
180         public SoneBuilder newSoneBuilder() {
181                 return new MemorySoneBuilder();
182         }
183
184         @Override
185         public void storeSone(Sone sone) {
186                 lock.writeLock().lock();
187                 try {
188                         removeSone(sone);
189
190                         allSones.put(sone.getId(), sone);
191                         sonePosts.putAll(sone.getId(), sone.getPosts());
192                         for (Post post : sone.getPosts()) {
193                                 allPosts.put(post.getId(), post);
194                         }
195                         sonePostReplies.putAll(sone.getId(), sone.getReplies());
196                         for (PostReply postReply : sone.getReplies()) {
197                                 allPostReplies.put(postReply.getId(), postReply);
198                         }
199                         soneAlbums.putAll(sone.getId(), toAllAlbums.apply(sone));
200                         for (Album album : toAllAlbums.apply(sone)) {
201                                 allAlbums.put(album.getId(), album);
202                         }
203                         soneImages.putAll(sone.getId(), toAllImages.apply(sone));
204                         for (Image image : toAllImages.apply(sone)) {
205                                 allImages.put(image.getId(), image);
206                         }
207                 } finally {
208                         lock.writeLock().unlock();
209                 }
210         }
211
212         @Override
213         public void removeSone(Sone sone) {
214                 lock.writeLock().lock();
215                 try {
216                         allSones.remove(sone.getId());
217                         Collection<Post> removedPosts = sonePosts.removeAll(sone.getId());
218                         for (Post removedPost : removedPosts) {
219                                 allPosts.remove(removedPost.getId());
220                         }
221                         Collection<PostReply> removedPostReplies =
222                                         sonePostReplies.removeAll(sone.getId());
223                         for (PostReply removedPostReply : removedPostReplies) {
224                                 allPostReplies.remove(removedPostReply.getId());
225                         }
226                         Collection<Album> removedAlbums =
227                                         soneAlbums.removeAll(sone.getId());
228                         for (Album removedAlbum : removedAlbums) {
229                                 allAlbums.remove(removedAlbum.getId());
230                         }
231                         Collection<Image> removedImages =
232                                         soneImages.removeAll(sone.getId());
233                         for (Image removedImage : removedImages) {
234                                 allImages.remove(removedImage.getId());
235                         }
236                 } finally {
237                         lock.writeLock().unlock();
238                 }
239         }
240
241         @Override
242         public Optional<Sone> getSone(String soneId) {
243                 lock.readLock().lock();
244                 try {
245                         return fromNullable(allSones.get(soneId));
246                 } finally {
247                         lock.readLock().unlock();
248                 }
249         }
250
251         @Override
252         public Collection<Sone> getSones() {
253                 lock.readLock().lock();
254                 try {
255                         return new HashSet<Sone>(allSones.values());
256                 } finally {
257                         lock.readLock().unlock();
258                 }
259         }
260
261         @Override
262         public Collection<Sone> getLocalSones() {
263                 lock.readLock().lock();
264                 try {
265                         return from(allSones.values()).filter(LOCAL_SONE_FILTER).toSet();
266                 } finally {
267                         lock.readLock().unlock();
268                 }
269         }
270
271         @Override
272         public Collection<Sone> getRemoteSones() {
273                 lock.readLock().lock();
274                 try {
275                         return from(allSones.values())
276                                         .filter(not(LOCAL_SONE_FILTER)) .toSet();
277                 } finally {
278                         lock.readLock().unlock();
279                 }
280         }
281
282         //
283         // POSTPROVIDER METHODS
284         //
285
286         /** {@inheritDocs} */
287         @Override
288         public Optional<Post> getPost(String postId) {
289                 lock.readLock().lock();
290                 try {
291                         return fromNullable(allPosts.get(postId));
292                 } finally {
293                         lock.readLock().unlock();
294                 }
295         }
296
297         /** {@inheritDocs} */
298         @Override
299         public Collection<Post> getPosts(String soneId) {
300                 return new HashSet<Post>(getPostsFrom(soneId));
301         }
302
303         /** {@inheritDocs} */
304         @Override
305         public Collection<Post> getDirectedPosts(final String recipientId) {
306                 lock.readLock().lock();
307                 try {
308                         return from(sonePosts.values()).filter(new Predicate<Post>() {
309                                 @Override
310                                 public boolean apply(Post post) {
311                                         return post.getRecipientId().asSet().contains(recipientId);
312                                 }
313                         }).toSet();
314                 } finally {
315                         lock.readLock().unlock();
316                 }
317         }
318
319         //
320         // POSTBUILDERFACTORY METHODS
321         //
322
323         /** {@inheritDocs} */
324         @Override
325         public PostBuilder newPostBuilder() {
326                 return new MemoryPostBuilder(this, soneProvider);
327         }
328
329         //
330         // POSTSTORE METHODS
331         //
332
333         /** {@inheritDocs} */
334         @Override
335         public void storePost(Post post) {
336                 checkNotNull(post, "post must not be null");
337                 lock.writeLock().lock();
338                 try {
339                         allPosts.put(post.getId(), post);
340                         getPostsFrom(post.getSone().getId()).add(post);
341                 } finally {
342                         lock.writeLock().unlock();
343                 }
344         }
345
346         /** {@inheritDocs} */
347         @Override
348         public void removePost(Post post) {
349                 checkNotNull(post, "post must not be null");
350                 lock.writeLock().lock();
351                 try {
352                         allPosts.remove(post.getId());
353                         getPostsFrom(post.getSone().getId()).remove(post);
354                         post.getSone().removePost(post);
355                 } finally {
356                         lock.writeLock().unlock();
357                 }
358         }
359
360         //
361         // POSTREPLYPROVIDER METHODS
362         //
363
364         /** {@inheritDocs} */
365         @Override
366         public Optional<PostReply> getPostReply(String id) {
367                 lock.readLock().lock();
368                 try {
369                         return fromNullable(allPostReplies.get(id));
370                 } finally {
371                         lock.readLock().unlock();
372                 }
373         }
374
375         /** {@inheritDocs} */
376         @Override
377         public List<PostReply> getReplies(final String postId) {
378                 lock.readLock().lock();
379                 try {
380                         return from(allPostReplies.values())
381                                         .filter(new Predicate<PostReply>() {
382                                                 @Override
383                                                 public boolean apply(PostReply postReply) {
384                                                         return postReply.getPostId().equals(postId);
385                                                 }
386                                         }).toSortedList(TIME_COMPARATOR);
387                 } finally {
388                         lock.readLock().unlock();
389                 }
390         }
391
392         //
393         // POSTREPLYBUILDERFACTORY METHODS
394         //
395
396         /** {@inheritDocs} */
397         @Override
398         public PostReplyBuilder newPostReplyBuilder() {
399                 return new MemoryPostReplyBuilder(this, soneProvider);
400         }
401
402         //
403         // POSTREPLYSTORE METHODS
404         //
405
406         /** {@inheritDocs} */
407         @Override
408         public void storePostReply(PostReply postReply) {
409                 lock.writeLock().lock();
410                 try {
411                         allPostReplies.put(postReply.getId(), postReply);
412                 } finally {
413                         lock.writeLock().unlock();
414                 }
415         }
416
417         /** {@inheritDocs} */
418         @Override
419         public void removePostReply(PostReply postReply) {
420                 lock.writeLock().lock();
421                 try {
422                         allPostReplies.remove(postReply.getId());
423                 } finally {
424                         lock.writeLock().unlock();
425                 }
426         }
427
428         //
429         // ALBUMPROVDER METHODS
430         //
431
432         @Override
433         public Optional<Album> getAlbum(String albumId) {
434                 lock.readLock().lock();
435                 try {
436                         return fromNullable(allAlbums.get(albumId));
437                 } finally {
438                         lock.readLock().unlock();
439                 }
440         }
441
442         //
443         // ALBUMBUILDERFACTORY METHODS
444         //
445
446         @Override
447         public AlbumBuilder newAlbumBuilder() {
448                 return new AlbumBuilderImpl();
449         }
450
451         //
452         // ALBUMSTORE METHODS
453         //
454
455         @Override
456         public void storeAlbum(Album album) {
457                 lock.writeLock().lock();
458                 try {
459                         allAlbums.put(album.getId(), album);
460                         soneAlbums.put(album.getSone().getId(), album);
461                 } finally {
462                         lock.writeLock().unlock();
463                 }
464         }
465
466         @Override
467         public void removeAlbum(Album album) {
468                 lock.writeLock().lock();
469                 try {
470                         allAlbums.remove(album.getId());
471                         soneAlbums.remove(album.getSone().getId(), album);
472                 } finally {
473                         lock.writeLock().unlock();
474                 }
475         }
476
477         //
478         // IMAGEPROVIDER METHODS
479         //
480
481         @Override
482         public Optional<Image> getImage(String imageId) {
483                 lock.readLock().lock();
484                 try {
485                         return fromNullable(allImages.get(imageId));
486                 } finally {
487                         lock.readLock().unlock();
488                 }
489         }
490
491         //
492         // IMAGEBUILDERFACTORY METHODS
493         //
494
495         @Override
496         public ImageBuilder newImageBuilder() {
497                 return new ImageBuilderImpl();
498         }
499
500         //
501         // IMAGESTORE METHODS
502         //
503
504         @Override
505         public void storeImage(Image image) {
506                 lock.writeLock().lock();
507                 try {
508                         allImages.put(image.getId(), image);
509                         soneImages.put(image.getSone().getId(), image);
510                 } finally {
511                         lock.writeLock().unlock();
512                 }
513         }
514
515         @Override
516         public void removeImage(Image image) {
517                 lock.writeLock().lock();
518                 try {
519                         allImages.remove(image.getId());
520                         soneImages.remove(image.getSone().getId(), image);
521                 } finally {
522                         lock.writeLock().unlock();
523                 }
524         }
525
526         @Override
527         public void bookmarkPost(Post post) {
528                 memoryBookmarkDatabase.bookmarkPost(post);
529         }
530
531         @Override
532         public void unbookmarkPost(Post post) {
533                 memoryBookmarkDatabase.unbookmarkPost(post);
534         }
535
536         @Override
537         public boolean isPostBookmarked(Post post) {
538                 return memoryBookmarkDatabase.isPostBookmarked(post);
539         }
540
541         @Override
542         public Set<Post> getBookmarkedPosts() {
543                 return memoryBookmarkDatabase.getBookmarkedPosts();
544         }
545
546         //
547         // PACKAGE-PRIVATE METHODS
548         //
549
550         /**
551          * Returns whether the given post is known.
552          *
553          * @param post
554          *              The post
555          * @return {@code true} if the post is known, {@code false} otherwise
556          */
557         boolean isPostKnown(Post post) {
558                 lock.readLock().lock();
559                 try {
560                         return knownPosts.contains(post.getId());
561                 } finally {
562                         lock.readLock().unlock();
563                 }
564         }
565
566         /**
567          * Sets whether the given post is known.
568          *
569          * @param post
570          *              The post
571          * @param known
572          *              {@code true} if the post is known, {@code false} otherwise
573          */
574         void setPostKnown(Post post, boolean known) {
575                 lock.writeLock().lock();
576                 try {
577                         if (known) {
578                                 knownPosts.add(post.getId());
579                         } else {
580                                 knownPosts.remove(post.getId());
581                         }
582                 } finally {
583                         lock.writeLock().unlock();
584                 }
585         }
586
587         /**
588          * Returns whether the given post reply is known.
589          *
590          * @param postReply
591          *              The post reply
592          * @return {@code true} if the given post reply is known, {@code false}
593          *         otherwise
594          */
595         boolean isPostReplyKnown(PostReply postReply) {
596                 lock.readLock().lock();
597                 try {
598                         return knownPostReplies.contains(postReply.getId());
599                 } finally {
600                         lock.readLock().unlock();
601                 }
602         }
603
604         /**
605          * Sets whether the given post reply is known.
606          *
607          * @param postReply
608          *              The post reply
609          * @param known
610          *              {@code true} if the post reply is known, {@code false} otherwise
611          */
612         void setPostReplyKnown(PostReply postReply, boolean known) {
613                 lock.writeLock().lock();
614                 try {
615                         if (known) {
616                                 knownPostReplies.add(postReply.getId());
617                         } else {
618                                 knownPostReplies.remove(postReply.getId());
619                         }
620                 } finally {
621                         lock.writeLock().unlock();
622                 }
623         }
624
625         //
626         // PRIVATE METHODS
627         //
628
629         /**
630          * Gets all posts for the given Sone, creating a new collection if there is
631          * none yet.
632          *
633          * @param soneId
634          *              The ID of the Sone to get the posts for
635          * @return All posts
636          */
637         private Collection<Post> getPostsFrom(String soneId) {
638                 lock.readLock().lock();
639                 try {
640                         return sonePosts.get(soneId);
641                 } finally {
642                         lock.readLock().unlock();
643                 }
644         }
645
646         /** Loads the known posts. */
647         private void loadKnownPosts() {
648                 Set<String> knownPosts = configurationLoader.loadKnownPosts();
649                 lock.writeLock().lock();
650                 try {
651                         this.knownPosts.clear();
652                         this.knownPosts.addAll(knownPosts);
653                 } finally {
654                         lock.writeLock().unlock();
655                 }
656         }
657
658         /**
659          * Saves the known posts to the configuration.
660          *
661          * @throws DatabaseException
662          *              if a configuration error occurs
663          */
664         private void saveKnownPosts() throws DatabaseException {
665                 lock.readLock().lock();
666                 try {
667                         int postCounter = 0;
668                         for (String knownPostId : knownPosts) {
669                                 configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
670                         }
671                         configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
672                 } catch (ConfigurationException ce1) {
673                         throw new DatabaseException("Could not save database.", ce1);
674                 } finally {
675                         lock.readLock().unlock();
676                 }
677         }
678
679         /**
680          * Returns all replies by the given Sone.
681          *
682          * @param id
683          *              The ID of the Sone
684          * @return The post replies of the Sone, sorted by time (newest first)
685          */
686         private Collection<PostReply> getRepliesFrom(String id) {
687                 lock.readLock().lock();
688                 try {
689                         return unmodifiableCollection(sonePostReplies.get(id));
690                 } finally {
691                         lock.readLock().unlock();
692                 }
693         }
694
695         /** Loads the known post replies. */
696         private void loadKnownPostReplies() {
697                 Set<String> knownPostReplies = configurationLoader.loadKnownPostReplies();
698                 lock.writeLock().lock();
699                 try {
700                         this.knownPostReplies.clear();
701                         this.knownPostReplies.addAll(knownPostReplies);
702                 } finally {
703                         lock.writeLock().unlock();
704                 }
705         }
706
707         /**
708          * Saves the known post replies to the configuration.
709          *
710          * @throws DatabaseException
711          *              if a configuration error occurs
712          */
713         private void saveKnownPostReplies() throws DatabaseException {
714                 lock.readLock().lock();
715                 try {
716                         int replyCounter = 0;
717                         for (String knownReplyId : knownPostReplies) {
718                                 configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
719                         }
720                         configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
721                 } catch (ConfigurationException ce1) {
722                         throw new DatabaseException("Could not save database.", ce1);
723                 } finally {
724                         lock.readLock().unlock();
725                 }
726         }
727
728 }