+package net.pterodactylus.sone.database.memory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.database.DatabaseException;
+import net.pterodactylus.sone.utils.Optionals;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+
+/**
+ * Groups {@link Post}-related database functions.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+class MemoryPostDatabase {
+
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ private final MemoryDatabase database;
+ private final ConfigurationLoader configurationLoader;
+ private final Multimap<String, String> sonePosts = HashMultimap.create();
+ private final Map<String, String> postSones = new HashMap<String, String>();
+ private final Map<String, Long> postTimes = new HashMap<String, Long>();
+ private final Map<String, String> postRecipients = new HashMap<String, String>();
+ private final Multimap<String, String> recipientPosts = HashMultimap.create();
+ private final Map<String, String> postTexts = new HashMap<String, String>();
+ private final Set<String> knownPosts = new HashSet<String>();
+
+ MemoryPostDatabase(MemoryDatabase database, ConfigurationLoader configurationLoader) {
+ this.database = database;
+ this.configurationLoader = configurationLoader;
+ }
+
+ void start() {
+ loadKnownPosts();
+ }
+
+ void stop() {
+ saveKnownPosts();
+ }
+
+ Optional<Post> getPost(String postId) {
+ lock.readLock().lock();
+ try {
+ if (!postSones.containsKey(postId)) {
+ return Optional.absent();
+ }
+ String sender = postSones.get(postId);
+ String recipientId = postRecipients.get(postId);
+ long time = postTimes.get(postId);
+ String text = postTexts.get(postId);
+ return Optional.<Post>of(
+ new MemoryPost(database, database, postId, sender, recipientId, time, text));
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ Collection<Post> getPostsFrom(String soneId) {
+ lock.readLock().lock();
+ try {
+ return FluentIterable.from(sonePosts.get(soneId))
+ .transform(postLoader)
+ .transform(Optionals.<Post>get())
+ .toList();
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ Collection<Post> getDirectedPosts(String recipientId) {
+ lock.readLock().lock();
+ try {
+ return FluentIterable.from(recipientPosts.get(recipientId))
+ .transform(postLoader)
+ .transform(Optionals.<Post>get())
+ .toList();
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ void storePosts(String soneId, Collection<Post> posts) {
+ lock.writeLock().lock();
+ try {
+ removePostsFor(soneId);
+ for (Post post : posts) {
+ storePost(post);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void storePost(Post post) {
+ String soneId = post.getSone().getId();
+ final String postId = post.getId();
+ sonePosts.put(soneId, postId);
+ postSones.put(postId, soneId);
+ postRecipients.put(postId, post.getRecipientId().orNull());
+ if (post.getRecipientId().isPresent()) {
+ recipientPosts.put(post.getRecipientId().get(), postId);
+ }
+ postTimes.put(postId, post.getTime());
+ postTexts.put(postId, post.getText());
+ }
+
+ void removePostsFor(String soneId) {
+ lock.writeLock().lock();
+ try {
+ for (String postId : sonePosts.removeAll(soneId)) {
+ removePost(postId);
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void removePost(String postId) {
+ lock.writeLock().lock();
+ try {
+ postSones.remove(postId);
+ String recipient = postRecipients.remove(postId);
+ if (recipient != null) {
+ recipientPosts.remove(recipient, postId);
+ }
+ postTimes.remove(postId);
+ postTexts.remove(postId);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ boolean isPostKnown(String postId) {
+ lock.readLock().lock();
+ try {
+ return knownPosts.contains(postId);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ void setPostKnown(String postId, boolean known) {
+ lock.writeLock().lock();
+ try {
+ if (known) {
+ knownPosts.add(postId);
+ } else {
+ knownPosts.remove(postId);
+ }
+ saveKnownPosts();
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private void loadKnownPosts() {
+ Set<String> knownPosts = configurationLoader.loadKnownPosts();
+ lock.writeLock().lock();
+ try {
+ this.knownPosts.clear();
+ this.knownPosts.addAll(knownPosts);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ private void saveKnownPosts() {
+ lock.readLock().lock();
+ try {
+ configurationLoader.saveKnownPosts(knownPosts);
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ private Function<String, Optional<Post>> postLoader = new Function<String, Optional<Post>>() {
+ @Override
+ public Optional<Post> apply(String input) {
+ return getPost(input);
+ }
+ };
+
+}