Merge branch 'less-critical' into run
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 25 Jan 2013 06:23:10 +0000 (07:23 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 25 Jan 2013 06:23:10 +0000 (07:23 +0100)
43 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/PostProvider.java
src/main/java/net/pterodactylus/sone/core/PostReplyProvider.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/core/SoneUri.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/Post.java
src/main/java/net/pterodactylus/sone/data/PostBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/PostBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/PostReply.java
src/main/java/net/pterodactylus/sone/data/PostReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/PostReplyBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/ReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/AbstractReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/PostImpl.java
src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java
src/main/java/net/pterodactylus/sone/template/ReplyGroupFilter.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java
src/main/java/net/pterodactylus/sone/web/DeletePostPage.java
src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java
src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java
src/main/java/net/pterodactylus/sone/web/NewPage.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/ViewPostPage.java
src/main/java/net/pterodactylus/sone/web/ViewSonePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java

diff --git a/pom.xml b/pom.xml
index 6743885..d4637ff 100644 (file)
--- a/pom.xml
+++ b/pom.xml
                        <artifactId>guava</artifactId>
                        <version>14.0-rc1</version>
                </dependency>
+               <dependency>
+                       <groupId>commons-lang</groupId>
+                       <artifactId>commons-lang</artifactId>
+                       <version>2.6</version>
+               </dependency>
        </dependencies>
        <repositories>
                <repository>
index a51247e..33e57c7 100644 (file)
@@ -56,7 +56,11 @@ import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostBuilder;
+import net.pterodactylus.sone.data.PostBuilderFactory;
 import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.PostReplyBuilder;
+import net.pterodactylus.sone.data.PostReplyBuilderFactory;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Reply;
@@ -64,8 +68,6 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
 import net.pterodactylus.sone.data.TemporaryImage;
-import net.pterodactylus.sone.data.impl.PostImpl;
-import net.pterodactylus.sone.data.impl.PostReplyImpl;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
 import net.pterodactylus.sone.freenet.wot.Identity;
@@ -85,9 +87,13 @@ import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.service.AbstractService;
 import net.pterodactylus.util.thread.NamedThreadFactory;
 
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Collections2;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Ordering;
 import com.google.common.eventbus.EventBus;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
@@ -99,7 +105,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Core extends AbstractService implements SoneProvider, PostProvider {
+public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(Core.class);
@@ -168,12 +174,18 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        /** All known Sones. */
        private final Set<String> knownSones = new HashSet<String>();
 
+       /** The post builder. */
+       private final PostBuilderFactory postBuilderFactory;
+
        /** All posts. */
        private final Map<String, Post> posts = new HashMap<String, Post>();
 
        /** All known posts. */
        private final Set<String> knownPosts = new HashSet<String>();
 
+       /** The post reply builder factory. */
+       private final PostReplyBuilderFactory postReplyBuilderFactory;
+
        /** All replies. */
        private final Map<String, PostReply> replies = new HashMap<String, PostReply>();
 
@@ -215,9 +227,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         *            The WebOfTrust updater
         * @param eventBus
         *            The event bus
+        * @param postBuilderFactory
+        *            The post builder
+        * @param postReplyBuilderFactory
+        *            The post reply builder factory
         */
        @Inject
-       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus) {
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, PostBuilderFactory postBuilderFactory, PostReplyBuilderFactory postReplyBuilderFactory) {
                super("Sone Core");
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
@@ -227,6 +243,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                this.updateChecker = new UpdateChecker(eventBus, freenetInterface);
                this.webOfTrustUpdater = webOfTrustUpdater;
                this.eventBus = eventBus;
+               this.postBuilderFactory = postBuilderFactory;
+               this.postReplyBuilderFactory = postReplyBuilderFactory;
        }
 
        //
@@ -511,35 +529,21 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        }
 
        /**
-        * Returns the post with the given ID.
+        * Returns a post builder.
         *
-        * @param postId
-        *            The ID of the post to get
-        * @return The post with the given ID, or a new post with the given ID
+        * @return A new post builder
         */
