2 * Sone - MemoryPostDatabase.java - Copyright © 2014 David Roden
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.
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.
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/>.
18 package net.pterodactylus.sone.database.memory;
20 import static com.google.common.base.Optional.fromNullable;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.collect.FluentIterable.from;
23 import static com.google.common.collect.HashMultimap.create;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
31 import java.util.concurrent.locks.ReadWriteLock;
33 import net.pterodactylus.sone.data.Post;
34 import net.pterodactylus.sone.data.Sone;
35 import net.pterodactylus.sone.database.DatabaseException;
36 import net.pterodactylus.sone.database.PostDatabase;
37 import net.pterodactylus.util.config.Configuration;
38 import net.pterodactylus.util.config.ConfigurationException;
40 import com.google.common.base.Function;
41 import com.google.common.base.Optional;
42 import com.google.common.collect.Multimap;
43 import com.google.common.collect.SetMultimap;
46 * Memory-based {@link PostDatabase} implementation.
48 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
50 public class MemoryPostDatabase implements PostDatabase {
52 private final MemoryDatabase memoryDatabase;
53 private final ReadWriteLock readWriteLock;
54 private final Configuration configuration;
55 private final Map<String, Post> allPosts = new HashMap<String, Post>();
56 private final Multimap<String, Post> sonePosts = create();
57 private final SetMultimap<String, String> likedPostsBySone = create();
58 private final SetMultimap<String, String> postLikingSones = create();
59 private final Multimap<String, Post> recipientPosts = create();
60 private final Set<String> knownPosts = new HashSet<String>();
62 public MemoryPostDatabase(MemoryDatabase memoryDatabase, ReadWriteLock readWriteLock, Configuration configuration) {
63 this.memoryDatabase = memoryDatabase;
64 this.readWriteLock = readWriteLock;
65 this.configuration = configuration;
69 public Function<String, Optional<Post>> getPost() {
70 return new Function<String, Optional<Post>>() {
72 public Optional<Post> apply(String postId) {
73 return (postId == null) ? Optional.<Post>absent() : getPost(postId);
79 public Optional<Post> getPost(String postId) {
80 readWriteLock.readLock().lock();
82 return fromNullable(allPosts.get(postId));
84 readWriteLock.readLock().unlock();
89 public Collection<Post> getPosts(String soneId) {
90 readWriteLock.readLock().lock();
92 return new HashSet<Post>(sonePosts.get(soneId));
94 readWriteLock.readLock().unlock();
99 public Collection<Post> getDirectedPosts(String recipientId) {
100 readWriteLock.readLock().lock();
102 Collection<Post> posts = recipientPosts.get(recipientId);
103 return (posts == null) ? Collections.<Post>emptySet() : new HashSet<Post>(posts);
105 readWriteLock.readLock().unlock();
110 * Returns whether the given post is known.
114 * @return {@code true} if the post is known, {@code false} otherwise
117 public boolean isPostKnown(Post post) {
118 readWriteLock.readLock().lock();
120 return knownPosts.contains(post.getId());
122 readWriteLock.readLock().unlock();
127 * Sets whether the given post is known.
133 public void setPostKnown(Post post) {
134 readWriteLock.writeLock().lock();
136 knownPosts.add(post.getId());
138 readWriteLock.writeLock().unlock();
143 public void likePost(Post post, Sone localSone) {
144 readWriteLock.writeLock().lock();
146 likedPostsBySone.put(localSone.getId(), post.getId());
147 postLikingSones.put(post.getId(), localSone.getId());
149 readWriteLock.writeLock().unlock();
154 public void unlikePost(Post post, Sone localSone) {
155 readWriteLock.writeLock().lock();
157 likedPostsBySone.remove(localSone.getId(), post.getId());
158 postLikingSones.remove(post.getId(), localSone.getId());
160 readWriteLock.writeLock().unlock();
164 public boolean isLiked(Post post, Sone sone) {
165 readWriteLock.readLock().lock();
167 return likedPostsBySone.containsEntry(sone.getId(), post.getId());
169 readWriteLock.readLock().unlock();
174 public Set<Sone> getLikes(Post post) {
175 readWriteLock.readLock().lock();
177 return from(postLikingSones.get(post.getId())).transform(memoryDatabase.getSone()).transformAndConcat(MemoryDatabase.<Sone>unwrap()).toSet();
179 readWriteLock.readLock().unlock();
184 public void storePost(Post post) {
185 checkNotNull(post, "post must not be null");
186 readWriteLock.writeLock().lock();
188 allPosts.put(post.getId(), post);
189 sonePosts.put(post.getSone().getId(), post);
190 if (post.getRecipientId().isPresent()) {
191 recipientPosts.put(post.getRecipientId().get(), post);
194 readWriteLock.writeLock().unlock();
199 public void removePost(Post post) {
200 checkNotNull(post, "post must not be null");
201 readWriteLock.writeLock().lock();
203 allPosts.remove(post.getId());
204 sonePosts.remove(post.getSone().getId(), post);
205 if (post.getRecipientId().isPresent()) {
206 recipientPosts.remove(post.getRecipientId().get(), post);
208 post.getSone().removePost(post);
210 readWriteLock.writeLock().unlock();
215 public void storePosts(Sone sone, Collection<Post> posts) throws IllegalArgumentException {
216 checkNotNull(sone, "sone must not be null");
217 /* verify that all posts are from the same Sone. */
218 for (Post post : posts) {
219 if (!sone.equals(post.getSone())) {
220 throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post));
224 readWriteLock.writeLock().lock();
226 /* remove all posts by the Sone. */
227 sonePosts.removeAll(sone.getId());
228 for (Post post : posts) {
229 allPosts.remove(post.getId());
230 if (post.getRecipientId().isPresent()) {
231 recipientPosts.remove(post.getRecipientId().get(), post);
236 sonePosts.putAll(sone.getId(), posts);
237 for (Post post : posts) {
238 allPosts.put(post.getId(), post);
239 if (post.getRecipientId().isPresent()) {
240 recipientPosts.put(post.getRecipientId().get(), post);
244 readWriteLock.writeLock().unlock();
249 public void removePosts(Sone sone) {
250 checkNotNull(sone, "sone must not be null");
251 readWriteLock.writeLock().lock();
253 /* remove all posts by the Sone. */
254 sonePosts.removeAll(sone.getId());
255 for (Post post : sone.getPosts()) {
256 allPosts.remove(post.getId());
257 if (post.getRecipientId().isPresent()) {
258 recipientPosts.remove(post.getRecipientId().get(), post);
262 readWriteLock.writeLock().unlock();
266 public void start() {
267 readWriteLock.writeLock().lock();
271 String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null);
272 if (knownPostId == null) {
275 knownPosts.add(knownPostId);
278 readWriteLock.writeLock().unlock();
282 public void stop() throws DatabaseException {
286 public void save() throws DatabaseException {
287 readWriteLock.readLock().lock();
290 for (String knownPostId : knownPosts) {
291 configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
293 configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
294 } catch (ConfigurationException ce1) {
295 throw new DatabaseException("Could not save database.", ce1);
297 readWriteLock.readLock().unlock();