import static java.util.concurrent.TimeUnit.*;
import static java.util.logging.Logger.getLogger;
import static java.util.stream.Collectors.toList;
+import static net.pterodactylus.sone.data.PostKt.newestFirst;
import java.io.*;
import java.nio.charset.Charset;
soneProperties.put("name", sone.getName());
soneProperties.put("time", currentTimeMillis());
soneProperties.put("profile", sone.getProfile());
- soneProperties.put("posts", Ordering.from(Post.NEWEST_FIRST).sortedCopy(sone.getPosts()));
+ soneProperties.put("posts", Ordering.from(newestFirst()).sortedCopy(sone.getPosts()));
soneProperties.put("replies", Ordering.from(Reply.TIME_COMPARATOR).reverse().sortedCopy(sone.getReplies()));
soneProperties.put("likedPostIds", new HashSet<>(sone.getLikedPostIds()));
soneProperties.put("likedReplyIds", new HashSet<>(sone.getLikedReplyIds()));
import static com.google.common.base.Optional.absent;
-import java.util.Comparator;
-
import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
/**
* A post is a short message that a user writes in his Sone to let other users
*/
public interface Post extends Identified {
- /** Comparator for posts, sorts descending by time. */
- public static final Comparator<Post> NEWEST_FIRST = new Comparator<Post>() {
-
- @Override
- public int compare(Post leftPost, Post rightPost) {
- return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, rightPost.getTime() - leftPost.getTime()));
- }
-
- };
-
- /** Filter for posts with timestamps from the future. */
- public static final Predicate<Post> FUTURE_POSTS_FILTER = new Predicate<Post>() {
-
- @Override
- public boolean apply(Post post) {
- return (post != null) && (post.getTime() <= System.currentTimeMillis());
- }
-
- };
-
//
// ACCESSORS
//
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.logging.Logger.getLogger;
+import static net.pterodactylus.sone.data.PostKt.newestFirst;
import static net.pterodactylus.sone.data.SoneKt.*;
import java.net.MalformedURLException;
synchronized (this) {
sortedPosts = new ArrayList<>(posts);
}
- Collections.sort(sortedPosts, Post.NEWEST_FIRST);
+ sortedPosts.sort(newestFirst());
return sortedPosts;
}
import freenet.support.SimpleFieldSet;
+import static net.pterodactylus.sone.data.PostKt.newestFirst;
+import static net.pterodactylus.sone.data.PostKt.noFuturePost;
+
/**
* Implementation of an FCP interface for other clients or plugins to
* communicate with Sone.
allPosts.addAll(friendSone.getPosts());
}
allPosts.addAll(getCore().getDirectedPosts(sone.getId()));
- allPosts = Collections2.filter(allPosts, Post.FUTURE_POSTS_FILTER);
+ allPosts = Collections2.filter(allPosts, noFuturePost()::invoke);
List<Post> sortedPosts = new ArrayList<>(allPosts);
- Collections.sort(sortedPosts, Post.NEWEST_FIRST);
+ sortedPosts.sort(newestFirst());
if (sortedPosts.size() < startPost) {
return new Response("PostFeed", encodePosts(Collections.<Post> emptyList(), "Posts.", false));
--- /dev/null
+package net.pterodactylus.sone.data
+
+import java.util.Comparator.comparing
+
+/**
+ * Predicate that returns whether a post is _not_ from the future,
+ * i.e. whether it should be visible now.
+ */
+@get:JvmName("noFuturePost")
+val noFuturePost: (Post) -> Boolean = { it.time <= System.currentTimeMillis() }
+
+/**
+ * Comparator that orders posts by their time, newest posts first.
+ */
+@get:JvmName("newestFirst")
+val newestFirst: Comparator<Post> = comparing(Post::getTime).reversed()
val requiresWriteAccess: Boolean = false) : AbstractCommand() {
@Throws(FcpException::class)
- protected fun getSone(simpleFieldSet: SimpleFieldSet, parameterName: String, localOnly: Boolean): Sone =
- getSone(simpleFieldSet, parameterName, localOnly, true).get()
+ protected fun SimpleFieldSet.getSone(parameterName: String, localOnly: Boolean): Sone =
+ getSone(parameterName, localOnly, true).get()
@Throws(FcpException::class)
- protected fun getSone(simpleFieldSet: SimpleFieldSet, parameterName: String, localOnly: Boolean, mandatory: Boolean): Optional<Sone> {
- val soneId = simpleFieldSet.get(parameterName)
+ protected fun SimpleFieldSet.getSone(parameterName: String, localOnly: Boolean, mandatory: Boolean): Optional<Sone> {
+ val soneId = get(parameterName)
.throwOnNullIf(mandatory) { FcpException("Could not load Sone ID from “$parameterName”.") }
?: return Optional.absent()
val sone = core.getSone(soneId)
}
@Throws(FcpException::class)
- protected fun getPost(simpleFieldSet: SimpleFieldSet, parameterName: String): Post {
+ protected fun SimpleFieldSet.getPost(parameterName: String): Post {
try {
- val postId = simpleFieldSet.getString(parameterName)
+ val postId = getString(parameterName)
return core.getPost(postId)
?: throw FcpException("Could not load post from “$postId”.")
} catch (fspe1: FSParseException) {
}
@Throws(FcpException::class)
- protected fun getReply(simpleFieldSet: SimpleFieldSet, parameterName: String): PostReply {
+ protected fun SimpleFieldSet.getReply(parameterName: String): PostReply {
try {
- val replyId = simpleFieldSet.getString(parameterName)
+ val replyId = getString(parameterName)
return core.getPostReply(replyId)
?: throw FcpException("Could not load reply from “$replyId”.")
} catch (fspe1: FSParseException) {
val postPagination = cache.get(phrases) {
soneRequest.core.sones
.flatMap(Sone::getPosts)
- .filter { Post.FUTURE_POSTS_FILTER.apply(it) }
+ .filter(noFuturePost)
.scoreAndPaginate(phrases, soneRequest.core.preferences.postsPerPage) { it.allText(soneNameCache, soneRequest.core::getReplies) }
}.apply { page = soneRequest.parameters["postPage"].emptyToNull?.toIntOrNull() ?: 0 }
--- /dev/null
+package net.pterodactylus.sone.data
+
+import net.pterodactylus.sone.test.createPost
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.greaterThan
+import org.hamcrest.Matchers.lessThan
+import java.util.concurrent.TimeUnit.DAYS
+import kotlin.test.Test
+
+/**
+ * Unit test for the utilities in `Post.kt`.
+ */
+class PostTest {
+
+ @Test
+ fun `noFuturePost filter recognizes post from future`() {
+ val post = createPost(time = System.currentTimeMillis() + DAYS.toMillis(1))
+ assertThat(noFuturePost(post), equalTo(false))
+ }
+
+ @Test
+ fun `noFuturePost filter recognizes post not from future`() {
+ val post = createPost(time = System.currentTimeMillis())
+ assertThat(noFuturePost(post), equalTo(true))
+ }
+
+ @Test
+ fun `newestFirst comparator returns less-than 0 if first is newer than second`() {
+ val newerPost = createPost(time = 2000)
+ val olderPost = createPost(time = 1000)
+ assertThat(newestFirst.compare(newerPost, olderPost), lessThan(0))
+ }
+
+ @Test
+ fun `newestFirst comparator returns greater-than 0 if first is older than second`() {
+ val newerPost = createPost(time = 2000)
+ val olderPost = createPost(time = 1000)
+ assertThat(newestFirst.compare(olderPost, newerPost), greaterThan(0))
+ }
+
+ @Test
+ fun `newestFirst comparator returns 0 if first and second are the same age`() {
+ val post1 = createPost(time = 1000)
+ val post2 = createPost(time = 1000)
+ assertThat(newestFirst.compare(post2, post1), equalTo(0))
+ }
+
+}
}
fun createRemoteSone(id: String? = createId()) = IdOnlySone(id)
-fun createPost(text: String = "", sone: Sone = remoteSone1, known: Boolean = false): Post.EmptyPost {
+fun createPost(text: String = "", sone: Sone = remoteSone1, known: Boolean = false, time: Long = 1): Post.EmptyPost {
return object : Post.EmptyPost("post-id") {
override fun getSone() = sone
override fun getText() = text
override fun isKnown() = known
+ override fun getTime() = time
}
}