Merge branch 'release-0.9.8' 0.9.8
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 30 Nov 2017 05:55:54 +0000 (06:55 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 30 Nov 2017 05:55:54 +0000 (06:55 +0100)
153 files changed:
build.gradle
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/data/impl/PostImpl.java
src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java
src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java
src/main/java/net/pterodactylus/sone/database/AlbumBuilder.java [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumBuilder.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.java [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/AlbumDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumDatabase.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/AlbumProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumProvider.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/AlbumStore.java [deleted file]
src/main/java/net/pterodactylus/sone/database/AlbumStore.kt [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/BookmarkDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/Database.java [deleted file]
src/main/java/net/pterodactylus/sone/database/FriendDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/FriendProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/database/FriendStore.java [deleted file]
src/main/java/net/pterodactylus/sone/database/ImageBuilder.java [deleted file]
src/main/java/net/pterodactylus/sone/database/ImageBuilderFactory.java [deleted file]
src/main/java/net/pterodactylus/sone/database/ImageDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/ImageProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/database/ImageStore.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostBuilder.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostBuilderFactory.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostReplyBuilder.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostReplyBuilderFactory.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostReplyStore.java [deleted file]
src/main/java/net/pterodactylus/sone/database/PostStore.java [deleted file]
src/main/java/net/pterodactylus/sone/database/ReplyBuilder.java [deleted file]
src/main/java/net/pterodactylus/sone/database/SoneBuilder.java [deleted file]
src/main/java/net/pterodactylus/sone/database/SoneBuilderFactory.java [deleted file]
src/main/java/net/pterodactylus/sone/database/SoneDatabase.java [deleted file]
src/main/java/net/pterodactylus/sone/database/SoneProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/database/SoneStore.java [deleted file]
src/main/java/net/pterodactylus/sone/database/memory/MemoryBookmarkDatabase.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java
src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostFeedCommand.java
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java [deleted file]
src/main/kotlin/net/pterodactylus/sone/database/BookmarkDatabase.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/Database.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/FriendDatabase.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/FriendProvider.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/FriendStore.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/ImageBuilder.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/ImageBuilderFactory.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/ImageDatabase.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/ImageProvider.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/ImageStore.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostBuilder.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostBuilderFactory.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostDatabase.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostProvider.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilder.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilderFactory.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostReplyDatabase.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostReplyStore.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/PostStore.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/ReplyBuilder.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/SoneBuilder.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/SoneBuilderFactory.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/SoneDatabase.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/SoneProvider.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/database/SoneStore.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/template/ParserFilter.kt
src/main/kotlin/net/pterodactylus/sone/template/RenderFilter.kt
src/main/kotlin/net/pterodactylus/sone/text/FreenetLinkPart.kt
src/main/kotlin/net/pterodactylus/sone/text/LinkPart.kt
src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/utils/Memoize.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/LikeAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/TrustAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/BookmarkPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateAlbumPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/CreatePostPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/CreateReplyPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteAlbumPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeletePostPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteProfileFieldPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteReplyPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/DistrustPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/EditProfilePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/FollowSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/IndexPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/LikePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/LoggedInPage.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/web/pages/LogoutPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/MarkAsKnownPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/SearchPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/TrustPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UnbookmarkPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UnfollowSonePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UnlikePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UntrustPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ViewPostPage.kt
src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt
src/test/java/net/pterodactylus/sone/core/CoreTest.java
src/test/java/net/pterodactylus/sone/core/SoneInserterTest.java
src/test/java/net/pterodactylus/sone/database/memory/MemoryBookmarkDatabaseTest.java
src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java
src/test/kotlin/net/pterodactylus/sone/fcp/CreatePostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/CreateReplyCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/DeletePostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/DeleteReplyCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostFeedCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostsCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/GetSoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/LikePostCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/LikeReplyCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/LockSoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/SoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.kt
src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/template/RenderFilterTest.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/TestObjects.kt
src/test/kotlin/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt
src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt

index ecdbfdd..8111c2a 100644 (file)
@@ -1,8 +1,8 @@
 group = 'net.pterodactylus'
-version = '0.9.7'
+version = '0.9.8'
 
 buildscript {
-    ext.kotlinVersion = '1.1.51'
+    ext.kotlinVersion = '1.2.0'
     repositories {
         mavenCentral()
     }
@@ -54,7 +54,7 @@ dependencies {
 
     testCompile group: 'org.jetbrains.kotlin', name: 'kotlin-test'
     testCompile group: 'junit', name: 'junit', version: '4.11'
-    testCompile group: 'org.mockito', name: 'mockito-core', version: '2.1.0'
+    testCompile group: 'org.mockito', name: 'mockito-core', version: '2.10.0'
     testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
 }
 
index 66677de..7384eda 100644 (file)
@@ -51,6 +51,7 @@ import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound
 import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor;
 import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
+import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
 import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
 import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
@@ -99,7 +100,6 @@ import net.pterodactylus.util.service.AbstractService;
 import net.pterodactylus.util.thread.NamedThreadFactory;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashMultimap;
@@ -109,6 +109,7 @@ import com.google.common.eventbus.EventBus;
 import com.google.common.eventbus.Subscribe;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import kotlin.jvm.functions.Function1;
 
 /**
  * The Sone core.
@@ -323,9 +324,10 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                return database.getSones();
        }
 
+       @Nonnull
        @Override
-       public Function<String, Optional<Sone>> soneLoader() {
-               return database.soneLoader();
+       public Function1<String, Sone> getSoneLoader() {
+               return database.getSoneLoader();
        }
 
        /**
@@ -338,7 +340,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *         Sone
         */
        @Override
-       public Optional<Sone> getSone(String id) {
+       @Nullable
+       public Sone getSone(@Nonnull String id) {
                return database.getSone(id);
        }
 
@@ -358,9 +361,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The Sone with the given ID, or {@code null}
         */
        public Sone getLocalSone(String id) {
-               Optional<Sone> sone = database.getSone(id);
-               if (sone.isPresent() && sone.get().isLocal()) {
-                       return sone.get();
+               Sone sone = database.getSone(id);
+               if ((sone != null) && sone.isLocal()) {
+                       return sone;
                }
                return null;
        }
@@ -382,7 +385,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         * @return The Sone with the given ID
         */
        public Sone getRemoteSone(String id) {
-               return database.getSone(id).orNull();
+               return database.getSone(id);
        }
 
        /**
@@ -420,11 +423,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                return database.newPostBuilder();
        }
 
-       /**
-        * {@inheritDoc}
-        */
+       @Nullable
        @Override
-       public Optional<Post> getPost(String postId) {
+       public Post getPost(@Nonnull String postId) {
                return database.getPost(postId);
        }
 
@@ -457,8 +458,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
        /**
         * {@inheritDoc}
         */
+       @Nullable
        @Override
-       public Optional<PostReply> getPostReply(String replyId) {
+       public PostReply getPostReply(String replyId) {
                return database.getPostReply(replyId);
        }
 
@@ -540,7 +542,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Nullable
        public Album getAlbum(@Nonnull String albumId) {
-               return database.getAlbum(albumId).orNull();
+               return database.getAlbum(albumId);
        }
 
        public ImageBuilder imageBuilder() {
@@ -573,9 +575,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         */
        @Nullable
        public Image getImage(String imageId, boolean create) {
-               Optional<Image> image = database.getImage(imageId);
-               if (image.isPresent()) {
-                       return image.get();
+               Image image = database.getImage(imageId);
+               if (image != null) {
+                       return image;
                }
                if (!create) {
                        return null;
@@ -653,6 +655,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                sone.setClient(new Client("Sone", SonePlugin.getPluginVersion()));
                sone.setKnown(true);
                SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, ownIdentity.getId());
+               soneInserter.insertionDelayChanged(new InsertionDelayChangedEvent(preferences.getInsertionDelay()));
                eventBus.register(soneInserter);
                synchronized (soneInserters) {
                        soneInserters.put(sone, soneInserter);
@@ -697,12 +700,12 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
                String property = fromNullable(identity.getProperty("Sone.LatestEdition")).or("0");
                long latestEdition = fromNullable(tryParse(property)).or(0L);
-               Optional<Sone> existingSone = getSone(identity.getId());
-               if (existingSone.isPresent() && existingSone.get().isLocal()) {
-                       return existingSone.get();
+               Sone existingSone = getSone(identity.getId());
+               if ((existingSone != null )&& existingSone.isLocal()) {
+                       return existingSone;
                }
-               boolean newSone = !existingSone.isPresent();
-               Sone sone = !newSone ? existingSone.get() : database.newSoneBuilder().from(identity).build();
+               boolean newSone = existingSone == null;
+               Sone sone = !newSone ? existingSone : database.newSoneBuilder().from(identity).build();
                sone.setLatestEdition(latestEdition);
                if (newSone) {
                        synchronized (knownSones) {
@@ -740,16 +743,16 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                        if (!soneFollowingTimes.containsKey(soneId)) {
                                long now = System.currentTimeMillis();
                                soneFollowingTimes.put(soneId, now);
-                               Optional<Sone> followedSone = getSone(soneId);
-                               if (!followedSone.isPresent()) {
+                               Sone followedSone = getSone(soneId);
+                               if (followedSone == null) {
                                        return;
                                }
-                               for (Post post : followedSone.get().getPosts()) {
+                               for (Post post : followedSone.getPosts()) {
                                        if (post.getTime() < now) {
                                                markPostKnown(post);
                                        }
                                }
-                               for (PostReply reply : followedSone.get().getReplies()) {
+                               for (PostReply reply : followedSone.getReplies()) {
                                        if (reply.getTime() < now) {
                                                markReplyKnown(reply);
                                        }
@@ -874,20 +877,20 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
         *            of the age of the given Sone
         */
        public void updateSone(final Sone sone, boolean soneRescueMode) {
-               Optional<Sone> storedSone = getSone(sone.getId());
-               if (storedSone.isPresent()) {
-                       if (!soneRescueMode && !(sone.getTime() > storedSone.get().getTime())) {
+               Sone storedSone = getSone(sone.getId());
+               if (storedSone != null) {
+                       if (!soneRescueMode && !(sone.getTime() > storedSone.getTime())) {
                                logger.log(Level.FINE, String.format("Downloaded Sone %s is not newer than stored Sone %s.", sone, storedSone));
                                return;
                        }
                        List<Object> events =
-                                       collectEventsForChangesInSone(storedSone.get(), sone);
+                                       collectEventsForChangesInSone(storedSone, sone);
                        database.storeSone(sone);
                        for (Object event : events) {
                                eventBus.post(event);
                        }
-                       sone.setOptions(storedSone.get().getOptions());
-                       sone.setKnown(storedSone.get().isKnown());
+                       sone.setOptions(storedSone.getOptions());
+                       sone.setKnown(storedSone.isKnown());
                        sone.setStatus((sone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
                        if (sone.isLocal()) {
                                touchConfiguration();
@@ -1709,7 +1712,13 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                if (sone.isLocal()) {
                        return;
                }
-               sone.setLatestEdition(fromNullable(tryParse(identity.getProperty("Sone.LatestEdition"))).or(sone.getLatestEdition()));
+               String newLatestEdition = identity.getProperty("Sone.LatestEdition");
+               if (newLatestEdition != null) {
+                       Long parsedNewLatestEdition = tryParse(newLatestEdition);
+                       if (parsedNewLatestEdition != null) {
+                               sone.setLatestEdition(parsedNewLatestEdition);
+                       }
+               }
                soneDownloader.addSone(sone);
                soneDownloaders.execute(soneDownloader.fetchSoneAction(sone));
        }
@@ -1733,19 +1742,19 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                                return;
                        }
                }
-               Optional<Sone> sone = getSone(identity.getId());
-               if (!sone.isPresent()) {
+               Sone sone = getSone(identity.getId());
+               if (sone == null) {
                        /* TODO - we don’t have the Sone anymore. should this happen? */
                        return;
                }
-               for (PostReply postReply : sone.get().getReplies()) {
+               for (PostReply postReply : sone.getReplies()) {
                        eventBus.post(new PostReplyRemovedEvent(postReply));
                }
-               for (Post post : sone.get().getPosts()) {
+               for (Post post : sone.getPosts()) {
                        eventBus.post(new PostRemovedEvent(post));
                }
-               eventBus.post(new SoneRemovedEvent(sone.get()));
-               database.removeSone(sone.get());
+               eventBus.post(new SoneRemovedEvent(sone));
+               database.removeSone(sone);
        }
 
        /**
index 26135ea..3c32f7f 100644 (file)
@@ -59,7 +59,6 @@ import net.pterodactylus.util.template.XmlFilter;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Charsets;
-import com.google.common.base.Optional;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Ordering;
 import com.google.common.eventbus.EventBus;
@@ -125,20 +124,20 @@ public class SoneInserter extends AbstractService {
                this(core, eventBus, freenetInterface, soneId, new SoneModificationDetector(new LockableFingerprintProvider() {
                        @Override
                        public boolean isLocked() {
-                               final Optional<Sone> sone = core.getSone(soneId);
-                               if (!sone.isPresent()) {
+                               Sone sone = core.getSone(soneId);
+                               if (sone == null) {
                                        return false;
                                }
-                               return core.isLocked(sone.get());
+                               return core.isLocked(sone);
                        }
 
                        @Override
                        public String getFingerprint() {
-                               final Optional<Sone> sone = core.getSone(soneId);
-                               if (!sone.isPresent()) {
+                               Sone sone = core.getSone(soneId);
+                               if (sone == null) {
                                        return null;
                                }
-                               return sone.get().getFingerprint();
+                               return sone.getFingerprint();
                        }
                }, insertionDelay), 1000);
        }
@@ -219,12 +218,11 @@ public class SoneInserter extends AbstractService {
                                sleep(delay);
 
                                if (soneModificationDetector.isEligibleForInsert()) {
-                                       Optional<Sone> soneOptional = core.getSone(soneId);
-                                       if (!soneOptional.isPresent()) {
+                                       Sone sone = core.getSone(soneId);
+                                       if (sone == null) {
                                                logger.log(Level.WARNING, format("Sone %s has disappeared, exiting inserter.", soneId));
                                                return;
                                        }
-                                       Sone sone = soneOptional.get();
                                        InsertInformation insertInformation = new InsertInformation(sone);
                                        logger.log(Level.INFO, String.format("Inserting Sone “%s”…", sone.getName()));
 
index 86f4098..2b785f9 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.data.impl;
 
+import static com.google.common.base.Optional.fromNullable;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.database.SoneProvider;
@@ -99,7 +101,7 @@ public class PostImpl implements Post {
         */
        @Override
        public Sone getSone() {
-               return soneProvider.getSone(soneId).get();
+               return soneProvider.getSone(soneId);
        }
 
        /**
@@ -107,7 +109,7 @@ public class PostImpl implements Post {
         */
        @Override
        public Optional<String> getRecipientId() {
-               return Optional.fromNullable(recipientId);
+               return fromNullable(recipientId);
        }
 
        /**
@@ -115,7 +117,7 @@ public class PostImpl implements Post {
         */
        @Override
        public Optional<Sone> getRecipient() {
-               return soneProvider.getSone(recipientId);
+               return fromNullable(soneProvider.getSone(recipientId));
        }
 
        /**
index 5084c1c..3571172 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.data.impl;
 
+import static com.google.common.base.Optional.fromNullable;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.database.PostProvider;
@@ -78,7 +80,7 @@ public class PostReplyImpl extends ReplyImpl<PostReply> implements PostReply {
         */
        @Override
        public Optional<Post> getPost() {
-               return postProvider.getPost(postId);
+               return fromNullable(postProvider.getPost(postId));
        }
 
 }
index 4105749..372c80a 100644 (file)
@@ -83,7 +83,7 @@ public abstract class ReplyImpl<T extends Reply<T>> implements Reply<T> {
         */
        @Override
        public Sone getSone() {
-               return soneProvider.getSone(soneId).get();
+               return soneProvider.getSone(soneId);
        }
 
        /**
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumBuilder.java b/src/main/java/net/pterodactylus/sone/database/AlbumBuilder.java
deleted file mode 100644 (file)
index 2460306..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Sone - AlbumBuilder.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Album;
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Builder for {@link Album} objects.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface AlbumBuilder {
-
-       /**
-        * Configures this builder to create an album with a random ID.
-        *
-        * @return This album builder
-        */
-       AlbumBuilder randomId();
-
-       /**
-        * Configures this builder to create an album with the given ID.
-        *
-        * @param id
-        *              The ID of the album
-        * @return This album builder
-        */
-       AlbumBuilder withId(String id);
-
-       AlbumBuilder by(Sone sone);
-
-       /**
-        * Creates the album.
-        *
-        * @return The created album
-        * @throws IllegalStateException
-        *              if the album could not be created
-        */
-       Album build() throws IllegalStateException;
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumBuilder.kt b/src/main/java/net/pterodactylus/sone/database/AlbumBuilder.kt
new file mode 100644 (file)
index 0000000..ed441e0
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Sone - AlbumBuilder.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Album
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Builder for [Album] objects.
+ */
+interface AlbumBuilder {
+
+       fun randomId(): AlbumBuilder
+       fun withId(id: String): AlbumBuilder
+
+       fun by(sone: Sone): AlbumBuilder
+
+       fun build(): Album
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.java b/src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.java
deleted file mode 100644 (file)
index 07d7ae0..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Sone - AlbumBuilderFactory.java - Copyright © 2013–2016 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.database;
-
-/**
- * Factory for {@link AlbumBuilder}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface AlbumBuilderFactory {
-
-       /**
-        * Creates a new album builder.
-        *
-        * @return A new album builder
-        */
-       AlbumBuilder newAlbumBuilder();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.kt b/src/main/java/net/pterodactylus/sone/database/AlbumBuilderFactory.kt
new file mode 100644 (file)
index 0000000..6f854db
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Sone - AlbumBuilderFactory.java - Copyright © 2013–2016 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.database
+
+/**
+ * Factory for [AlbumBuilder]s.
+ */
+interface AlbumBuilderFactory {
+
+       fun newAlbumBuilder(): AlbumBuilder
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumDatabase.java b/src/main/java/net/pterodactylus/sone/database/AlbumDatabase.java
deleted file mode 100644 (file)
index 8e6fa45..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Sone - AlbumDatabase.java - Copyright © 2013–2016 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.database;
-
-/**
- * Combines an {@link AlbumProvider} and an {@link AlbumStore} into an album
- * database.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface AlbumDatabase extends AlbumProvider, AlbumBuilderFactory, AlbumStore {
-
-       /* nothing here. */
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumDatabase.kt b/src/main/java/net/pterodactylus/sone/database/AlbumDatabase.kt
new file mode 100644 (file)
index 0000000..69daa21
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sone - AlbumDatabase.java - Copyright © 2013–2016 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.database
+
+/**
+ * Combines an [AlbumProvider] and an [AlbumStore] into an album
+ * database.
+ */
+interface AlbumDatabase : AlbumProvider, AlbumBuilderFactory, AlbumStore
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumProvider.java b/src/main/java/net/pterodactylus/sone/database/AlbumProvider.java
deleted file mode 100644 (file)
index 746f014..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Sone - AlbumProvider.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Album;
-
-import com.google.common.base.Optional;
-
-/**
- * Interface for objects that can provide {@link Album}s by their ID.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface AlbumProvider {
-
-       /**
-        * Returns the album with the given ID, or {@link Optional#absent()} if no such
-        * album exists.
-        *
-        * @param albumId
-        *              The ID of the album
-        * @return The album, or {@link Optional#absent()} if the album does not exist
-        */
-       Optional<Album> getAlbum(String albumId);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumProvider.kt b/src/main/java/net/pterodactylus/sone/database/AlbumProvider.kt
new file mode 100644 (file)
index 0000000..c0b09e8
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Sone - AlbumProvider.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Album
+
+import com.google.common.base.Optional
+
+/**
+ * Interface for objects that can provide [Album]s by their ID.
+ */
+interface AlbumProvider {
+
+       fun getAlbum(albumId: String): Album?
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumStore.java b/src/main/java/net/pterodactylus/sone/database/AlbumStore.java
deleted file mode 100644 (file)
index 2a9bfd0..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Sone - AlbumStore.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Album;
-
-/**
- * Interface for a store of albums.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface AlbumStore {
-
-       /**
-        * Stores the given album.
-        *
-        * @param album
-        *              The album to store
-        */
-       void storeAlbum(Album album);
-
-       /**
-        * Removes the given album from the store.
-        *
-        * @param album
-        *              The album to remove
-        */
-       void removeAlbum(Album album);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/AlbumStore.kt b/src/main/java/net/pterodactylus/sone/database/AlbumStore.kt
new file mode 100644 (file)
index 0000000..e2a31c7
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - AlbumStore.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Album
+
+/**
+ * Interface for a store of albums.
+ */
+interface AlbumStore {
+
+       fun storeAlbum(album: Album)
+       fun removeAlbum(album: Album)
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/BookmarkDatabase.java b/src/main/java/net/pterodactylus/sone/database/BookmarkDatabase.java
deleted file mode 100644 (file)
index 6eb9c38..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-package net.pterodactylus.sone.database;
-
-import java.util.Set;
-
-import net.pterodactylus.sone.data.Post;
-
-/**
- * Database interface for bookmark-related functionality.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface BookmarkDatabase {
-
-       void bookmarkPost(Post post);
-       void unbookmarkPost(Post post);
-       boolean isPostBookmarked(Post post);
-       Set<Post> getBookmarkedPosts();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/Database.java b/src/main/java/net/pterodactylus/sone/database/Database.java
deleted file mode 100644 (file)
index db63ba3..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Sone - Database.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.database.memory.MemoryDatabase;
-
-import com.google.common.util.concurrent.Service;
-import com.google.inject.ImplementedBy;
-
-/**
- * Database for Sone data. This interface combines the various provider,
- * store, and builder factory interfaces into a single interface.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-@ImplementedBy(MemoryDatabase.class)
-public interface Database extends Service, SoneDatabase, FriendDatabase, PostDatabase, PostReplyDatabase, AlbumDatabase, ImageDatabase, BookmarkDatabase {
-
-       /**
-        * Saves the database.
-        *
-        * @throws DatabaseException
-        *             if an error occurs while saving
-        */
-       public void save() throws DatabaseException;
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/FriendDatabase.java b/src/main/java/net/pterodactylus/sone/database/FriendDatabase.java
deleted file mode 100644 (file)
index 761d356..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.pterodactylus.sone.database;
-
-/**
- * Combines a {@link FriendProvider} and a {@link FriendStore} into a friend database.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface FriendDatabase extends FriendProvider, FriendStore {
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/FriendProvider.java b/src/main/java/net/pterodactylus/sone/database/FriendProvider.java
deleted file mode 100644 (file)
index 3665d1b..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package net.pterodactylus.sone.database;
-
-import java.util.Collection;
-
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Provides information about {@link Sone#getFriends() friends} of a {@link Sone}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface FriendProvider {
-
-       Collection<String> getFriends(Sone localSone);
-       boolean isFriend(Sone localSone, String friendSoneId);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/FriendStore.java b/src/main/java/net/pterodactylus/sone/database/FriendStore.java
deleted file mode 100644 (file)
index 38c1c80..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package net.pterodactylus.sone.database;
-
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Stores information about the {@link Sone#getFriends() friends} of a {@link Sone}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface FriendStore {
-
-       void addFriend(Sone localSone, String friendSoneId);
-       void removeFriend(Sone localSone, String friendSoneId);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageBuilder.java b/src/main/java/net/pterodactylus/sone/database/ImageBuilder.java
deleted file mode 100644 (file)
index 7b5c9e0..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Sone - ImageBuilder.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Image;
-
-/**
- * Builder for {@link Image} objects.
- *
- * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
- */
-public interface ImageBuilder {
-
-       ImageBuilder randomId();
-
-       ImageBuilder withId(String id);
-
-       Image build() throws IllegalStateException;
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageBuilderFactory.java b/src/main/java/net/pterodactylus/sone/database/ImageBuilderFactory.java
deleted file mode 100644 (file)
index 900c989..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Sone - ImageBuilderFactory.java - Copyright © 2013–2016 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.database;
-
-/**
- * Factory for {@link ImageBuilder}s.
- *
- * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
- */
-public interface ImageBuilderFactory {
-
-       ImageBuilder newImageBuilder();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageDatabase.java b/src/main/java/net/pterodactylus/sone/database/ImageDatabase.java
deleted file mode 100644 (file)
index 090aa3b..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Sone - ImageDatabase.java - Copyright © 2013–2016 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.database;
-
-/**
- * Combines an {@link ImageProvider}, an {@link ImageBuilderFactory}, and an
- * {@link ImageStore} into an image database.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface ImageDatabase extends ImageProvider, ImageBuilderFactory, ImageStore {
-
-       /* nothing here. */
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageProvider.java b/src/main/java/net/pterodactylus/sone/database/ImageProvider.java
deleted file mode 100644 (file)
index 8ee3e5f..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Sone - ImageProvider.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Image;
-
-import com.google.common.base.Optional;
-
-/**
- * Provides {@link Image}.
- *
- * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
- */
-public interface ImageProvider {
-
-       Optional<Image> getImage(String imageId);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/ImageStore.java b/src/main/java/net/pterodactylus/sone/database/ImageStore.java
deleted file mode 100644 (file)
index 97a7240..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Sone - ImageStore.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Image;
-
-/**
- * Manages {@link Image} storage.
- *
- * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
- */
-public interface ImageStore {
-
-       void storeImage(Image image);
-
-       void removeImage(Image image);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostBuilder.java b/src/main/java/net/pterodactylus/sone/database/PostBuilder.java
deleted file mode 100644 (file)
index 45cc062..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Sone - PostBuilder.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * 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 senderId
-        *            The ID of the sender of the post
-        * @return This post builder
-        */
-       public PostBuilder from(String senderId);
-
-       /**
-        * 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 recipientId
-        *            The ID of the recipient of the post
-        * @return This post builder
-        */
-       public PostBuilder to(String recipientId);
-
-       /**
-        * 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(String) 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) recipient} must either not have been set, or
-        * it must have been set to a {@link Sone} other than {@link #from(String)
-        * 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/database/PostBuilderFactory.java b/src/main/java/net/pterodactylus/sone/database/PostBuilderFactory.java
deleted file mode 100644 (file)
index e5cee38..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Sone - PostBuilderFactory.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.database.memory.MemoryDatabase;
-
-import com.google.inject.ImplementedBy;
-
-/**
- * Factory for {@link PostBuilder}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-@ImplementedBy(MemoryDatabase.class)
-public interface PostBuilderFactory {
-
-       /**
-        * Creates a new post builder.
-        *
-        * @return A new post builder
-        */
-       public PostBuilder newPostBuilder();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostDatabase.java b/src/main/java/net/pterodactylus/sone/database/PostDatabase.java
deleted file mode 100644 (file)
index 648c77e..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Sone - PostDatabase.java - Copyright © 2013–2016 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.database;
-
-/**
- * Combines a {@link PostProvider}, a {@link PostBuilderFactory}, and a
- * {@link PostStore} into a complete post database.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface PostDatabase extends PostProvider, PostBuilderFactory, PostStore {
-
-       /* nothing here. */
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostProvider.java b/src/main/java/net/pterodactylus/sone/database/PostProvider.java
deleted file mode 100644 (file)
index bea59b9..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Sone - PostProvider.java - Copyright © 2011–2016 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.database;
-
-import java.util.Collection;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.database.memory.MemoryDatabase;
-
-import com.google.common.base.Optional;
-import com.google.inject.ImplementedBy;
-
-/**
- * Interface for objects that can provide {@link Post}s by their ID.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-@ImplementedBy(MemoryDatabase.class)
-public interface PostProvider {
-
-       /**
-        * Returns the post with the given ID.
-        *
-        * @param postId
-        *            The ID of the post to return
-        * @return The post with the given ID, or {@code null}
-        */
-       public Optional<Post> getPost(String postId);
-
-       /**
-        * Returns all posts from the given Sone.
-        *
-        * @param soneId
-        *            The ID of the Sone
-        * @return All posts from the given Sone
-        */
-       public Collection<Post> getPosts(String soneId);
-
-       /**
-        * Returns all posts that have the given Sone as recipient.
-        *
-        * @see Post#getRecipient()
-        * @param recipientId
-        *            The ID of the recipient of the posts
-        * @return All posts that have the given Sone as recipient
-        */
-       public Collection<Post> getDirectedPosts(String recipientId);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostReplyBuilder.java b/src/main/java/net/pterodactylus/sone/database/PostReplyBuilder.java
deleted file mode 100644 (file)
index 16569d9..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Sone - PostReplyBuilder.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.PostReply;
-
-/**
- * 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(String) 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/database/PostReplyBuilderFactory.java b/src/main/java/net/pterodactylus/sone/database/PostReplyBuilderFactory.java
deleted file mode 100644 (file)
index f85b18b..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Sone - PostReplyBuilderFactory.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.database.memory.MemoryDatabase;
-
-import com.google.inject.ImplementedBy;
-
-/**
- * Factory for {@link PostReplyBuilder}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-@ImplementedBy(MemoryDatabase.class)
-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/database/PostReplyDatabase.java b/src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java
deleted file mode 100644 (file)
index f226d73..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Sone - PostReplyDatabase.java - Copyright © 2013–2016 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.database;
-
-/**
- * Combines a {@link PostReplyProvider}, a {@link PostReplyBuilderFactory}, and
- * a {@link PostReplyStore} into a complete post reply database.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface PostReplyDatabase extends PostReplyProvider, PostReplyBuilderFactory, PostReplyStore {
-
-       /* nothing here. */
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java b/src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java
deleted file mode 100644 (file)
index e982599..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Sone - PostReplyProvider.java - Copyright © 2013–2016 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.database;
-
-import java.util.List;
-
-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 postId
-        *            The ID of the post to get all replies for
-        * @return All replies for the given post
-        */
-       public List<PostReply> getReplies(String postId);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostReplyStore.java b/src/main/java/net/pterodactylus/sone/database/PostReplyStore.java
deleted file mode 100644 (file)
index 7956a92..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Sone - PostReplyStore.java - Copyright © 2013–2016 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.database;
-
-import java.util.Collection;
-
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Defines a store for {@link PostReply post replies}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface PostReplyStore {
-
-       /**
-        * Stores the given post reply.
-        *
-        * @param postReply
-        *            The post reply
-        */
-       public void storePostReply(PostReply postReply);
-
-       /**
-        * Removes the given post reply from this store.
-        *
-        * @param postReply
-        *            The post reply to remove
-        */
-       public void removePostReply(PostReply postReply);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostStore.java b/src/main/java/net/pterodactylus/sone/database/PostStore.java
deleted file mode 100644 (file)
index 7c3e07f..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Sone - PostStore.java - Copyright © 2013–2016 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.database;
-
-import java.util.Collection;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Interface for a store for posts.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface PostStore {
-
-       /**
-        * Adds the given post to the store.
-        *
-        * @param post
-        *            The post to store
-        */
-       public void storePost(Post post);
-
-       /**
-        * Removes the given post.
-        *
-        * @param post
-        *            The post to remove
-        */
-       public void removePost(Post post);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/ReplyBuilder.java b/src/main/java/net/pterodactylus/sone/database/ReplyBuilder.java
deleted file mode 100644 (file)
index 21ba89c..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Sone - ReplyBuilder.java - Copyright © 2013–2016 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.database;
-
-import net.pterodactylus.sone.data.Reply;
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * 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 ID of the given {@link Sone} as sender
-        * of the reply.
-        *
-        * @param senderId
-        *            The ID of the sender of the reply
-        * @return This builder
-        */
-       public B from(String senderId);
-
-       /**
-        * 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/database/SoneBuilder.java b/src/main/java/net/pterodactylus/sone/database/SoneBuilder.java
deleted file mode 100644 (file)
index d2047af..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-package net.pterodactylus.sone.database;
-
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.freenet.wot.Identity;
-
-/**
- * Builder for {@link Sone} objects.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface SoneBuilder {
-
-       SoneBuilder from(Identity identity);
-       SoneBuilder local();
-
-       Sone build() throws IllegalStateException;
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/SoneBuilderFactory.java b/src/main/java/net/pterodactylus/sone/database/SoneBuilderFactory.java
deleted file mode 100644 (file)
index c95251f..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-package net.pterodactylus.sone.database;
-
-/**
- * Factory for {@link SoneBuilder}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface SoneBuilderFactory {
-
-       SoneBuilder newSoneBuilder();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/SoneDatabase.java b/src/main/java/net/pterodactylus/sone/database/SoneDatabase.java
deleted file mode 100644 (file)
index f5c5cda..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.pterodactylus.sone.database;
-
-/**
- * Combines a {@link SoneProvider} and a {@link SoneStore} into a Sone
- * database.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface SoneDatabase extends SoneProvider, SoneBuilderFactory, SoneStore {
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/SoneProvider.java b/src/main/java/net/pterodactylus/sone/database/SoneProvider.java
deleted file mode 100644 (file)
index 69f7eaf..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Sone - SoneProvider.java - Copyright © 2011–2016 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.database;
-
-import java.util.Collection;
-
-import javax.annotation.Nonnull;
-
-import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.data.Sone;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.inject.ImplementedBy;
-
-/**
- * Interface for objects that can provide {@link Sone}s by their ID.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-@ImplementedBy(Core.class)
-public interface SoneProvider {
-
-       Function<String, Optional<Sone>> soneLoader();
-
-       /**
-        * Returns the Sone with the given ID, or {@link Optional#absent()} if it
-        * does not exist.
-        *
-        * @param soneId
-        *            The ID of the Sone to return
-        * @return The Sone with the given ID, or {@link Optional#absent()}
-        */
-       public Optional<Sone> getSone(String soneId);
-
-       /**
-        * Returns all Sones.
-        *
-        * @return All Sones
-        */
-       @Nonnull
-       public Collection<Sone> getSones();
-
-       /**
-        * Returns all local Sones.
-        *
-        * @return All local Sones
-        */
-       public Collection<Sone> getLocalSones();
-
-       /**
-        * Returns all remote Sones.
-        *
-        * @return All remote Sones
-        */
-       public Collection<Sone> getRemoteSones();
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/database/SoneStore.java b/src/main/java/net/pterodactylus/sone/database/SoneStore.java
deleted file mode 100644 (file)
index 3684d48..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package net.pterodactylus.sone.database;
-
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Interface for a store for {@link Sone}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface SoneStore {
-
-       void storeSone(Sone sone);
-       void removeSone(Sone sone);
-
-}
index 594cf2b..4c4bc3f 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.database.memory;
 
+import static com.google.common.base.Optional.fromNullable;
 import static com.google.common.collect.FluentIterable.from;
 
 import java.util.HashSet;
@@ -99,7 +100,7 @@ public class MemoryBookmarkDatabase implements BookmarkDatabase {
                                        new Function<String, Post>() {
                                                @Override
                                                public Post apply(String postId) {
-                                                       return memoryDatabase.getPost(postId)
+                                                       return fromNullable(memoryDatabase.getPost(postId))
                                                                        .or(new EmptyPost(postId));
                                                }
                                        }).toSet();
index aa4a2a8..5e5021c 100644 (file)
@@ -37,6 +37,9 @@ import java.util.Set;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
@@ -56,8 +59,6 @@ import net.pterodactylus.sone.database.SoneProvider;
 import net.pterodactylus.util.config.Configuration;
 import net.pterodactylus.util.config.ConfigurationException;
 
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
@@ -66,6 +67,7 @@ import com.google.common.collect.TreeMultimap;
 import com.google.common.util.concurrent.AbstractService;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
+import kotlin.jvm.functions.Function1;
 
 /**
  * Memory-based {@link PostDatabase} implementation.
@@ -241,21 +243,22 @@ public class MemoryDatabase extends AbstractService implements Database {
                }
        }
 
+       @Nonnull
        @Override
-       public Function<String, Optional<Sone>> soneLoader() {
-               return new Function<String, Optional<Sone>>() {
+       public Function1<String, Sone> getSoneLoader() {
+               return new Function1<String, Sone>() {
                        @Override
-                       public Optional<Sone> apply(String soneId) {
+                       public Sone invoke(String soneId) {
                                return getSone(soneId);
                        }
                };
        }
 
        @Override
-       public Optional<Sone> getSone(String soneId) {
+       public Sone getSone(String soneId) {
                lock.readLock().lock();
                try {
-                       return fromNullable(allSones.get(soneId));
+                       return allSones.get(soneId);
                } finally {
                        lock.readLock().unlock();
                }
@@ -328,12 +331,12 @@ public class MemoryDatabase extends AbstractService implements Database {
        // POSTPROVIDER METHODS
        //
 
-       /** {@inheritDocs} */
+       @Nullable
        @Override
-       public Optional<Post> getPost(String postId) {
+       public Post getPost(@Nonnull String postId) {
                lock.readLock().lock();
                try {
-                       return fromNullable(allPosts.get(postId));
+                       return allPosts.get(postId);
                } finally {
                        lock.readLock().unlock();
                }
@@ -406,12 +409,12 @@ public class MemoryDatabase extends AbstractService implements Database {
        // POSTREPLYPROVIDER METHODS
        //
 
-       /** {@inheritDocs} */
+       @Nullable
        @Override
-       public Optional<PostReply> getPostReply(String id) {
+       public PostReply getPostReply(String id) {
                lock.readLock().lock();
                try {
-                       return fromNullable(allPostReplies.get(id));
+                       return allPostReplies.get(id);
                } finally {
                        lock.readLock().unlock();
                }
@@ -474,11 +477,12 @@ public class MemoryDatabase extends AbstractService implements Database {
        // ALBUMPROVDER METHODS
        //
 
+       @Nullable
        @Override
-       public Optional<Album> getAlbum(String albumId) {
+       public Album getAlbum(@Nonnull String albumId) {
                lock.readLock().lock();
                try {
-                       return fromNullable(allAlbums.get(albumId));
+                       return allAlbums.get(albumId);
                } finally {
                        lock.readLock().unlock();
                }
@@ -523,11 +527,12 @@ public class MemoryDatabase extends AbstractService implements Database {
        // IMAGEPROVIDER METHODS
        //
 
+       @Nullable
        @Override
-       public Optional<Image> getImage(String imageId) {
+       public Image getImage(@Nonnull String imageId) {
                lock.readLock().lock();
                try {
-                       return fromNullable(allImages.get(imageId));
+                       return allImages.get(imageId);
                } finally {
                        lock.readLock().unlock();
                }
index acf2374..724c627 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.database.memory;
 
+import static com.google.common.base.Optional.fromNullable;
+
 import java.util.UUID;
 
 import net.pterodactylus.sone.data.Post;
@@ -104,7 +106,7 @@ class MemoryPost implements Post {
         */
        @Override
        public Sone getSone() {
-               return soneProvider.getSone(soneId).get();
+               return soneProvider.getSone(soneId);
        }
 
        /**
@@ -112,7 +114,7 @@ class MemoryPost implements Post {
         */
        @Override
        public Optional<String> getRecipientId() {
-               return Optional.fromNullable(recipientId);
+               return fromNullable(recipientId);
        }
 
        /**
@@ -120,7 +122,7 @@ class MemoryPost implements Post {
         */
        @Override
        public Optional<Sone> getRecipient() {
-               return soneProvider.getSone(recipientId);
+               return fromNullable(soneProvider.getSone(recipientId));
        }
 
        /**
index 714b84e..687c28a 100644 (file)
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.database.memory;
 
+import static com.google.common.base.Optional.fromNullable;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
@@ -97,7 +99,7 @@ class MemoryPostReply implements PostReply {
         */
        @Override
        public Sone getSone() {
-               return soneProvider.getSone(soneId).get();
+               return soneProvider.getSone(soneId);
        }
 
        /**
@@ -150,7 +152,7 @@ class MemoryPostReply implements PostReply {
         */
        @Override
        public Optional<Post> getPost() {
-               return database.getPost(postId);
+               return fromNullable(database.getPost(postId));
        }
 
        //
index 98a43ab..9942af7 100644 (file)
@@ -161,11 +161,11 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                if (mandatory && (soneId == null)) {
                        throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
                }
-               Optional<Sone> sone = core.getSone(soneId);
-               if ((mandatory && !sone.isPresent()) || (sone.isPresent() && localOnly && !sone.get().isLocal())) {
+               Sone sone = core.getSone(soneId);
+               if ((mandatory && (sone == null)) || ((sone != null) && localOnly && !sone.isLocal())) {
                        throw new FcpException("Could not load Sone from “" + soneId + "”.");
                }
-               return sone;
+               return Optional.fromNullable(sone);
        }
 
        /**
@@ -184,11 +184,11 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
        protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
                try {
                        String postId = simpleFieldSet.getString(parameterName);
-                       Optional<Post> post = core.getPost(postId);
-                       if (!post.isPresent()) {
+                       Post post = core.getPost(postId);
+                       if (post == null) {
                                throw new FcpException("Could not load post from “" + postId + "”.");
                        }
-                       return post.get();
+                       return post;
                } catch (FSParseException fspe1) {
                        throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1);
                }
@@ -210,11 +210,11 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
        protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
                try {
                        String replyId = simpleFieldSet.getString(parameterName);
-                       Optional<PostReply> reply = core.getPostReply(replyId);
-                       if (!reply.isPresent()) {
+                       PostReply reply = core.getPostReply(replyId);
+                       if (reply == null) {
                                throw new FcpException("Could not load reply from “" + replyId + "”.");
                        }
-                       return reply.get();
+                       return reply;
                } catch (FSParseException fspe1) {
                        throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1);
                }
index 7b804fb..4ee15e3 100644 (file)
@@ -28,7 +28,6 @@ import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 
-import com.google.common.base.Optional;
 import com.google.common.collect.Collections2;
 
 import freenet.support.SimpleFieldSet;
@@ -63,11 +62,11 @@ public class GetPostFeedCommand extends AbstractSoneCommand {
                Collection<Post> allPosts = new HashSet<Post>();
                allPosts.addAll(sone.getPosts());
                for (String friendSoneId : sone.getFriends()) {
-                       Optional<Sone> friendSone = getCore().getSone(friendSoneId);
-                       if (!friendSone.isPresent()) {
+                       Sone friendSone = getCore().getSone(friendSoneId);
+                       if (friendSone == null) {
                                continue;
                        }
-                       allPosts.addAll(friendSone.get().getPosts());
+                       allPosts.addAll(friendSone.getPosts());
                }
                allPosts.addAll(getCore().getDirectedPosts(sone.getId()));
                allPosts = Collections2.filter(allPosts, Post.FUTURE_POSTS_FILTER);
index c1628eb..5cf708f 100644 (file)
@@ -26,7 +26,11 @@ import java.util.logging.Level;
 import java.util.logging.LogRecord;
 import java.util.logging.Logger;
 
+import javax.inject.Singleton;
+
 import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.database.memory.MemoryDatabase;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
 import net.pterodactylus.sone.freenet.wot.Context;
@@ -117,7 +121,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        /** The current year at time of release. */
        private static final int YEAR = 2017;
        private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
-       private static final int LATEST_EDITION = 76;
+       private static final int LATEST_EDITION = 77;
 
        /** The logger. */
        private static final Logger logger = getLogger(SonePlugin.class.getName());
@@ -252,6 +256,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                                bind(PluginVersion.class).toInstance(new PluginVersion(getVersion()));
                                bind(PluginYear.class).toInstance(new PluginYear(getYear()));
                                bind(PluginHomepage.class).toInstance(new PluginHomepage(getHomepage()));
+                               bind(Database.class).to(MemoryDatabase.class).in(Singleton.class);
                                if (startConfiguration.getBooleanValue("Developer.LoadFromFilesystem").getValue(false)) {
                                        String path = startConfiguration.getStringValue("Developer.FilesystemPath").getValue(null);
                                        if (path != null) {
diff --git a/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java b/src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
deleted file mode 100644 (file)
index 39d26db..0000000
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Sone - SoneTextParser.java - Copyright © 2010–2016 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.text;
-
-import static com.google.common.base.Optional.absent;
-import static com.google.common.base.Optional.of;
-import static java.util.logging.Logger.getLogger;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
-import java.net.MalformedURLException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.data.impl.IdOnlySone;
-import net.pterodactylus.sone.database.PostProvider;
-import net.pterodactylus.sone.database.SoneProvider;
-
-import com.google.common.base.Optional;
-import org.bitpedia.util.Base32;
-
-import freenet.keys.FreenetURI;
-import freenet.support.Base64;
-
-/**
- * {@link Parser} implementation that can recognize Freenet URIs.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SoneTextParser implements Parser<SoneTextParserContext> {
-
-       /** The logger. */
-       private static final Logger logger = getLogger(SoneTextParser.class.getName());
-
-       /** Pattern to detect whitespace. */
-       private static final Pattern whitespacePattern = Pattern.compile("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]");
-
-       private static class NextLink {
-
-               private final int position;
-               private final String link;
-               private final String remainder;
-               private final LinkType linkType;
-
-               private NextLink(int position, String link, String remainder, LinkType linkType) {
-                       this.position = position;
-                       this.link = link;
-                       this.remainder = remainder;
-                       this.linkType = linkType;
-               }
-
-               public int getPosition() {
-                       return position;
-               }
-
-               public String getLink() {
-                       return link;
-               }
-
-               public String getRemainder() {
-                       return remainder;
-               }
-
-               public LinkType getLinkType() {
-                       return linkType;
-               }
-
-       }
-
-       /**
-        * Enumeration for all recognized link types.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       private enum LinkType {
-
-               KSK("KSK@", true),
-               CHK("CHK@", true),
-               SSK("SSK@", true),
-               USK("USK@", true),
-               HTTP("http://", false),
-               HTTPS("https://", false),
-               SONE("sone://", false),
-               POST("post://", false),
-
-               FREEMAIL("", true) {
-                       @Override
-                       public Optional<NextLink> findNext(String line) {
-                               int nextFreemailSuffix = line.indexOf(".freemail");
-                               if (nextFreemailSuffix < 54) {
-                                       /* 52 chars for the id, 1 on @, at least 1 for the local part. */
-                                       return absent();
-                               }
-                               if (line.charAt(nextFreemailSuffix - 53) != '@') {
-                                       return absent();
-                               }
-                               if (!line.substring(nextFreemailSuffix - 52, nextFreemailSuffix).matches("^[a-z2-7]*$")) {
-                                       return absent();
-                               }
-                               int startOfLocalPart = nextFreemailSuffix - 54;
-                               if (!isAllowedInLocalPart(line.charAt(startOfLocalPart))) {
-                                       return absent();
-                               }
-                               while ((startOfLocalPart > 0) && isAllowedInLocalPart(line.charAt(startOfLocalPart - 1))) {
-                                       startOfLocalPart--;
-                               }
-                               return of(new NextLink(startOfLocalPart, line.substring(startOfLocalPart, nextFreemailSuffix + 9), line.substring(nextFreemailSuffix + 9), this));
-                       }
-
-                       private boolean isAllowedInLocalPart(char character) {
-                               return ((character >= 'A') && (character <= 'Z'))
-                                               || ((character >= 'a') && (character <= 'z'))
-                                               || ((character >= '0') && (character <= '9'))
-                                               || (character == '.') || (character == '-') || (character == '_');
-                       }
-               };
-
-               private final String scheme;
-               private final boolean freenetLink;
-
-               LinkType(String scheme, boolean freenetLink) {
-                       this.scheme = scheme;
-                       this.freenetLink = freenetLink;
-               }
-
-               /**
-                * Returns the scheme of this link type.
-                *
-                * @return The scheme of this link type
-                */
-               public String getScheme() {
-                       return scheme;
-               }
-
-               public boolean isFreenetLink() {
-                       return freenetLink;
-               }
-
-               public Optional<NextLink> findNext(String line) {
-                       int nextLinkPosition = line.indexOf(getScheme());
-                       if (nextLinkPosition == -1) {
-                               return absent();
-                       }
-                       int endOfLink = findEndOfLink(line.substring(nextLinkPosition));
-                       return of(new NextLink(nextLinkPosition, line.substring(nextLinkPosition, nextLinkPosition + endOfLink), line.substring(nextLinkPosition + endOfLink), this));
-               }
-
-               private static int findEndOfLink(String line) {
-                       Matcher matcher = whitespacePattern.matcher(line);
-                       int endOfLink = matcher.find() ? matcher.start() : line.length();
-                       while (isPunctuation(line.charAt(endOfLink - 1))) {
-                               endOfLink--;
-                       }
-                       int openParens = 0;
-                       for (int i = 0; i < endOfLink; i++) {
-                               switch (line.charAt(i)) {
-                                       case '(':
-                                               openParens++;
-                                               break;
-                                       case ')':
-                                               openParens--;
-                                               if (openParens < 0) {
-                                                       return i;
-                                               }
-                                       default:
-                               }
-                       }
-                       return endOfLink;
-               }
-
-       }
-
-       /** The Sone provider. */
-       private final SoneProvider soneProvider;
-
-       /** The post provider. */
-       private final PostProvider postProvider;
-
-       /**
-        * Creates a new freenet link parser.
-        *
-        * @param soneProvider
-        *            The Sone provider
-        * @param postProvider
-        *            The post provider
-        */
-       public SoneTextParser(SoneProvider soneProvider, PostProvider postProvider) {
-               this.soneProvider = soneProvider;
-               this.postProvider = postProvider;
-       }
-
-       //
-       // PART METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Nonnull
-       @Override
-       public Iterable<Part> parse(@Nonnull String source, @Nullable SoneTextParserContext context) {
-               List<Part> parts = new ArrayList<>();
-               try (Reader sourceReader = new StringReader(source);
-                               BufferedReader bufferedReader = new BufferedReader(sourceReader)) {
-                       String line;
-                       boolean lastLineEmpty = true;
-                       int emptyLines = 0;
-                       while ((line = bufferedReader.readLine()) != null) {
-                               if (line.trim().length() == 0) {
-                                       if (lastLineEmpty) {
-                                               continue;
-                                       }
-                                       parts.add(new PlainTextPart("\n"));
-                                       ++emptyLines;
-                                       lastLineEmpty = emptyLines == 2;
-                                       continue;
-                               }
-                               emptyLines = 0;
-                               /*
-                                * lineComplete tracks whether the block you are parsing is the
-                                * first block of the line. this is important because sometimes
-                                * you have to add an additional line break.
-                                */
-                               boolean lineComplete = true;
-                               while (line.length() > 0) {
-                                       Optional<NextLink> nextLink = findNextLink(line);
-                                       if (!nextLink.isPresent()) {
-                                               if (lineComplete && !lastLineEmpty) {
-                                                       parts.add(new PlainTextPart("\n" + line));
-                                               } else {
-                                                       parts.add(new PlainTextPart(line));
-                                               }
-                                               break;
-                                       }
-                                       LinkType linkType = nextLink.get().getLinkType();
-                                       int next = nextLink.get().getPosition();
-
-                                       /* cut off “freenet:” from before keys. */
-                                       if (linkType.isFreenetLink() && (next >= 8) && (line.substring(next - 8, next).equals("freenet:"))) {
-                                               next -= 8;
-                                               line = line.substring(0, next) + line.substring(next + 8);
-                                       }
-
-                                       /* if there is text before the next item, write it out. */
-                                       if (lineComplete && !lastLineEmpty) {
-                                               parts.add(new PlainTextPart("\n"));
-                                       }
-                                       if (next > 0) {
-                                               parts.add(new PlainTextPart(line.substring(0, next)));
-                                               line = line.substring(next);
-                                       }
-                                       lineComplete = false;
-
-                                       String link = nextLink.get().getLink();
-                                       logger.log(Level.FINER, String.format("Found link: %s", link));
-
-                                       /* if there is no text after the scheme, it’s not a link! */
-                                       if (link.equals(linkType.getScheme())) {
-                                               parts.add(new PlainTextPart(linkType.getScheme()));
-                                               line = line.substring(linkType.getScheme().length());
-                                               continue;
-                                       }
-
-                                       switch (linkType) {
-                                               case SONE:
-                                                       renderSoneLink(parts, link);
-                                                       break;
-                                               case POST:
-                                                       renderPostLink(parts, link);
-                                                       break;
-                                               case KSK:
-                                               case CHK:
-                                               case SSK:
-                                               case USK:
-                                                       renderFreenetLink(parts, link, linkType, context);
-                                                       break;
-                                               case HTTP:
-                                               case HTTPS:
-                                                       renderHttpLink(parts, link, linkType);
-                                                       break;
-                                               case FREEMAIL:
-                                                       renderFreemailLink(parts, link);
-                                       }
-
-                                       line = nextLink.get().getRemainder();
-                               }
-                               lastLineEmpty = false;
-                       }
-               } catch (IOException ioe1) {
-                       // a buffered reader around a string reader should never throw.
-                       throw new RuntimeException(ioe1);
-               }
-               for (int partIndex = parts.size() - 1; partIndex >= 0; --partIndex) {
-                       Part part = parts.get(partIndex);
-                       if (!(part instanceof PlainTextPart) || !"\n".equals(part.getText())) {
-                               break;
-                       }
-                       parts.remove(partIndex);
-               }
-               return parts;
-       }
-
-       public static Optional<NextLink> findNextLink(String line) {
-               int earliestLinkPosition = Integer.MAX_VALUE;
-               NextLink earliestNextLink = null;
-               for (LinkType possibleLinkType : LinkType.values()) {
-                       Optional<NextLink> nextLink = possibleLinkType.findNext(line);
-                       if (nextLink.isPresent()) {
-                               if (nextLink.get().getPosition() < earliestLinkPosition) {
-                                       earliestNextLink = nextLink.get();
-                                       earliestLinkPosition = earliestNextLink.getPosition();
-                               }
-                       }
-               }
-               return Optional.fromNullable(earliestNextLink);
-       }
-
-       private void renderSoneLink(List<Part> parts, String line) {
-               if (line.length() >= (7 + 43)) {
-                       String soneId = line.substring(7, 50);
-                       Optional<Sone> sone = soneProvider.getSone(soneId);
-                       parts.add(new SonePart(sone.or(new IdOnlySone(soneId))));
-               } else {
-                       parts.add(new PlainTextPart(line));
-               }
-       }
-
-       private void renderPostLink(List<Part> parts, String line) {
-               if (line.length() >= (7 + 36)) {
-                       String postId = line.substring(7, 43);
-                       Optional<Post> post = postProvider.getPost(postId);
-                       if (post.isPresent()) {
-                               parts.add(new PostPart(post.get()));
-                       } else {
-                               parts.add(new PlainTextPart(line.substring(0, 43)));
-                       }
-               } else {
-                       parts.add(new PlainTextPart(line));
-               }
-       }
-
-       private void renderFreenetLink(List<Part> parts, String link, LinkType linkType, @Nullable SoneTextParserContext context) {
-               String name = link;
-               String linkWithoutParameters = link;
-               if (name.indexOf('?') > -1) {
-                       linkWithoutParameters = name = name.substring(0, name.indexOf('?'));
-               }
-               if (name.endsWith("/")) {
-                       name = name.substring(0, name.length() - 1);
-               }
-               try {
-                       FreenetURI uri = new FreenetURI(name);
-                       name = uri.lastMetaString();
-                       if (name == null) {
-                               name = uri.getDocName();
-                       }
-                       if (name == null) {
-                               name = link.substring(0, Math.min(9, link.length()));
-                       }
-                       boolean fromPostingSone = ((linkType == LinkType.SSK) || (linkType == LinkType.USK)) && (context != null) && (context.getPostingSone() != null) && link.substring(4, Math.min(link.length(), 47)).equals(context.getPostingSone().getId());
-                       parts.add(new FreenetLinkPart(link, name, linkWithoutParameters, fromPostingSone));
-               } catch (MalformedURLException mue1) {
-                       /* not a valid link, insert as plain text. */
-                       parts.add(new PlainTextPart(link));
-               } catch (NullPointerException npe1) {
-                       /* FreenetURI sometimes throws these, too. */
-                       parts.add(new PlainTextPart(link));
-               } catch (ArrayIndexOutOfBoundsException aioobe1) {
-                       /* oh, and these, too. */
-                       parts.add(new PlainTextPart(link));
-               }
-       }
-
-       private void renderHttpLink(List<Part> parts, String link, LinkType linkType) {
-               String name = link.substring(linkType == LinkType.HTTP ? 7 : 8);
-               int firstSlash = name.indexOf('/');
-               int lastSlash = name.lastIndexOf('/');
-               if ((lastSlash - firstSlash) > 3) {
-                       name = name.substring(0, firstSlash + 1) + "…" + name.substring(lastSlash);
-               }
-               if (name.endsWith("/")) {
-                       name = name.substring(0, name.length() - 1);
-               }
-               if (((name.indexOf('/') > -1) && (name.indexOf('.') < name.lastIndexOf('.', name.indexOf('/'))) || ((name.indexOf('/') == -1) && (name.indexOf('.') < name.lastIndexOf('.')))) && name.startsWith("www.")) {
-                       name = name.substring(4);
-               }
-               if (name.indexOf('?') > -1) {
-                       name = name.substring(0, name.indexOf('?'));
-               }
-               parts.add(new LinkPart(link, name));
-       }
-
-       private void renderFreemailLink(List<Part> parts, String line) {
-               int separator = line.indexOf('@');
-               String freemailId = line.substring(separator + 1, separator + 53);
-               String identityId = Base64.encode(Base32.decode(freemailId));
-               String emailLocalPart = line.substring(0, separator);
-               parts.add(new FreemailPart(emailLocalPart, freemailId, identityId));
-       }
-
-       private static boolean isPunctuation(char character) {
-               return (character == '.') || (character == ',') || (character == '!') || (character == '?');
-       }
-
-}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/BookmarkDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/BookmarkDatabase.kt
new file mode 100644 (file)
index 0000000..f44c1b8
--- /dev/null
@@ -0,0 +1,16 @@
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.Post
+
+/**
+ * Database interface for bookmark-related functionality.
+ */
+interface BookmarkDatabase {
+
+       val bookmarkedPosts: Set<Post>
+
+       fun bookmarkPost(post: Post)
+       fun unbookmarkPost(post: Post)
+       fun isPostBookmarked(post: Post): Boolean
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/Database.kt b/src/main/kotlin/net/pterodactylus/sone/database/Database.kt
new file mode 100644 (file)
index 0000000..522ce32
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Sone - Database.java - Copyright © 2013–2016 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.database
+
+import com.google.common.util.concurrent.Service
+
+/**
+ * Database for Sone data. This interface combines the various provider,
+ * store, and builder factory interfaces into a single interface.
+ */
+interface Database : Service, SoneDatabase, FriendDatabase, PostDatabase, PostReplyDatabase, AlbumDatabase, ImageDatabase, BookmarkDatabase {
+
+       @Throws(DatabaseException::class)
+       fun save()
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/FriendDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/FriendDatabase.kt
new file mode 100644 (file)
index 0000000..75e5623
--- /dev/null
@@ -0,0 +1,6 @@
+package net.pterodactylus.sone.database
+
+/**
+ * Combines a [FriendProvider] and a [FriendStore] into a friend database.
+ */
+interface FriendDatabase : FriendProvider, FriendStore
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/FriendProvider.kt b/src/main/kotlin/net/pterodactylus/sone/database/FriendProvider.kt
new file mode 100644 (file)
index 0000000..f56f2f7
--- /dev/null
@@ -0,0 +1,13 @@
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Provides information about [friends][Sone.getFriends] of a [Sone].
+ */
+interface FriendProvider {
+
+       fun getFriends(localSone: Sone): Collection<String>
+       fun isFriend(localSone: Sone, friendSoneId: String): Boolean
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/FriendStore.kt b/src/main/kotlin/net/pterodactylus/sone/database/FriendStore.kt
new file mode 100644 (file)
index 0000000..5e7a222
--- /dev/null
@@ -0,0 +1,13 @@
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Stores information about the [friends][Sone.getFriends] of a [Sone].
+ */
+interface FriendStore {
+
+       fun addFriend(localSone: Sone, friendSoneId: String)
+       fun removeFriend(localSone: Sone, friendSoneId: String)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/ImageBuilder.kt b/src/main/kotlin/net/pterodactylus/sone/database/ImageBuilder.kt
new file mode 100644 (file)
index 0000000..5ceb28e
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Sone - ImageBuilder.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Image
+
+/**
+ * Builder for [Image] objects.
+ */
+interface ImageBuilder {
+
+       fun randomId(): ImageBuilder
+       fun withId(id: String): ImageBuilder
+
+       fun build(): Image
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/ImageBuilderFactory.kt b/src/main/kotlin/net/pterodactylus/sone/database/ImageBuilderFactory.kt
new file mode 100644 (file)
index 0000000..bae797c
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Sone - ImageBuilderFactory.java - Copyright © 2013–2016 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.database
+
+/**
+ * Factory for [ImageBuilder]s.
+ */
+interface ImageBuilderFactory {
+
+       fun newImageBuilder(): ImageBuilder
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/ImageDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/ImageDatabase.kt
new file mode 100644 (file)
index 0000000..4617cef
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sone - ImageDatabase.java - Copyright © 2013–2016 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.database
+
+/**
+ * Combines an [ImageProvider], an [ImageBuilderFactory], and an
+ * [ImageStore] into an image database.
+ */
+interface ImageDatabase : ImageProvider, ImageBuilderFactory, ImageStore
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/ImageProvider.kt b/src/main/kotlin/net/pterodactylus/sone/database/ImageProvider.kt
new file mode 100644 (file)
index 0000000..c7cc75e
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Sone - ImageProvider.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Image
+
+import com.google.common.base.Optional
+
+/**
+ * Provides [Image]s.
+ */
+interface ImageProvider {
+
+       fun getImage(imageId: String): Image?
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/ImageStore.kt b/src/main/kotlin/net/pterodactylus/sone/database/ImageStore.kt
new file mode 100644 (file)
index 0000000..91cff2d
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - ImageStore.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Image
+
+/**
+ * Manages [Image] storage.
+ */
+interface ImageStore {
+
+       fun storeImage(image: Image)
+       fun removeImage(image: Image)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostBuilder.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostBuilder.kt
new file mode 100644 (file)
index 0000000..c1377ca
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Sone - PostBuilder.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Builder for [Post] objects.
+ *
+ *
+ * A [Post] consists of the following elements:
+ *
+ *  * an ID,
+ *  * a [sender][Sone],
+ *  * an optional [recipient][Sone],
+ *  * a time,
+ *  * and a text.
+ *
+ * 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.
+ */
+interface PostBuilder {
+
+       fun copyPost(post: Post): PostBuilder
+
+       fun from(senderId: String): PostBuilder
+
+       fun randomId(): PostBuilder
+       fun withId(id: String): PostBuilder
+
+       fun currentTime(): PostBuilder
+       fun withTime(time: Long): PostBuilder
+
+       fun withText(text: String): PostBuilder
+
+       fun to(recipientId: String): PostBuilder
+
+       /**
+        * Verifies this builder’s configuration and creates a new post.
+        *
+        * The following conditions must be met in order for this builder to be
+        * configured correctly:
+        *
+        *  * Exactly one of [randomId] or [withId] must have been called.
+        *  * The [sender][from] must not be `null`.
+        *  * Exactly one of [currentTime] or [withTime] must have been called.
+        *  * The [text][withText] must not be `null` and must contain something other than whitespace.
+        *  * The [recipient][to] must either not have been set, or it must have been set to a [Sone] other than [the sender][from].
+        *
+        * @return A new post
+        * @throws IllegalStateException if this builder’s configuration is not valid
+        */
+       fun build(): Post
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostBuilderFactory.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostBuilderFactory.kt
new file mode 100644 (file)
index 0000000..d7604c3
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Sone - PostBuilderFactory.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.database.memory.MemoryDatabase
+
+import com.google.inject.ImplementedBy
+
+/**
+ * Factory for [PostBuilder]s.
+ */
+@ImplementedBy(MemoryDatabase::class)
+interface PostBuilderFactory {
+
+       fun newPostBuilder(): PostBuilder
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostDatabase.kt
new file mode 100644 (file)
index 0000000..7b97c6a
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sone - PostDatabase.java - Copyright © 2013–2016 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.database
+
+/**
+ * Combines a [PostProvider], a [PostBuilderFactory], and a
+ * [PostStore] into a complete post database.
+ */
+interface PostDatabase : PostProvider, PostBuilderFactory, PostStore
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostProvider.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostProvider.kt
new file mode 100644 (file)
index 0000000..37cb2a1
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Sone - PostProvider.java - Copyright © 2011–2016 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.database
+
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.database.memory.MemoryDatabase
+
+import com.google.common.base.Optional
+import com.google.inject.ImplementedBy
+
+/**
+ * Interface for objects that can provide [Post]s by their ID.
+ */
+@ImplementedBy(MemoryDatabase::class)
+interface PostProvider {
+
+       fun getPost(postId: String): Post?
+       fun getPosts(soneId: String): Collection<Post>
+       fun getDirectedPosts(recipientId: String): Collection<Post>
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilder.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilder.kt
new file mode 100644 (file)
index 0000000..0c333da
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Sone - PostReplyBuilder.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.PostReply
+
+/**
+ * Builder for a [PostReply] object.
+ */
+interface PostReplyBuilder : ReplyBuilder<PostReplyBuilder> {
+
+       fun to(postId: String): PostReplyBuilder
+
+       /**
+        * Verifies the configuration of this builder and creates a new post reply.
+        *
+        * The following conditions must be met in order for the configuration to be
+        * considered valid:
+        *
+        *  * Exactly one of [randomId] or [withId] must have been called.
+        *  * The [sender][from] must not be `null`.
+        *  * Exactly one of [currentTime] or [withTime] must have been called.
+        *  * The [text][withText] must not be `null` and must contain something other than whitespace.
+        *  * The [post][to] must have been set.
+        *
+        * @return The created post reply
+        * @throws IllegalStateException if this builder’s configuration is not valid
+        */
+       fun build(): PostReply
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilderFactory.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostReplyBuilderFactory.kt
new file mode 100644 (file)
index 0000000..d039cda
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Sone - PostReplyBuilderFactory.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.database.memory.MemoryDatabase
+
+import com.google.inject.ImplementedBy
+
+/**
+ * Factory for [PostReplyBuilder]s.
+ */
+@ImplementedBy(MemoryDatabase::class)
+interface PostReplyBuilderFactory {
+
+       fun newPostReplyBuilder(): PostReplyBuilder
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostReplyDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostReplyDatabase.kt
new file mode 100644 (file)
index 0000000..8f08cf3
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Sone - PostReplyDatabase.java - Copyright © 2013–2016 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.database
+
+/**
+ * Combines a [PostReplyProvider], a [PostReplyBuilderFactory], and
+ * a [PostReplyStore] into a complete post reply database.
+ */
+interface PostReplyDatabase : PostReplyProvider, PostReplyBuilderFactory, PostReplyStore
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostReplyProvider.kt
new file mode 100644 (file)
index 0000000..7e09017
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Sone - PostReplyProvider.java - Copyright © 2013–2016 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.database
+
+import com.google.common.base.Optional
+import net.pterodactylus.sone.data.PostReply
+
+/**
+ * Interface for objects that can provide [PostReply]s.
+ */
+interface PostReplyProvider {
+
+       fun getPostReply(id: String): PostReply?
+
+       /**
+        * Returns all replies for the given post, order ascending by time.
+        *
+        * @param postId The ID of the post to get all replies for
+        * @return All replies for the given post
+        */
+       fun getReplies(postId: String): List<PostReply>
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostReplyStore.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostReplyStore.kt
new file mode 100644 (file)
index 0000000..08ba380
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Sone - PostReplyStore.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.PostReply
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Defines a store for [post replies][PostReply].
+ */
+interface PostReplyStore {
+
+       fun storePostReply(postReply: PostReply)
+       fun removePostReply(postReply: PostReply)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/PostStore.kt b/src/main/kotlin/net/pterodactylus/sone/database/PostStore.kt
new file mode 100644 (file)
index 0000000..3cb5f17
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Sone - PostStore.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Interface for a store for posts.
+ */
+interface PostStore {
+
+       fun storePost(post: Post)
+       fun removePost(post: Post)
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/ReplyBuilder.kt b/src/main/kotlin/net/pterodactylus/sone/database/ReplyBuilder.kt
new file mode 100644 (file)
index 0000000..1aa6039
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - ReplyBuilder.java - Copyright © 2013–2016 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.database
+
+import net.pterodactylus.sone.data.Reply
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Methods that all reply builders need to implement in order to be able to
+ * create any kind of [Reply].
+ *
+ * @param B The type of the builder
+ */
+interface ReplyBuilder<B : ReplyBuilder<B>> {
+
+       fun randomId(): B
+       fun withId(id: String): B
+
+       fun from(senderId: String): B
+       fun currentTime(): B
+       fun withTime(time: Long): B
+       fun withText(text: String): B
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/SoneBuilder.kt b/src/main/kotlin/net/pterodactylus/sone/database/SoneBuilder.kt
new file mode 100644 (file)
index 0000000..35c444c
--- /dev/null
@@ -0,0 +1,16 @@
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.freenet.wot.Identity
+
+/**
+ * Builder for [Sone] objects.
+ */
+interface SoneBuilder {
+
+       fun from(identity: Identity): SoneBuilder
+       fun local(): SoneBuilder
+
+       fun build(): Sone
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/SoneBuilderFactory.kt b/src/main/kotlin/net/pterodactylus/sone/database/SoneBuilderFactory.kt
new file mode 100644 (file)
index 0000000..c1dee54
--- /dev/null
@@ -0,0 +1,10 @@
+package net.pterodactylus.sone.database
+
+/**
+ * Factory for [SoneBuilder]s.
+ */
+interface SoneBuilderFactory {
+
+       fun newSoneBuilder(): SoneBuilder
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/SoneDatabase.kt b/src/main/kotlin/net/pterodactylus/sone/database/SoneDatabase.kt
new file mode 100644 (file)
index 0000000..3533f69
--- /dev/null
@@ -0,0 +1,7 @@
+package net.pterodactylus.sone.database
+
+/**
+ * Combines a [SoneProvider] and a [SoneStore] into a Sone
+ * database.
+ */
+interface SoneDatabase : SoneProvider, SoneBuilderFactory, SoneStore
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/SoneProvider.kt b/src/main/kotlin/net/pterodactylus/sone/database/SoneProvider.kt
new file mode 100644 (file)
index 0000000..b8ce5e1
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Sone - SoneProvider.java - Copyright © 2011–2016 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.database
+
+import net.pterodactylus.sone.core.Core
+import net.pterodactylus.sone.data.Sone
+
+import com.google.common.base.Function
+import com.google.common.base.Optional
+import com.google.inject.ImplementedBy
+
+/**
+ * Interface for objects that can provide [Sone]s by their ID.
+ */
+@ImplementedBy(Core::class)
+interface SoneProvider {
+
+       val sones: Collection<Sone>
+       val localSones: Collection<Sone>
+       val remoteSones: Collection<Sone>
+       val soneLoader: (String) -> Sone?
+
+       fun getSone(soneId: String): Sone?
+
+}
diff --git a/src/main/kotlin/net/pterodactylus/sone/database/SoneStore.kt b/src/main/kotlin/net/pterodactylus/sone/database/SoneStore.kt
new file mode 100644 (file)
index 0000000..20cc3f4
--- /dev/null
@@ -0,0 +1,13 @@
+package net.pterodactylus.sone.database
+
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Interface for a store for [Sone]s.
+ */
+interface SoneStore {
+
+       fun storeSone(sone: Sone)
+       fun removeSone(sone: Sone)
+
+}
index cec5d56..67b03f3 100644 (file)
@@ -20,7 +20,7 @@ class ParserFilter @Inject constructor(private val core: Core, private val soneT
                val text = data?.toString() ?: return listOf<Part>()
                val soneParameter = parameters?.get("sone")
                val sone = when (soneParameter) {
-                       is String -> core.getSone(soneParameter).orNull()
+                       is String -> core.getSone(soneParameter)
                        is Sone -> soneParameter
                        else -> null
                }
index 43331c3..25df239 100644 (file)
@@ -101,7 +101,7 @@ class RenderFilter(private val core: Core, private val templateContextFactory: T
 
        private fun render(writer: Writer, freemailPart: FreemailPart) {
                val sone = core.getSone(freemailPart.identityId)
-               val soneName = sone.transform(SoneAccessor::getNiceName).or(freemailPart.identityId)
+               val soneName = sone?.let(SoneAccessor::getNiceName) ?: freemailPart.identityId
                renderLink(writer,
                                "/Freemail/NewMessage?to=${freemailPart.identityId}",
                                "${freemailPart.emailLocalPart}@$soneName.freemail",
index 3744fd7..43b9f4a 100644 (file)
@@ -5,7 +5,7 @@ package net.pterodactylus.sone.text
  * link is an SSK or USK link and the post was created by an identity that owns
  * the keyspace in question.
  */
-data class FreenetLinkPart(val link: String, override val text: String, val title: String, val trusted: Boolean) : Part {
+data class FreenetLinkPart(val link: String, override val text: String, val title: String, val trusted: Boolean = false) : Part {
 
        constructor(link: String, text: String, trusted: Boolean) : this(link, text, link, trusted)
 
index 7099b93..3563b37 100644 (file)
@@ -5,8 +5,4 @@ package net.pterodactylus.sone.text
  * attributes: the link itself, the text that is shown instead of the link, and
  * an explanatory text that can be displayed e.g. as a tooltip.
  */
-data class LinkPart(val link: String, override val text: String, val title: String) : Part {
-
-       constructor(link: String, text: String) : this(link, text, link)
-
-}
+data class LinkPart @JvmOverloads constructor(val link: String, override val text: String, val title: String = link) : Part
diff --git a/src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt b/src/main/kotlin/net/pterodactylus/sone/text/SoneTextParser.kt
new file mode 100644 (file)
index 0000000..64125c1
--- /dev/null
@@ -0,0 +1,197 @@
+package net.pterodactylus.sone.text
+
+import freenet.keys.FreenetURI
+import freenet.support.Base64
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.data.impl.IdOnlySone
+import net.pterodactylus.sone.database.PostProvider
+import net.pterodactylus.sone.database.SoneProvider
+import net.pterodactylus.sone.text.LinkType.CHK
+import net.pterodactylus.sone.text.LinkType.FREEMAIL
+import net.pterodactylus.sone.text.LinkType.HTTP
+import net.pterodactylus.sone.text.LinkType.HTTPS
+import net.pterodactylus.sone.text.LinkType.KSK
+import net.pterodactylus.sone.text.LinkType.POST
+import net.pterodactylus.sone.text.LinkType.SONE
+import net.pterodactylus.sone.text.LinkType.SSK
+import net.pterodactylus.sone.text.LinkType.USK
+import net.pterodactylus.sone.utils.let
+import org.bitpedia.util.Base32
+import java.net.MalformedURLException
+
+/**
+ * [Parser] implementation that can recognize Freenet URIs.
+ */
+class SoneTextParser(private val soneProvider: SoneProvider?, private val postProvider: PostProvider?) {
+
+       fun parse(source: String, context: SoneTextParserContext?) =
+                       source.split("\n")
+                                       .dropWhile { it.trim() == "" }
+                                       .dropLastWhile { it.trim() == "" }
+                                       .mergeMultipleEmptyLines()
+                                       .flatMap { splitLineIntoParts(it, context) }
+                                       .removeEmptyPlainTextParts()
+                                       .mergeAdjacentPlainTextParts()
+
+       private fun splitLineIntoParts(line: String, context: SoneTextParserContext?) =
+                       generateSequence(PlainTextPart("") as Part to line) { remainder ->
+                               if (remainder.second == "")
+                                       null
+                               else
+                                       LinkType.values()
+                                                       .mapNotNull { it.findNext(remainder.second) }
+                                                       .minBy { it.position }
+                                                       .let {
+                                                               when {
+                                                                       it == null -> PlainTextPart(remainder.second) to ""
+                                                                       it.position == 0 -> it.toPart(context) to it.remainder
+                                                                       else -> PlainTextPart(remainder.second.substring(0, it.position)) to (it.link + it.remainder)
+                                                               }
+                                                       }
+                       }.map { it.first }.toList()
+
+       private fun NextLink.toPart(context: SoneTextParserContext?) = when (linkType) {
+               KSK, CHK -> try {
+                       FreenetURI(link).let { freenetUri ->
+                               FreenetLinkPart(
+                                               link,
+                                               if (freenetUri.isKSK) {
+                                                       freenetUri.guessableKey
+                                               } else {
+                                                       freenetUri.metaString ?: freenetUri.docName ?: link.substring(0, 9)
+                                               },
+                                               link.split('?').first()
+                               )
+                       }
+               } catch (e: MalformedURLException) {
+                       PlainTextPart(link)
+               }
+               SSK, USK ->
+                       try {
+                                FreenetLinkPart(link, FreenetURI(link).docName, trusted = context?.routingKey?.contentEquals(FreenetURI(link).routingKey) == true)
+                       } catch (e: MalformedURLException) {
+                               PlainTextPart(link)
+                       }
+               SONE -> link.substring(7).let { SonePart(soneProvider?.getSone(it) ?: IdOnlySone(it)) }
+               POST -> postProvider?.getPost(link.substring(7))?.let { PostPart(it) } ?: PlainTextPart(link)
+               FREEMAIL -> link.indexOf('@').let { atSign ->
+                       link.substring(atSign + 1, link.length - 9).let { freemailId ->
+                               FreemailPart(link.substring(0, atSign), freemailId, freemailId.decodedId)
+                       }
+               }
+               HTTP, HTTPS -> LinkPart(link, link
+                               .withoutProtocol
+                               .withoutWwwPrefix
+                               .withoutUrlParameters
+                               .withoutMiddlePathComponents
+                               .withoutTrailingSlash)
+       }
+
+}
+
+private fun List<String>.mergeMultipleEmptyLines() = fold(emptyList<String>()) { previous, current ->
+       if (previous.isEmpty()) {
+               previous + current
+       } else {
+               if ((previous.last() == "\n") && (current == "")) {
+                       previous
+               } else {
+                       previous + ("\n" + current)
+               }
+       }
+}
+
+private fun List<Part>.mergeAdjacentPlainTextParts() = fold(emptyList<Part>()) { parts, part ->
+       if ((parts.lastOrNull() is PlainTextPart) && (part is PlainTextPart)) {
+               parts.dropLast(1) + PlainTextPart(parts.last().text + part.text)
+       } else {
+               parts + part
+       }
+}
+
+private fun List<Part>.removeEmptyPlainTextParts() = filterNot { it == PlainTextPart("") }
+
+private val String.decodedId: String get() = Base64.encode(Base32.decode(this))
+private val String.withoutProtocol get() = substring(indexOf("//") + 2)
+private val String.withoutUrlParameters get() = split('?').first()
+
+private val String.withoutWwwPrefix
+       get() = split("/")
+                       .replaceFirst { it.split(".").dropWhile { it == "www" }.joinToString(".") }
+                       .joinToString("/")
+
+private fun <T> List<T>.replaceFirst(replacement: (T) -> T) = mapIndexed { index, element ->
+       if (index == 0) replacement(element) else element
+}
+
+private val String.withoutMiddlePathComponents
+       get() = split("/").let {
+               if (it.size > 2) {
+                       "${it.first()}/…/${it.last()}"
+               } else {
+                       it.joinToString("/")
+               }
+       }
+private val String.withoutTrailingSlash get() = if (endsWith("/")) substring(0, length - 1) else this
+private val SoneTextParserContext.routingKey: ByteArray? get() = postingSone?.routingKey
+private val Sone.routingKey: ByteArray get() = Base64.decode(id)
+
+private enum class LinkType(private val scheme: String, private val freenetLink: Boolean) {
+
+       KSK("KSK@", true),
+       CHK("CHK@", true),
+       SSK("SSK@", true),
+       USK("USK@", true),
+       HTTP("http://", false),
+       HTTPS("https://", false),
+       SONE("sone://", false) {
+               override fun validateLinkLength(length: Int) = length.takeIf { it == 50 }
+       },
+       POST("post://", false),
+       FREEMAIL("", true) {
+               override fun findNext(line: String): NextLink? {
+                       val nextFreemailSuffix = line.indexOf(".freemail").takeIf { it >= 54 } ?: return null
+                       if (line[nextFreemailSuffix - 53] != '@') return null
+                       if (!line.substring(nextFreemailSuffix - 52, nextFreemailSuffix).matches(Regex("^[a-z2-7]*\$"))) return null
+                       val firstCharacterIndex = generateSequence(nextFreemailSuffix - 53) {
+                               it.minus(1).takeIf { (it >= 0) && line[it].validLocalPart }
+                       }.lastOrNull() ?: return null
+                       return NextLink(firstCharacterIndex, this, line.substring(firstCharacterIndex, nextFreemailSuffix + 9), line.substring(nextFreemailSuffix + 9))
+               }
+
+               private val Char.validLocalPart get() = (this in ('A'..'Z')) || (this in ('a'..'z')) || (this in ('0'..'9')) || (this == '-') || (this == '_') || (this == '.')
+       };
+
+       open fun findNext(line: String): NextLink? {
+               val nextLinkPosition = line.indexOf(scheme).takeIf { it != -1 } ?: return null
+               val endOfLink = line.substring(nextLinkPosition).findEndOfLink().validate() ?: return null
+               val link = line.substring(nextLinkPosition, nextLinkPosition + endOfLink)
+               val realNextLinkPosition = if (freenetLink && line.substring(0, nextLinkPosition).endsWith("freenet:")) nextLinkPosition - 8 else nextLinkPosition
+               return NextLink(realNextLinkPosition, this, link, line.substring(nextLinkPosition + endOfLink))
+       }
+
+       private fun String.findEndOfLink() =
+                       substring(0, whitespace.find(this)?.range?.start ?: length)
+                                       .dropLastWhile(::isPunctuation)
+                                       .upToFirstUnmatchedParen()
+
+       private fun Int.validate() = validateLinkLength(this)
+       protected open fun validateLinkLength(length: Int) = length.takeIf { it > scheme.length }
+
+       private fun String.upToFirstUnmatchedParen() =
+                       foldIndexed(Pair<Int, Int?>(0, null)) { index, (openParens, firstUnmatchedParen), currentChar ->
+                               when (currentChar) {
+                                       '(' -> (openParens + 1) to firstUnmatchedParen
+                                       ')' -> ((openParens - 1) to (if (openParens == 0) (firstUnmatchedParen ?: index) else firstUnmatchedParen))
+                                       else -> openParens to firstUnmatchedParen
+                               }
+                       }.second ?: length
+
+}
+
+private val punctuationChars = listOf('.', ',', '?', '!')
+private fun isPunctuation(char: Char) = char in punctuationChars
+
+private val whitespace = Regex("[\\u000a\u0020\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u200c\u200d\u202f\u205f\u2060\u2800\u3000]")
+
+private data class NextLink(val position: Int, val linkType: LinkType, val link: String, val remainder: String)
diff --git a/src/main/kotlin/net/pterodactylus/sone/utils/Memoize.kt b/src/main/kotlin/net/pterodactylus/sone/utils/Memoize.kt
new file mode 100644 (file)
index 0000000..bd67ae2
--- /dev/null
@@ -0,0 +1,12 @@
+package net.pterodactylus.sone.utils
+
+class Memoize<in T, out R>(private val calc: (T) -> R) : (T) -> R {
+
+       private val values = mutableMapOf<T, R>()
+
+       override fun invoke(value: T) =
+                       values.getOrPut(value, { calc(value) })
+
+}
+
+fun <T, R> ((T) -> R).memoize(): (T) -> R = Memoize(this)
index b185d2b..35ac0e2 100644 (file)
@@ -1,6 +1,5 @@
 package net.pterodactylus.sone.web.ajax
 
-import net.pterodactylus.sone.utils.also
 import net.pterodactylus.sone.utils.emptyToNull
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -15,9 +14,10 @@ class BookmarkAjaxPage(webInterface: WebInterface) : JsonPage("bookmark.ajax", w
 
        override fun createJsonObject(request: FreenetRequest) =
                        request.parameters["post"].emptyToNull
-                                       ?.let(core::getPost)
-                                       ?.also(core::bookmarkPost)
-                                       ?.let { createSuccessJsonObject() }
+                                       ?.let { postId ->
+                                               core.getPost(postId)?.also(core::bookmarkPost)
+                                               createSuccessJsonObject()
+                                       }
                                        ?: createErrorJsonObject("invalid-post-id")
 
 }
index ec65b55..868e990 100644 (file)
@@ -2,6 +2,7 @@ package net.pterodactylus.sone.web.ajax
 
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.text.TextFilter
+import net.pterodactylus.sone.utils.asOptional
 import net.pterodactylus.sone.utils.emptyToNull
 import net.pterodactylus.sone.utils.headers
 import net.pterodactylus.sone.utils.let
@@ -18,13 +19,13 @@ class CreatePostAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("createP
                        request.parameters["text"].emptyToNull
                                        ?.let { TextFilter.filter(request.headers["Host"], it) }
                                        ?.let { text ->
-                                               val sender = request.parameters["sender"].emptyToNull?.let(core::getSone)?.orNull() ?: currentSone
-                                               val recipient = request.parameters["recipient"].let(core::getSone)
-                                               core.createPost(sender, recipient, text).let { post ->
+                                               val sender = request.parameters["sender"].emptyToNull?.let(core::getSone) ?: currentSone
+                                               val recipient = request.parameters["recipient"]?.let(core::getSone)
+                                               core.createPost(sender, recipient.asOptional(), text).let { post ->
                                                        createSuccessJsonObject().apply {
                                                                put("postId", post.id)
                                                                put("sone", sender.id)
-                                                               put("recipient", recipient.let(Sone::getId))
+                                                               put("recipient", recipient?.id)
                                                        }
                                                }
                                        } ?: createErrorJsonObject("text-required")
index 76d867e..6dcf032 100644 (file)
@@ -14,7 +14,7 @@ class DeletePostAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("deleteP
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["post"]
-                                       .let(core::getPost)
+                                       ?.let(core::getPost)
                                        ?.let { post ->
                                                post.sone.isLocal.ifTrue {
                                                        createSuccessJsonObject().also {
index 2108daf..d01c077 100644 (file)
@@ -14,7 +14,7 @@ class DeleteReplyAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("delete
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["reply"]
-                                       .let(core::getPostReply)
+                                       ?.let(core::getPostReply)
                                        ?.let { reply ->
                                                reply.sone.isLocal.ifTrue {
                                                        createSuccessJsonObject().also {
index e4f8435..4eadfde 100644 (file)
@@ -16,7 +16,7 @@ class DistrustAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("distrustS
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
-                                       .let(core::getSone)
+                                       ?.let(core::getSone)
                                        ?.let { sone ->
                                                createSuccessJsonObject()
                                                                .put("trustValue", core.preferences.negativeTrust)
index c6aa25e..0c37ac0 100644 (file)
@@ -14,7 +14,7 @@ class FollowSoneAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("followS
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
-                                       .let(core::getSone)
+                                       ?.let(core::getSone)
                                        ?.also { core.followSone(currentSone, it.id) }
                                        ?.also(core::markSoneKnown)
                                        ?.let { createSuccessJsonObject() }
index 335f269..77e2c87 100644 (file)
@@ -20,12 +20,12 @@ class GetLikesAjaxPage(webInterface: WebInterface) : JsonPage("getLikes.ajax", w
        override fun createJsonObject(request: FreenetRequest) =
                        when (request.parameters["type"]) {
                                "post" -> request.parameters["post"]
-                                               .let(core::getPost)
+                                               ?.let(core::getPost)
                                                ?.let(core::getLikes)
                                                ?.toReply()
                                                ?: createErrorJsonObject("invalid-post-id")
                                "reply" -> request.parameters["reply"]
-                                               .let(core::getPostReply)
+                                               ?.let(core::getPostReply)
                                                ?.let(core::getLikes)
                                                ?.toReply()
                                                ?: createErrorJsonObject("invalid-reply-id")
index 2d902a6..d91fe90 100644 (file)
@@ -19,8 +19,8 @@ class GetPostAjaxPage(webInterface: WebInterface, private val postTemplate: Temp
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["post"]
-                                       .let(core::getPost)
-                                       .let { post ->
+                                       ?.let(core::getPost)
+                                       ?.let { post ->
                                                createSuccessJsonObject().
                                                                put("post", jsonObject(
                                                                                "id" to post.id,
index fc4b482..b02b85b 100644 (file)
@@ -19,7 +19,7 @@ class GetReplyAjaxPage(webInterface: WebInterface, private val template: Templat
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["reply"]
-                                       .let(core::getPostReply)
+                                       ?.let(core::getPostReply)
                                        ?.let { it.toJson(currentSone, request) }
                                        ?.let { replyJson ->
                                                createSuccessJsonObject().apply {
index acb8cec..7646704 100644 (file)
@@ -36,7 +36,7 @@ class GetStatusAjaxPage(webInterface: WebInterface, private val elementLoader: E
                                        this["loggedIn"] = currentSone != null
                                        this["options"] = currentSone?.options?.toJsonOptions() ?: jsonObject {}
                                        this["notificationHash"] = webInterface.getNotifications(currentSone).sortedBy { it.createdTime }.hashCode()
-                                       this["sones"] = request.httpRequest.getParam("soneIds").split(',').mapPresent(core::getSone).plus(currentSone).filterNotNull().toJsonSones()
+                                       this["sones"] = request.httpRequest.getParam("soneIds").split(',').mapNotNull(core::getSone).plus(currentSone).filterNotNull().toJsonSones()
                                        this["newPosts"] = webInterface.getNewPosts(currentSone).toJsonPosts()
                                        this["newReplies"] = webInterface.getNewReplies(currentSone).toJsonReplies()
                                        this["linkedElements"] = request.httpRequest.getParam("elements", "[]").asJson().map(JsonNode::asText).map(elementLoader::loadElement).toJsonElements()
index fb2592e..cb3b86d 100644 (file)
@@ -3,7 +3,6 @@ package net.pterodactylus.sone.web.ajax
 import net.pterodactylus.sone.freenet.L10nFilter
 import net.pterodactylus.sone.text.TimeTextConverter
 import net.pterodactylus.sone.utils.jsonObject
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
index 4257725..718e6e7 100644 (file)
@@ -14,13 +14,13 @@ class LikeAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("like.ajax", w
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        when (request.parameters["type"]) {
                                "post" -> request.parameters["post"]
-                                               .let(core::getPost)
+                                               ?.let(core::getPost)
                                                ?.let { currentSone.addLikedPostId(it.id) }
                                                ?.also { core.touchConfiguration() }
                                                ?.let { createSuccessJsonObject() }
                                                ?: createErrorJsonObject("invalid-post-id")
                                "reply" -> request.parameters["reply"]
-                                               .let(core::getPostReply)
+                                               ?.let(core::getPostReply)
                                                ?.let { currentSone.addLikedReplyId(it.id) }
                                                ?.also { core.touchConfiguration() }
                                                ?.let { createSuccessJsonObject() }
index 9e4e4a3..0451eaa 100644 (file)
@@ -1,7 +1,5 @@
 package net.pterodactylus.sone.web.ajax
 
-import com.google.common.base.Optional
-import net.pterodactylus.sone.utils.mapPresent
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -21,10 +19,10 @@ class MarkAsKnownAjaxPage(webInterface: WebInterface) : JsonPage("markAsKnown.aj
                else -> createErrorJsonObject("invalid-type")
        }
 
-       private fun <T> processIds(request: FreenetRequest, getter: (String) -> Optional<T>, marker: (T) -> Unit) =
+       private fun <T : Any> processIds(request: FreenetRequest, getter: (String) -> T?, marker: (T) -> Unit) =
                        request.parameters["id"]
                                        ?.split(Regex(" +"))
-                                       ?.mapPresent(getter)
+                                       ?.mapNotNull(getter)
                                        ?.onEach(marker)
                                        .let { createSuccessJsonObject() }
 
index 9a01af8..1dfb058 100644 (file)
@@ -15,7 +15,7 @@ class TrustAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("trustSone.aj
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
-                                       .let(core::getSone)
+                                       ?.let(core::getSone)
                                        ?.let { core.trustSone(currentSone, it) }
                                        ?.let { createSuccessJsonObject().put("trustValue", core.preferences.positiveTrust) }
                                        ?: createErrorJsonObject("invalid-sone-id")
index 6889d04..83b474c 100644 (file)
@@ -12,8 +12,8 @@ class UnfollowSoneAjaxPage(webInterface: WebInterface) : LoggedInJsonPage("unfol
 
        override fun createJsonObject(currentSone: Sone, request: FreenetRequest) =
                        request.parameters["sone"]
-                                       ?.takeIf { core.getSone(it).isPresent }
-                                       ?.also { core.unfollowSone(currentSone, it) }
+                                       ?.let(core::getSone)
+                                       ?.also { core.unfollowSone(currentSone, it.id) }
                                        ?.let { createSuccessJsonObject() }
                                        ?: createErrorJsonObject("invalid-sone-id")
 
index 519d49e..7fb2320 100644 (file)
@@ -16,7 +16,7 @@ class BookmarkPage(template: Template, webInterface: WebInterface)
                if (freenetRequest.isPOST) {
                        val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
                        val postId = freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36)
-                       webInterface.core.getPost(postId).orNull()?.let {
+                       webInterface.core.getPost(postId)?.let {
                                webInterface.core.bookmarkPost(it)
                        }
                        throw RedirectException(returnPage)
index ee3936e..b0eea08 100644 (file)
@@ -1,6 +1,7 @@
 package net.pterodactylus.sone.web.pages
 
 import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.text.TextFilter
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
@@ -12,9 +13,9 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user create a new album.
  */
 class CreateAlbumPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface, true) {
+               LoggedInPage("createAlbum.html", template, "Page.CreateAlbum.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val name = freenetRequest.httpRequest.getPartAsStringFailsafe("name", 64).trim()
                        if (name.isEmpty()) {
@@ -22,7 +23,6 @@ class CreateAlbumPage(template: Template, webInterface: WebInterface):
                                return
                        }
                        val description = freenetRequest.httpRequest.getPartAsStringFailsafe("description", 256).trim()
-                       val currentSone = webInterface.getCurrentSoneCreatingSession(freenetRequest.toadletContext)
                        val parentId = freenetRequest.httpRequest.getPartAsStringFailsafe("parent", 36)
                        val parent = if (parentId == "") currentSone.rootAlbum else webInterface.core.getAlbum(parentId)
                        val album = webInterface.core.createAlbum(currentSone, parent)
index 30a1c31..31896f7 100644 (file)
@@ -1,6 +1,8 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.text.TextFilter
+import net.pterodactylus.sone.utils.asOptional
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -11,9 +13,9 @@ import net.pterodactylus.util.template.TemplateContext
  * This page lets the user create a new [Post].
  */
 class CreatePostPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("createPost.html", template, "Page.CreatePost.Title", webInterface, true) {
+               LoggedInPage("createPost.html", template, "Page.CreatePost.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
                templateContext["returnPage"] = returnPage
                if (freenetRequest.isPOST) {
@@ -22,9 +24,9 @@ class CreatePostPage(template: Template, webInterface: WebInterface):
                                templateContext["errorTextEmpty"] = true
                                return
                        }
-                       val sender = webInterface.core.getLocalSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: getCurrentSone(freenetRequest.toadletContext)
+                       val sender = webInterface.core.getLocalSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: currentSone
                        val recipient = webInterface.core.getSone(freenetRequest.httpRequest.getPartAsStringFailsafe("recipient", 43))
-                       webInterface.core.createPost(sender, recipient, TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), text))
+                       webInterface.core.createPost(sender, recipient.asOptional(), TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), text))
                        throw RedirectException(returnPage)
                }
        }
index a6c58e4..bc6faa4 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.text.TextFilter
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
@@ -11,9 +12,9 @@ import net.pterodactylus.util.template.TemplateContext
  * This page lets the user post a reply to a post.
  */
 class CreateReplyPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("createReply.html", template, "Page.CreateReply.Title", webInterface, true) {
+               LoggedInPage("createReply.html", template, "Page.CreateReply.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                val postId = freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36).apply { templateContext["postId"] = this }
                val text = freenetRequest.httpRequest.getPartAsStringFailsafe("text", 65536).trim().apply { templateContext["text"] = this }
                val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256).apply { templateContext["returnPage"] = this }
@@ -22,8 +23,8 @@ class CreateReplyPage(template: Template, webInterface: WebInterface):
                                templateContext["errorTextEmpty"] = true
                                return
                        }
-                       val post = webInterface.core.getPost(postId).orNull() ?: throw RedirectException("noPermission.html")
-                       val sender = webInterface.core.getLocalSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: getCurrentSone(freenetRequest.toadletContext)
+                       val post = webInterface.core.getPost(postId) ?: throw RedirectException("noPermission.html")
+                       val sender = webInterface.core.getLocalSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sender", 43)) ?: currentSone
                        webInterface.core.createReply(sender, post, TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), text))
                        throw RedirectException(returnPage)
                }
index 98eea86..eff79b9 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -10,9 +11,9 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user delete an {@link Album}.
  */
 class DeleteAlbumPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("deleteAlbum.html", template, "Page.DeleteAlbum.Title", webInterface, true) {
+               LoggedInPage("deleteAlbum.html", template, "Page.DeleteAlbum.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val album = webInterface.core.getAlbum(freenetRequest.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html")
                        if (!album.sone.isLocal) {
index 61db304..fb77707 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -10,9 +11,9 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user delete an {@link Image}.
  */
 class DeleteImagePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("deleteImage.html", template, "Page.DeleteImage.Title", webInterface, true) {
+               LoggedInPage("deleteImage.html", template, "Page.DeleteImage.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val image = webInterface.core.getImage(freenetRequest.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html")
                        if (!image.sone.isLocal) {
index 21465d7..8eccfb4 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -10,11 +11,11 @@ import net.pterodactylus.util.template.TemplateContext
  * Lets the user delete a post they made.
  */
 class DeletePostPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("deletePost.html", template, "Page.DeletePost.Title", webInterface, true) {
+               LoggedInPage("deletePost.html", template, "Page.DeletePost.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
-                       val post = webInterface.core.getPost(freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36)).orNull() ?: throw RedirectException("noPermission.html")
+                       val post = webInterface.core.getPost(freenetRequest.httpRequest.getPartAsStringFailsafe("post", 36)) ?: throw RedirectException("noPermission.html")
                        val returnPage = freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256)
                        if (!post.sone.isLocal) {
                                throw RedirectException("noPermission.html")
@@ -29,7 +30,7 @@ class DeletePostPage(template: Template, webInterface: WebInterface):
                        templateContext["returnPage"] = returnPage
                        return
                }
-               templateContext["post"] = webInterface.core.getPost(freenetRequest.httpRequest.getParam("post")).orNull() ?: throw RedirectException("noPermission.html")
+               templateContext["post"] = webInterface.core.getPost(freenetRequest.httpRequest.getParam("post")) ?: throw RedirectException("noPermission.html")
                templateContext["returnPage"] = freenetRequest.httpRequest.getParam("returnPage")
        }
 
index cf5ac6e..f25299f 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -10,10 +11,9 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user confirm the deletion of a profile field.
  */
 class DeleteProfileFieldPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("deleteProfileField.html", template, "Page.DeleteProfileField.Title", webInterface, true) {
+               LoggedInPage("deleteProfileField.html", template, "Page.DeleteProfileField.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               val currentSone = getCurrentSone(freenetRequest.toadletContext)!!
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val field = currentSone.profile.getFieldById(freenetRequest.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html")
                        if (freenetRequest.httpRequest.getPartAsStringFailsafe("confirm", 4) == "true") {
index c67d620..29d8c81 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -10,12 +11,12 @@ import net.pterodactylus.util.template.TemplateContext
  * This page lets the user delete a reply.
  */
 class DeleteReplyPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("deleteReply.html", template, "Page.DeleteReply.Title", webInterface, true) {
+               LoggedInPage("deleteReply.html", template, "Page.DeleteReply.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val replyId = freenetRequest.httpRequest.getPartAsStringFailsafe("reply", 36)
-                       val reply = webInterface.core.getPostReply(replyId).orNull() ?: throw RedirectException("noPermission.html")
+                       val reply = webInterface.core.getPostReply(replyId) ?: throw RedirectException("noPermission.html")
                        if (!reply.sone.isLocal) {
                                throw RedirectException("noPermission.html")
                        }
index 0cce5ba..b2a6d7b 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -12,12 +13,12 @@ import net.pterodactylus.util.template.TemplateContext
  * installation.
  */
 class DeleteSonePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("deleteSone.html", template, "Page.DeleteSone.Title", webInterface, true) {
+               LoggedInPage("deleteSone.html", template, "Page.DeleteSone.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        if (freenetRequest.httpRequest.isPartSet("deleteSone")) {
-                               webInterface.core.deleteSone(getCurrentSone(freenetRequest.toadletContext))
+                               webInterface.core.deleteSone(currentSone)
                        }
                        throw RedirectException("index.html")
                }
index ce5aaec..70755f5 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -13,12 +14,12 @@ import net.pterodactylus.util.template.TemplateContext
  * @see net.pterodactylus.sone.core.Core#distrustSone(Sone, Sone)
  */
 class DistrustPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("distrust.html", template, "Page.Distrust.Title", webInterface, true) {
+               LoggedInPage("distrust.html", template, "Page.Distrust.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
-                       val sone = webInterface.core.getSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sone", 44)).orNull()
-                       sone?.run { webInterface.core.distrustSone(getCurrentSone(freenetRequest.toadletContext), this) }
+                       webInterface.core.getSone(freenetRequest.httpRequest.getPartAsStringFailsafe("sone", 44))
+                                       ?.run { webInterface.core.distrustSone(currentSone, this) }
                        throw RedirectException(freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256))
                }
        }
index bd612c3..26d7c3a 100644 (file)
@@ -1,6 +1,7 @@
 package net.pterodactylus.sone.web.pages
 
 import net.pterodactylus.sone.data.Album.Modifier.AlbumTitleMustNotBeEmpty
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -11,9 +12,9 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user edit the name and description of an album.
  */
 class EditAlbumPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("editAlbum.html", template, "Page.EditAlbum.Title", webInterface, true) {
+               LoggedInPage("editAlbum.html", template, "Page.EditAlbum.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val album = webInterface.core.getAlbum(freenetRequest.httpRequest.getPartAsStringFailsafe("album", 36)) ?: throw RedirectException("invalid.html")
                        album.takeUnless { it.sone.isLocal }?.run { throw RedirectException("noPermission.html") }
index b674cb9..350dec7 100644 (file)
@@ -1,6 +1,7 @@
 package net.pterodactylus.sone.web.pages
 
 import net.pterodactylus.sone.data.Image.Modifier.ImageTitleMustNotBeEmpty
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.text.TextFilter
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
@@ -12,9 +13,9 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user edit title and description of an {@link Image}.
  */
 class EditImagePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("editImage.html", template, "Page.EditImage.Title", webInterface, true) {
+               LoggedInPage("editImage.html", template, "Page.EditImage.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val image = webInterface.core.getImage(freenetRequest.httpRequest.getPartAsStringFailsafe("image", 36)) ?: throw RedirectException("invalid.html")
                        if (!image.sone.isLocal) {
index 5e3d3c6..f3486d2 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -9,32 +10,30 @@ import net.pterodactylus.util.template.TemplateContext
 /**
  * Page that lets the user edit the name of a profile field.
  */
-class EditProfileFieldPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("editProfileField.html", template, "Page.EditProfileField.Title", webInterface, true) {
+class EditProfileFieldPage(template: Template, webInterface: WebInterface) :
+               LoggedInPage("editProfileField.html", template, "Page.EditProfileField.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               sessionProvider.getCurrentSone(freenetRequest.toadletContext)!!.let { currentSone ->
-                       currentSone.profile.let { profile ->
-                               if (freenetRequest.isPOST) {
-                                       if (freenetRequest.httpRequest.getPartAsStringFailsafe("cancel", 4) == "true") {
-                                               throw RedirectException("editProfile.html#profile-fields")
-                                       }
-                                       val field = profile.getFieldById(freenetRequest.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html")
-                                       freenetRequest.httpRequest.getPartAsStringFailsafe("name", 256).let { name ->
-                                               try {
-                                                       if (name != field.name) {
-                                                               field.name = name
-                                                               currentSone.profile = profile
-                                                       }
-                                                       throw RedirectException("editProfile.html#profile-fields")
-                                               } catch (e: IllegalArgumentException) {
-                                                       templateContext["duplicateFieldName"] = true
-                                                       return
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
+               currentSone.profile.let { profile ->
+                       if (freenetRequest.isPOST) {
+                               if (freenetRequest.httpRequest.getPartAsStringFailsafe("cancel", 4) == "true") {
+                                       throw RedirectException("editProfile.html#profile-fields")
+                               }
+                               val field = profile.getFieldById(freenetRequest.httpRequest.getPartAsStringFailsafe("field", 36)) ?: throw RedirectException("invalid.html")
+                               freenetRequest.httpRequest.getPartAsStringFailsafe("name", 256).let { name ->
+                                       try {
+                                               if (name != field.name) {
+                                                       field.name = name
+                                                       currentSone.profile = profile
                                                }
+                                               throw RedirectException("editProfile.html#profile-fields")
+                                       } catch (e: IllegalArgumentException) {
+                                               templateContext["duplicateFieldName"] = true
+                                               return
                                        }
                                }
-                               templateContext["field"] = profile.getFieldById(freenetRequest.httpRequest.getParam("field")) ?: throw RedirectException("invalid.html")
                        }
+                       templateContext["field"] = profile.getFieldById(freenetRequest.httpRequest.getParam("field")) ?: throw RedirectException("invalid.html")
                }
        }
 
index 5ed0c2f..95c40c8 100644 (file)
@@ -1,6 +1,7 @@
 package net.pterodactylus.sone.web.pages
 
 import net.pterodactylus.sone.data.Profile.DuplicateField
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.text.TextFilter
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
@@ -11,11 +12,11 @@ import net.pterodactylus.util.template.TemplateContext
 /**
  * This page lets the user edit her profile.
  */
-class EditProfilePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("editProfile.html", template, "Page.EditProfile.Title", webInterface, true) {
+class EditProfilePage(template: Template, webInterface: WebInterface) :
+               LoggedInPage("editProfile.html", template, "Page.EditProfile.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               freenetRequest.currentSone!!.profile.let { profile ->
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
+               currentSone.profile.let { profile ->
                        templateContext["firstName"] = profile.firstName
                        templateContext["middleName"] = profile.middleName
                        templateContext["lastName"] = profile.lastName
@@ -36,13 +37,14 @@ class EditProfilePage(template: Template, webInterface: WebInterface):
                                        profile.fields.forEach { field ->
                                                field.value = TextFilter.filter(freenetRequest.httpRequest.getHeader("Host"), freenetRequest.httpRequest.getPartAsStringFailsafe("field-${field.id}", 400).trim())
                                        }
+                                       currentSone.profile = profile
                                        webInterface.core.touchConfiguration()
                                        throw RedirectException("editProfile.html")
                                } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("add-field", 4) == "true") {
                                        val fieldName = freenetRequest.httpRequest.getPartAsStringFailsafe("field-name", 100)
                                        try {
                                                profile.addField(fieldName)
-                                               freenetRequest.currentSone!!.profile = profile
+                                               currentSone.profile = profile
                                                webInterface.core.touchConfiguration()
                                                throw RedirectException("editProfile.html#profile-fields")
                                        } catch (e: DuplicateField) {
@@ -56,11 +58,11 @@ class EditProfilePage(template: Template, webInterface: WebInterface):
                                                throw RedirectException("editProfileField.html?field=${field.id}")
                                        } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("move-down-field-${field.id}", 4) == "true") {
                                                profile.moveFieldDown(field)
-                                               freenetRequest.currentSone!!.profile = profile
+                                               currentSone.profile = profile
                                                throw RedirectException("editProfile.html#profile-fields")
                                        } else if (freenetRequest.httpRequest.getPartAsStringFailsafe("move-up-field-${field.id}", 4) == "true") {
                                                profile.moveFieldUp(field)
-                                               freenetRequest.currentSone!!.profile = profile
+                                               currentSone.profile = profile
                                                throw RedirectException("editProfile.html#profile-fields")
                                        }
                                }
@@ -68,6 +70,4 @@ class EditProfilePage(template: Template, webInterface: WebInterface):
                }
        }
 
-       private val FreenetRequest.currentSone get() = sessionProvider.getCurrentSone(toadletContext)
-
 }
index 781e07f..cf5bcee 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -10,22 +11,19 @@ import net.pterodactylus.util.template.TemplateContext
  * This page lets the user follow another Sone.
  */
 class FollowSonePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("followSone.html", template, "Page.FollowSone.Title", webInterface, true) {
+               LoggedInPage("followSone.html", template, "Page.FollowSone.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        freenetRequest.httpRequest.getPartAsStringFailsafe("sone", 1200).split(Regex("[ ,]+"))
                                        .map { it to webInterface.core.getSone(it) }
-                                       .filter { it.second.isPresent }
-                                       .map { it.first to it.second.get() }
+                                       .filterNot { it.second == null }
                                        .forEach { sone ->
-                                               webInterface.core.followSone(freenetRequest.currentSone, sone.first)
+                                               webInterface.core.followSone(currentSone, sone.first)
                                                webInterface.core.markSoneKnown(sone.second)
                                        }
                        throw RedirectException(freenetRequest.httpRequest.getPartAsStringFailsafe("returnPage", 256))
                }
        }
 
-       private val FreenetRequest.currentSone get() = sessionProvider.getCurrentSone(toadletContext)
-
 }
index 3181355..e9911e6 100644 (file)
@@ -14,9 +14,9 @@ import java.net.URI
  * The image browser page is the entry page for the image management.
  */
 class ImageBrowserPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface, true) {
+               LoggedInPage("imageBrowser.html", template, "Page.ImageBrowser.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if ("album" in freenetRequest.parameters) {
                        templateContext["albumRequested"] = true
                        templateContext["album"] = webInterface.core.getAlbum(freenetRequest.parameters["album"]!!)
@@ -40,7 +40,7 @@ class ImageBrowserPage(template: Template, webInterface: WebInterface):
                                        }
                } else {
                        templateContext["soneRequested"] = true
-                       templateContext["sone"] = webInterface.core.getSone(freenetRequest.httpRequest.getParam("sone")).orNull() ?: getCurrentSone(freenetRequest.toadletContext)
+                       templateContext["sone"] = webInterface.core.getSone(freenetRequest.httpRequest.getParam("sone")) ?: currentSone
                }
        }
 
index bf6e0c4..0257cb1 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.notify.PostVisibilityFilter
 import net.pterodactylus.sone.utils.Pagination
 import net.pterodactylus.sone.utils.parameters
@@ -13,15 +14,12 @@ import net.pterodactylus.util.template.TemplateContext
  * of all friends of the current user.
  */
 class IndexPage(template: Template, webInterface: WebInterface, private val postVisibilityFilter: PostVisibilityFilter):
-               SoneTemplatePage("index.html", template, "Page.Index.Title", webInterface, true) {
+               LoggedInPage("index.html", template, "Page.Index.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               getCurrentSone(freenetRequest.toadletContext)!!.let { currentSone ->
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                        (currentSone.posts +
                                        currentSone.friends
-                                                       .map { webInterface.core.getSone(it) }
-                                                       .filter { it.isPresent }
-                                                       .map { it.get() }
+                                                       .mapNotNull(webInterface.core::getSone)
                                                        .flatMap { it.posts } +
                                        webInterface.core.getDirectedPosts(currentSone.id)
                                        ).distinct()
@@ -35,7 +33,6 @@ class IndexPage(template: Template, webInterface: WebInterface, private val post
                                                        templateContext["posts"] = pagination.items
                                                }
                                        }
-               }
        }
 
 }
index 43753a3..ef622df 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -10,20 +11,18 @@ import net.pterodactylus.util.template.TemplateContext
 /**
  * Page that lets the user like [net.pterodactylus.sone.data.Post]s and [net.pterodactylus.sone.data.Reply]s.
  */
-class LikePage(template: Template, webInterface: WebInterface)
-       : SoneTemplatePage("like.html", template, "Page.Like.Title", webInterface, true) {
+class LikePage(template: Template, webInterface: WebInterface) :
+               LoggedInPage("like.html", template, "Page.Like.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
-                       getCurrentSone(freenetRequest.toadletContext)!!.let { currentSone ->
-                               freenetRequest.parameters["type", 16]?.also { type ->
-                                       when(type) {
-                                               "post" -> currentSone.addLikedPostId(freenetRequest.parameters["post", 36]!!)
-                                               "reply" -> currentSone.addLikedReplyId(freenetRequest.parameters["reply", 36]!!)
-                                       }
+                       freenetRequest.parameters["type", 16]?.also { type ->
+                               when (type) {
+                                       "post" -> currentSone.addLikedPostId(freenetRequest.parameters["post", 36]!!)
+                                       "reply" -> currentSone.addLikedReplyId(freenetRequest.parameters["reply", 36]!!)
                                }
-                               throw RedirectException(freenetRequest.parameters["returnPage", 256]!!)
                        }
+                       throw RedirectException(freenetRequest.parameters["returnPage", 256]!!)
                }
        }
 
diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/LoggedInPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/LoggedInPage.kt
new file mode 100644 (file)
index 0000000..c88499e
--- /dev/null
@@ -0,0 +1,21 @@
+package net.pterodactylus.sone.web.pages
+
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.web.WebInterface
+import net.pterodactylus.sone.web.page.FreenetRequest
+import net.pterodactylus.util.template.Template
+import net.pterodactylus.util.template.TemplateContext
+
+/**
+ * Base class for [SoneTemplatePage] implementations that require a logged in user.
+ */
+abstract class LoggedInPage(path: String, template: Template, pageTitleKey: String, webInterface: WebInterface) :
+               SoneTemplatePage(path, template, pageTitleKey, webInterface, true) {
+
+       final override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+               handleRequest(freenetRequest, getCurrentSone(freenetRequest.toadletContext, false)!!, templateContext)
+       }
+
+       protected abstract fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext)
+
+}
index 3a2a023..7e608ff 100644 (file)
@@ -1,6 +1,7 @@
 package net.pterodactylus.sone.web.pages
 
 import freenet.clients.http.ToadletContext
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
 import net.pterodactylus.util.template.Template
@@ -10,9 +11,9 @@ import net.pterodactylus.util.template.TemplateContext
  * Logs a user out.
  */
 class LogoutPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("logout.html", template, "Page.Logout.Title", webInterface, true) {
+               LoggedInPage("logout.html", template, "Page.Logout.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                setCurrentSone(freenetRequest.toadletContext, null)
                throw RedirectException("index.html")
        }
index 570e570..49c1075 100644 (file)
@@ -18,9 +18,9 @@ class MarkAsKnownPage(template: Template, webInterface: WebInterface):
        override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
                val ids = freenetRequest.parameters["id", 65536]!!.split(" ")
                when (freenetRequest.parameters["type", 5]) {
-                       "sone" -> ids.mapPresent(webInterface.core::getSone).forEach(webInterface.core::markSoneKnown)
-                       "post" -> ids.mapPresent(webInterface.core::getPost).forEach(webInterface.core::markPostKnown)
-                       "reply" -> ids.mapPresent(webInterface.core::getPostReply).forEach(webInterface.core::markReplyKnown)
+                       "sone" -> ids.mapNotNull(webInterface.core::getSone).forEach(webInterface.core::markSoneKnown)
+                       "post" -> ids.mapNotNull(webInterface.core::getPost).forEach(webInterface.core::markPostKnown)
+                       "reply" -> ids.mapNotNull(webInterface.core::getPostReply).forEach(webInterface.core::markReplyKnown)
                        else -> throw RedirectException("invalid.html")
                }
                throw RedirectException(freenetRequest.parameters["returnPage", 256]!!)
index dbcf59f..c3fabdb 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -11,10 +12,10 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user control the rescue mode for a Sone.
  */
 class RescuePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("rescue.html", template, "Page.Rescue.Title", webInterface, true) {
+               LoggedInPage("rescue.html", template, "Page.Rescue.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               val soneRescuer = webInterface.core.getSoneRescuer(getCurrentSone(freenetRequest.toadletContext)!!)
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
+               val soneRescuer = webInterface.core.getSoneRescuer(currentSone)
                templateContext["soneRescuer"] = soneRescuer
                if (freenetRequest.isPOST) {
                        freenetRequest.parameters["edition", 9]?.toIntOrNull()?.also {
index aff6aaf..f296e03 100644 (file)
@@ -3,11 +3,13 @@ package net.pterodactylus.sone.web.pages
 import com.google.common.base.Ticker
 import com.google.common.cache.Cache
 import com.google.common.cache.CacheBuilder
+import freenet.support.Logger
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.Pagination
 import net.pterodactylus.sone.utils.emptyToNull
+import net.pterodactylus.sone.utils.memoize
 import net.pterodactylus.sone.utils.paginate
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -31,6 +33,7 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
        private val cache: Cache<Iterable<Phrase>, Pagination<Post>> = CacheBuilder.newBuilder().ticker(ticker).expireAfterAccess(5, MINUTES).build()
 
        override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+               val startTime = System.currentTimeMillis()
                val phrases = try {
                        freenetRequest.parameters["query"].emptyToNull?.parse()
                } catch (te: TextException) {
@@ -42,25 +45,27 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
                        0 -> redirect("index.html")
                        1 -> phrases.first().phrase.also { word ->
                                when {
-                                       word.removePrefix("sone://").let(webInterface.core::getSone).isPresent -> redirect("viewSone.html?sone=${word.removePrefix("sone://")}")
-                                       word.removePrefix("post://").let(webInterface.core::getPost).isPresent -> redirect("viewPost.html?post=${word.removePrefix("post://")}")
-                                       word.removePrefix("reply://").let(webInterface.core::getPostReply).isPresent -> redirect("viewPost.html?post=${word.removePrefix("reply://").let(webInterface.core::getPostReply).get().postId}")
+                                       word.removePrefix("sone://").let(webInterface.core::getSone) != null -> redirect("viewSone.html?sone=${word.removePrefix("sone://")}")
+                                       word.removePrefix("post://").let(webInterface.core::getPost) != null -> redirect("viewPost.html?post=${word.removePrefix("post://")}")
+                                       word.removePrefix("reply://").let(webInterface.core::getPostReply) != null -> redirect("viewPost.html?post=${word.removePrefix("reply://").let(webInterface.core::getPostReply)?.postId}")
                                        word.removePrefix("album://").let(webInterface.core::getAlbum) != null -> redirect("imageBrowser.html?album=${word.removePrefix("album://")}")
                                        word.removePrefix("image://").let { webInterface.core.getImage(it, false) } != null -> redirect("imageBrowser.html?image=${word.removePrefix("image://")}")
                                }
                        }
                }
 
+               val soneNameCache = { sone: Sone -> sone.names() }.memoize()
                val sonePagination = webInterface.core.sones
-                               .scoreAndPaginate(phrases) { it.allText() }
+                               .scoreAndPaginate(phrases) { it.allText(soneNameCache) }
                                .apply { page = freenetRequest.parameters["sonePage"].emptyToNull?.toIntOrNull() ?: 0 }
                val postPagination = cache.get(phrases) {
                        webInterface.core.sones
                                        .flatMap(Sone::getPosts)
                                        .filter { Post.FUTURE_POSTS_FILTER.apply(it) }
-                                       .scoreAndPaginate(phrases) { it.allText() }
+                                       .scoreAndPaginate(phrases) { it.allText(soneNameCache) }
                }.apply { page = freenetRequest.parameters["postPage"].emptyToNull?.toIntOrNull() ?: 0 }
 
+               Logger.normal(SearchPage::class.java, "Finished search for “${freenetRequest.parameters["query"]}” in ${System.currentTimeMillis() - startTime}ms.")
                templateContext["sonePagination"] = sonePagination
                templateContext["soneHits"] = sonePagination.items
                templateContext["postPagination"] = postPagination
@@ -75,17 +80,19 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
                                        .paginate(webInterface.core.preferences.postsPerPage)
 
        private fun Sone.names() =
-                       listOf(name, profile.firstName, profile.middleName, profile.lastName)
-                                       .filterNotNull()
-                                       .joinToString("")
+                       with(profile) {
+                               listOf(name, firstName, middleName, lastName)
+                                               .filterNotNull()
+                                               .joinToString("")
+                       }
 
-       private fun Sone.allText() =
-                       (names() + profile.fields.map { "${it.name} ${it.value}" }.joinToString(" ", " ")).toLowerCase()
+       private fun Sone.allText(soneNameCache: (Sone) -> String) =
+                       (soneNameCache(this) + profile.fields.map { "${it.name} ${it.value}" }.joinToString(" ", " ")).toLowerCase()
 
-       private fun Post.allText() =
-                       (text + recipient.orNull()?.let { " ${it.names()}" } + webInterface.core.getReplies(id)
+       private fun Post.allText(soneNameCache: (Sone) -> String) =
+                       (text + recipient.orNull()?.let { " ${soneNameCache(it)}" } + webInterface.core.getReplies(id)
                                        .filter { PostReply.FUTURE_REPLY_FILTER.apply(it) }
-                                       .map { "${it.sone.names()} ${it.text}" }.joinToString(" ", " ")).toLowerCase()
+                                       .map { "${soneNameCache(it.sone)} ${it.text}" }.joinToString(" ", " ")).toLowerCase()
 
        private fun score(text: String, phrases: Iterable<Phrase>): Double {
                val requiredPhrases = phrases.count { it.required }
@@ -106,15 +113,12 @@ class SearchPage @JvmOverloads constructor(template: Template, webInterface: Web
                return requiredHits * 3 + optionalHits + (requiredHits - requiredPhrases) * 5 - (forbiddenHits * 2)
        }
 
-       private fun String.findAll(needle: String): List<Int> {
-               var nextIndex = indexOf(needle)
-               val positions = mutableListOf<Int>()
-               while (nextIndex != -1) {
-                       positions += nextIndex
-                       nextIndex = indexOf(needle, nextIndex + 1)
-               }
-               return positions
-       }
+       private fun String.findAll(needle: String) =
+                       generateSequence(indexOf(needle).takeIf { it > -1 }) { lastPosition ->
+                               lastPosition
+                                               .let { indexOf(needle, it + 1) }
+                                               .takeIf { it > -1 }
+                       }.toList()
 
        private fun String.parse() =
                        StringEscaper.parseLine(this)
index bdc8952..758ae07 100644 (file)
@@ -1,7 +1,7 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
 import net.pterodactylus.sone.web.page.FreenetRequest
@@ -12,15 +12,13 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user trust another Sone. This will assign a configurable
  * amount of trust to an identity.
  */
-class TrustPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("trust.html", template, "Page.Trust.Title", webInterface, true) {
+class TrustPage(template: Template, webInterface: WebInterface) :
+               LoggedInPage("trust.html", template, "Page.Trust.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
-                       getCurrentSone(freenetRequest.toadletContext)?.also { currentSone ->
-                               webInterface.core.getSone(freenetRequest.parameters["sone"]).let { sone ->
-                                       webInterface.core.trustSone(currentSone, sone)
-                               }
+                       webInterface.core.getSone(freenetRequest.parameters["sone"]!!)?.let { sone ->
+                               webInterface.core.trustSone(currentSone, sone)
                        }
                        throw RedirectException(freenetRequest.parameters["returnPage", 256])
                }
index 01a0fde..c742542 100644 (file)
@@ -26,8 +26,8 @@ class UnbookmarkPage(template: Template, webInterface: WebInterface):
                        }
                        freenetRequest.isPOST -> {
                                freenetRequest.parameters["post", 36]
-                                               .let(webInterface.core::getPost)
-                                               .also(webInterface.core::unbookmarkPost)
+                                               ?.let(webInterface.core::getPost)
+                                               ?.also(webInterface.core::unbookmarkPost)
                                throw RedirectException(freenetRequest.parameters["returnPage", 256])
                        }
                }
index cc411e2..2521ef0 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -10,15 +11,13 @@ import net.pterodactylus.util.template.TemplateContext
 /**
  * This page lets the user unfollow another Sone.
  */
-class UnfollowSonePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("unfollowSone.html", template, "Page.UnfollowSone.Title", webInterface, true) {
+class UnfollowSonePage(template: Template, webInterface: WebInterface) :
+               LoggedInPage("unfollowSone.html", template, "Page.UnfollowSone.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
-                       getCurrentSone(freenetRequest.toadletContext)!!.also { currentSone ->
-                               freenetRequest.parameters["sone"]!!.split(Regex("[ ,]+"))
-                                               .forEach { webInterface.core.unfollowSone(currentSone, it) }
-                       }
+                       freenetRequest.parameters["sone"]!!.split(Regex("[ ,]+"))
+                                       .forEach { webInterface.core.unfollowSone(currentSone, it) }
                        throw RedirectException(freenetRequest.parameters["returnPage", 256])
                }
        }
index 42e552e..6bfabe2 100644 (file)
@@ -1,5 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -11,13 +12,13 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user unlike a [net.pterodactylus.sone.data.Post] or [net.pterodactylus.sone.data.Reply].
  */
 class UnlikePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("unlike.html", template, "Page.Unlike.Title", webInterface, true) {
+               LoggedInPage("unlike.html", template, "Page.Unlike.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        when (freenetRequest.parameters["type"]) {
-                               "post" -> getCurrentSone(freenetRequest.toadletContext)!!.removeLikedPostId(freenetRequest.parameters["post"]!!)
-                               "reply" -> getCurrentSone(freenetRequest.toadletContext)!!.removeLikedReplyId(freenetRequest.parameters["reply"]!!)
+                               "post" -> currentSone.removeLikedPostId(freenetRequest.parameters["post"]!!)
+                               "reply" -> currentSone.removeLikedReplyId(freenetRequest.parameters["reply"]!!)
                        }
                        throw RedirectException(freenetRequest.parameters["returnPage", 256])
                }
index a46b272..f522c34 100644 (file)
@@ -1,6 +1,6 @@
 package net.pterodactylus.sone.web.pages
 
-import net.pterodactylus.sone.utils.also
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.utils.isPOST
 import net.pterodactylus.sone.utils.parameters
 import net.pterodactylus.sone.web.WebInterface
@@ -12,16 +12,14 @@ import net.pterodactylus.util.template.TemplateContext
  * Page that lets the user untrust another Sone. This will remove all trust
  * assignments for an identity.
  */
-class UntrustPage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("untrust.html", template, "Page.Untrust.Title", webInterface, true) {
+class UntrustPage(template: Template, webInterface: WebInterface) :
+               LoggedInPage("untrust.html", template, "Page.Untrust.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
-                       getCurrentSone(freenetRequest.toadletContext)!!.also { currentSone ->
-                               freenetRequest.parameters["sone", 44]
-                                               .let(webInterface.core::getSone)
-                                               .also { webInterface.core.untrustSone(currentSone, it) }
-                       }
+                       freenetRequest.parameters["sone", 44]!!
+                                       .let(webInterface.core::getSone)
+                                       ?.also { webInterface.core.untrustSone(currentSone, it) }
                        throw RedirectException(freenetRequest.parameters["returnPage", 256])
                }
        }
index 782c90d..4aa35ac 100644 (file)
@@ -1,6 +1,7 @@
 package net.pterodactylus.sone.web.pages
 
 import freenet.support.api.Bucket
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.text.TextFilter
 import net.pterodactylus.sone.utils.emptyToNull
 import net.pterodactylus.sone.utils.headers
@@ -20,12 +21,12 @@ import javax.imageio.ImageIO
  * Page implementation that lets the user upload an image.
  */
 class UploadImagePage(template: Template, webInterface: WebInterface):
-               SoneTemplatePage("uploadImage.html", template, "Page.UploadImage.Title", webInterface, true) {
+               LoggedInPage("uploadImage.html", template, "Page.UploadImage.Title", webInterface) {
 
-       override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
+       override fun handleRequest(freenetRequest: FreenetRequest, currentSone: Sone, templateContext: TemplateContext) {
                if (freenetRequest.isPOST) {
                        val parentAlbum = freenetRequest.parameters["parent"]!!.let(webInterface.core::getAlbum) ?: throw RedirectException("noPermission.html")
-                       if (parentAlbum.sone != getCurrentSone(freenetRequest.toadletContext)) {
+                       if (parentAlbum.sone != currentSone) {
                                throw RedirectException("noPermission.html")
                        }
                        val title = freenetRequest.parameters["title", 200].emptyToNull ?: throw RedirectException("emptyImageTitle.html")
@@ -39,7 +40,7 @@ class UploadImagePage(template: Template, webInterface: WebInterface):
                        }
 
                        val temporaryImage = webInterface.core.createTemporaryImage(bytes.mimeType, bytes)
-                       webInterface.core.createImage(getCurrentSone(freenetRequest.toadletContext), parentAlbum, temporaryImage).modify().apply {
+                       webInterface.core.createImage(currentSone, parentAlbum, temporaryImage).modify().apply {
                                setWidth(bufferedImage.width)
                                setHeight(bufferedImage.height)
                                setTitle(title)
index 457e13f..910c939 100644 (file)
@@ -16,14 +16,14 @@ class ViewPostPage(template: Template, webInterface: WebInterface):
                SoneTemplatePage("viewPost.html", template, "Page.ViewPost.Title", webInterface, false) {
 
        override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
-               templateContext["post"] = freenetRequest.parameters["post"].let(webInterface.core::getPost).orNull()
+               templateContext["post"] = freenetRequest.parameters["post"]?.let(webInterface.core::getPost)
                templateContext["raw"] = freenetRequest.parameters["raw"] == "true"
        }
 
        override fun isLinkExcepted(link: URI?) = true
 
        public override fun getPageTitle(freenetRequest: FreenetRequest) =
-                       (freenetRequest.parameters["post"].let(webInterface.core::getPost).let {
+                       (freenetRequest.parameters["post"]?.let(webInterface.core::getPost)?.let {
                                if (it.text.length > 20) {
                                        it.text.substring(0..19) + "…"
                                } else {
index 90bc147..2e175af 100644 (file)
@@ -3,7 +3,6 @@ package net.pterodactylus.sone.web.pages
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.template.SoneAccessor
-import net.pterodactylus.sone.utils.let
 import net.pterodactylus.sone.utils.mapPresent
 import net.pterodactylus.sone.utils.paginate
 import net.pterodactylus.sone.utils.parameters
@@ -21,7 +20,7 @@ class ViewSonePage(template: Template, webInterface: WebInterface):
 
        override fun handleRequest(freenetRequest: FreenetRequest, templateContext: TemplateContext) {
                templateContext["soneId"] = freenetRequest.parameters["sone"]
-               freenetRequest.parameters["sone"].let(webInterface.core::getSone).let { sone ->
+               freenetRequest.parameters["sone"]!!.let(webInterface.core::getSone)?.let { sone ->
                        templateContext["sone"] = sone
                        val sonePosts = sone.posts
                        val directedPosts = webInterface.core.getDirectedPosts(sone.id)
@@ -51,7 +50,7 @@ class ViewSonePage(template: Template, webInterface: WebInterface):
        override fun isLinkExcepted(link: URI?) = true
 
        public override fun getPageTitle(freenetRequest: FreenetRequest): String =
-                       freenetRequest.parameters["sone"].let(webInterface.core::getSone).let { sone ->
+                       freenetRequest.parameters["sone"]!!.let(webInterface.core::getSone)?.let { sone ->
                                "${SoneAccessor.getNiceName(sone)} - ${webInterface.l10n.getString("Page.ViewSone.Title")}"
                        } ?: webInterface.l10n.getString("Page.ViewSone.Page.TitleWithoutSone")
 
index cea8ed7..dbe70d5 100644 (file)
@@ -22,7 +22,6 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent;
 import net.pterodactylus.util.config.Configuration;
 
-import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.eventbus.EventBus;
@@ -74,7 +73,7 @@ public class CoreTest {
                Identity identity = mock(Identity.class);
                when(identity.getId()).thenReturn("sone-id");
                Sone sone = mock(Sone.class);
-               when(database.getSone("sone-id")).thenReturn(Optional.of(sone));
+               when(database.getSone("sone-id")).thenReturn(sone);
                PostReply postReply1 = mock(PostReply.class);
                PostReply postReply2 = mock(PostReply.class);
                when(sone.getReplies()).thenReturn(ImmutableSet.of(postReply1, postReply2));
index 51947d1..5b94f08 100644 (file)
@@ -1,6 +1,5 @@
 package net.pterodactylus.sone.core;
 
-import static com.google.common.base.Optional.of;
 import static com.google.common.io.ByteStreams.toByteArray;
 import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
 import static java.lang.System.currentTimeMillis;
@@ -62,7 +61,7 @@ public class SoneInserterTest {
        public void setupCore() {
                UpdateChecker updateChecker = mock(UpdateChecker.class);
                when(core.getUpdateChecker()).thenReturn(updateChecker);
-               when(core.getSone(anyString())).thenReturn(Optional.<Sone>absent());
+               when(core.getSone(anyString())).thenReturn(null);
        }
 
        @Test
@@ -78,7 +77,7 @@ public class SoneInserterTest {
                when(sone.getInsertUri()).thenReturn(insertUri);
                when(sone.getFingerprint()).thenReturn(fingerprint);
                when(sone.getRootAlbum()).thenReturn(mock(Album.class));
-               when(core.getSone(anyString())).thenReturn(of(sone));
+               when(core.getSone(anyString())).thenReturn(sone);
                return sone;
        }
 
@@ -223,7 +222,7 @@ public class SoneInserterTest {
                                new SoneInserter(core, eventBus, freenetInterface, "SoneId",
                                                soneModificationDetector, 1);
                when(soneModificationDetector.isEligibleForInsert()).thenReturn(true);
-               when(core.getSone("SoneId")).thenReturn(Optional.<Sone>absent());
+               when(core.getSone("SoneId")).thenReturn(null);
                soneInserter.serviceRun();
        }
 
index e6bfffe..7edea06 100644 (file)
@@ -1,6 +1,5 @@
 package net.pterodactylus.sone.database.memory;
 
-import static com.google.common.base.Optional.fromNullable;
 import static net.pterodactylus.sone.test.Matchers.isPostWithId;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
@@ -19,7 +18,6 @@ import java.util.Set;
 
 import net.pterodactylus.sone.data.Post;
 
-import com.google.common.base.Optional;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
@@ -42,12 +40,11 @@ public class MemoryBookmarkDatabaseTest {
        @Before
        public void setupMemoryDatabase() {
                when(memoryDatabase.getPost(anyString())).thenAnswer(
-                               new Answer<Optional<Post>>() {
+                               new Answer<Post>() {
                                        @Override
-                                       public Optional<Post> answer(
+                                       public Post answer(
                                                        InvocationOnMock invocation) {
-                                               return fromNullable(
-                                                               posts.get(invocation.getArguments()[0]));
+                                               return posts.get(invocation.getArguments()[0]);
                                        }
                                });
        }
index 352b3b2..bb62eaf 100644 (file)
@@ -30,6 +30,7 @@ import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.emptyIterable;
+import static org.hamcrest.Matchers.nullValue;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -177,37 +178,36 @@ public class MemoryDatabaseTest {
                firstAlbum.addImage(thirdImage);
                secondAlbum.addImage(secondImage);
                memoryDatabase.storeSone(sone);
-               assertThat(memoryDatabase.getPost("post1").get(),
+               assertThat(memoryDatabase.getPost("post1"),
                                isPost(firstPost.getId(), 1000L, "post1",
                                                Optional.<String>absent()));
-               assertThat(memoryDatabase.getPost("post2").get(),
+               assertThat(memoryDatabase.getPost("post2"),
                                isPost(secondPost.getId(), 2000L, "post2", of(RECIPIENT_ID)));
-               assertThat(memoryDatabase.getPost("post3").isPresent(), is(false));
-               assertThat(memoryDatabase.getPostReply("reply1").get(),
+               assertThat(memoryDatabase.getPost("post3"), nullValue());
+               assertThat(memoryDatabase.getPostReply("reply1"),
                                isPostReply("reply1", "post1", 3000L, "reply1"));
-               assertThat(memoryDatabase.getPostReply("reply2").get(),
+               assertThat(memoryDatabase.getPostReply("reply2"),
                                isPostReply("reply2", "post2", 4000L, "reply2"));
-               assertThat(memoryDatabase.getPostReply("reply3").get(),
+               assertThat(memoryDatabase.getPostReply("reply3"),
                                isPostReply("reply3", "post1", 5000L, "reply3"));
-               assertThat(memoryDatabase.getPostReply("reply4").isPresent(),
-                               is(false));
-               assertThat(memoryDatabase.getAlbum("album1").get(),
+               assertThat(memoryDatabase.getPostReply("reply4"), nullValue());
+               assertThat(memoryDatabase.getAlbum("album1"),
                                isAlbum("album1", null, "album1", "album-description1"));
-               assertThat(memoryDatabase.getAlbum("album2").get(),
+               assertThat(memoryDatabase.getAlbum("album2"),
                                isAlbum("album2", null, "album2", "album-description2"));
-               assertThat(memoryDatabase.getAlbum("album3").get(),
+               assertThat(memoryDatabase.getAlbum("album3"),
                                isAlbum("album3", "album1", "album3", "album-description3"));
-               assertThat(memoryDatabase.getAlbum("album4").isPresent(), is(false));
-               assertThat(memoryDatabase.getImage("image1").get(),
+               assertThat(memoryDatabase.getAlbum("album4"), nullValue());
+               assertThat(memoryDatabase.getImage("image1"),
                                isImage("image1", 1000L, "KSK@image1", "image1",
                                                "image-description1", 16, 9));
-               assertThat(memoryDatabase.getImage("image2").get(),
+               assertThat(memoryDatabase.getImage("image2"),
                                isImage("image2", 2000L, "KSK@image2", "image2",
                                                "image-description2", 32, 18));
-               assertThat(memoryDatabase.getImage("image3").get(),
+               assertThat(memoryDatabase.getImage("image3"),
                                isImage("image3", 3000L, "KSK@image3", "image3",
                                                "image-description3", 48, 27));
-               assertThat(memoryDatabase.getImage("image4").isPresent(), is(false));
+               assertThat(memoryDatabase.getImage("image4"), nullValue());
        }
 
        @Test
@@ -266,11 +266,11 @@ public class MemoryDatabaseTest {
        @Test
        public void testBasicAlbumFunctionality() {
                Album newAlbum = new AlbumImpl(mock(Sone.class));
-               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), is(Optional.<Album>absent()));
+               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue());
                memoryDatabase.storeAlbum(newAlbum);
-               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), is(of(newAlbum)));
+               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), is(newAlbum));
                memoryDatabase.removeAlbum(newAlbum);
-               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), is(Optional.<Album>absent()));
+               assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue());
        }
 
        private void initializeFriends() {
index 0daf944..79f1974 100644 (file)
@@ -26,14 +26,17 @@ import static org.hamcrest.Matchers.notNullValue;
 import java.io.IOException;
 import java.util.Collection;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.impl.IdOnlySone;
 import net.pterodactylus.sone.database.PostProvider;
 import net.pterodactylus.sone.database.SoneProvider;
 
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
+import kotlin.jvm.functions.Function1;
 import org.junit.Test;
 
 /**
@@ -204,6 +207,12 @@ public class SoneTextParserTest {
        }
 
        @Test
+       public void invalidSskAndUskLinkIsParsedAsText() {
+               Iterable<Part> parts = soneTextParser.parse("SSK@a USK@a", null);
+               assertThat("Part Text", convertText(parts), is("SSK@a USK@a"));
+       }
+
+       @Test
        public void sskLinkWithoutContextIsNotTrusted() {
                Iterable<Part> parts = soneTextParser.parse("SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test", null);
                assertThat("Part Text", convertText(parts), is("[SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|SSK@qM1nmgU-YUnIttmEhqjTl7ifAF3Z6o~5EPwQW03uEQU,aztSUkT-VT1dWvfSUt9YpfyW~Flmf5yXpBnIE~v8sAg,AAMC--8/test|test]"));
@@ -404,22 +413,21 @@ public class SoneTextParserTest {
         */
        private static class TestSoneProvider implements SoneProvider {
 
+               @Nonnull
                @Override
-               public Function<String, Optional<Sone>> soneLoader() {
-                       return new Function<String, Optional<Sone>>() {
+               public Function1<String, Sone> getSoneLoader() {
+                       return new Function1<String, Sone>() {
                                @Override
-                               public Optional<Sone> apply(String soneId) {
+                               public Sone invoke(String soneId) {
                                        return getSone(soneId);
                                }
                        };
                }
 
-               /**
-                * {@inheritDoc}
-                */
+               @Nullable
                @Override
-               public Optional<Sone> getSone(final String soneId) {
-                       return Optional.<Sone>of(new IdOnlySone(soneId));
+               public Sone getSone(final String soneId) {
+                       return new IdOnlySone(soneId);
                }
 
                /**
@@ -451,17 +459,18 @@ public class SoneTextParserTest {
        private static class AbsentSoneProvider extends TestSoneProvider {
 
                @Override
-               public Optional<Sone> getSone(String soneId) {
-                       return Optional.absent();
+               public Sone getSone(String soneId) {
+                       return null;
                }
 
        }
 
        private static class TestPostProvider implements PostProvider {
 
+               @Nullable
                @Override
-               public Optional<Post> getPost(final String postId) {
-                       return Optional.<Post>of(new Post() {
+               public Post getPost(@Nonnull final String postId) {
+                       return new Post() {
                                @Override
                                public String getId() {
                                        return postId;
@@ -506,7 +515,7 @@ public class SoneTextParserTest {
                                public Post setKnown(boolean known) {
                                        return null;
                                }
-                       });
+                       };
                }
 
                @Override
@@ -523,9 +532,10 @@ public class SoneTextParserTest {
 
        private static class AbsentPostProvider extends TestPostProvider {
 
+               @Nullable
                @Override
-               public Optional<Post> getPost(String postId) {
-                       return Optional.absent();
+               public Post getPost(@Nonnull String postId) {
+                       return null;
                }
 
        }
index 5fc84f0..2d77c6e 100644 (file)
@@ -1,6 +1,5 @@
 package net.pterodactylus.sone.fcp
 
-import com.google.common.base.Optional
 import com.google.common.base.Optional.absent
 import com.google.common.base.Optional.of
 import net.pterodactylus.sone.core.Core
@@ -47,7 +46,7 @@ class CreatePostCommandTest : SoneCommandTest() {
        @Test
        fun `request without text results in fcp exception`() {
                parameters += "Sone" to "LocalSoneId"
-               whenever(core.getSone("LocalSoneId")).thenReturn(Optional.of(localSone))
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
                executeCommandAndExpectFcpException()
        }
 
@@ -55,7 +54,7 @@ class CreatePostCommandTest : SoneCommandTest() {
        fun `request with text creates post`() {
                parameters += "Sone" to "LocalSoneId"
                parameters += "Text" to "Test"
-               whenever(core.getSone("LocalSoneId")).thenReturn(of(localSone))
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
                val post = mock<Post>().apply { whenever(id).thenReturn("PostId") }
                whenever(core.createPost(localSone, absent(), "Test")).thenReturn(post)
                val response = command.execute(parameters)
@@ -68,7 +67,7 @@ class CreatePostCommandTest : SoneCommandTest() {
                parameters += "Sone" to "LocalSoneId"
                parameters += "Text" to "Test"
                parameters += "Recipient" to "InvalidSoneId"
-               whenever(core.getSone("LocalSoneId")).thenReturn(of(localSone))
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
                executeCommandAndExpectFcpException()
        }
 
@@ -77,7 +76,7 @@ class CreatePostCommandTest : SoneCommandTest() {
                parameters += "Sone" to "LocalSoneId"
                parameters += "Text" to "Test"
                parameters += "Recipient" to "LocalSoneId"
-               whenever(core.getSone("LocalSoneId")).thenReturn(of(localSone))
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
                val response = command.execute(parameters)
                assertThat(response.replyParameters["Message"], equalTo("Error"))
                assertThat(response.replyParameters["ErrorMessage"], notNullValue())
@@ -88,8 +87,8 @@ class CreatePostCommandTest : SoneCommandTest() {
                parameters += "Sone" to "LocalSoneId"
                parameters += "Text" to "Test"
                parameters += "Recipient" to "RemoteSoneId"
-               whenever(core.getSone("LocalSoneId")).thenReturn(of(localSone))
-               whenever(core.getSone("RemoteSoneId")).thenReturn(of(remoteSone))
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
+               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone)
                val post = mock<Post>().apply { whenever(id).thenReturn("PostId") }
                whenever(core.createPost(localSone, of(remoteSone), "Test")).thenReturn(post)
                val response = command.execute(parameters)
index f464de4..a6e69cd 100644 (file)
@@ -46,7 +46,7 @@ class CreateReplyCommandTest : SoneCommandTest() {
 
        private fun addValidLocalSoneParameter() {
                parameters += "Sone" to "LocalSoneId"
-               whenever(core.getSone("LocalSoneId")).thenReturn(of(localSone))
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
        }
 
        @Test
@@ -64,7 +64,7 @@ class CreateReplyCommandTest : SoneCommandTest() {
 
        private fun addValidPostParameter() {
                parameters += "Post" to "ValidPostId"
-               whenever(core.getPost("ValidPostId")).thenReturn(of(post))
+               whenever(core.getPost("ValidPostId")).thenReturn(post)
        }
 
        @Test
index 751ad24..30fc4cb 100644 (file)
@@ -38,7 +38,7 @@ class DeletePostCommandTest : SoneCommandTest() {
        @Test
        fun `request with post from remote sone returns error response`() {
                parameters += "Post" to "RemotePostId"
-               whenever(core.getPost("RemotePostId")).thenReturn(of(postFromRemoteSone))
+               whenever(core.getPost("RemotePostId")).thenReturn(postFromRemoteSone)
                val response = command.execute(parameters)
                assertThat(response.replyParameters["Message"], equalTo("Error"))
                assertThat(response.replyParameters["ErrorCode"], equalTo("401"))
@@ -47,7 +47,7 @@ class DeletePostCommandTest : SoneCommandTest() {
        @Test
        fun `request with post from local sone deletes posts`() {
                parameters += "Post" to "LocalPostId"
-               whenever(core.getPost("LocalPostId")).thenReturn(of(postFromLocalSone))
+               whenever(core.getPost("LocalPostId")).thenReturn(postFromLocalSone)
                val response = command.execute(parameters)
                assertThat(response.replyParameters["Message"], equalTo("PostDeleted"))
                verify(core).deletePost(postFromLocalSone)
index 013b1c1..6920a33 100644 (file)
@@ -39,7 +39,7 @@ class DeleteReplyCommandTest : SoneCommandTest() {
        @Test
        fun `request with remote post reply parameter results in error response`() {
            parameters += "Reply" to "RemoteReplyId"
-               whenever(core.getPostReply("RemoteReplyId")).thenReturn(of(remotePostReply))
+               whenever(core.getPostReply("RemoteReplyId")).thenReturn(remotePostReply)
                val response = command.execute(parameters)
                assertThat(response.replyParameters["Message"], equalTo("Error"))
                assertThat(response.replyParameters["ErrorCode"], equalTo("401"))
@@ -48,7 +48,7 @@ class DeleteReplyCommandTest : SoneCommandTest() {
        @Test
        fun `request with local post reply parameter deletes reply`() {
            parameters += "Reply" to "RemoteReplyId"
-               whenever(core.getPostReply("RemoteReplyId")).thenReturn(of(localPostReply))
+               whenever(core.getPostReply("RemoteReplyId")).thenReturn(localPostReply)
                val response = command.execute(parameters)
                assertThat(response.replyParameters["Message"], equalTo("ReplyDeleted"))
                verify(core).deleteReply(localPostReply)
index b7f911c..5c76c5b 100644 (file)
@@ -31,7 +31,7 @@ class GetPostCommandTest : SoneCommandTest() {
 
        @Before
        fun setupPostWithLikesAndReplies() {
-               whenever(core.getPost("ValidPostId")).thenReturn(post.asOptional())
+               whenever(core.getPost("ValidPostId")).thenReturn(post)
                whenever(core.getLikes(post)).thenReturn(setOf(sone1, sone2))
                val replies = listOf(postReply1, postReply2)
                whenever(core.getReplies("ValidPostId")).thenReturn(replies)
index 72daa07..d8ab243 100644 (file)
@@ -3,7 +3,6 @@ package net.pterodactylus.sone.fcp
 import freenet.support.SimpleFieldSet
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.containsInAnyOrder
 import org.hamcrest.Matchers.equalTo
@@ -58,8 +57,8 @@ class GetPostFeedCommandTest : SoneCommandTest() {
        private fun setupAllPostsAndReplies() {
                parameters += "Sone" to "ValidSoneId"
                whenever(localSone.id).thenReturn("ValidSoneId")
-               whenever(core.getSone("ValidSoneId")).thenReturn(localSone.asOptional())
-               whenever(core.getSone("Friend1")).thenReturn(friend1.asOptional())
+               whenever(core.getSone("ValidSoneId")).thenReturn(localSone)
+               whenever(core.getSone("Friend1")).thenReturn(friend1)
                whenever(core.getLikes(post1)).thenReturn(setOf(sone3, sone4))
                whenever(core.getLikes(post1Reply1)).thenReturn(setOf(sone2, sone3))
                whenever(core.getLikes(post1Reply2)).thenReturn(setOf(sone3))
index 6483085..acf1824 100644 (file)
@@ -3,7 +3,6 @@ package net.pterodactylus.sone.fcp
 import freenet.support.SimpleFieldSet
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.containsInAnyOrder
 import org.hamcrest.Matchers.equalTo
@@ -49,8 +48,8 @@ class GetPostsCommandTest : SoneCommandTest() {
                whenever(core.getReplies("Post2")).thenReturn(listOf(post2Reply1, post2Reply2))
                whenever(localSone.id).thenReturn("LocalSone")
                whenever(remoteSone.id).thenReturn("RemoteSone")
-               whenever(core.getSone("LocalSone")).thenReturn(localSone.asOptional())
-               whenever(core.getSone("ValidSoneId")).thenReturn(remoteSone.asOptional())
+               whenever(core.getSone("LocalSone")).thenReturn(localSone)
+               whenever(core.getSone("ValidSoneId")).thenReturn(remoteSone)
                whenever(remoteSone.posts).thenReturn(listOf(post2, post1))
                parameters += "Sone" to "ValidSoneId"
        }
index e73160b..79aad1a 100644 (file)
@@ -3,7 +3,6 @@ package net.pterodactylus.sone.fcp
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.freenet.fcp.FcpException
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.nullValue
@@ -43,8 +42,7 @@ class GetSoneCommandTest : SoneCommandTest() {
 
        @Test
        fun `request with valid Sone parameter results in response with Sone information`() {
-               whenever(core.getSone("SoneId")).thenReturn(sone.asOptional())
-               whenever(core.getSone(null)).thenReturn(null.asOptional())
+               whenever(core.getSone("SoneId")).thenReturn(sone)
                parameters += "Sone" to "SoneId"
                val replyParameters = command.execute(parameters).replyParameters
                assertThat(replyParameters["Message"], equalTo("Sone"))
@@ -54,8 +52,8 @@ class GetSoneCommandTest : SoneCommandTest() {
 
        @Test
        fun `request with local sone parameter results in followed being true for friend sone`() {
-               whenever(core.getSone("SoneId")).thenReturn(sone.asOptional())
-               whenever(core.getSone("LocalSone")).thenReturn(localSone.asOptional())
+               whenever(core.getSone("SoneId")).thenReturn(sone)
+               whenever(core.getSone("LocalSone")).thenReturn(localSone)
                whenever(localSone.id).thenReturn("LocalSone")
                whenever(localSone.hasFriend("SoneId")).thenReturn(true)
                parameters += "Sone" to "SoneId"
@@ -68,8 +66,8 @@ class GetSoneCommandTest : SoneCommandTest() {
        
        @Test
        fun `request with local sone parameter results in followed being false for non-friend sone`() {
-               whenever(core.getSone("SoneId")).thenReturn(sone.asOptional())
-               whenever(core.getSone("LocalSone")).thenReturn(localSone.asOptional())
+               whenever(core.getSone("SoneId")).thenReturn(sone)
+               whenever(core.getSone("LocalSone")).thenReturn(localSone)
                whenever(localSone.id).thenReturn("LocalSone")
                parameters += "Sone" to "SoneId"
                parameters += "LocalSone" to "LocalSone"
@@ -81,8 +79,8 @@ class GetSoneCommandTest : SoneCommandTest() {
 
        @Test
        fun `request with remote sone as local sone parameter results in fcp exception`() {
-               whenever(core.getSone("SoneId")).thenReturn(sone.asOptional())
-               whenever(core.getSone("RemoteSone")).thenReturn(remoteSone.asOptional())
+               whenever(core.getSone("SoneId")).thenReturn(sone)
+               whenever(core.getSone("RemoteSone")).thenReturn(remoteSone)
                whenever(localSone.id).thenReturn("RemoteSone")
                parameters += "Sone" to "SoneId"
                parameters += "LocalSone" to "RemoteSone"
index 54f784f..6c7d40c 100644 (file)
@@ -23,9 +23,9 @@ class LikePostCommandTest : SoneCommandTest() {
 
        @Before
        fun setupPostAndSones() {
-               whenever(core.getPost("PostId")).thenReturn(post.asOptional())
-               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone.asOptional())
-               whenever(core.getSone("LocalSoneId")).thenReturn(localSone.asOptional())
+               whenever(core.getPost("PostId")).thenReturn(post)
+               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone)
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
        }
 
        @Test
index 69ace0a..7b9256a 100644 (file)
@@ -24,9 +24,9 @@ class LikeReplyCommandTest : SoneCommandTest() {
 
        @Before
        fun setupRepliesAndSones() {
-               whenever(core.getPostReply("ReplyId")).thenReturn(reply.asOptional())
-               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone.asOptional())
-               whenever(core.getSone("LocalSoneId")).thenReturn(localSone.asOptional())
+               whenever(core.getPostReply("ReplyId")).thenReturn(reply)
+               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone)
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
        }
 
        @Test
index 1caea96..284df69 100644 (file)
@@ -2,7 +2,6 @@ package net.pterodactylus.sone.fcp
 
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
 import org.junit.Before
@@ -18,8 +17,8 @@ class LockSoneCommandTest : SoneCommandTest() {
 
        @Before
        fun setupSones() {
-               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone.asOptional())
-               whenever(core.getSone("LocalSoneId")).thenReturn(localSone.asOptional())
+               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone)
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
                whenever(localSone.id).thenReturn("LocalSoneId")
        }
 
index aa38f88..de92612 100644 (file)
@@ -1,6 +1,5 @@
 package net.pterodactylus.sone.fcp
 
-import com.google.common.base.Optional
 import com.google.common.base.Optional.absent
 import freenet.support.SimpleFieldSet
 import net.pterodactylus.sone.core.Core
@@ -39,9 +38,9 @@ abstract class SoneCommandTest {
 
        @Before
        fun setupCore() {
-               whenever(core.getSone(anyString())).thenReturn(absent())
-               whenever(core.getPost(anyString())).thenReturn(absent())
-               whenever(core.getPostReply(anyString())).thenReturn(absent())
+               whenever(core.getSone(anyString())).thenReturn(null)
+               whenever(core.getPost(anyString())).thenReturn(null)
+               whenever(core.getPostReply(anyString())).thenReturn(null)
        }
 
        protected fun createSone(id: String, name: String, firstName: String, lastName: String, time: Long) = mock<Sone>().apply {
@@ -92,7 +91,7 @@ abstract class SoneCommandTest {
 
        fun requestWithValidRemoteSoneParameterResultsInFcpException() {
                parameters += "Sone" to "RemoteSoneId"
-               whenever(core.getSone("RemoteSoneId")).thenReturn(Optional.of(remoteSone))
+               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone)
                executeCommandAndExpectFcpException()
        }
 
index c002db3..77e78b3 100644 (file)
@@ -2,7 +2,6 @@ package net.pterodactylus.sone.fcp
 
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.test.whenever
-import net.pterodactylus.sone.utils.asOptional
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
 import org.junit.Before
@@ -18,8 +17,8 @@ class UnlockSoneCommandTest : SoneCommandTest() {
 
        @Before
        fun setupSones() {
-               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone.asOptional())
-               whenever(core.getSone("LocalSoneId")).thenReturn(localSone.asOptional())
+               whenever(core.getSone("RemoteSoneId")).thenReturn(remoteSone)
+               whenever(core.getSone("LocalSoneId")).thenReturn(localSone)
                whenever(localSone.id).thenReturn("LocalSoneId")
        }
 
index ac2886a..0e44568 100644 (file)
@@ -1,6 +1,5 @@
 package net.pterodactylus.sone.template
 
-import com.google.common.base.Optional.of
 import com.google.inject.Guice
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.data.Sone
@@ -40,7 +39,7 @@ class ParserFilterTest {
        private fun setupSone(identity: String): Sone {
                val sone = mock<Sone>()
                `when`(sone.id).thenReturn(identity)
-               `when`(core.getSone(identity)).thenReturn(of(sone))
+               `when`(core.getSone(identity)).thenReturn(sone)
                return sone
        }
 
@@ -63,7 +62,7 @@ class ParserFilterTest {
                parameters.put("sone", soneOrSoneId)
                filter.format(templateContext, "text", parameters)
                val context = forClass(SoneTextParserContext::class.java)
-               verify(soneTextParser).parse(eq<String>("text"), context.capture())
+               verify(soneTextParser).parse(eq<String>("text") ?: "", context.capture())
                assertThat(context.value.postingSone, `is`(sone))
        }
 
index b8d6259..0920280 100644 (file)
@@ -1,6 +1,5 @@
 package net.pterodactylus.sone.template
 
-import com.google.common.base.Optional
 import net.pterodactylus.sone.core.Core
 import net.pterodactylus.sone.data.Post
 import net.pterodactylus.sone.data.Profile
@@ -101,7 +100,7 @@ class RenderFilterTest {
                `when`(sone.profile).thenReturn(Profile(sone))
                `when`(sone.name).thenReturn(name)
                sone.profile.firstName = firstName
-               `when`(core.getSone(identity)).thenReturn(Optional.of<Sone>(sone))
+               `when`(core.getSone(identity)).thenReturn(sone)
                return sone
        }
 
index 1cbba3d..a42e5ba 100644 (file)
@@ -91,12 +91,12 @@ open class TestObjects {
 
                whenever(core.preferences).thenReturn(preferences)
                whenever(core.updateChecker).thenReturn(updateChecker)
-               whenever(core.getSone(ArgumentMatchers.anyString())).thenAnswer { (localSones + remoteSones)[it.getArgument(0)].asOptional() }
+               whenever(core.getSone(ArgumentMatchers.anyString())).thenAnswer { (localSones + remoteSones)[it.getArgument(0)] }
                whenever(core.getLocalSone(ArgumentMatchers.anyString())).thenAnswer { localSones[it[0]] }
-               whenever(core.getPost(ArgumentMatchers.anyString())).thenAnswer { (posts + newPosts)[it[0]].asOptional() }
+               whenever(core.getPost(ArgumentMatchers.anyString())).thenAnswer { (posts + newPosts)[it[0]] }
                whenever(core.getLikes(ArgumentMatchers.any<Post>())).then { postLikes[it[0]] ?: emptySet<Sone>() }
                whenever(core.getLikes(ArgumentMatchers.any<PostReply>())).then { replyLikes[it[0]] ?: emptySet<Sone>() }
-               whenever(core.getPostReply(ArgumentMatchers.anyString())).then { replies[it[0]].asOptional() }
+               whenever(core.getPostReply(ArgumentMatchers.anyString())).then { replies[it[0]] }
                whenever(core.getAlbum(ArgumentMatchers.anyString())).then { albums[it[0]] }
                whenever(core.getImage(ArgumentMatchers.anyString())).then { images[it[0]] }
                whenever(core.getImage(ArgumentMatchers.anyString(), ArgumentMatchers.anyBoolean())).then { images[it[0]] }
index e4b53fd..b53cc8e 100644 (file)
@@ -1,6 +1,8 @@
 package net.pterodactylus.sone.web.ajax
 
+import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.whenever
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
 import org.junit.Test
@@ -24,7 +26,7 @@ class UnfollowSoneAjaxPageTest : JsonPageTest("unfollowSone.ajax", pageSupplier
 
        @Test
        fun `request with valid sone unfollows sone`() {
-               addSone(mock(), "sone-id")
+               addSone(mock<Sone>().apply { whenever(id).thenReturn("sone-id") })
                addRequestParameter("sone", "sone-id")
                assertThatJsonIsSuccessful()
                verify(core).unfollowSone(currentSone, "sone-id")
index f76786a..b4e6570 100644 (file)
@@ -86,6 +86,7 @@ class EditProfilePageTest: WebPageTest(::EditProfilePage) {
                addHttpRequestPart("save-profile", "true")
                addHttpRequestPart(fieldName, newValue.toString())
                verifyRedirect("editProfile.html") {
+                       verify(currentSone).profile = profile
                        verify(core).touchConfiguration()
                        assertThat(fieldAccessor(), equalTo(expectedValue))
                }
index a1fc2de..ce16c85 100644 (file)
@@ -89,11 +89,11 @@ open class WebPageTest(pageSupplier: (Template, WebInterface) -> SoneTemplatePag
                whenever(core.preferences).thenReturn(preferences)
                whenever(core.identityManager.allOwnIdentities).then { ownIdentities }
                whenever(core.sones).then { allSones.values }
-               whenever(core.getSone(anyString())).then { allSones[it[0]].asOptional() }
+               whenever(core.getSone(anyString())).then { allSones[it[0]] }
                whenever(core.localSones).then { localSones.values }
                whenever(core.getLocalSone(anyString())).then { localSones[it[0]] }
-               whenever(core.getPost(anyString())).then { allPosts[it[0]].asOptional() }
-               whenever(core.getPostReply(anyString())).then { allPostReplies[it[0]].asOptional() }
+               whenever(core.getPost(anyString())).then { allPosts[it[0]] }
+               whenever(core.getPostReply(anyString())).then { allPostReplies[it[0]] }
                whenever(core.getReplies(anyString())).then { perPostReplies[it[0]].asList() }
                whenever(core.getAlbum(anyString())).then { allAlbums[it[0]] }
                whenever(core.getImage(anyString())).then { allImages[it[0]]}