Only use post IDs in post reply provider interface.
[Sone.git] / src / main / java / net / pterodactylus / sone / database / memory / MemoryDatabase.java
1 /*
2  * Sone - MemoryPostDatabase.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.Preconditions.checkNotNull;
21
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.UUID;
29 import java.util.concurrent.locks.ReadWriteLock;
30 import java.util.concurrent.locks.ReentrantReadWriteLock;
31
32 import net.pterodactylus.sone.data.Post;
33 import net.pterodactylus.sone.data.Sone;
34 import net.pterodactylus.sone.data.impl.AbstractPostBuilder;
35 import net.pterodactylus.sone.database.PostBuilder;
36 import net.pterodactylus.sone.database.PostDatabase;
37 import net.pterodactylus.sone.database.SoneProvider;
38 import net.pterodactylus.util.config.Configuration;
39 import net.pterodactylus.util.config.ConfigurationException;
40
41 import com.google.common.base.Optional;
42 import com.google.inject.Inject;
43
44 /**
45  * Memory-based {@link PostDatabase} implementation.
46  *
47  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
48  */
49 public class MemoryPostDatabase implements PostDatabase {
50
51         /** The lock. */
52         private final ReadWriteLock lock = new ReentrantReadWriteLock();
53
54         /** The Sone provider. */
55         private final SoneProvider soneProvider;
56
57         /** All posts by their ID. */
58         private final Map<String, Post> allPosts = new HashMap<String, Post>();
59
60         /** All posts by their Sones. */
61         private final Map<String, Collection<Post>> sonePosts = new HashMap<String, Collection<Post>>();
62
63         /** All posts by their recipient. */
64         private final Map<String, Collection<Post>> recipientPosts = new HashMap<String, Collection<Post>>();
65
66         /** Whether posts are known. */
67         private final Set<String> knownPosts = new HashSet<String>();
68
69         /**
70          * Creates a new memory database.
71          *
72          * @param soneProvider
73          *            The Sone provider
74          */
75         @Inject
76         public MemoryPostDatabase(SoneProvider soneProvider) {
77                 this.soneProvider = soneProvider;
78         }
79
80         //
81         // POSTPROVIDER METHODS
82         //
83
84         /**
85          * {@inheritDocs}
86          */
87         @Override
88         public Optional<Post> getPost(String postId) {
89                 lock.readLock().lock();
90                 try {
91                         return Optional.fromNullable(allPosts.get(postId));
92                 } finally {
93                         lock.readLock().unlock();
94                 }
95         }
96
97         /**
98          * {@inheritDocs}
99          */
100         @Override
101         public Collection<Post> getPosts(String soneId) {
102                 return new HashSet<Post>(getPostsFrom(soneId));
103         }
104
105         /**
106          * {@inheritDocs}
107          */
108         @Override
109         public Collection<Post> getDirectedPosts(String recipientId) {
110                 lock.readLock().lock();
111                 try {
112                         Collection<Post> posts = recipientPosts.get(recipientId);
113                         return (posts == null) ? Collections.<Post> emptySet() : new HashSet<Post>(posts);
114                 } finally {
115                         lock.readLock().unlock();
116                 }
117         }
118
119         //
120         // POSTBUILDERFACTORY METHODS
121         //
122
123         /**
124          * {@inheritDocs}
125          */
126         @Override
127         public PostBuilder newPostBuilder() {
128                 return new MemoryPostBuilder(soneProvider);
129         }
130
131         //
132         // POSTSTORE METHODS
133         //
134
135         /**
136          * {@inheritDocs}
137          */
138         @Override
139         public void storePost(Post post) {
140                 checkNotNull(post, "post must not be null");
141                 lock.writeLock().lock();
142                 try {
143                         allPosts.put(post.getId(), post);
144                         getPostsFrom(post.getSone().getId()).add(post);
145                         if (post.getRecipientId().isPresent()) {
146                                 getPostsTo(post.getRecipientId().get()).add(post);
147                         }
148                 } finally {
149                         lock.writeLock().unlock();
150                 }
151         }
152
153         /**
154          * {@inheritDocs}
155          */
156         @Override
157         public void removePost(Post post) {
158                 checkNotNull(post, "post must not be null");
159                 lock.writeLock().lock();
160                 try {
161                         allPosts.remove(post.getId());
162                         getPostsFrom(post.getSone().getId()).remove(post);
163                         if (post.getRecipientId().isPresent()) {
164                                 getPostsTo(post.getRecipientId().get()).remove(post);
165                         }
166                         post.getSone().removePost(post);
167                 } finally {
168                         lock.writeLock().unlock();
169                 }
170         }
171
172         /**
173          * {@inheritDocs}
174          */
175         @Override
176         public void storePosts(Sone sone, Collection<Post> posts) throws IllegalArgumentException {
177                 checkNotNull(sone, "sone must not be null");
178                 /* verify that all posts are from the same Sone. */
179                 for (Post post : posts) {
180                         if (!sone.equals(post.getSone())) {
181                                 throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post));
182                         }
183                 }
184
185                 lock.writeLock().lock();
186                 try {
187                         /* remove all posts by the Sone. */
188                         getPostsFrom(sone.getId()).clear();
189                         for (Post post : posts) {
190                                 allPosts.remove(post.getId());
191                                 if (post.getRecipientId().isPresent()) {
192                                         getPostsTo(post.getRecipientId().get()).remove(post);
193                                 }
194                         }
195
196                         /* add new posts. */
197                         getPostsFrom(sone.getId()).addAll(posts);
198                         for (Post post : posts) {
199                                 allPosts.put(post.getId(), post);
200                                 if (post.getRecipientId().isPresent()) {
201                                         getPostsTo(post.getRecipientId().get()).add(post);
202                                 }
203                         }
204                 } finally {
205                         lock.writeLock().unlock();
206                 }
207         }
208
209         /**
210          * {@inheritDocs}
211          */
212         @Override
213         public void removePosts(Sone sone) {
214                 checkNotNull(sone, "sone must not be null");
215                 lock.writeLock().lock();
216                 try {
217                         /* remove all posts by the Sone. */
218                         getPostsFrom(sone.getId()).clear();
219                         for (Post post : sone.getPosts()) {
220                                 allPosts.remove(post.getId());
221                                 if (post.getRecipientId().isPresent()) {
222                                         getPostsTo(post.getRecipientId().get()).remove(post);
223                                 }
224                         }
225                 } finally {
226                         lock.writeLock().unlock();
227                 }
228         }
229
230         //
231         // POSTDATABASE METHODS
232         //
233
234         /**
235          * {@inheritDocs}
236          */
237         @Override
238         public void loadKnownPosts(Configuration configuration, String prefix) {
239                 lock.writeLock().lock();
240                 try {
241                         int postCounter = 0;
242                         while (true) {
243                                 String knownPostId = configuration.getStringValue(prefix + postCounter++ + "/ID").getValue(null);
244                                 if (knownPostId == null) {
245                                         break;
246                                 }
247                                 knownPosts.add(knownPostId);
248                         }
249                 } finally {
250                         lock.writeLock().unlock();
251                 }
252         }
253
254         /**
255          * {@inheritDocs}
256          */
257         @Override
258         public void saveKnownPosts(Configuration configuration, String prefix) throws ConfigurationException {
259                 lock.readLock().lock();
260                 try {
261                         int postCounter = 0;
262                         for (String knownPostId : knownPosts) {
263                                 configuration.getStringValue(prefix + postCounter++ + "/ID").setValue(knownPostId);
264                         }
265                         configuration.getStringValue(prefix + postCounter + "/ID").setValue(null);
266                 } finally {
267                         lock.readLock().unlock();
268                 }
269         }
270
271         //
272         // PACKAGE-PRIVATE METHODS
273         //
274
275         /**
276          * Returns whether the given post is known.
277          *
278          * @param post
279          *            The post
280          * @return {@code true} if the post is known, {@code false} otherwise
281          */
282         boolean isPostKnown(Post post) {
283                 lock.readLock().lock();
284                 try {
285                         return knownPosts.contains(post.getId());
286                 } finally {
287                         lock.readLock().unlock();
288                 }
289         }
290
291         /**
292          * Sets whether the given post is known.
293          *
294          * @param post
295          *            The post
296          * @param known
297          *            {@code true} if the post is known, {@code false} otherwise
298          */
299         void setPostKnown(Post post, boolean known) {
300                 lock.writeLock().lock();
301                 try {
302                         if (known) {
303                                 knownPosts.add(post.getId());
304                         } else {
305                                 knownPosts.remove(post.getId());
306                         }
307                 } finally {
308                         lock.writeLock().unlock();
309                 }
310         }
311
312         //
313         // PRIVATE METHODS
314         //
315
316         /**
317          * Gets all posts for the given Sone, creating a new collection if there is
318          * none yet.
319          *
320          * @param soneId
321          *            The ID of the Sone to get the posts for
322          * @return All posts
323          */
324         private Collection<Post> getPostsFrom(String soneId) {
325                 Collection<Post> posts = null;
326                 lock.readLock().lock();
327                 try {
328                         posts = sonePosts.get(soneId);
329                 } finally {
330                         lock.readLock().unlock();
331                 }
332                 if (posts != null) {
333                         return posts;
334                 }
335
336                 posts = new HashSet<Post>();
337                 lock.writeLock().lock();
338                 try {
339                         sonePosts.put(soneId, posts);
340                 } finally {
341                         lock.writeLock().unlock();
342                 }
343
344                 return posts;
345         }
346
347         /**
348          * Gets all posts that are directed the given Sone, creating a new
349          * collection if there is none yet.
350          *
351          * @param recipientId
352          *            The ID of the Sone to get the posts for
353          * @return All posts
354          */
355         private Collection<Post> getPostsTo(String recipientId) {
356                 Collection<Post> posts = null;
357                 lock.readLock().lock();
358                 try {
359                         posts = recipientPosts.get(recipientId);
360                 } finally {
361                         lock.readLock().unlock();
362                 }
363                 if (posts != null) {
364                         return posts;
365                 }
366
367                 posts = new HashSet<Post>();
368                 lock.writeLock().lock();
369                 try {
370                         recipientPosts.put(recipientId, posts);
371                 } finally {
372                         lock.writeLock().unlock();
373                 }
374
375                 return posts;
376         }
377
378         /**
379          * {@link PostBuilder} implementation that creates a {@link MemoryPost}.
380          *
381          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
382          */
383         private class MemoryPostBuilder extends AbstractPostBuilder {
384
385                 /**
386                  * Creates a new memory post builder.
387                  *
388                  * @param soneProvider
389                  *            The Sone provider
390                  */
391                 public MemoryPostBuilder(SoneProvider soneProvider) {
392                         super(soneProvider);
393                 }
394
395                 /**
396                  * {@inheritDocs}
397                  */
398                 @Override
399                 public Post build() throws IllegalStateException {
400                         validate();
401                         Post post = new MemoryPost(MemoryPostDatabase.this, soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, recipientId, currentTime ? System.currentTimeMillis() : time, text);
402                         post.setKnown(isPostKnown(post));
403                         return post;
404                 }
405
406         }
407
408 }