-       public Post getPost(String postId) {
-               return getPost(postId, true);
+       public PostBuilder postBuilder() {
+               return postBuilderFactory.newPostBuilder();
        }
 
        /**
-        * Returns the post with the given ID, optionally creating a new post.
-        *
-        * @param postId
-        *            The ID of the post to get
-        * @param create
-        *            {@code true} it create a new post if no post with the given ID
-        *            exists, {@code false} to return {@code null}
-        * @return The post, or {@code null} if there is no such post
+        * {@inheritDoc}
         */
        @Override
-       public Post getPost(String postId, boolean create) {
+       public Optional<Post> getPost(String postId) {
                synchronized (posts) {
-                       Post post = posts.get(postId);
-                       if ((post == null) && create) {
-                               post = new PostImpl(postId);
-                               posts.put(postId, post);
-                       }
-                       return post;
+                       return Optional.fromNullable(posts.get(postId));
                }
        }
 
@@ -565,47 +569,42 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        }
 
        /**
-        * Returns the reply with the given ID. If there is no reply with the given
-        * ID yet, a new one is created, unless {@code create} is false in which
-        * case {@code null} is returned.
+        * Returns a post reply builder.
         *
-        * @param replyId
-        *            The ID of the reply to get
-        * @param create
-        *            {@code true} to always return a {@link Reply}, {@code false}
-        *            to return {@code null} if no reply can be found
-        * @return The reply, or {@code null} if there is no such reply
+        * @return A new post reply builder
         */
-       public PostReply getPostReply(String replyId, boolean create) {
+       public PostReplyBuilder postReplyBuilder() {
+               return postReplyBuilderFactory.newPostReplyBuilder();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Optional<PostReply> getPostReply(String replyId) {
                synchronized (replies) {
-                       PostReply reply = replies.get(replyId);
-                       if (create && (reply == null)) {
-                               reply = new PostReplyImpl(replyId);
-                               replies.put(replyId, reply);
-                       }
-                       return reply;
+                       return Optional.fromNullable(replies.get(replyId));
                }
        }
 
        /**
-        * Returns all replies for the given post, order ascending by time.
-        *
-        * @param post
-        *            The post to get all replies for
-        * @return All replies for the given post
+        * {@inheritDoc}
         */
-       public List<PostReply> getReplies(Post post) {
-               Set<Sone> sones = getSones();
-               List<PostReply> replies = new ArrayList<PostReply>();
-               for (Sone sone : sones) {
-                       for (PostReply reply : sone.getReplies()) {
-                               if (reply.getPost().equals(post)) {
-                                       replies.add(reply);
-                               }
+       @Override
+       public List<PostReply> getReplies(final Post post) {
+               return Ordering.from(Reply.TIME_COMPARATOR).sortedCopy(FluentIterable.from(getSones()).transformAndConcat(new Function<Sone, Iterable<PostReply>>() {
+
+                       @Override
+                       public Iterable<PostReply> apply(Sone sone) {
+                               return sone.getReplies();
                        }
-               }
-               Collections.sort(replies, Reply.TIME_COMPARATOR);
-               return replies;
+               }).filter(new Predicate<PostReply>() {
+
+                       @Override
+                       public boolean apply(PostReply reply) {
+                               return post.equals(reply.getPost());
+                       }
+               }));
        }
 
        /**
@@ -677,9 +676,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                Set<Post> posts = new HashSet<Post>();
                synchronized (bookmarkedPosts) {
                        for (String bookmarkedPostId : bookmarkedPosts) {
-                               Post post = getPost(bookmarkedPostId, false);
-                               if (post != null) {
-                                       posts.add(post);
+                               Optional<Post> post = getPost(bookmarkedPostId);
+                               if (!post.isPresent()) {
+                                       posts.add(post.get());
                                }
                        }
                }
@@ -878,7 +877,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                synchronized (sones) {
                        final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity);
                        boolean newSone = sone.getRequestUri() == null;
-                       sone.setRequestUri(getSoneUri(identity.getRequestUri()));
+                       sone.setRequestUri(SoneUri.create(identity.getRequestUri()));
                        sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0));
                        if (newSone) {
                                synchronized (knownSones) {
@@ -1110,16 +1109,18 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                List<Post> storedPosts = storedSone.getPosts();
                                synchronized (knownPosts) {
                                        for (Post post : sone.getPosts()) {
-                                               post.setSone(storedSone).setKnown(knownPosts.contains(post.getId()));
-                                               if (!storedPosts.contains(post)) {
-                                                       if (post.getTime() < getSoneFollowingTime(sone)) {
-                                                               knownPosts.add(post.getId());
-                                                               post.setKnown(true);
-                                                       } else if (!knownPosts.contains(post.getId())) {
-                                                               eventBus.post(new NewPostFoundEvent(post));
+                                               PostBuilder postBuilder = postBuilderFactory.newPostBuilder();
+                                               postBuilder.copyPost(post).from(storedSone);
+                                               Post newPost = postBuilder.build().setKnown(knownPosts.contains(post.getId()));
+                                               if (!storedPosts.contains(newPost)) {
+                                                       if (newPost.getTime() < getSoneFollowingTime(sone)) {
+                                                               knownPosts.add(newPost.getId());
+                                                               newPost.setKnown(true);
+                                                       } else if (!knownPosts.contains(newPost.getId())) {
+                                                               eventBus.post(new NewPostFoundEvent(newPost));
                                                        }
                                                }
-                                               posts.put(post.getId(), post);
+                                               posts.put(newPost.getId(), newPost);
                                        }
                                }
                        }
@@ -1312,11 +1313,11 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                logger.log(Level.WARNING, "Invalid post found, aborting load!");
                                return;
                        }
-                       Post post = getPost(postId).setSone(sone).setTime(postTime).setText(postText);
+                       PostBuilder postBuilder = postBuilder().withId(postId).from(sone).withTime(postTime).withText(postText);
                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
-                               post.setRecipient(getSone(postRecipientId));
+                               postBuilder.to(getSone(postRecipientId));
                        }
-                       posts.add(post);
+                       posts.add(postBuilder.build());
                }
 
                /* load replies. */
@@ -1334,7 +1335,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                                logger.log(Level.WARNING, "Invalid reply found, aborting load!");
                                return;
                        }
-                       replies.add(getPostReply(replyId, true).setSone(sone).setPost(getPost(postId)).setTime(replyTime).setText(replyText));
+                       PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder();
+                       postReplyBuilder.withId(replyId).from(sone).to(postId).withTime(replyTime).withText(replyText);
+                       replies.add(postReplyBuilder.build());
                }
 
                /* load post likes. */
@@ -1538,10 +1541,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        logger.log(Level.FINE, String.format("Tried to create post for non-local Sone: %s", sone));
                        return null;
                }
-               final Post post = new PostImpl(sone, time, text.trim());
+               PostBuilder postBuilder = postBuilderFactory.newPostBuilder();
+               postBuilder.from(sone).randomId().withTime(time).withText(text.trim());
                if (recipient != null) {
-                       post.setRecipient(recipient);
+                       postBuilder.to(recipient);
                }
+               final Post post = postBuilder.build();
                synchronized (posts) {
                        posts.put(post.getId(), post);
                }
@@ -1657,30 +1662,15 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
         * @return The created reply
         */
        public PostReply createReply(Sone sone, Post post, String text) {
-               return createReply(sone, post, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new reply.
-        *
-        * @param sone
-        *            The Sone that creates the reply
-        * @param post
-        *            The post that this reply refers to
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        * @return The created reply
-        */
-       public PostReply createReply(Sone sone, Post post, long time, String text) {
                checkNotNull(text, "text must not be null");
                checkArgument(text.trim().length() > 0, "text must not be empty");
                if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone));
                        return null;
                }
-               final PostReply reply = new PostReplyImpl(sone, post, System.currentTimeMillis(), text.trim());
+               PostReplyBuilder postReplyBuilder = postReplyBuilderFactory.newPostReplyBuilder();
+               postReplyBuilder.randomId().from(sone).to(post.getId()).currentTime().withText(text.trim());
+               final PostReply reply = postReplyBuilder.build();
                synchronized (replies) {
                        replies.put(reply.getId(), reply);
                }
@@ -2018,7 +2008,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
                        for (PostReply reply : sone.getReplies()) {
                                String replyPrefix = sonePrefix + "/Replies/" + replyCounter++;
                                configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
-                               configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPost().getId());
+                               configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId());
                                configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime());
                                configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText());
                        }
@@ -2320,23 +2310,6 @@ public class Core extends AbstractService implements SoneProvider, PostProvider
        }
 
        /**
-        * Generate a Sone URI from the given URI and latest edition.
-        *
-        * @param uriString
-        *            The URI to derive the Sone URI from
-        * @return The derived URI
-        */
-       private static FreenetURI getSoneUri(String uriString) {
-               try {
-                       FreenetURI uri = new FreenetURI(uriString).setDocName("Sone").setMetaString(new String[0]);
-                       return uri;
-               } catch (MalformedURLException mue1) {
-                       logger.log(Level.WARNING, String.format("Could not create Sone URI from URI: %s", uriString), mue1);
-                       return null;
-               }
-       }
-
-       /**
         * Notifies the core that a new {@link OwnIdentity} was added.
         *
         * @param ownIdentityAddedEvent
index c73ad65..19a0e5d 100644 (file)
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.core;
 
 import net.pterodactylus.sone.data.Post;
 
+import com.google.common.base.Optional;
+
 /**
  * Interface for objects that can provide {@link Post}s by their ID.
  *
@@ -27,17 +29,12 @@ import net.pterodactylus.sone.data.Post;
 public interface PostProvider {
 
        /**
-        * Returns the post with the given ID, if it exists. If it does not exist
-        * and {@code create} is {@code false}, {@code null} is returned; otherwise,
-        * a new post with the given ID is created and returned.
+        * Returns the post with the given ID.
         *
         * @param postId
         *            The ID of the post to return
-        * @param create
-        *            {@code true} to create a new post if no post with the given ID
-        *            exists, {@code false} to return {@code null} instead
         * @return The post with the given ID, or {@code null}
         */
-       public Post getPost(String postId, boolean create);
+       public Optional<Post> getPost(String postId);
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/core/PostReplyProvider.java b/src/main/java/net/pterodactylus/sone/core/PostReplyProvider.java
new file mode 100644 (file)
index 0000000..5decdc7
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Sone - PostReplyProvider.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import java.util.List;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+
+import com.google.common.base.Optional;
+
+/**
+ * Interface for objects that can provide {@link PostReply}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostReplyProvider {
+
+       /**
+        * Returns the reply with the given ID.
+        *
+        * @param id
+        *            The ID of the reply to get
+        * @return The reply, or {@code null} if there is no such reply
+        */
+       public Optional<PostReply> getPostReply(String id);
+
+       /**
+        * Returns all replies for the given post, order ascending by time.
+        *
+        * @param post
+        *            The post to get all replies for
+        * @return All replies for the given post
+        */
+       public List<PostReply> getReplies(Post post);
+
+}
index 70a193a..cf85d03 100644 (file)
@@ -31,7 +31,9 @@ import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostBuilder;
 import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.PostReplyBuilder;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
@@ -372,11 +374,13 @@ public class SoneDownloader extends AbstractService {
                                        return null;
                                }
                                try {
-                                       Post post = core.getPost(postId).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText);
+                                       PostBuilder postBuilder = core.postBuilder();
+                                       /* TODO - parse time correctly. */
+                                       postBuilder.withId(postId).from(sone).withTime(Long.parseLong(postTime)).withText(postText);
                                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
-                                               post.setRecipient(core.getSone(postRecipientId));
+                                               postBuilder.to(core.getSone(postRecipientId));
                                        }
