From 268574fb0cd808a3a575843af256f8b98b643cb6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Sat, 2 Feb 2013 22:49:29 +0100 Subject: [PATCH] Add memory-based post database implementation. --- .../sone/database/memory/MemoryPost.java | 186 ++++++++++ .../sone/database/memory/MemoryPostDatabase.java | 408 +++++++++++++++++++++ 2 files changed, 594 insertions(+) create mode 100644 src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java create mode 100644 src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java new file mode 100644 index 0000000..84f3714 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java @@ -0,0 +1,186 @@ +/* + * Sone - PostImpl.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database.memory; + +import java.util.UUID; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.database.SoneProvider; + +import com.google.common.base.Optional; + +/** + * A post is a short message that a user writes in his Sone to let other users + * know what is going on. + * + * @author David ‘Bombe’ Roden + */ +class MemoryPost implements Post { + + /** The post database. */ + private final MemoryPostDatabase postDatabase; + + /** The Sone provider. */ + private final SoneProvider soneProvider; + + /** The GUID of the post. */ + private final UUID id; + + /** The ID of the owning Sone. */ + private final String soneId; + + /** The ID of the recipient Sone. */ + private final String recipientId; + + /** The time of the post (in milliseconds since Jan 1, 1970 UTC). */ + private final long time; + + /** The text of the post. */ + private final String text; + + /** + * Creates a new post. + * + * @param postDatabase + * The post database + * @param soneProvider + * The Sone provider + * @param id + * The ID of the post + * @param soneId + * The ID of the Sone this post belongs to + * @param recipientId + * The ID of the recipient of the post + * @param time + * The time of the post (in milliseconds since Jan 1, 1970 UTC) + * @param text + * The text of the post + */ + public MemoryPost(MemoryPostDatabase postDatabase, SoneProvider soneProvider, String id, String soneId, String recipientId, long time, String text) { + this.postDatabase = postDatabase; + this.soneProvider = soneProvider; + this.id = UUID.fromString(id); + this.soneId = soneId; + this.recipientId = recipientId; + this.time = time; + this.text = text; + } + + // + // ACCESSORS + // + + /** + * {@inheritDoc} + */ + @Override + public String getId() { + return id.toString(); + } + + /** + * {@inheritDoc} + */ + @Override + public Sone getSone() { + return soneProvider.getSone(soneId).get(); + } + + /** + * {@inheritDocs} + */ + @Override + public Optional getRecipientId() { + return Optional.fromNullable(recipientId); + } + + /** + * {@inheritDoc} + */ + @Override + public Optional getRecipient() { + return soneProvider.getSone(recipientId); + } + + /** + * {@inheritDoc} + */ + @Override + public long getTime() { + return time; + } + + /** + * {@inheritDoc} + */ + @Override + public String getText() { + return text; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isKnown() { + return postDatabase.isPostKnown(this); + } + + /** + * {@inheritDoc} + */ + @Override + public MemoryPost setKnown(boolean known) { + postDatabase.setPostKnown(this, known); + return this; + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return id.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof MemoryPost)) { + return false; + } + MemoryPost post = (MemoryPost) object; + return post.id.equals(id); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return String.format("%s[id=%s,sone=%s,recipient=%s,time=%d,text=%s]", getClass().getName(), id, soneId, recipientId, time, text); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java new file mode 100644 index 0000000..eed00ea --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostDatabase.java @@ -0,0 +1,408 @@ +/* + * Sone - MemoryPostDatabase.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database.memory; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import net.pterodactylus.sone.data.Post; +import net.pterodactylus.sone.data.Sone; +import net.pterodactylus.sone.data.impl.AbstractPostBuilder; +import net.pterodactylus.sone.database.PostBuilder; +import net.pterodactylus.sone.database.PostDatabase; +import net.pterodactylus.sone.database.SoneProvider; +import net.pterodactylus.util.config.Configuration; +import net.pterodactylus.util.config.ConfigurationException; + +import com.google.common.base.Optional; +import com.google.inject.Inject; + +/** + * Memory-based {@link PostDatabase} implementation. + * + * @author David ‘Bombe’ Roden + */ +public class MemoryPostDatabase implements PostDatabase { + + /** The lock. */ + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + /** The Sone provider. */ + private final SoneProvider soneProvider; + + /** All posts by their ID. */ + private final Map allPosts = new HashMap(); + + /** All posts by their Sones. */ + private final Map> sonePosts = new HashMap>(); + + /** All posts by their recipient. */ + private final Map> recipientPosts = new HashMap>(); + + /** Whether posts are known. */ + private final Set knownPosts = new HashSet(); + + /** + * Creates a new memory database. + * + * @param soneProvider + * The Sone provider + */ + @Inject + public MemoryPostDatabase(SoneProvider soneProvider) { + this.soneProvider = soneProvider; + } + + // + // POSTPROVIDER METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public Optional getPost(String postId) { + lock.readLock().lock(); + try { + return Optional.fromNullable(allPosts.get(postId)); + } finally { + lock.readLock().unlock(); + } + } + + /** + * {@inheritDocs} + */ + @Override + public Collection getPosts(String soneId) { + return new HashSet(getPostsFrom(soneId)); + } + + /** + * {@inheritDocs} + */ + @Override + public Collection getDirectedPosts(String recipientId) { + lock.readLock().lock(); + try { + Collection posts = recipientPosts.get(recipientId); + return (posts == null) ? Collections. emptySet() : new HashSet(posts); + } finally { + lock.readLock().unlock(); + } + } + + // + // POSTBUILDERFACTORY METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public PostBuilder newPostBuilder() { + return new MemoryPostBuilder(soneProvider); + } + + // + // POSTSTORE METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public void storePost(Post post) { + checkNotNull(post, "post must not be null"); + lock.writeLock().lock(); + try { + allPosts.put(post.getId(), post); + getPostsFrom(post.getSone().getId()).add(post); + if (post.getRecipientId().isPresent()) { + getPostsTo(post.getRecipientId().get()).add(post); + } + } finally { + lock.writeLock().unlock(); + } + } + + /** + * {@inheritDocs} + */ + @Override + public void removePost(Post post) { + checkNotNull(post, "post must not be null"); + lock.writeLock().lock(); + try { + allPosts.remove(post.getId()); + getPostsFrom(post.getSone().getId()).remove(post); + if (post.getRecipientId().isPresent()) { + getPostsTo(post.getRecipientId().get()).remove(post); + } + post.getSone().removePost(post); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * {@inheritDocs} + */ + @Override + public void storePosts(Sone sone, Collection posts) throws IllegalArgumentException { + checkNotNull(sone, "sone must not be null"); + /* verify that all posts are from the same Sone. */ + for (Post post : posts) { + if (!sone.equals(post.getSone())) { + throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post)); + } + } + + lock.writeLock().lock(); + try { + /* remove all posts by the Sone. */ + getPostsFrom(sone.getId()).clear(); + for (Post post : posts) { + allPosts.remove(post.getId()); + if (post.getRecipientId().isPresent()) { + getPostsTo(post.getRecipientId().get()).remove(post); + } + } + + /* add new posts. */ + getPostsFrom(sone.getId()).addAll(posts); + for (Post post : posts) { + allPosts.put(post.getId(), post); + if (post.getRecipientId().isPresent()) { + getPostsTo(post.getRecipientId().get()).add(post); + } + } + } finally { + lock.writeLock().unlock(); + } + } + + /** + * {@inheritDocs} + */ + @Override + public void removePosts(Sone sone) { + checkNotNull(sone, "sone must not be null"); + lock.writeLock().lock(); + try { + /* remove all posts by the Sone. */ + getPostsFrom(sone.getId()).clear(); + for (Post post : sone.getPosts()) { + allPosts.remove(post.getId()); + if (post.getRecipientId().isPresent()) { + getPostsTo(post.getRecipientId().get()).remove(post); + } + } + } finally { + lock.writeLock().unlock(); + } + } + + // + // POSTDATABASE METHODS + // + + /** + * {@inheritDocs} + */ + @Override + public void loadKnownPosts(Configuration configuration, String prefix) { + lock.writeLock().lock(); + try { + int postCounter = 0; + while (true) { + String knownPostId = configuration.getStringValue(prefix + postCounter++ + "/ID").getValue(null); + if (knownPostId == null) { + break; + } + knownPosts.add(knownPostId); + } + } finally { + lock.writeLock().unlock(); + } + } + + /** + * {@inheritDocs} + */ + @Override + public void saveKnownPosts(Configuration configuration, String prefix) throws ConfigurationException { + lock.readLock().lock(); + try { + int postCounter = 0; + for (String knownPostId : knownPosts) { + configuration.getStringValue(prefix + postCounter++ + "/ID").setValue(knownPostId); + } + configuration.getStringValue(prefix + postCounter + "/ID").setValue(null); + } finally { + lock.readLock().unlock(); + } + } + + // + // PACKAGE-PRIVATE METHODS + // + + /** + * Returns whether the given post is known. + * + * @param post + * The post + * @return {@code true} if the post is known, {@code false} otherwise + */ + boolean isPostKnown(Post post) { + lock.readLock().lock(); + try { + return knownPosts.contains(post.getId()); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Sets whether the given post is known. + * + * @param post + * The post + * @param known + * {@code true} if the post is known, {@code false} otherwise + */ + void setPostKnown(Post post, boolean known) { + lock.writeLock().lock(); + try { + if (known) { + knownPosts.add(post.getId()); + } else { + knownPosts.remove(post.getId()); + } + } finally { + lock.writeLock().unlock(); + } + } + + // + // PRIVATE METHODS + // + + /** + * Gets all posts for the given Sone, creating a new collection if there is + * none yet. + * + * @param soneId + * The ID of the Sone to get the posts for + * @return All posts + */ + private Collection getPostsFrom(String soneId) { + Collection posts = null; + lock.readLock().lock(); + try { + posts = sonePosts.get(soneId); + } finally { + lock.readLock().unlock(); + } + if (posts != null) { + return posts; + } + + posts = new HashSet(); + lock.writeLock().lock(); + try { + sonePosts.put(soneId, posts); + } finally { + lock.writeLock().unlock(); + } + + return posts; + } + + /** + * Gets all posts that are directed the given Sone, creating a new + * collection if there is none yet. + * + * @param recipientId + * The ID of the Sone to get the posts for + * @return All posts + */ + private Collection getPostsTo(String recipientId) { + Collection posts = null; + lock.readLock().lock(); + try { + posts = recipientPosts.get(recipientId); + } finally { + lock.readLock().unlock(); + } + if (posts != null) { + return posts; + } + + posts = new HashSet(); + lock.writeLock().lock(); + try { + recipientPosts.put(recipientId, posts); + } finally { + lock.writeLock().unlock(); + } + + return posts; + } + + /** + * {@link PostBuilder} implementation that creates a {@link MemoryPost}. + * + * @author David ‘Bombe’ Roden + */ + private class MemoryPostBuilder extends AbstractPostBuilder { + + /** + * Creates a new memory post builder. + * + * @param soneProvider + * The Sone provider + */ + public MemoryPostBuilder(SoneProvider soneProvider) { + super(soneProvider); + } + + /** + * {@inheritDocs} + */ + @Override + public Post build() throws IllegalStateException { + validate(); + Post post = new MemoryPost(MemoryPostDatabase.this, soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, recipientId, currentTime ? System.currentTimeMillis() : time, text); + post.setKnown(isPostKnown(post)); + return post; + } + + } + +} -- 2.7.4