-                                       posts.add(post);
+                                       posts.add(postBuilder.build());
                                } catch (NumberFormatException nfe1) {
                                        /* TODO - mark Sone as bad. */
                                        logger.log(Level.WARNING, String.format("Downloaded post for Sone %s with invalid time: %s", sone, postTime));
@@ -403,7 +407,10 @@ public class SoneDownloader extends AbstractService {
                                        return null;
                                }
                                try {
-                                       replies.add(core.getPostReply(replyId, true).setSone(sone).setPost(core.getPost(replyPostId)).setTime(Long.parseLong(replyTime)).setText(replyText));
+                                       PostReplyBuilder postReplyBuilder = core.postReplyBuilder();
+                                       /* TODO - parse time correctly. */
+                                       postReplyBuilder.withId(replyId).from(sone).to(replyPostId).withTime(Long.parseLong(replyTime)).withText(replyText);
+                                       replies.add(postReplyBuilder.build());
                                } catch (NumberFormatException nfe1) {
                                        /* TODO - mark Sone as bad. */
                                        logger.log(Level.WARNING, String.format("Downloaded reply for Sone %s with invalid time: %s", sone, replyTime));
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneUri.java b/src/main/java/net/pterodactylus/sone/core/SoneUri.java
new file mode 100644 (file)
index 0000000..6c5bf16
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - SoneUri.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import java.net.MalformedURLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.util.logging.Logging;
+import freenet.keys.FreenetURI;
+
+/**
+ * Helper class that creates {@link FreenetURI}s for Sone to insert to and
+ * request from.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneUri {
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(SoneUri.class);
+
+       /**
+        * Generate a Sone URI from the given URI.
+        *
+        * @param uri
+        *            The URI to derive the Sone URI from
+        * @return The derived URI
+        */
+       public static FreenetURI create(String uri) {
+               try {
+                       return new FreenetURI(uri).setDocName("Sone").setMetaString(new String[0]);
+               } catch (MalformedURLException mue1) {
+                       /* this should never happen. */
+                       logger.log(Level.WARNING, String.format("Could not create Sone URI from URI: %s", uri), mue1);
+                       return null;
+               }
+       }
+
+}
index 3486bed..160bb32 100644 (file)
@@ -68,15 +68,6 @@ public interface Post {
        public Sone getSone();
 
        /**
-        * Sets the Sone of this post.
-        *
-        * @param sone
-        *            The Sone of this post
-        * @return This post (for method chaining)
-        */
-       public Post setSone(Sone sone);
-
-       /**
         * Returns the recipient of this post, if any.
         *
         * @return The recipient of this post, or {@code null}
@@ -84,15 +75,6 @@ public interface Post {
        public Sone getRecipient();
 
        /**
-        * Sets the recipient of this post.
-        *
-        * @param recipient
-        *            The recipient of this post, or {@code null}
-        * @return This post (for method chaining)
-        */
-       public Post setRecipient(Sone recipient);
-
-       /**
         * Returns the time of the post.
         *
         * @return The time of the post (in milliseconds since Jan 1, 1970 UTC)
@@ -100,15 +82,6 @@ public interface Post {
        public long getTime();
 
        /**
-        * Sets the time of this post.
-        *
-        * @param time
-        *            The time of this post (in milliseconds since Jan 1, 1970 UTC)
-        * @return This post (for method chaining)
-        */
-       public Post setTime(long time);
-
-       /**
         * Returns the text of the post.
         *
         * @return The text of the post
@@ -116,15 +89,6 @@ public interface Post {
        public String getText();
 
        /**
-        * Sets the text of this post.
-        *
-        * @param text
-        *            The text of this post
-        * @return This post (for method chaining)
-        */
-       public Post setText(String text);
-
-       /**
         * Returns whether this post is known.
         *
         * @return {@code true} if this post is known, {@code false} otherwise
diff --git a/src/main/java/net/pterodactylus/sone/data/PostBuilder.java b/src/main/java/net/pterodactylus/sone/data/PostBuilder.java
new file mode 100644 (file)
index 0000000..00f7354
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Sone - PostBuilder.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data;
+
+/**
+ * Builder for {@link Post} objects.
+ * <p>
+ * A {@link Post} consists of the following elements:
+ * <ul>
+ * <li>an ID,</li>
+ * <li>a {@link Sone sender},</li>
+ * <li>an optional {@link Sone recipient},</li>
+ * <li>a time,</li>
+ * <li>and a text.</li>
+ * </ul>
+ * Except for the recipient, all this elements have to be configured on this
+ * builder. For the ID you have the possibility to configure either a random ID
+ * (which should be used for new posts) or a custom ID you specify (for creating
+ * an existing post). For the time you can use the current time (again, for
+ * creating new posts) or the given time (for loading posts). It is an error to
+ * specify both ways for either the ID or the time.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostBuilder {
+
+       /**
+        * Copies all attributes of the given post to this post builder.
+        *
+        * @param post
+        *            The post whose attributes to copy into this builder
+        * @return This builder
+        * @throws NullPointerException
+        *             if {@code post} is {@code null}
+        */
+       public PostBuilder copyPost(Post post) throws NullPointerException;
+
+       /**
+        * Configures this builder to use the given Sone as sender of the new post.
+        *
+        * @param sender
+        *            The sender of the post
+        * @return This post builder
+        */
+       public PostBuilder from(Sone sender);
+
+       /**
+        * Configures this builder to use a random ID for the new post. If this
+        * method is used, {@link #withId(String)} must not be used.
+        *
+        * @return This post builder
+        */
+       public PostBuilder randomId();
+
+       /**
+        * Configures this builder to use the given ID as ID for the new post. If
+        * this method is used, {@link #randomId()} must not be used.
+        *
+        * @param id
+        *            The ID to use for the post
+        * @return This post builder
+        */
+       public PostBuilder withId(String id);
+
+       /**
+        * Configures this builder to use the current time when creating the post.
+        * If this method is used, {@link #withTime(long)} must not be used.
+        *
+        * @return This post builder
+        */
+       public PostBuilder currentTime();
+
+       /**
+        * Configures the builder to use the given time as time for the new post. If
+        * this method is used, {@link #currentTime()} must not be used.
+        *
+        * @param time
+        *            The time to use for the post
+        * @return This post builder
+        */
+       public PostBuilder withTime(long time);
+
+       /**
+        * Configures the builder to use the given text for the new post.
+        *
+        * @param text
+        *            The text to use for the post
+        * @return This post builder
+        */
+       public PostBuilder withText(String text);
+
+       /**
+        * Configures the builder to use the given {@link Sone} as recipient for the
+        * post.
+        *
+        * @param recipient
+        *            The recipient of the post
+        * @return This post builder
+        */
+       public PostBuilder to(Sone recipient);
+
+       /**
+        * Verifies this builder’s configuration and creates a new post.
+        * <p>
+        * The following conditions must be met in order for this builder to be
+        * configured correctly:
+        * <ul>
+        * <li>Exactly one of {@link #randomId()} or {@link #withId(String)} must
+        * have been called.</li>
+        * <li>The {@link #from(Sone) sender} must not be {@code null}.</li>
+        * <li>Exactly one of {@link #currentTime()} or {@link #withTime(long)} must
+        * have been called.</li>
+        * <li>The {@link #withText(String) text} must not be {@code null} and must
+        * contain something other than whitespace.</li>
+        * <li>The {@link #to(Sone) recipient} must either not have been set, or it
+        * must have been set to a {@link Sone} other than {@link #from(Sone) the
+        * sender}.</li>
+        * </ul>
+        *
+        * @return A new post
+        * @throws IllegalStateException
+        *             if this builder’s configuration is not valid
+        */
+       public Post build() throws IllegalStateException;
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/PostBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/PostBuilderFactory.java
new file mode 100644 (file)
index 0000000..845cdf6
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Sone - PostBuilderFactory.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data;
+
+/**
+ * Factory for {@link PostBuilder}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostBuilderFactory {
+
+       /**
+        * Creates a new post builder.
+        *
+        * @return A new post builder
+        */
+       public PostBuilder newPostBuilder();
+
+}
index a4d9ef3..1dc5f44 100644 (file)
@@ -17,6 +17,9 @@
 
 package net.pterodactylus.sone.data;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
 /**
  * A reply is like a {@link Post} but can never be posted on its own, it always
  * refers to another {@link Post}.
@@ -26,19 +29,38 @@ package net.pterodactylus.sone.data;
 public interface PostReply extends Reply<PostReply> {
 
        /**
+        * Filter that selects {@link PostReply}s that have a
+        * {@link Optional#isPresent() present} {@link #getPost() post}.
+        */
+       public static final Predicate<PostReply> HAS_POST_FILTER = new Predicate<PostReply>() {
+
+               @Override
+               public boolean apply(PostReply postReply) {
+                       return postReply.getPost().isPresent();
+               }
+       };
+
+       /**
+        * Returns the ID of the post this reply refers to.
+        *
+        * @return The ID of the post this reply refers to
+        */
+       public String getPostId();
+
+       /**
         * Returns the post this reply refers to.
         *
         * @return The post this reply refers to
         */
-       public Post getPost();
+       public Optional<Post> getPost();
 
        /**
         * Sets the post this reply refers to.
         *
-        * @param post
-        *            The post this reply refers to
+        * @param postId
+        *            The ID of the post to reply to
         * @return This reply
         */
-       public PostReply setPost(Post post);
+       public PostReply setPost(String postId);
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/data/PostReplyBuilder.java b/src/main/java/net/pterodactylus/sone/data/PostReplyBuilder.java
new file mode 100644 (file)
index 0000000..1ba7fd5
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Sone - PostReplyBuilder.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data;
+
+/**
+ * Builder for a {@link PostReply} object.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostReplyBuilder extends ReplyBuilder<PostReplyBuilder> {
+
+       /**
+        * Configures this builder to set the given post as post the created reply
+        * refers to.
+        *
+        * @param postId
+        *            The ID of the post the reply refers to
+        * @return This builder
+        */
+       public PostReplyBuilder to(String postId);
+
+       /**
+        * Verifies the configuration of this builder and creates a new post reply.
+        * <p>
+        * The following conditions must be met in order for the configuration to be
+        * considered valid:
+        * <ul>
+        * <li>Exactly one of {@link #randomId()} or {@link #withId(String)} must
+        * have been called.</li>
+        * <li>The {@link #from(Sone) sender} must not be {@code null}.</li>
+        * <li>Exactly one of {@link #currentTime()} or {@link #withTime(long)} must
+        * have been called.</li>
+        * <li>The {@link #withText(String) text} must not be {@code null} and must
+        * contain something other than whitespace.</li>
+        * <li>The {@link #to(String) post} have been set.</li>
+        * </ul>
+        *
+        * @return The created post reply
+        * @throws IllegalStateException
+        *             if this builder’s configuration is not valid
+        */
+       public PostReply build() throws IllegalStateException;
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/PostReplyBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/PostReplyBuilderFactory.java
new file mode 100644 (file)
index 0000000..17a92f3
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Sone - PostReplyBuilderFactory.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data;
+
+/**
+ * Factory for {@link PostReplyBuilder}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostReplyBuilderFactory {
+
+       /**
+        * Creates a new post reply builder.
+        *
+        * @return A new post reply builder
+        */
+       public PostReplyBuilder newPostReplyBuilder();
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/ReplyBuilder.java b/src/main/java/net/pterodactylus/sone/data/ReplyBuilder.java
new file mode 100644 (file)
index 0000000..53f3153
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Sone - ReplyBuilder.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data;
+
+/**
+ * Methods that all reply builders need to implement in order to be able to
+ * create any kind of {@link Reply}.
+ *
+ * @param <B>
+ *            The type of the builder
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface ReplyBuilder<B extends ReplyBuilder<B>> {
+
+       /**
+        * Configures this builder to use a random ID when creating the reply. If
+        * this method is used, {@link #withId(String)} must not be used.
+        *
+        * @return This builder
+        */
+       public B randomId();
+
+       /**
+        * Configures this builder to use the given ID when creating the reply. If
+        * this method is used, {@link #randomId()} must not be used.
+        *
+        * @param id
+        *            The ID of the reply
+        * @return This builder
+        */
+       public B withId(String id);
+
+       /**
+        * Configures this builder to use the given {@link Sone} as sender of the
+        * reply.
+        *
+        * @param sender
+        *            The sender of the reply
+        * @return This builder
+        */
+       public B from(Sone sender);
+
+       /**
+        * Configures this builder to use the current time when creating the reply.
+        * If this method is used, {@link #withTime(long)} must not be used.
+        *
+        * @return This builder
+        */
+       public B currentTime();
+
+       /**
+        * Configures this builder to use the given time when creating the reply. If
+        * this method is used, {@link #currentTime()} must not be used.
+        *
+        * @param time
+        *            The time of the reply
+        * @return This builder
+        */
+       public B withTime(long time);
+
+       /**
+        * Configures this builder to use the given text when creating the reply.
+        *
+        * @param text
+        *            The text of the reply
+        * @return This builder
+        */
+       public B withText(String text);
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/AbstractReplyBuilder.java b/src/main/java/net/pterodactylus/sone/data/impl/AbstractReplyBuilder.java
new file mode 100644 (file)
index 0000000..e04e854
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Sone - ReplyBuilder.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.data.ReplyBuilder;
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Abstract implementation of a {@link ReplyBuilder}.
+ *
+ * @param <B>
+ *            The interface implemented and exposed by the builder
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class AbstractReplyBuilder<B extends ReplyBuilder<B>> implements ReplyBuilder<B> {
+
+       /** Whether to use a random ID for the reply. */
+       protected boolean randomId;
+
+       /** The ID of the reply. */
+       protected String id;
+
+       /** The sender of the reply. */
+       protected Sone sender;
+
+       /** Whether to use the current time when creating the reply. */
+       protected boolean currentTime;
+
+       /** The time of the reply. */
+       protected long time;
+
+       /** The text of the reply. */
+       protected String text;
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B randomId() {
+               this.randomId = true;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B withId(String id) {
+               this.id = id;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B from(Sone sender) {
+               this.sender = sender;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B currentTime() {
+               this.currentTime = true;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B withTime(long time) {
+               this.time = time;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B withText(String text) {
+               this.text = text;
+               return (B) this;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java
new file mode 100644 (file)
index 0000000..402b99a
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - DefaultPostBuilderFactory.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.data.PostBuilder;
+import net.pterodactylus.sone.data.PostBuilderFactory;
+
+/**
+ * {@link PostBuilderFactory} implementation that creates
+ * {@link PostBuilderImpl}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DefaultPostBuilderFactory implements PostBuilderFactory {
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder newPostBuilder() {
+               return new PostBuilderImpl();
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java
new file mode 100644 (file)
index 0000000..80bc3c7
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - DefaultPostReplyBuilderFactory.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.core.PostProvider;
+import net.pterodactylus.sone.data.PostReplyBuilder;
+import net.pterodactylus.sone.data.PostReplyBuilderFactory;
+
+import com.google.inject.Inject;
+
+/**
+ * {@link PostReplyBuilderFactory} that creates {@link PostReplyBuilderImpl}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DefaultPostReplyBuilderFactory implements PostReplyBuilderFactory {
+
+       /** The post provider. */
+       private final PostProvider postProvider;
+
+       /**
+        * Creates a new default post reply builder factory.
+        *
+        * @param postProvider
+        *            The post provider
+        */
+       @Inject
+       public DefaultPostReplyBuilderFactory(PostProvider postProvider) {
+               this.postProvider = postProvider;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostReplyBuilder newPostReplyBuilder() {
+               return new PostReplyBuilderImpl(postProvider);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java
new file mode 100644 (file)
index 0000000..967de9d
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Sone - PostBuilderImpl.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.UUID;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostBuilder;
+import net.pterodactylus.sone.data.Sone;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * {@link PostBuilder} implementation that creates {@link PostImpl} objects.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostBuilderImpl implements PostBuilder {
+
+       /** Wether to create a post with a random ID. */
+       private boolean randomId;
+
+       /** The ID of the post. */
+       private String id;
+
+       /** The sender of the post. */
+       private Sone sender;
+
+       /** Whether to use the current time when creating the post. */
+       private boolean currentTime;
+
+       /** The time of the post. */
+       private long time;
+
+       /** The text of the post. */
+       private String text;
+
+       /** The (optional) recipient of the post. */
+       private Sone recipient;
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder copyPost(Post post) {
+               this.randomId = false;
+               this.id = post.getId();
+               this.sender = post.getSone();
+               this.currentTime = false;
+               this.time = post.getTime();
+               this.text = post.getText();
+               this.recipient = post.getRecipient();
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder randomId() {
+               randomId = true;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder withId(String id) {
+               this.id = id;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder from(Sone sender) {
+               this.sender = sender;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder currentTime() {
+               currentTime = true;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder withTime(long time) {
+               this.time = time;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder withText(String text) {
+               this.text = text;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder to(Sone recipient) {
+               this.recipient = recipient;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Post build() {
+               checkState((randomId && (id == null)) || (!randomId && (id != null)), "exactly one of random ID or custom ID must be set");
+               checkState(sender != null, "sender must not be null");
+               checkState((currentTime && (time == 0)) || (!currentTime && (time > 0)), "one of current time or custom time must be set");
+               checkState(!StringUtils.isBlank(text), "text must not be empty");
+               checkState((recipient == null) || !recipient.equals(sender), "sender and recipient must not be the same");
+               return new PostImpl(randomId ? UUID.randomUUID().toString() : id, sender, currentTime ? System.currentTimeMillis() : time, text).setRecipient(recipient);
+       }
+
+}
index f45292b..2cc0b00 100644 (file)
@@ -124,9 +124,12 @@ public class PostImpl implements Post {
        }
 
        /**
-        * {@inheritDoc}
+        * Sets the Sone of this post.
+        *
+        * @param sone
+        *            The Sone of this post
+        * @return This post (for method chaining)
         */
-       @Override
        public PostImpl setSone(Sone sone) {
                this.sone = sone;
                return this;
@@ -141,9 +144,12 @@ public class PostImpl implements Post {
        }
 
        /**
-        * {@inheritDoc}
+        * Sets the recipient of this post.
+        *
+        * @param recipient
+        *            The recipient of this post, or {@code null}
+        * @return This post (for method chaining)
         */
-       @Override
        public PostImpl setRecipient(Sone recipient) {
                if (!sone.equals(recipient)) {
                        this.recipient = recipient;
@@ -160,9 +166,12 @@ public class PostImpl implements Post {
        }
 
        /**
-        * {@inheritDoc}
+        * Sets the time of this post.
+        *
+        * @param time
+        *            The time of this post (in milliseconds since Jan 1, 1970 UTC)
+        * @return This post (for method chaining)
         */
-       @Override
        public PostImpl setTime(long time) {
                this.time = time;
                return this;
@@ -177,9 +186,12 @@ public class PostImpl implements Post {
        }
 
        /**
-        * {@inheritDoc}
+        * Sets the text of this post.
+        *
+        * @param text
+        *            The text of this post
+        * @return This post (for method chaining)
         */
-       @Override
        public PostImpl setText(String text) {
                this.text = text;
                return this;
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java
new file mode 100644 (file)
index 0000000..18ce200
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Sone - PostReplyBuilderImpl.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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.UUID;
+
+import net.pterodactylus.sone.core.PostProvider;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.PostReplyBuilder;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * {@link PostReplyBuilder} implementation that creates {@link PostReplyImpl}
+ * objects.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostReplyBuilderImpl extends AbstractReplyBuilder<PostReplyBuilder> implements PostReplyBuilder {
+
+       /** The post builder. */
+       private final PostProvider postProvider;
+
+       /** The ID of the post the created reply refers to. */
+       private String postId;
+
+       /**
+        * Creates a new post reply builder.
+        *
+        * @param postProvider
+        *            The post provider
+        */
+       public PostReplyBuilderImpl(PostProvider postProvider) {
+               this.postProvider = postProvider;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostReplyBuilder to(String postId) {
+               this.postId = postId;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostReply build() {
+               checkState((randomId && (id == null)) || (!randomId && (id != null)), "either random ID nor custom ID must be set");
+               checkState(sender != null, "sender must not be null");
+               checkState((currentTime && (time == 0)) || (!currentTime && (time >= 0)), "either current time or custom time must be set");
+               checkState(!StringUtils.isBlank(text), "text must not be empty");
+               checkState(postId != null, "post must not be null");
+
+               /* create new post reply. */
+               PostReplyImpl postReplyImpl = new PostReplyImpl(postProvider, randomId ? UUID.randomUUID().toString() : id);
+               postReplyImpl.setSone(sender);
+               postReplyImpl.setPost(postId);
+               postReplyImpl.setTime(currentTime ? System.currentTimeMillis() : time);
+               postReplyImpl.setText(text);
+               return postReplyImpl;
+       }
+}
index 0e66a8f..fdedaa0 100644 (file)
 
 package net.pterodactylus.sone.data.impl;
 
-import java.util.UUID;
-
+import net.pterodactylus.sone.core.PostProvider;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
+
+import com.google.common.base.Optional;
 
 /**
  * Simple {@link PostReply} implementation.
@@ -30,90 +30,56 @@ import net.pterodactylus.sone.data.Sone;
  */
 public class PostReplyImpl extends ReplyImpl<PostReply> implements PostReply {
 
+       /** The post provider. */
+       private final PostProvider postProvider;
+
        /** The Post this reply refers to. */
-       private volatile Post post;
+       private volatile String postId;
 
        /**
         * Creates a new reply.
         *
+        * @param postProvider
+        *            The post provider
         * @param id
         *            The ID of the reply
         */
-       public PostReplyImpl(String id) {
-               this(id, null, null, 0, null);
-       }
-
-       /**
-        * Creates a new reply.
-        *
-        * @param sone
-        *            The sone that posted the reply
-        * @param post
-        *            The post to reply to
-        * @param text
-        *            The text of the reply
-        */
-       public PostReplyImpl(Sone sone, Post post, String text) {
-               this(sone, post, System.currentTimeMillis(), text);
+       public PostReplyImpl(PostProvider postProvider, String id) {
+               super(id);
+               this.postProvider = postProvider;
+               this.postId = postId;
        }
 
-       /**
-        * Creates a new reply-
-        *
-        * @param sone
-        *            The sone that posted the reply
-        * @param post
-        *            The post to reply to
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        */
-       public PostReplyImpl(Sone sone, Post post, long time, String text) {
-               this(UUID.randomUUID().toString(), sone, post, time, text);
-       }
+       //
+       // ACCESSORS
+       //
 
        /**
-        * Creates a new reply-
-        *
-        * @param sone
-        *            The sone that posted the reply
-        * @param id
-        *            The ID of the reply
-        * @param post
-        *            The post to reply to
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
+        * {@inheritDocs}
         */
-       public PostReplyImpl(String id, Sone sone, Post post, long time, String text) {
-               super(id, sone, time, text);
-               this.post = post;
+       @Override
+       public String getPostId() {
+               return postId;
        }
 
-       //
-       // ACCESSORS
-       //
-
        /**
         * {@inheritDoc}
         */
        @Override
-       public Post getPost() {
-               return post;
+       public Optional<Post> getPost() {
+               return postProvider.getPost(postId);
        }
 
        /**
         * Sets the post this reply refers to.
         *
-        * @param post
-        *            The post this reply refers to
+        * @param postId
+        *            The ID of the post to reply to
         * @return This reply (for method chaining)
         */
        @Override
-       public PostReply setPost(Post post) {
-               this.post = post;
+       public PostReply setPost(String postId) {
+               this.postId = postId;
                return this;
        }
 
index a483b01..368f49a 100644 (file)
@@ -33,6 +33,7 @@ import net.pterodactylus.sone.freenet.fcp.Command;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import net.pterodactylus.sone.template.SoneAccessor;
 
+import com.google.common.base.Optional;
 import com.google.common.collect.Collections2;
 
 import freenet.node.FSParseException;
@@ -185,11 +186,11 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
        protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
                try {
                        String postId = simpleFieldSet.getString(parameterName);
-                       Post post = core.getPost(postId, false);
-                       if (post == null) {
+                       Optional<Post> post = core.getPost(postId);
+                       if (!post.isPresent()) {
                                throw new FcpException("Could not load post from “" + postId + "”.");
                        }
-                       return post;
+                       return post.get();
                } catch (FSParseException fspe1) {
                        throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1);
                }
@@ -211,11 +212,11 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
        protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
                try {
                        String replyId = simpleFieldSet.getString(parameterName);
-                       PostReply reply = core.getPostReply(replyId, false);
-                       if (reply == null) {
+                       Optional<PostReply> reply = core.getPostReply(replyId);
+                       if (!reply.isPresent()) {
                                throw new FcpException("Could not load reply from “" + replyId + "”.");
                        }
-                       return reply;
+                       return reply.get();
                } catch (FSParseException fspe1) {
                        throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1);
                }
index 3ea818f..eb2e434 100644 (file)
@@ -24,7 +24,12 @@ import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.core.FreenetInterface;
+import net.pterodactylus.sone.core.PostProvider;
 import net.pterodactylus.sone.core.WebOfTrustUpdater;
+import net.pterodactylus.sone.data.PostBuilderFactory;
+import net.pterodactylus.sone.data.PostReplyBuilderFactory;
+import net.pterodactylus.sone.data.impl.DefaultPostBuilderFactory;
+import net.pterodactylus.sone.data.impl.DefaultPostReplyBuilderFactory;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
 import net.pterodactylus.sone.freenet.plugin.PluginConnector;
@@ -218,6 +223,9 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                                bind(String.class).annotatedWith(Names.named("WebOfTrustContext")).toInstance("Sone");
                                bind(SonePlugin.class).toInstance(SonePlugin.this);
                                bind(FcpInterface.class).in(Singleton.class);
+                               bind(PostBuilderFactory.class).to(DefaultPostBuilderFactory.class).in(Singleton.class);
+                               bind(PostReplyBuilderFactory.class).to(DefaultPostReplyBuilderFactory.class).in(Singleton.class);
+                               bind(PostProvider.class).to(Core.class).in(Singleton.class);
                                bindListener(Matchers.any(), new TypeListener() {
 
                                        @Override
index 1881a1b..dbf09a3 100644 (file)
@@ -31,6 +31,8 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.freenet.wot.Trust;
 import net.pterodactylus.util.notify.Notification;
 
+import com.google.common.base.Optional;
+
 /**
  * Filter for {@link ListNotification}s.
  *
@@ -282,11 +284,11 @@ public class ListNotificationFilters {
         */
        public static boolean isReplyVisible(Sone sone, PostReply reply) {
                checkNotNull(reply, "reply must not be null");
-               Post post = reply.getPost();
-               if (post == null) {
+               Optional<Post> post = reply.getPost();
+               if (!post.isPresent()) {
                        return false;
                }
-               if (!isPostVisible(sone, post)) {
+               if (!isPostVisible(sone, post.get())) {
                        return false;
                }
                if (reply.getTime() > System.currentTimeMillis()) {
index 4806cee..bcf73d8 100644 (file)
@@ -29,6 +29,8 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+
 /**
  * {@link Filter} implementation that groups replies by the post the are in
  * reply to, returning a map with the post as key and the list of replies as
@@ -48,17 +50,21 @@ public class ReplyGroupFilter implements Filter {
                Map<Post, Set<Sone>> postSones = new HashMap<Post, Set<Sone>>();
                Map<Post, Set<PostReply>> postReplies = new HashMap<Post, Set<PostReply>>();
                for (PostReply reply : allReplies) {
-                       Post post = reply.getPost();
-                       Set<Sone> sones = postSones.get(post);
+                       /*
+                        * All replies from a new-reply notification have posts,
+                        * ListNotificationFilters takes care of that.
+                        */
+                       Optional<Post> post = reply.getPost();
+                       Set<Sone> sones = postSones.get(post.get());
                        if (sones == null) {
                                sones = new HashSet<Sone>();
-                               postSones.put(post, sones);
+                               postSones.put(post.get(), sones);
                        }
                        sones.add(reply.getSone());
-                       Set<PostReply> replies = postReplies.get(post);
+                       Set<PostReply> replies = postReplies.get(post.get());
                        if (replies == null) {
                                replies = new HashSet<PostReply>();
-                               postReplies.put(post, replies);
+                               postReplies.put(post.get(), replies);
                        }
                        replies.add(reply);
                }
index 5c59912..6e84c70 100644 (file)
@@ -26,6 +26,8 @@ import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.PostProvider;
 import net.pterodactylus.sone.core.SoneProvider;
 import net.pterodactylus.sone.data.Post;
@@ -258,9 +260,9 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        if (linkType == LinkType.POST) {
                                                if (line.length() >= (7 + 36)) {
                                                        String postId = line.substring(7, 43);
-                                                       Post post = postProvider.getPost(postId, false);
-                                                       if ((post != null) && (post.getSone() != null)) {
-                                                               parts.add(new PostPart(post));
+                                                       Optional<Post> post = postProvider.getPost(postId);
+                                                       if (post.isPresent()) {
+                                                               parts.add(new PostPart(post.get()));
                                                        } else {
                                                                parts.add(new PlainTextPart(line.substring(0, 43)));
                                                        }
index e6a5735..c8979c1 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.text.TextFilter;
@@ -58,7 +60,10 @@ public class CreateReplyPage extends SoneTemplatePage {
                String text = request.getHttpRequest().getPartAsStringFailsafe("text", 65536).trim();
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
-                       Post post = webInterface.getCore().getPost(postId);
+                       Optional<Post> post = webInterface.getCore().getPost(postId);
+                       if (!post.isPresent()) {
+                               throw new RedirectException("noPermission.html");
+                       }
                        if (text.length() > 0) {
                                String senderId = request.getHttpRequest().getPartAsStringFailsafe("sender", 43);
                                Sone sender = webInterface.getCore().getLocalSone(senderId, false);
@@ -66,7 +71,7 @@ public class CreateReplyPage extends SoneTemplatePage {
                                        sender = getCurrentSone(request.getToadletContext());
                                }
                                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
-                               webInterface.getCore().createReply(sender, post, text);
+                               webInterface.getCore().createReply(sender, post.get(), text);
                                throw new RedirectException(returnPage);
                        }
                        templateContext.set("errorTextEmpty", true);
index 1363ac9..6ef86f0 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.template.Template;
@@ -55,19 +57,22 @@ public class DeletePostPage extends SoneTemplatePage {
                if (request.getMethod() == Method.GET) {
                        String postId = request.getHttpRequest().getParam("post");
                        String returnPage = request.getHttpRequest().getParam("returnPage");
-                       Post post = webInterface.getCore().getPost(postId);
-                       templateContext.set("post", post);
+                       Optional<Post> post = webInterface.getCore().getPost(postId);
+                       if (!post.isPresent()) {
+                               throw new RedirectException("noPermission.html");
+                       }
+                       templateContext.set("post", post.get());
                        templateContext.set("returnPage", returnPage);
                        return;
                } else if (request.getMethod() == Method.POST) {
                        String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
-                       Post post = webInterface.getCore().getPost(postId);
-                       if (!post.getSone().isLocal()) {
+                       Optional<Post> post = webInterface.getCore().getPost(postId);
+                       if (!post.isPresent() || !post.get().getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
-                               webInterface.getCore().deletePost(post);
+                               webInterface.getCore().deletePost(post.get());
                                throw new RedirectException(returnPage);
                        } else if (request.getHttpRequest().isPartSet("abortDelete")) {
                                throw new RedirectException(returnPage);
index 8c07716..f7ca132 100644 (file)
@@ -23,6 +23,8 @@ import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.web.Method;
 
+import com.google.common.base.Optional;
+
 /**
  * This page lets the user delete a reply.
  *
@@ -53,14 +55,14 @@ public class DeleteReplyPage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                String replyId = request.getHttpRequest().getPartAsStringFailsafe("reply", 36);
-               PostReply reply = webInterface.getCore().getPostReply(replyId, false);
+               Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
-                       if (!reply.getSone().isLocal()) {
+                       if (!reply.get().getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
-                               webInterface.getCore().deleteReply(reply);
+                               webInterface.getCore().deleteReply(reply.get());
                                throw new RedirectException(returnPage);
                        } else if (request.getHttpRequest().isPartSet("abortDelete")) {
                                throw new RedirectException(returnPage);
index d552b91..f183ff4 100644 (file)
@@ -27,6 +27,8 @@ import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+
 /**
  * Page that lets the user mark a number of {@link Sone}s, {@link Post}s, or
  * {@link Reply Replie}s as known.
@@ -65,17 +67,17 @@ public class MarkAsKnownPage extends SoneTemplatePage {
                for (StringTokenizer idTokenizer = new StringTokenizer(ids); idTokenizer.hasMoreTokens();) {
                        String id = idTokenizer.nextToken();
                        if (type.equals("post")) {
-                               Post post = webInterface.getCore().getPost(id, false);
-                               if (post == null) {
+                               Optional<Post> post = webInterface.getCore().getPost(id);
+                               if (!post.isPresent()) {
                                        continue;
                                }
-                               webInterface.getCore().markPostKnown(post);
+                               webInterface.getCore().markPostKnown(post.get());
                        } else if (type.equals("reply")) {
-                               PostReply reply = webInterface.getCore().getPostReply(id, false);
-                               if (reply == null) {
+                               Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
+                               if (!reply.isPresent()) {
                                        continue;
                                }
-                               webInterface.getCore().markReplyKnown(reply);
+                               webInterface.getCore().markReplyKnown(reply.get());
                        } else if (type.equals("sone")) {
                                Sone sone = webInterface.getCore().getSone(id, false);
                                if (sone == null) {
index 2d4ec55..22d27c8 100644 (file)
@@ -67,7 +67,7 @@ public class NewPage extends SoneTemplatePage {
                /* collect new elements from notifications. */
                Set<Post> posts = new HashSet<Post>(webInterface.getNewPosts());
                for (PostReply reply : webInterface.getNewReplies()) {
-                       posts.add(reply.getPost());
+                       posts.add(reply.getPost().get());
                }
 
                /* filter and sort them. */
index 1bca8cd..6cb2c0c 100644 (file)
@@ -44,6 +44,7 @@ import net.pterodactylus.util.text.StringEscaper;
 import net.pterodactylus.util.text.TextException;
 
 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
@@ -322,7 +323,7 @@ public class SearchPage extends SoneTemplatePage {
         */
        private String getPostId(String phrase) {
                String postId = phrase.startsWith("post://") ? phrase.substring(7) : phrase;
-               return (webInterface.getCore().getPost(postId, false) != null) ? postId : null;
+               return (webInterface.getCore().getPost(postId) != null) ? postId : null;
        }
 
        /**
@@ -336,7 +337,11 @@ public class SearchPage extends SoneTemplatePage {
         */
        private String getReplyPostId(String phrase) {
                String replyId = phrase.startsWith("reply://") ? phrase.substring(8) : phrase;
-               return (webInterface.getCore().getPostReply(replyId, false) != null) ? webInterface.getCore().getPostReply(replyId, false).getPost().getId() : null;
+               Optional<PostReply> postReply = webInterface.getCore().getPostReply(replyId);
+               if (!postReply.isPresent()) {
+                       return null;
+               }
+               return postReply.get().getPostId();
        }
 
        /**
index aaf013d..50719ee 100644 (file)
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.web;
 
 import java.net.URI;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -54,11 +56,11 @@ public class ViewPostPage extends SoneTemplatePage {
        @Override
        protected String getPageTitle(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
-               Post post = webInterface.getCore().getPost(postId, false);
+               Optional<Post> post = webInterface.getCore().getPost(postId);
                String title = "";
-               if ((post != null) && (post.getSone() != null)) {
-                       title = post.getText().substring(0, Math.min(20, post.getText().length())) + "…";
-                       title += " - " + SoneAccessor.getNiceName(post.getSone()) + " - ";
+               if (post.isPresent()) {
+                       title = post.get().getText().substring(0, Math.min(20, post.get().getText().length())) + "…";
+                       title += " - " + SoneAccessor.getNiceName(post.get().getSone()) + " - ";
                }
                title += webInterface.getL10n().getString("Page.ViewPost.Title");
                return title;
@@ -72,8 +74,8 @@ public class ViewPostPage extends SoneTemplatePage {
                super.processTemplate(request, templateContext);
                String postId = request.getHttpRequest().getParam("post");
                boolean raw = request.getHttpRequest().getParam("raw").equals("true");
-               Post post = webInterface.getCore().getPost(postId);
-               templateContext.set("post", post);
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               templateContext.set("post", post.get());
                templateContext.set("raw", raw);
        }
 
index dec4fe2..5572bca 100644 (file)
@@ -36,6 +36,8 @@ import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+
 /**
  * Lets the user browser another Sone.
  *
@@ -95,11 +97,11 @@ public class ViewSonePage extends SoneTemplatePage {
                Set<PostReply> replies = sone.getReplies();
                final Map<Post, List<PostReply>> repliedPosts = new HashMap<Post, List<PostReply>>();
                for (PostReply reply : replies) {
-                       Post post = reply.getPost();
-                       if (repliedPosts.containsKey(post) || sone.equals(post.getSone()) || (sone.equals(post.getRecipient()))) {
+                       Optional<Post> post = reply.getPost();
+                       if (!post.isPresent() || repliedPosts.containsKey(post.get()) || sone.equals(post.get().getSone()) || (sone.equals(post.get().getRecipient()))) {
                                continue;
                        }
-                       repliedPosts.put(post, webInterface.getCore().getReplies(post));
+                       repliedPosts.put(post.get(), webInterface.getCore().getReplies(post.get()));
                }
                List<Post> posts = new ArrayList<Post>(repliedPosts.keySet());
                Collections.sort(posts, new Comparator<Post>() {
index d9a9dfb..3bb11d5 100644 (file)
@@ -857,8 +857,8 @@ public class WebInterface {
                }
                if (!hasFirstStartNotification()) {
                        notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
-                       if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && (reply.getPost().getSone() != null) && (reply.getTime() <= System.currentTimeMillis())) {
-                               mentionNotification.add(reply.getPost());
+                       if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && reply.getPost().isPresent() && (reply.getTime() <= System.currentTimeMillis())) {
+                               mentionNotification.add(reply.getPost().get());
                                notificationManager.addNotification(mentionNotification);
                        }
                } else {
@@ -900,7 +900,7 @@ public class WebInterface {
        public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
                newReplyNotification.remove(markPostReplyKnownEvent.postReply());
                localReplyNotification.remove(markPostReplyKnownEvent.postReply());
-               mentionNotification.remove(markPostReplyKnownEvent.postReply().getPost());
+               mentionNotification.remove(markPostReplyKnownEvent.postReply().getPost().get());
        }
 
        /**
@@ -940,11 +940,11 @@ public class WebInterface {
                localReplyNotification.remove(reply);
                if (!getMentionedSones(reply.getText()).isEmpty()) {
                        boolean isMentioned = false;
-                       for (PostReply existingReply : getCore().getReplies(reply.getPost())) {
+                       for (PostReply existingReply : getCore().getReplies(reply.getPost().get())) {
                                isMentioned |= !reply.isKnown() && !getMentionedSones(existingReply.getText()).isEmpty();
                        }
                        if (!isMentioned) {
-                               mentionNotification.remove(reply.getPost());
+                               mentionNotification.remove(reply.getPost().get());
                        }
                }
        }
index 164ec19..8a35185 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
@@ -58,12 +60,12 @@ public class CreateReplyAjaxPage extends JsonPage {
                if (sender == null) {
                        sender = getCurrentSone(request.getToadletContext());
                }
-               Post post = webInterface.getCore().getPost(postId);
-               if ((post == null) || (post.getSone() == null)) {
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               if (!post.isPresent()) {
                        return createErrorJsonObject("invalid-post-id");
                }
                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
-               PostReply reply = webInterface.getCore().createReply(sender, post, text);
+               PostReply reply = webInterface.getCore().createReply(sender, post.get(), text);
                return createSuccessJsonObject().put("reply", reply.getId()).put("sone", sender.getId());
        }
 
index a8c36d5..71b0016 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -49,14 +51,14 @@ public class DeletePostAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
-               Post post = webInterface.getCore().getPost(postId, false);
-               if ((post == null) || (post.getSone() == null)) {
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               if (!post.isPresent()) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               if (!post.getSone().isLocal()) {
+               if (!post.get().getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
-               webInterface.getCore().deletePost(post);
+               webInterface.getCore().deletePost(post.get());
                return createSuccessJsonObject();
        }
 
index 67bf901..2aa217d 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -49,14 +51,14 @@ public class DeleteReplyAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
-               PostReply reply = webInterface.getCore().getPostReply(replyId, false);
-               if (reply == null) {
+               Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
+               if (!reply.isPresent()) {
                        return createErrorJsonObject("invalid-reply-id");
                }
-               if (!reply.getSone().isLocal()) {
+               if (!reply.get().getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
-               webInterface.getCore().deleteReply(reply);
+               webInterface.getCore().deleteReply(reply.get());
                return createSuccessJsonObject();
        }
 
index c1c532b..1a5286f 100644 (file)
@@ -31,6 +31,8 @@ import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonArray;
 import net.pterodactylus.util.json.JsonObject;
 
+import com.google.common.base.Optional;
+
 /**
  * AJAX page that retrieves the number of “likes” a {@link Post} has.
  *
@@ -63,12 +65,18 @@ public class GetLikesAjaxPage extends JsonPage {
                        return createErrorJsonObject("invalid-" + type + "-id");
                }
                if ("post".equals(type)) {
-                       Post post = webInterface.getCore().getPost(id);
-                       Set<Sone> sones = webInterface.getCore().getLikes(post);
+                       Optional<Post> post = webInterface.getCore().getPost(id);
+                       if (!post.isPresent()) {
+                               return createErrorJsonObject("invalid-post-id");
+                       }
+                       Set<Sone> sones = webInterface.getCore().getLikes(post.get());
                        return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
                } else if ("reply".equals(type)) {
-                       PostReply reply = webInterface.getCore().getPostReply(id, false);
-                       Set<Sone> sones = webInterface.getCore().getLikes(reply);
+                       Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
+                       if (!reply.isPresent()) {
+                               return createErrorJsonObject("invalid-reply-id");
+                       }
+                       Set<Sone> sones = webInterface.getCore().getLikes(reply.get());
                        return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
                }
                return createErrorJsonObject("invalid-type");
index ae813ee..e32573d 100644 (file)
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.web.ajax;
 
 import java.io.StringWriter;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -59,11 +61,11 @@ public class GetPostAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
-               Post post = webInterface.getCore().getPost(postId, false);
-               if (post == null) {
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               if (!post.isPresent()) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               return createSuccessJsonObject().put("post", createJsonPost(request, post, getCurrentSone(request.getToadletContext())));
+               return createSuccessJsonObject().put("post", createJsonPost(request, post.get(), getCurrentSone(request.getToadletContext())));
        }
 
        /**
index 99a7220..b617608 100644 (file)
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.web.ajax;
 
 import java.io.StringWriter;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -62,11 +64,11 @@ public class GetReplyAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
-               PostReply reply = webInterface.getCore().getPostReply(replyId, false);
-               if ((reply == null) || (reply.getSone() == null)) {
+               Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
+               if (!reply.isPresent()) {
                        return createErrorJsonObject("invalid-reply-id");
                }
-               return createSuccessJsonObject().put("reply", createJsonReply(request, reply, getCurrentSone(request.getToadletContext())));
+               return createSuccessJsonObject().put("reply", createJsonReply(request, reply.get(), getCurrentSone(request.getToadletContext())));
        }
 
        /**
@@ -95,7 +97,7 @@ public class GetReplyAjaxPage extends JsonPage {
        private JsonObject createJsonReply(FreenetRequest request, PostReply reply, Sone currentSone) {
                JsonObject jsonReply = new JsonObject();
                jsonReply.put("id", reply.getId());
-               jsonReply.put("postId", reply.getPost().getId());
+               jsonReply.put("postId", reply.getPostId());
                jsonReply.put("soneId", reply.getSone().getId());
                jsonReply.put("time", reply.getTime());
                StringWriter stringWriter = new StringWriter();
index 8657a6a..6caa2fd 100644 (file)
@@ -122,20 +122,14 @@ public class GetStatusAjaxPage extends JsonPage {
                        });
                }
                /* remove replies to unknown posts. */
-               newReplies = Collections2.filter(newReplies, new Predicate<PostReply>() {
-
-                       @Override
-                       public boolean apply(PostReply reply) {
-                               return (reply.getPost() != null) && (reply.getPost().getSone() != null);
-                       }
-               });
+               newReplies = Collections2.filter(newReplies, PostReply.HAS_POST_FILTER);
                JsonArray jsonReplies = new JsonArray();
                for (PostReply reply : newReplies) {
                        JsonObject jsonReply = new JsonObject();
                        jsonReply.put("id", reply.getId());
                        jsonReply.put("sone", reply.getSone().getId());
-                       jsonReply.put("post", reply.getPost().getId());
-                       jsonReply.put("postSone", reply.getPost().getSone().getId());
+                       jsonReply.put("post", reply.getPostId());
+                       jsonReply.put("postSone", reply.getPost().get().getSone().getId());
                        jsonReplies.add(jsonReply);
                }
                return createSuccessJsonObject().put("loggedIn", currentSone != null).put("options", createJsonOptions(currentSone)).put("sones", jsonSones).put("notificationHash", notifications.hashCode()).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
index a7f02c9..4f54571 100644 (file)
@@ -28,6 +28,8 @@ import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonObject;
 
+import com.google.common.base.Optional;
+
 /**
  * Ajax page that returns a formatted, relative timestamp for replies or posts.
  *
@@ -58,16 +60,16 @@ public class GetTimesAjaxPage extends JsonPage {
                if (allIds.length() > 0) {
                        String[] ids = allIds.split(",");
                        for (String id : ids) {
-                               Post post = webInterface.getCore().getPost(id, false);
-                               if (post == null) {
+                               Optional<Post> post = webInterface.getCore().getPost(id);
+                               if (!post.isPresent()) {
                                        continue;
                                }
                                JsonObject postTime = new JsonObject();
-                               Time time = getTime(post.getTime());
+                               Time time = getTime(post.get().getTime());
                                postTime.put("timeText", time.getText());
                                postTime.put("refreshTime", TimeUnit.MILLISECONDS.toSeconds(time.getRefresh()));
                                synchronized (dateFormat) {
-                                       postTime.put("tooltip", dateFormat.format(new Date(post.getTime())));
+                                       postTime.put("tooltip", dateFormat.format(new Date(post.get().getTime())));
                                }
                                postTimes.put(id, postTime);
                        }
@@ -77,16 +79,16 @@ public class GetTimesAjaxPage extends JsonPage {
                if (allIds.length() > 0) {
                        String[] ids = allIds.split(",");
                        for (String id : ids) {
-                               PostReply reply = webInterface.getCore().getPostReply(id, false);
-                               if (reply == null) {
+                               Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
+                               if (!reply.isPresent()) {
                                        continue;
                                }
                                JsonObject replyTime = new JsonObject();
-                               Time time = getTime(reply.getTime());
+                               Time time = getTime(reply.get().getTime());
                                replyTime.put("timeText", time.getText());
                                replyTime.put("refreshTime", TimeUnit.MILLISECONDS.toSeconds(time.getRefresh()));
                                synchronized (dateFormat) {
-                                       replyTime.put("tooltip", dateFormat.format(new Date(reply.getTime())));
+                                       replyTime.put("tooltip", dateFormat.format(new Date(reply.get().getTime())));
                                }
                                replyTimes.put(id, replyTime);
                        }
index a193d75..997c9dc 100644 (file)
@@ -26,6 +26,8 @@ import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonObject;
 
+import com.google.common.base.Optional;
+
 /**
  * AJAX page that lets the user mark a number of {@link Sone}s, {@link Post}s,
  * or {@link Reply}s as known.
@@ -57,17 +59,17 @@ public class MarkAsKnownAjaxPage extends JsonPage {
                Core core = webInterface.getCore();
                for (String id : ids) {
                        if (type.equals("post")) {
-                               Post post = core.getPost(id, false);
-                               if (post == null) {
+                               Optional<Post> post = core.getPost(id);
+                               if (!post.isPresent()) {
                                        continue;
                                }
-                               core.markPostKnown(post);
+                               core.markPostKnown(post.get());
                        } else if (type.equals("reply")) {
-                               PostReply reply = core.getPostReply(id, false);
-                               if (reply == null) {
+                               Optional<PostReply> reply = core.getPostReply(id);
+                               if (!reply.isPresent()) {
                                        continue;
                                }
-                               core.markReplyKnown(reply);
+                               core.markReplyKnown(reply.get());
                        } else if (type.equals("sone")) {
                                Sone sone = core.getSone(id, false);
                                if (sone == null) {