Merge branch 'release-0.8.5' 0.8.5
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 29 Jun 2013 14:19:50 +0000 (16:19 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 29 Jun 2013 14:19:50 +0000 (16:19 +0200)
262 files changed:
pom.xml
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/CoreListener.java [deleted file]
src/main/java/net/pterodactylus/sone/core/CoreListenerManager.java [deleted file]
src/main/java/net/pterodactylus/sone/core/FreenetInterface.java
src/main/java/net/pterodactylus/sone/core/ImageInsertListener.java [deleted file]
src/main/java/net/pterodactylus/sone/core/ImageInserter.java
src/main/java/net/pterodactylus/sone/core/Options.java
src/main/java/net/pterodactylus/sone/core/PostProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/core/Preferences.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/core/SoneException.java
src/main/java/net/pterodactylus/sone/core/SoneInsertException.java
src/main/java/net/pterodactylus/sone/core/SoneInsertListener.java [deleted file]
src/main/java/net/pterodactylus/sone/core/SoneInsertListenerManager.java [deleted file]
src/main/java/net/pterodactylus/sone/core/SoneInserter.java
src/main/java/net/pterodactylus/sone/core/SoneProvider.java [deleted file]
src/main/java/net/pterodactylus/sone/core/SoneRescuer.java
src/main/java/net/pterodactylus/sone/core/SoneUri.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/UpdateChecker.java
src/main/java/net/pterodactylus/sone/core/UpdateListener.java [deleted file]
src/main/java/net/pterodactylus/sone/core/UpdateListenerManager.java [deleted file]
src/main/java/net/pterodactylus/sone/core/WebOfTrustUpdater.java
src/main/java/net/pterodactylus/sone/core/event/ImageEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/ImageInsertAbortedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/ImageInsertFailedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/ImageInsertFinishedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/ImageInsertStartedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/MarkPostKnownEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/MarkPostReplyKnownEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/MarkSoneKnownEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/NewPostFoundEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/NewSoneFoundEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/PostEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/PostRemovedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/PostReplyEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/SoneEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/SoneInsertAbortedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/SoneInsertedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/SoneInsertingEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/SoneLockedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/SoneRemovedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/SoneUnlockedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/event/UpdateFoundEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/Album.java
src/main/java/net/pterodactylus/sone/data/Client.java
src/main/java/net/pterodactylus/sone/data/Fingerprintable.java
src/main/java/net/pterodactylus/sone/data/Image.java
src/main/java/net/pterodactylus/sone/data/Post.java
src/main/java/net/pterodactylus/sone/data/PostReply.java
src/main/java/net/pterodactylus/sone/data/Profile.java
src/main/java/net/pterodactylus/sone/data/Reply.java
src/main/java/net/pterodactylus/sone/data/Sone.java
src/main/java/net/pterodactylus/sone/data/TemporaryImage.java
src/main/java/net/pterodactylus/sone/data/impl/AbstractPostBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/AbstractPostReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/AbstractReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/PostImpl.java
src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/Database.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/DatabaseException.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostDatabase.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostProvider.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostReplyBuilderFactory.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostReplyProvider.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostReplyStore.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/PostStore.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/ReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/SoneProvider.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemoryPostBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReplyBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/CreateReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/DeletePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/DeleteReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/FcpInterface.java
src/main/java/net/pterodactylus/sone/fcp/GetLocalSonesCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostFeedCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetPostsCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetSoneCommand.java
src/main/java/net/pterodactylus/sone/fcp/GetSonesCommand.java
src/main/java/net/pterodactylus/sone/fcp/LikePostCommand.java
src/main/java/net/pterodactylus/sone/fcp/LikeReplyCommand.java
src/main/java/net/pterodactylus/sone/fcp/LockSoneCommand.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/fcp/UnlockSoneCommand.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/fcp/VersionCommand.java
src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java
src/main/java/net/pterodactylus/sone/freenet/PluginStoreConfigurationBackend.java
src/main/java/net/pterodactylus/sone/freenet/SimpleFieldSetBuilder.java
src/main/java/net/pterodactylus/sone/freenet/StringBucket.java
src/main/java/net/pterodactylus/sone/freenet/fcp/AbstractCommand.java
src/main/java/net/pterodactylus/sone/freenet/fcp/Command.java
src/main/java/net/pterodactylus/sone/freenet/fcp/FcpException.java
src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListenerManager.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java
src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java
src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java
src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java
src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListener.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListenerManager.java [deleted file]
src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java
src/main/java/net/pterodactylus/sone/freenet/wot/OwnIdentity.java
src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java
src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/notify/ListNotification.java
src/main/java/net/pterodactylus/sone/notify/ListNotificationFilters.java
src/main/java/net/pterodactylus/sone/template/AlbumAccessor.java
src/main/java/net/pterodactylus/sone/template/CollectionAccessor.java
src/main/java/net/pterodactylus/sone/template/CssClassNameFilter.java
src/main/java/net/pterodactylus/sone/template/GetPagePlugin.java
src/main/java/net/pterodactylus/sone/template/HttpRequestAccessor.java
src/main/java/net/pterodactylus/sone/template/IdentityAccessor.java
src/main/java/net/pterodactylus/sone/template/ImageAccessor.java
src/main/java/net/pterodactylus/sone/template/ImageLinkFilter.java
src/main/java/net/pterodactylus/sone/template/JavascriptFilter.java
src/main/java/net/pterodactylus/sone/template/ParserFilter.java
src/main/java/net/pterodactylus/sone/template/PostAccessor.java
src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyAccessor.java
src/main/java/net/pterodactylus/sone/template/ReplyGroupFilter.java
src/main/java/net/pterodactylus/sone/template/RequestChangeFilter.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/template/SubstringFilter.java
src/main/java/net/pterodactylus/sone/template/TrustAccessor.java
src/main/java/net/pterodactylus/sone/template/UniqueElementFilter.java
src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java
src/main/java/net/pterodactylus/sone/text/FreenetLinkPart.java
src/main/java/net/pterodactylus/sone/text/LinkPart.java
src/main/java/net/pterodactylus/sone/text/Parser.java
src/main/java/net/pterodactylus/sone/text/ParserContext.java
src/main/java/net/pterodactylus/sone/text/Part.java
src/main/java/net/pterodactylus/sone/text/PartContainer.java
src/main/java/net/pterodactylus/sone/text/PlainTextPart.java
src/main/java/net/pterodactylus/sone/text/PostPart.java
src/main/java/net/pterodactylus/sone/text/SonePart.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/text/SoneTextParserContext.java
src/main/java/net/pterodactylus/sone/text/TextFilter.java
src/main/java/net/pterodactylus/sone/utils/IntegerRangePredicate.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/web/AboutPage.java
src/main/java/net/pterodactylus/sone/web/BookmarkPage.java
src/main/java/net/pterodactylus/sone/web/BookmarksPage.java
src/main/java/net/pterodactylus/sone/web/CreateAlbumPage.java
src/main/java/net/pterodactylus/sone/web/CreatePostPage.java
src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java
src/main/java/net/pterodactylus/sone/web/CreateSonePage.java
src/main/java/net/pterodactylus/sone/web/DeleteAlbumPage.java
src/main/java/net/pterodactylus/sone/web/DeleteImagePage.java
src/main/java/net/pterodactylus/sone/web/DeletePostPage.java
src/main/java/net/pterodactylus/sone/web/DeleteProfileFieldPage.java
src/main/java/net/pterodactylus/sone/web/DeleteReplyPage.java
src/main/java/net/pterodactylus/sone/web/DeleteSonePage.java
src/main/java/net/pterodactylus/sone/web/DismissNotificationPage.java
src/main/java/net/pterodactylus/sone/web/DistrustPage.java
src/main/java/net/pterodactylus/sone/web/EditAlbumPage.java
src/main/java/net/pterodactylus/sone/web/EditImagePage.java
src/main/java/net/pterodactylus/sone/web/EditProfileFieldPage.java
src/main/java/net/pterodactylus/sone/web/EditProfilePage.java
src/main/java/net/pterodactylus/sone/web/FollowSonePage.java
src/main/java/net/pterodactylus/sone/web/GetImagePage.java
src/main/java/net/pterodactylus/sone/web/ImageBrowserPage.java
src/main/java/net/pterodactylus/sone/web/IndexPage.java
src/main/java/net/pterodactylus/sone/web/KnownSonesPage.java
src/main/java/net/pterodactylus/sone/web/LikePage.java
src/main/java/net/pterodactylus/sone/web/LockSonePage.java
src/main/java/net/pterodactylus/sone/web/LoginPage.java
src/main/java/net/pterodactylus/sone/web/LogoutPage.java
src/main/java/net/pterodactylus/sone/web/MarkAsKnownPage.java
src/main/java/net/pterodactylus/sone/web/NewPage.java
src/main/java/net/pterodactylus/sone/web/OptionsPage.java
src/main/java/net/pterodactylus/sone/web/RescuePage.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java
src/main/java/net/pterodactylus/sone/web/SoneTemplatePage.java
src/main/java/net/pterodactylus/sone/web/TrustPage.java
src/main/java/net/pterodactylus/sone/web/UnbookmarkPage.java
src/main/java/net/pterodactylus/sone/web/UnfollowSonePage.java
src/main/java/net/pterodactylus/sone/web/UnlikePage.java
src/main/java/net/pterodactylus/sone/web/UnlockSonePage.java
src/main/java/net/pterodactylus/sone/web/UntrustPage.java
src/main/java/net/pterodactylus/sone/web/UploadImagePage.java
src/main/java/net/pterodactylus/sone/web/ViewPostPage.java
src/main/java/net/pterodactylus/sone/web/ViewSonePage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/main/java/net/pterodactylus/sone/web/ajax/BookmarkAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/CreatePostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/CreateReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeletePostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DeleteReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DismissNotificationAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/DistrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditAlbumAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditImageAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/EditProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/FollowSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetLikesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetNotificationsAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetPostAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetReplyAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetStatusAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTimesAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/GetTranslationPage.java
src/main/java/net/pterodactylus/sone/web/ajax/JsonPage.java
src/main/java/net/pterodactylus/sone/web/ajax/LikeAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/LockSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MarkAsKnownAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/MoveProfileFieldAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/TrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnbookmarkAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnfollowSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnlikeAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UnlockSoneAjaxPage.java
src/main/java/net/pterodactylus/sone/web/ajax/UntrustAjaxPage.java
src/main/java/net/pterodactylus/sone/web/page/FreenetPage.java
src/main/java/net/pterodactylus/sone/web/page/FreenetRequest.java
src/main/java/net/pterodactylus/sone/web/page/FreenetTemplatePage.java
src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java
src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java
src/main/javadoc/overview.html [new file with mode: 0644]
src/main/resources/i18n/sone.de.properties
src/main/resources/i18n/sone.en.properties
src/main/resources/i18n/sone.fr.properties
src/main/resources/i18n/sone.ja.properties
src/main/resources/i18n/sone.no.properties
src/main/resources/i18n/sone.pl.properties
src/main/resources/i18n/sone.ru.properties
src/main/resources/static/javascript/sone.js
src/main/resources/templates/editProfile.html
src/main/resources/templates/include/viewPost.html
src/main/resources/templates/insert/sone.xml
src/main/resources/templates/knownSones.html
src/test/java/net/pterodactylus/sone/fcp/LockSoneCommandTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.java [new file with mode: 0644]
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java

diff --git a/pom.xml b/pom.xml
index 3b894ae..a3d564e 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -2,17 +2,23 @@
        <modelVersion>4.0.0</modelVersion>
        <groupId>net.pterodactylus</groupId>
        <artifactId>sone</artifactId>
-       <version>0.8.4</version>
+       <version>0.8.5</version>
        <dependencies>
                <dependency>
                        <groupId>net.pterodactylus</groupId>
                        <artifactId>utils</artifactId>
-                       <version>0.12.2</version>
+                       <version>0.12.3</version>
                </dependency>
                <dependency>
                        <groupId>junit</groupId>
                        <artifactId>junit</artifactId>
-                       <version>3.8.2</version>
+                       <version>4.11</version>
+                       <scope>test</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.mockito</groupId>
+                       <artifactId>mockito-all</artifactId>
+                       <version>1.9.5</version>
                        <scope>test</scope>
                </dependency>
                <dependency>
                        <artifactId>utils.json</artifactId>
                        <version>0.1</version>
                </dependency>
+               <dependency>
+                       <groupId>com.google.inject</groupId>
+                       <artifactId>guice</artifactId>
+                       <version>3.0</version>
+               </dependency>
+               <dependency>
+                       <groupId>com.google.guava</groupId>
+                       <artifactId>guava</artifactId>
+                       <version>14.0-rc1</version>
+               </dependency>
+               <dependency>
+                       <groupId>commons-lang</groupId>
+                       <artifactId>commons-lang</artifactId>
+                       <version>2.6</version>
+               </dependency>
        </dependencies>
        <repositories>
                <repository>
@@ -47,6 +68,7 @@
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-compiler-plugin</artifactId>
+                               <version>2.0.2</version>
                                <configuration>
                                        <source>1.6</source>
                                        <target>1.6</target>
@@ -55,6 +77,7 @@
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-jar-plugin</artifactId>
+                               <version>2.2</version>
                                <configuration>
                                        <archive>
                                                <manifestEntries>
                                        </execution>
                                </executions>
                        </plugin>
-               </plugins>
-       </build>
-       <reporting>
-               <plugins>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-javadoc-plugin</artifactId>
+                               <version>2.7</version>
                                <configuration>
-                                       <links>
-                                               <link>http://download.oracle.com/javase/6/docs/api/</link>
-                                               <link>http://java.pterodactylus.net/utils/apidocs/</link>
-                                               <link>http://java.pterodactylus.net/utils.json/apidocs/</link>
-                                       </links>
+                                       <detectLinks>true</detectLinks>
+                                       <detectJavaApiLink>true</detectJavaApiLink>
+                                       <show>private</show>
+                                       <footer>© 2010–2013 David ‘Bombe’ Roden</footer>
                                </configuration>
                        </plugin>
                </plugins>
-       </reporting>
+       </build>
 </project>
index c391582..d66e032 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Core.java - Copyright © 2010–2012 David Roden
+ * Sone - Core.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.core;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.net.MalformedURLException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -28,12 +32,26 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Options.DefaultOption;
 import net.pterodactylus.sone.core.Options.Option;
 import net.pterodactylus.sone.core.Options.OptionWatcher;
+import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
+import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
+import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
+import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
+import net.pterodactylus.sone.core.event.NewPostFoundEvent;
+import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent;
+import net.pterodactylus.sone.core.event.NewSoneFoundEvent;
+import net.pterodactylus.sone.core.event.PostRemovedEvent;
+import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
+import net.pterodactylus.sone.core.event.SoneLockedEvent;
+import net.pterodactylus.sone.core.event.SoneRemovedEvent;
+import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
 import net.pterodactylus.sone.data.Image;
@@ -46,25 +64,41 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
 import net.pterodactylus.sone.data.TemporaryImage;
-import net.pterodactylus.sone.data.impl.PostImpl;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.database.DatabaseException;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.PostReplyBuilder;
+import net.pterodactylus.sone.database.PostReplyProvider;
+import net.pterodactylus.sone.database.SoneProvider;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
 import net.pterodactylus.sone.freenet.wot.Identity;
-import net.pterodactylus.sone.freenet.wot.IdentityListener;
 import net.pterodactylus.sone.freenet.wot.IdentityManager;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+import net.pterodactylus.sone.freenet.wot.event.IdentityAddedEvent;
+import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent;
+import net.pterodactylus.sone.freenet.wot.event.IdentityUpdatedEvent;
+import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent;
+import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent;
 import net.pterodactylus.sone.main.SonePlugin;
+import net.pterodactylus.sone.utils.IntegerRangePredicate;
 import net.pterodactylus.util.config.Configuration;
 import net.pterodactylus.util.config.ConfigurationException;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.service.AbstractService;
-import net.pterodactylus.util.thread.Ticker;
-import net.pterodactylus.util.validation.EqualityValidator;
-import net.pterodactylus.util.validation.IntegerRangeValidator;
-import net.pterodactylus.util.validation.OrValidator;
-import net.pterodactylus.util.validation.Validation;
-import net.pterodactylus.util.version.Version;
+import net.pterodactylus.util.thread.NamedThreadFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.FluentIterable;
+import com.google.common.eventbus.EventBus;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+
 import freenet.keys.FreenetURI;
 
 /**
@@ -72,7 +106,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Core extends AbstractService implements IdentityListener, UpdateListener, SoneProvider, PostProvider, SoneInsertListener, ImageInsertListener {
+public class Core extends AbstractService implements SoneProvider, PostProvider, PostReplyProvider {
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(Core.class);
@@ -86,8 +120,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        /** The preferences. */
        private final Preferences preferences = new Preferences(options);
 
-       /** The core listener manager. */
-       private final CoreListenerManager coreListenerManager = new CoreListenerManager(this);
+       /** The event bus. */
+       private final EventBus eventBus;
 
        /** The configuration. */
        private Configuration configuration;
@@ -108,7 +142,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        private final ImageInserter imageInserter;
 
        /** Sone downloader thread-pool. */
-       private final ExecutorService soneDownloaders = Executors.newFixedThreadPool(10);
+       private final ExecutorService soneDownloaders = Executors.newFixedThreadPool(10, new NamedThreadFactory("Sone Downloader %2$d"));
 
        /** The update checker. */
        private final UpdateChecker updateChecker;
@@ -120,42 +154,29 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        private volatile FcpInterface fcpInterface;
 
        /** The times Sones were followed. */
-       private final Map<Sone, Long> soneFollowingTimes = new HashMap<Sone, Long>();
+       private final Map<String, Long> soneFollowingTimes = new HashMap<String, Long>();
 
        /** Locked local Sones. */
        /* synchronize on itself. */
        private final Set<Sone> lockedSones = new HashSet<Sone>();
 
        /** Sone inserters. */
-       /* synchronize access on this on localSones. */
+       /* synchronize access on this on sones. */
        private final Map<Sone, SoneInserter> soneInserters = new HashMap<Sone, SoneInserter>();
 
        /** Sone rescuers. */
-       /* synchronize access on this on localSones. */
+       /* synchronize access on this on sones. */
        private final Map<Sone, SoneRescuer> soneRescuers = new HashMap<Sone, SoneRescuer>();
 
-       /** All local Sones. */
-       /* synchronize access on this on itself. */
-       private final Map<String, Sone> localSones = new HashMap<String, Sone>();
-
-       /** All remote Sones. */
+       /** All Sones. */
        /* synchronize access on this on itself. */
-       private final Map<String, Sone> remoteSones = new HashMap<String, Sone>();
+       private final Map<String, Sone> sones = new HashMap<String, Sone>();
 
        /** All known Sones. */
        private final Set<String> knownSones = new HashSet<String>();
 
-       /** All posts. */
-       private final Map<String, Post> posts = new HashMap<String, Post>();
-
-       /** All known posts. */
-       private final Set<String> knownPosts = new HashSet<String>();
-
-       /** All replies. */
-       private final Map<String, PostReply> replies = new HashMap<String, PostReply>();
-
-       /** All known replies. */
-       private final Set<String> knownReplies = new HashSet<String>();
+       /** The post database. */
+       private final Database database;
 
        /** All bookmarked posts. */
        /* synchronize access on itself. */
@@ -174,7 +195,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        private final Map<String, TemporaryImage> temporaryImages = new HashMap<String, TemporaryImage>();
 
        /** Ticker for threads that mark own elements as known. */
-       private final Ticker localElementTicker = new Ticker();
+       private final ScheduledExecutorService localElementTicker = Executors.newScheduledThreadPool(1);
 
        /** The time the configuration was last touched. */
        private volatile long lastConfigurationUpdate;
@@ -190,40 +211,23 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The identity manager
         * @param webOfTrustUpdater
         *            The WebOfTrust updater
+        * @param eventBus
+        *            The event bus
+        * @param database
+        *            The database
         */
-       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) {
+       @Inject
+       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater, EventBus eventBus, Database database) {
                super("Sone Core");
                this.configuration = configuration;
                this.freenetInterface = freenetInterface;
                this.identityManager = identityManager;
                this.soneDownloader = new SoneDownloader(this, freenetInterface);
-               this.imageInserter = new ImageInserter(this, freenetInterface);
-               this.updateChecker = new UpdateChecker(freenetInterface);
+               this.imageInserter = new ImageInserter(freenetInterface);
+               this.updateChecker = new UpdateChecker(eventBus, freenetInterface);
                this.webOfTrustUpdater = webOfTrustUpdater;
-       }
-
-       //
-       // LISTENER MANAGEMENT
-       //
-
-       /**
-        * Adds a new core listener.
-        *
-        * @param coreListener
-        *            The listener to add
-        */
-       public void addCoreListener(CoreListener coreListener) {
-               coreListenerManager.addListener(coreListener);
-       }
-
-       /**
-        * Removes a core listener.
-        *
-        * @param coreListener
-        *            The listener to remove
-        */
-       public void removeCoreListener(CoreListener coreListener) {
-               coreListenerManager.removeListener(coreListener);
+               this.eventBus = eventBus;
+               this.database = database;
        }
 
        //
@@ -296,8 +300,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return The Sone rescuer for the given Sone
         */
        public SoneRescuer getSoneRescuer(Sone sone) {
-               Validation.begin().isNotNull("Sone", sone).check().is("Local Sone", isLocalSone(sone)).check();
-               synchronized (localSones) {
+               checkNotNull(sone, "sone must not be null");
+               checkArgument(sone.isLocal(), "sone must be local");
+               synchronized (sones) {
                        SoneRescuer soneRescuer = soneRescuers.get(sone);
                        if (soneRescuer == null) {
                                soneRescuer = new SoneRescuer(this, soneDownloader, sone);
@@ -322,28 +327,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        }
 
        /**
-        * Returns all Sones, remote and local.
-        *
-        * @return All Sones
+        * {@inheritDocs}
         */
-       public Set<Sone> getSones() {
-               Set<Sone> allSones = new HashSet<Sone>();
-               allSones.addAll(getLocalSones());
-               allSones.addAll(getRemoteSones());
-               return allSones;
-       }
-
-       /**
-        * Returns the Sone with the given ID, regardless whether it’s local or
-        * remote.
-        *
-        * @param id
-        *            The ID of the Sone to get
-        * @return The Sone with the given ID, or {@code null} if there is no such
-        *         Sone
-        */
-       public Sone getSone(String id) {
-               return getSone(id, true);
+       @Override
+       public Collection<Sone> getSones() {
+               synchronized (sones) {
+                       return Collections.unmodifiableCollection(sones.values());
+               }
        }
 
        /**
@@ -352,83 +342,33 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *
         * @param id
         *            The ID of the Sone to get
-        * @param create
-        *            {@code true} to create a new Sone if none exists,
-        *            {@code false} to return {@code null} if a Sone with the given
-        *            ID does not exist
         * @return The Sone with the given ID, or {@code null} if there is no such
         *         Sone
         */
        @Override
-       public Sone getSone(String id, boolean create) {
-               if (isLocalSone(id)) {
-                       return getLocalSone(id);
-               }
-               return getRemoteSone(id, create);
-       }
-
-       /**
-        * Checks whether the core knows a Sone with the given ID.
-        *
-        * @param id
-        *            The ID of the Sone
-        * @return {@code true} if there is a Sone with the given ID, {@code false}
-        *         otherwise
-        */
-       public boolean hasSone(String id) {
-               return isLocalSone(id) || isRemoteSone(id);
-       }
-
-       /**
-        * Returns whether the given Sone is a local Sone.
-        *
-        * @param sone
-        *            The Sone to check for its locality
-        * @return {@code true} if the given Sone is local, {@code false} otherwise
-        */
-       public boolean isLocalSone(Sone sone) {
-               synchronized (localSones) {
-                       return localSones.containsKey(sone.getId());
+       public Optional<Sone> getSone(String id) {
+               synchronized (sones) {
+                       return Optional.fromNullable(sones.get(id));
                }
        }
 
        /**
-        * Returns whether the given ID is the ID of a local Sone.
-        *
-        * @param id
-        *            The Sone ID to check for its locality
-        * @return {@code true} if the given ID is a local Sone, {@code false}
-        *         otherwise
+        * {@inheritDocs}
         */
-       public boolean isLocalSone(String id) {
-               synchronized (localSones) {
-                       return localSones.containsKey(id);
-               }
-       }
+       @Override
+       public Collection<Sone> getLocalSones() {
+               synchronized (sones) {
+                       return Collections2.filter(sones.values(), new Predicate<Sone>() {
 
-       /**
-        * Returns all local Sones.
-        *
-        * @return All local Sones
-        */
-       public Set<Sone> getLocalSones() {
-               synchronized (localSones) {
-                       return new HashSet<Sone>(localSones.values());
+                               @Override
+                               public boolean apply(Sone sone) {
+                                       return sone.isLocal();
+                               }
+                       });
                }
        }
 
        /**
-        * Returns the local Sone with the given ID.
-        *
-        * @param id
-        *            The ID of the Sone to get
-        * @return The Sone with the given ID
-        */
-       public Sone getLocalSone(String id) {
-               return getLocalSone(id, true);
-       }
-
-       /**
         * Returns the local Sone with the given ID, optionally creating a new Sone.
         *
         * @param id
@@ -439,24 +379,33 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return The Sone with the given ID, or {@code null}
         */
        public Sone getLocalSone(String id, boolean create) {
-               synchronized (localSones) {
-                       Sone sone = localSones.get(id);
+               synchronized (sones) {
+                       Sone sone = sones.get(id);
                        if ((sone == null) && create) {
-                               sone = new Sone(id);
-                               localSones.put(id, sone);
+                               sone = new Sone(id, true);
+                               sones.put(id, sone);
+                       }
+                       if ((sone != null) && !sone.isLocal()) {
+                               sone = new Sone(id, true);
+                               sones.put(id, sone);
                        }
                        return sone;
                }
        }
 
        /**
-        * Returns all remote Sones.
-        *
-        * @return All remote Sones
+        * {@inheritDocs}
         */
-       public Set<Sone> getRemoteSones() {
-               synchronized (remoteSones) {
-                       return new HashSet<Sone>(remoteSones.values());
+       @Override
+       public Collection<Sone> getRemoteSones() {
+               synchronized (sones) {
+                       return Collections2.filter(sones.values(), new Predicate<Sone>() {
+
+                               @Override
+                               public boolean apply(Sone sone) {
+                                       return !sone.isLocal();
+                               }
+                       });
                }
        }
 
@@ -471,45 +420,17 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return The Sone with the given ID
         */
        public Sone getRemoteSone(String id, boolean create) {
-               synchronized (remoteSones) {
-                       Sone sone = remoteSones.get(id);
+               synchronized (sones) {
+                       Sone sone = sones.get(id);
                        if ((sone == null) && create && (id != null) && (id.length() == 43)) {
-                               sone = new Sone(id);
-                               remoteSones.put(id, sone);
+                               sone = new Sone(id, false);
+                               sones.put(id, sone);
                        }
                        return sone;
                }
        }
 
        /**
-        * Returns whether the given Sone is a remote Sone.
-        *
-        * @param sone
-        *            The Sone to check
-        * @return {@code true} if the given Sone is a remote Sone, {@code false}
-        *         otherwise
-        */
-       public boolean isRemoteSone(Sone sone) {
-               synchronized (remoteSones) {
-                       return remoteSones.containsKey(sone.getId());
-               }
-       }
-
-       /**
-        * Returns whether the Sone with the given ID is a remote Sone.
-        *
-        * @param id
-        *            The ID of the Sone to check
-        * @return {@code true} if the Sone with the given ID is a remote Sone,
-        *         {@code false} otherwise
-        */
-       public boolean isRemoteSone(String id) {
-               synchronized (remoteSones) {
-                       return remoteSones.containsKey(id);
-               }
-       }
-
-       /**
         * Returns whether the given Sone has been modified.
         *
         * @param sone
@@ -531,10 +452,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        public long getSoneFollowingTime(Sone sone) {
                synchronized (soneFollowingTimes) {
-                       if (soneFollowingTimes.containsKey(sone)) {
-                               return soneFollowingTimes.get(sone);
-                       }
-                       return Long.MAX_VALUE;
+                       return Optional.fromNullable(soneFollowingTimes.get(sone.getId())).or(Long.MAX_VALUE);
                }
        }
 
@@ -548,118 +466,69 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return {@code true} if the target Sone is trusted by the origin Sone
         */
        public boolean isSoneTrusted(Sone origin, Sone target) {
-               Validation.begin().isNotNull("Origin", origin).isNotNull("Target", target).check().isInstanceOf("Origin’s OwnIdentity", origin.getIdentity(), OwnIdentity.class).check();
+               checkNotNull(origin, "origin must not be null");
+               checkNotNull(target, "target must not be null");
+               checkArgument(origin.getIdentity() instanceof OwnIdentity, "origin’s identity must be an OwnIdentity");
                return trustedIdentities.containsKey(origin.getIdentity()) && trustedIdentities.get(origin.getIdentity()).contains(target.getIdentity());
        }
 
        /**
-        * Returns the post with the given ID.
+        * Returns a post builder.
         *
-        * @param postId
-        *            The ID of the post to get
-        * @return The post with the given ID, or a new post with the given ID
+        * @return A new post builder
         */
-       public Post getPost(String postId) {
-               return getPost(postId, true);
+       public PostBuilder postBuilder() {
+               return database.newPostBuilder();
        }
 
        /**
-        * Returns the post with the given ID, optionally creating a new post.
-        *
-        * @param postId
-        *            The ID of the post to get
-        * @param create
-        *            {@code true} it create a new post if no post with the given ID
-        *            exists, {@code false} to return {@code null}
-        * @return The post, or {@code null} if there is no such post
+        * {@inheritDoc}
         */
        @Override
-       public Post getPost(String postId, boolean create) {
-               synchronized (posts) {
-                       Post post = posts.get(postId);
-                       if ((post == null) && create) {
-                               post = new PostImpl(postId);
-                               posts.put(postId, post);
-                       }
-                       return post;
-               }
+       public Optional<Post> getPost(String postId) {
+               return database.getPost(postId);
        }
 
        /**
-        * Returns all posts that have the given Sone as recipient.
-        *
-        * @see Post#getRecipient()
-        * @param recipient
-        *            The recipient of the posts
-        * @return All posts that have the given Sone as recipient
-        */
-       public Set<Post> getDirectedPosts(Sone recipient) {
-               Validation.begin().isNotNull("Recipient", recipient).check();
-               Set<Post> directedPosts = new HashSet<Post>();
-               synchronized (posts) {
-                       for (Post post : posts.values()) {
-                               if (recipient.equals(post.getRecipient())) {
-                                       directedPosts.add(post);
-                               }
-                       }
-               }
-               return directedPosts;
+        * {@inheritDocs}
+        */
+       @Override
+       public Collection<Post> getPosts(String soneId) {
+               return database.getPosts(soneId);
        }
 
        /**
-        * Returns the reply with the given ID. If there is no reply with the given
-        * ID yet, a new one is created.
-        *
-        * @param replyId
-        *            The ID of the reply to get
-        * @return The reply
+        * {@inheritDoc}
         */
-       public PostReply getReply(String replyId) {
-               return getReply(replyId, true);
+       @Override
+       public Collection<Post> getDirectedPosts(final String recipientId) {
+               checkNotNull(recipientId, "recipient must not be null");
+               return database.getDirectedPosts(recipientId);
        }
 
        /**
-        * Returns the reply with the given ID. If there is no reply with the given
-        * ID yet, a new one is created, unless {@code create} is false in which
-        * case {@code null} is returned.
+        * Returns a post reply builder.
         *
-        * @param replyId
-        *            The ID of the reply to get
-        * @param create
-        *            {@code true} to always return a {@link Reply}, {@code false}
-        *            to return {@code null} if no reply can be found
-        * @return The reply, or {@code null} if there is no such reply
+        * @return A new post reply builder
         */
-       public PostReply getReply(String replyId, boolean create) {
-               synchronized (replies) {
-                       PostReply reply = replies.get(replyId);
-                       if (create && (reply == null)) {
-                               reply = new PostReply(replyId);
-                               replies.put(replyId, reply);
-                       }
-                       return reply;
-               }
+       public PostReplyBuilder postReplyBuilder() {
+               return database.newPostReplyBuilder();
        }
 
        /**
-        * Returns all replies for the given post, order ascending by time.
-        *
-        * @param post
-        *            The post to get all replies for
-        * @return All replies for the given post
+        * {@inheritDoc}
         */
-       public List<PostReply> getReplies(Post post) {
-               Set<Sone> sones = getSones();
-               List<PostReply> replies = new ArrayList<PostReply>();
-               for (Sone sone : sones) {
-                       for (PostReply reply : sone.getReplies()) {
-                               if (reply.getPost().equals(post)) {
-                                       replies.add(reply);
-                               }
-                       }
-               }
-               Collections.sort(replies, Reply.TIME_COMPARATOR);
-               return replies;
+       @Override
+       public Optional<PostReply> getPostReply(String replyId) {
+               return database.getPostReply(replyId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public List<PostReply> getReplies(final String postId) {
+               return database.getReplies(postId);
        }
 
        /**
@@ -731,9 +600,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                Set<Post> posts = new HashSet<Post>();
                synchronized (bookmarkedPosts) {
                        for (String bookmarkedPostId : bookmarkedPosts) {
-                               Post post = getPost(bookmarkedPostId, false);
-                               if (post != null) {
-                                       posts.add(post);
+                               Optional<Post> post = getPost(bookmarkedPostId);
+                               if (!post.isPresent()) {
+                                       posts.add(post.get());
                                }
                        }
                }
@@ -838,7 +707,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        public void lockSone(Sone sone) {
                synchronized (lockedSones) {
                        if (lockedSones.add(sone)) {
-                               coreListenerManager.fireSoneLocked(sone);
+                               eventBus.post(new SoneLockedEvent(sone));
                        }
                }
        }
@@ -853,7 +722,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        public void unlockSone(Sone sone) {
                synchronized (lockedSones) {
                        if (lockedSones.remove(sone)) {
-                               coreListenerManager.fireSoneUnlocked(sone);
+                               eventBus.post(new SoneUnlockedEvent(sone));
                        }
                }
        }
@@ -870,10 +739,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        logger.log(Level.WARNING, "Given OwnIdentity is null!");
                        return null;
                }
-               synchronized (localSones) {
+               synchronized (sones) {
                        final Sone sone;
                        try {
-                               sone = getLocalSone(ownIdentity.getId()).setIdentity(ownIdentity).setInsertUri(new FreenetURI(ownIdentity.getInsertUri())).setRequestUri(new FreenetURI(ownIdentity.getRequestUri()));
+                               sone = getLocalSone(ownIdentity.getId(), true).setIdentity(ownIdentity).setInsertUri(new FreenetURI(ownIdentity.getInsertUri())).setRequestUri(new FreenetURI(ownIdentity.getRequestUri()));
                        } catch (MalformedURLException mue1) {
                                logger.log(Level.SEVERE, String.format("Could not convert the Identity’s URIs to Freenet URIs: %s, %s", ownIdentity.getInsertUri(), ownIdentity.getRequestUri()), mue1);
                                return null;
@@ -882,9 +751,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        sone.setClient(new Client("Sone", SonePlugin.VERSION.toString()));
                        sone.setKnown(true);
                        /* TODO - load posts ’n stuff */
-                       localSones.put(ownIdentity.getId(), sone);
-                       final SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone);
-                       soneInserter.addSoneInsertListener(this);
+                       trustedIdentities.put(ownIdentity, Collections.synchronizedSet(new HashSet<Identity>()));
+                       sones.put(ownIdentity.getId(), sone);
+                       final SoneInserter soneInserter = new SoneInserter(this, eventBus, freenetInterface, sone);
                        soneInserters.put(sone, soneInserter);
                        sone.setStatus(SoneStatus.idle);
                        loadSone(sone);
@@ -913,7 +782,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                sone.getOptions().addBooleanOption("ShowNotification/NewReplies", new DefaultOption<Boolean>(true));
                sone.getOptions().addEnumOption("ShowCustomAvatars", new DefaultOption<ShowCustomAvatars>(ShowCustomAvatars.NEVER));
 
-               followSone(sone, getSone("nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI"));
+               followSone(sone, "nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI");
                touchConfiguration();
                return sone;
        }
@@ -930,10 +799,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        logger.log(Level.WARNING, "Given Identity is null!");
                        return null;
                }
-               synchronized (remoteSones) {
+               synchronized (sones) {
                        final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity);
                        boolean newSone = sone.getRequestUri() == null;
-                       sone.setRequestUri(getSoneUri(identity.getRequestUri()));
+                       sone.setRequestUri(SoneUri.create(identity.getRequestUri()));
                        sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0));
                        if (newSone) {
                                synchronized (knownSones) {
@@ -941,10 +810,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                }
                                sone.setKnown(!newSone);
                                if (newSone) {
-                                       coreListenerManager.fireNewSoneFound(sone);
+                                       eventBus.post(new NewSoneFoundEvent(sone));
                                        for (Sone localSone : getLocalSones()) {
                                                if (localSone.getOptions().getBooleanOption("AutoFollow").get()) {
-                                                       followSone(localSone, sone);
+                                                       followSone(localSone, sone.getId());
                                                }
                                        }
                                }
@@ -972,39 +841,23 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The ID of the Sone to follow
         */
        public void followSone(Sone sone, String soneId) {
-               Validation.begin().isNotNull("Sone", sone).isNotNull("Sone ID", soneId).check();
-               Sone followedSone = getSone(soneId, true);
-               if (followedSone == null) {
-                       logger.log(Level.INFO, String.format("Ignored Sone with invalid ID: %s", soneId));
-                       return;
-               }
-               followSone(sone, getSone(soneId));
-       }
-
-       /**
-        * Lets the given local Sone follow the other given Sone. If the given Sone
-        * was not followed by any local Sone before, this will mark all elements of
-        * the followed Sone as read that have been created before the current
-        * moment.
-        *
-        * @param sone
-        *            The local Sone that should follow the other Sone
-        * @param followedSone
-        *            The Sone that should be followed
-        */
-       public void followSone(Sone sone, Sone followedSone) {
-               Validation.begin().isNotNull("Sone", sone).isNotNull("Followed Sone", followedSone).check();
-               sone.addFriend(followedSone.getId());
+               checkNotNull(sone, "sone must not be null");
+               checkNotNull(soneId, "soneId must not be null");
+               sone.addFriend(soneId);
                synchronized (soneFollowingTimes) {
-                       if (!soneFollowingTimes.containsKey(followedSone)) {
+                       if (!soneFollowingTimes.containsKey(soneId)) {
                                long now = System.currentTimeMillis();
-                               soneFollowingTimes.put(followedSone, now);
-                               for (Post post : followedSone.getPosts()) {
+                               soneFollowingTimes.put(soneId, now);
+                               Optional<Sone> followedSone = getSone(soneId);
+                               if (!followedSone.isPresent()) {
+                                       return;
+                               }
+                               for (Post post : followedSone.get().getPosts()) {
                                        if (post.getTime() < now) {
                                                markPostKnown(post);
                                        }
                                }
-                               for (PostReply reply : followedSone.getReplies()) {
+                               for (PostReply reply : followedSone.get().getReplies()) {
                                        if (reply.getTime() < now) {
                                                markReplyKnown(reply);
                                        }
@@ -1023,30 +876,16 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The ID of the Sone being unfollowed
         */
        public void unfollowSone(Sone sone, String soneId) {
-               Validation.begin().isNotNull("Sone", sone).isNotNull("Sone ID", soneId).check();
-               unfollowSone(sone, getSone(soneId, false));
-       }
-
-       /**
-        * Lets the given local Sone unfollow the other given Sone. If the given
-        * local Sone is the last local Sone that followed the given Sone, its
-        * following time will be removed.
-        *
-        * @param sone
-        *            The local Sone that should unfollow another Sone
-        * @param unfollowedSone
-        *            The Sone being unfollowed
-        */
-       public void unfollowSone(Sone sone, Sone unfollowedSone) {
-               Validation.begin().isNotNull("Sone", sone).isNotNull("Unfollowed Sone", unfollowedSone).check();
-               sone.removeFriend(unfollowedSone.getId());
+               checkNotNull(sone, "sone must not be null");
+               checkNotNull(soneId, "soneId must not be null");
+               sone.removeFriend(soneId);
                boolean unfollowedSoneStillFollowed = false;
                for (Sone localSone : getLocalSones()) {
-                       unfollowedSoneStillFollowed |= localSone.hasFriend(unfollowedSone.getId());
+                       unfollowedSoneStillFollowed |= localSone.hasFriend(soneId);
                }
                if (!unfollowedSoneStillFollowed) {
                        synchronized (soneFollowingTimes) {
-                               soneFollowingTimes.remove(unfollowedSone);
+                               soneFollowingTimes.remove(soneId);
                        }
                }
                touchConfiguration();
@@ -1063,7 +902,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The trust value (from {@code -100} to {@code 100})
         */
        public void setTrust(Sone origin, Sone target, int trustValue) {
-               Validation.begin().isNotNull("Trust Origin", origin).check().isInstanceOf("Trust Origin", origin.getIdentity(), OwnIdentity.class).isNotNull("Trust Target", target).isLessOrEqual("Trust Value", trustValue, 100).isGreaterOrEqual("Trust Value", trustValue, -100).check();
+               checkNotNull(origin, "origin must not be null");
+               checkArgument(origin.getIdentity() instanceof OwnIdentity, "origin must be a local Sone");
+               checkNotNull(target, "target must not be null");
+               checkArgument((trustValue >= -100) && (trustValue <= 100), "trustValue must be within [-100, 100]");
                webOfTrustUpdater.setTrust((OwnIdentity) origin.getIdentity(), target.getIdentity(), trustValue, preferences.getTrustComment());
        }
 
@@ -1076,7 +918,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The trust target
         */
        public void removeTrust(Sone origin, Sone target) {
-               Validation.begin().isNotNull("Trust Origin", origin).isNotNull("Trust Target", target).check().isInstanceOf("Trust Origin Identity", origin.getIdentity(), OwnIdentity.class).check();
+               checkNotNull(origin, "origin must not be null");
+               checkNotNull(target, "target must not be null");
+               checkArgument(origin.getIdentity() instanceof OwnIdentity, "origin must be a local Sone");
                webOfTrustUpdater.setTrust((OwnIdentity) origin.getIdentity(), target.getIdentity(), null, null);
        }
 
@@ -1138,65 +982,54 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            of the age of the given Sone
         */
        public void updateSone(Sone sone, boolean soneRescueMode) {
-               if (hasSone(sone.getId())) {
-                       Sone storedSone = getSone(sone.getId());
-                       if (!soneRescueMode && !(sone.getTime() > storedSone.getTime())) {
+               Optional<Sone> storedSone = getSone(sone.getId());
+               if (storedSone.isPresent()) {
+                       if (!soneRescueMode && !(sone.getTime() > storedSone.get().getTime())) {
                                logger.log(Level.FINE, String.format("Downloaded Sone %s is not newer than stored Sone %s.", sone, storedSone));
                                return;
                        }
-                       synchronized (posts) {
-                               if (!soneRescueMode) {
-                                       for (Post post : storedSone.getPosts()) {
-                                               posts.remove(post.getId());
-                                               if (!sone.getPosts().contains(post)) {
-                                                       coreListenerManager.firePostRemoved(post);
-                                               }
-                                       }
+                       /* find removed posts. */
+                       Collection<Post> existingPosts = database.getPosts(sone.getId());
+                       for (Post oldPost : existingPosts) {
+                               if (!sone.getPosts().contains(oldPost)) {
+                                       eventBus.post(new PostRemovedEvent(oldPost));
                                }
-                               List<Post> storedPosts = storedSone.getPosts();
-                               synchronized (knownPosts) {
-                                       for (Post post : sone.getPosts()) {
-                                               post.setSone(storedSone).setKnown(knownPosts.contains(post.getId()));
-                                               if (!storedPosts.contains(post)) {
-                                                       if (post.getTime() < getSoneFollowingTime(sone)) {
-                                                               knownPosts.add(post.getId());
-                                                       } else if (!knownPosts.contains(post.getId())) {
-                                                               sone.setKnown(false);
-                                                               coreListenerManager.fireNewPostFound(post);
-                                                       }
-                                               }
-                                               posts.put(post.getId(), post);
-                                       }
+                       }
+                       /* find new posts. */
+                       for (Post newPost : sone.getPosts()) {
+                               if (existingPosts.contains(newPost)) {
+                                       continue;
+                               }
+                               if (newPost.getTime() < getSoneFollowingTime(sone)) {
+                                       newPost.setKnown(true);
+                               } else if (!newPost.isKnown()) {
+                                       eventBus.post(new NewPostFoundEvent(newPost));
                                }
                        }
-                       synchronized (replies) {
-                               if (!soneRescueMode) {
-                                       for (PostReply reply : storedSone.getReplies()) {
-                                               replies.remove(reply.getId());
-                                               if (!sone.getReplies().contains(reply)) {
-                                                       coreListenerManager.fireReplyRemoved(reply);
-                                               }
+                       /* store posts. */
+                       database.storePosts(sone, sone.getPosts());
+                       if (!soneRescueMode) {
+                               for (PostReply reply : storedSone.get().getReplies()) {
+                                       if (!sone.getReplies().contains(reply)) {
+                                               eventBus.post(new PostReplyRemovedEvent(reply));
                                        }
                                }
-                               Set<PostReply> storedReplies = storedSone.getReplies();
-                               synchronized (knownReplies) {
-                                       for (PostReply reply : sone.getReplies()) {
-                                               reply.setSone(storedSone).setKnown(knownReplies.contains(reply.getId()));
-                                               if (!storedReplies.contains(reply)) {
-                                                       if (reply.getTime() < getSoneFollowingTime(sone)) {
-                                                               knownReplies.add(reply.getId());
-                                                       } else if (!knownReplies.contains(reply.getId())) {
-                                                               reply.setKnown(false);
-                                                               coreListenerManager.fireNewReplyFound(reply);
-                                                       }
-                                               }
-                                               replies.put(reply.getId(), reply);
-                                       }
+                       }
+                       Set<PostReply> storedReplies = storedSone.get().getReplies();
+                       for (PostReply reply : sone.getReplies()) {
+                               if (storedReplies.contains(reply)) {
+                                       continue;
+                               }
+                               if (reply.getTime() < getSoneFollowingTime(sone)) {
+                                       reply.setKnown(true);
+                               } else if (!reply.isKnown()) {
+                                       eventBus.post(new NewPostReplyFoundEvent(reply));
                                }
                        }
+                       database.storePostReplies(sone, sone.getReplies());
                        synchronized (albums) {
                                synchronized (images) {
-                                       for (Album album : storedSone.getAlbums()) {
+                                       for (Album album : storedSone.get().getAlbums()) {
                                                albums.remove(album.getId());
                                                for (Image image : album.getImages()) {
                                                        images.remove(image.getId());
@@ -1210,44 +1043,17 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                        }
                                }
                        }
-                       synchronized (storedSone) {
-                               if (!soneRescueMode || (sone.getTime() > storedSone.getTime())) {
-                                       storedSone.setTime(sone.getTime());
-                               }
-                               storedSone.setClient(sone.getClient());
-                               storedSone.setProfile(sone.getProfile());
-                               if (soneRescueMode) {
-                                       for (Post post : sone.getPosts()) {
-                                               storedSone.addPost(post);
-                                       }
-                                       for (PostReply reply : sone.getReplies()) {
-                                               storedSone.addReply(reply);
-                                       }
-                                       for (String likedPostId : sone.getLikedPostIds()) {
-                                               storedSone.addLikedPostId(likedPostId);
-                                       }
-                                       for (String likedReplyId : sone.getLikedReplyIds()) {
-                                               storedSone.addLikedReplyId(likedReplyId);
-                                       }
-                                       for (Album album : sone.getAlbums()) {
-                                               storedSone.addAlbum(album);
-                                       }
-                               } else {
-                                       storedSone.setPosts(sone.getPosts());
-                                       storedSone.setReplies(sone.getReplies());
-                                       storedSone.setLikePostIds(sone.getLikedPostIds());
-                                       storedSone.setLikeReplyIds(sone.getLikedReplyIds());
-                                       storedSone.setAlbums(sone.getAlbums());
-                               }
-                               storedSone.setLatestEdition(sone.getLatestEdition());
+                       synchronized (sones) {
+                               sone.setOptions(storedSone.get().getOptions());
+                               sones.put(sone.getId(), sone);
                        }
                }
        }
 
        /**
         * Deletes the given Sone. This will remove the Sone from the
-        * {@link #getLocalSone(String) local Sones}, stops its {@link SoneInserter}
-        * and remove the context from its identity.
+        * {@link #getLocalSones() local Sones}, stop its {@link SoneInserter} and
+        * remove the context from its identity.
         *
         * @param sone
         *            The Sone to delete
@@ -1257,14 +1063,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        logger.log(Level.WARNING, String.format("Tried to delete Sone of non-own identity: %s", sone));
                        return;
                }
-               synchronized (localSones) {
-                       if (!localSones.containsKey(sone.getId())) {
+               synchronized (sones) {
+                       if (!getLocalSones().contains(sone)) {
                                logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
                                return;
                        }
-                       localSones.remove(sone.getId());
+                       sones.remove(sone.getId());
                        SoneInserter soneInserter = soneInserters.remove(sone);
-                       soneInserter.removeSoneInsertListener(this);
                        soneInserter.stop();
                }
                webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
@@ -1278,7 +1083,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
 
        /**
         * Marks the given Sone as known. If the Sone was not {@link Post#isKnown()
-        * known} before, a {@link CoreListener#markSoneKnown(Sone)} event is fired.
+        * known} before, a {@link MarkSoneKnownEvent} is fired.
         *
         * @param sone
         *            The Sone to mark as known
@@ -1289,7 +1094,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        synchronized (knownSones) {
                                knownSones.add(sone.getId());
                        }
-                       coreListenerManager.fireMarkSoneKnown(sone);
+                       eventBus.post(new MarkSoneKnownEvent(sone));
                        touchConfiguration();
                }
        }
@@ -1302,7 +1107,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The Sone to load and update
         */
        public void loadSone(Sone sone) {
-               if (!isLocalSone(sone)) {
+               if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to load non-local Sone: %s", sone));
                        return;
                }
@@ -1359,11 +1164,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                logger.log(Level.WARNING, "Invalid post found, aborting load!");
                                return;
                        }
-                       Post post = getPost(postId).setSone(sone).setTime(postTime).setText(postText);
+                       PostBuilder postBuilder = postBuilder().withId(postId).from(sone.getId()).withTime(postTime).withText(postText);
                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
-                               post.setRecipient(getSone(postRecipientId));
+                               postBuilder.to(postRecipientId);
                        }
-                       posts.add(post);
+                       posts.add(postBuilder.build());
                }
 
                /* load replies. */
@@ -1381,7 +1186,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                logger.log(Level.WARNING, "Invalid reply found, aborting load!");
                                return;
                        }
-                       replies.add(getReply(replyId).setSone(sone).setPost(getPost(postId)).setTime(replyTime).setText(replyText));
+                       PostReplyBuilder postReplyBuilder = postReplyBuilder().withId(replyId).from(sone.getId()).to(postId).withTime(replyTime).withText(replyText);
+                       replies.add(postReplyBuilder.build());
                }
 
                /* load post likes. */
@@ -1508,15 +1314,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                knownSones.add(friend);
                        }
                }
-               synchronized (knownPosts) {
-                       for (Post post : posts) {
-                               knownPosts.add(post.getId());
-                       }
+               database.storePosts(sone, posts);
+               for (Post post : posts) {
+                       post.setKnown(true);
                }
-               synchronized (knownReplies) {
-                       for (PostReply reply : replies) {
-                               knownReplies.add(reply.getId());
-                       }
+               database.storePostReplies(sone, replies);
+               for (PostReply reply : replies) {
+                       reply.setKnown(true);
                }
        }
 
@@ -1560,7 +1364,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The text of the post
         * @return The created post
         */
-       public Post createPost(Sone sone, Sone recipient, String text) {
+       public Post createPost(Sone sone, Optional<Sone> recipient, String text) {
                return createPost(sone, recipient, System.currentTimeMillis(), text);
        }
 
@@ -1578,22 +1382,24 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The text of the post
         * @return The created post
         */
-       public Post createPost(Sone sone, Sone recipient, long time, String text) {
-               if (!isLocalSone(sone)) {
+       public Post createPost(Sone sone, Optional<Sone> recipient, long time, String text) {
+               checkNotNull(text, "text must not be null");
+               checkArgument(text.trim().length() > 0, "text must not be empty");
+               if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to create post for non-local Sone: %s", sone));
                        return null;
                }
-               final Post post = new PostImpl(sone, time, text);
-               if (recipient != null) {
-                       post.setRecipient(recipient);
+               PostBuilder postBuilder = database.newPostBuilder();
+               postBuilder.from(sone.getId()).randomId().withTime(time).withText(text.trim());
+               if (recipient.isPresent()) {
+                       postBuilder.to(recipient.get().getId());
                }
-               synchronized (posts) {
-                       posts.put(post.getId(), post);
-               }
-               coreListenerManager.fireNewPostFound(post);
+               final Post post = postBuilder.build();
+               database.storePost(post);
+               eventBus.post(new NewPostFoundEvent(post));
                sone.addPost(post);
                touchConfiguration();
-               localElementTicker.registerEvent(System.currentTimeMillis() + 10 * 1000, new Runnable() {
+               localElementTicker.schedule(new Runnable() {
 
                        /**
                         * {@inheritDoc}
@@ -1602,7 +1408,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        public void run() {
                                markPostKnown(post);
                        }
-               }, "Mark " + post + " read.");
+               }, 10, TimeUnit.SECONDS);
                return post;
        }
 
@@ -1613,15 +1419,12 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The post to delete
         */
        public void deletePost(Post post) {
-               if (!isLocalSone(post.getSone())) {
+               if (!post.getSone().isLocal()) {
                        logger.log(Level.WARNING, String.format("Tried to delete post of non-local Sone: %s", post.getSone()));
                        return;
                }
-               post.getSone().removePost(post);
-               synchronized (posts) {
-                       posts.remove(post.getId());
-               }
-               coreListenerManager.firePostRemoved(post);
+               database.removePost(post);
+               eventBus.post(new PostRemovedEvent(post));
                markPostKnown(post);
                touchConfiguration();
        }
@@ -1635,13 +1438,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        public void markPostKnown(Post post) {
                post.setKnown(true);
-               synchronized (knownPosts) {
-                       coreListenerManager.fireMarkPostKnown(post);
-                       if (knownPosts.add(post.getId())) {
-                               touchConfiguration();
-                       }
-               }
-               for (PostReply reply : getReplies(post)) {
+               eventBus.post(new MarkPostKnownEvent(post));
+               touchConfiguration();
+               for (PostReply reply : getReplies(post.getId())) {
                        markReplyKnown(reply);
                }
        }
@@ -1702,37 +1501,20 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return The created reply
         */
        public PostReply createReply(Sone sone, Post post, String text) {
-               return createReply(sone, post, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new reply.
-        *
-        * @param sone
-        *            The Sone that creates the reply
-        * @param post
-        *            The post that this reply refers to
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        * @return The created reply
-        */
-       public PostReply createReply(Sone sone, Post post, long time, String text) {
-               if (!isLocalSone(sone)) {
+               checkNotNull(text, "text must not be null");
+               checkArgument(text.trim().length() > 0, "text must not be empty");
+               if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone));
                        return null;
                }
-               final PostReply reply = new PostReply(sone, post, System.currentTimeMillis(), text);
-               synchronized (replies) {
-                       replies.put(reply.getId(), reply);
-               }
-               synchronized (knownReplies) {
-                       coreListenerManager.fireNewReplyFound(reply);
-               }
+               PostReplyBuilder postReplyBuilder = postReplyBuilder();
+               postReplyBuilder.randomId().from(sone.getId()).to(post.getId()).currentTime().withText(text.trim());
+               final PostReply reply = postReplyBuilder.build();
+               database.storePostReply(reply);
+               eventBus.post(new NewPostReplyFoundEvent(reply));
                sone.addReply(reply);
                touchConfiguration();
-               localElementTicker.registerEvent(System.currentTimeMillis() + 10 * 1000, new Runnable() {
+               localElementTicker.schedule(new Runnable() {
 
                        /**
                         * {@inheritDoc}
@@ -1741,7 +1523,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        public void run() {
                                markReplyKnown(reply);
                        }
-               }, "Mark " + reply + " read.");
+               }, 10, TimeUnit.SECONDS);
                return reply;
        }
 
@@ -1753,17 +1535,12 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        public void deleteReply(PostReply reply) {
                Sone sone = reply.getSone();
-               if (!isLocalSone(sone)) {
+               if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to delete non-local reply: %s", reply));
                        return;
                }
-               synchronized (replies) {
-                       replies.remove(reply.getId());
-               }
-               synchronized (knownReplies) {
-                       markReplyKnown(reply);
-                       knownReplies.remove(reply.getId());
-               }
+               database.removePostReply(reply);
+               markReplyKnown(reply);
                sone.removeReply(reply);
                touchConfiguration();
        }
@@ -1776,12 +1553,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The reply to mark as known
         */
        public void markReplyKnown(PostReply reply) {
+               boolean previouslyKnown = reply.isKnown();
                reply.setKnown(true);
-               synchronized (knownReplies) {
-                       coreListenerManager.fireMarkReplyKnown(reply);
-                       if (knownReplies.add(reply.getId())) {
-                               touchConfiguration();
-                       }
+               eventBus.post(new MarkPostReplyKnownEvent(reply));
+               if (!previouslyKnown) {
+                       touchConfiguration();
                }
        }
 
@@ -1828,7 +1604,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The album to remove
         */
        public void deleteAlbum(Album album) {
-               Validation.begin().isNotNull("Album", album).check().is("Local Sone", isLocalSone(album.getSone())).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.getSone().isLocal(), "album’s Sone must be a local Sone");
                if (!album.isEmpty()) {
                        return;
                }
@@ -1855,7 +1632,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return The newly created image
         */
        public Image createImage(Sone sone, Album album, TemporaryImage temporaryImage) {
-               Validation.begin().isNotNull("Sone", sone).isNotNull("Album", album).isNotNull("Temporary Image", temporaryImage).check().is("Local Sone", isLocalSone(sone)).check().isEqual("Owner and Album Owner", sone, album.getSone()).check();
+               checkNotNull(sone, "sone must not be null");
+               checkNotNull(album, "album must not be null");
+               checkNotNull(temporaryImage, "temporaryImage must not be null");
+               checkArgument(sone.isLocal(), "sone must be a local Sone");
+               checkArgument(sone.equals(album.getSone()), "album must belong to the given Sone");
                Image image = new Image(temporaryImage.getId()).setSone(sone).setCreationTime(System.currentTimeMillis());
                album.addImage(image);
                synchronized (images) {
@@ -1874,7 +1655,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The image to delete
         */
        public void deleteImage(Image image) {
-               Validation.begin().isNotNull("Image", image).check().is("Local Sone", isLocalSone(image.getSone())).check();
+               checkNotNull(image, "image must not be null");
+               checkArgument(image.getSone().isLocal(), "image must belong to a local Sone");
                deleteTemporaryImage(image.getId());
                image.getAlbum().removeImage(image);
                synchronized (images) {
@@ -1908,7 +1690,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The temporary image to delete
         */
        public void deleteTemporaryImage(TemporaryImage temporaryImage) {
-               Validation.begin().isNotNull("Temporary Image", temporaryImage).check();
+               checkNotNull(temporaryImage, "temporaryImage must not be null");
                deleteTemporaryImage(temporaryImage.getId());
        }
 
@@ -1919,7 +1701,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The ID of the temporary image to delete
         */
        public void deleteTemporaryImage(String imageId) {
-               Validation.begin().isNotNull("Temporary Image ID", imageId).check();
+               checkNotNull(imageId, "imageId must not be null");
                synchronized (temporaryImages) {
                        temporaryImages.remove(imageId);
                }
@@ -1948,9 +1730,11 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        @Override
        public void serviceStart() {
                loadConfiguration();
-               updateChecker.addUpdateListener(this);
                updateChecker.start();
+               identityManager.start();
+               webOfTrustUpdater.init();
                webOfTrustUpdater.start();
+               database.start();
        }
 
        /**
@@ -1977,18 +1761,20 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        @Override
        public void serviceStop() {
-               synchronized (localSones) {
+               localElementTicker.shutdownNow();
+               synchronized (sones) {
                        for (Entry<Sone, SoneInserter> soneInserter : soneInserters.entrySet()) {
-                               soneInserter.getValue().removeSoneInsertListener(this);
                                soneInserter.getValue().stop();
                                saveSone(soneInserter.getKey());
                        }
                }
                saveConfiguration();
+               database.stop();
                webOfTrustUpdater.stop();
                updateChecker.stop();
-               updateChecker.removeUpdateListener(this);
                soneDownloader.stop();
+               soneDownloaders.shutdown();
+               identityManager.stop();
        }
 
        //
@@ -2003,7 +1789,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *            The Sone to save
         */
        private synchronized void saveSone(Sone sone) {
-               if (!isLocalSone(sone)) {
+               if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to save non-local Sone: %s", sone));
                        return;
                }
@@ -2043,7 +1829,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        for (Post post : sone.getPosts()) {
                                String postPrefix = sonePrefix + "/Posts/" + postCounter++;
                                configuration.getStringValue(postPrefix + "/ID").setValue(post.getId());
-                               configuration.getStringValue(postPrefix + "/Recipient").setValue((post.getRecipient() != null) ? post.getRecipient().getId() : null);
+                               configuration.getStringValue(postPrefix + "/Recipient").setValue(post.getRecipientId().orNull());
                                configuration.getLongValue(postPrefix + "/Time").setValue(post.getTime());
                                configuration.getStringValue(postPrefix + "/Text").setValue(post.getText());
                        }
@@ -2054,7 +1840,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        for (PostReply reply : sone.getReplies()) {
                                String replyPrefix = sonePrefix + "/Replies/" + replyCounter++;
                                configuration.getStringValue(replyPrefix + "/ID").setValue(reply.getId());
-                               configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPost().getId());
+                               configuration.getStringValue(replyPrefix + "/Post/ID").setValue(reply.getPostId());
                                configuration.getLongValue(replyPrefix + "/Time").setValue(reply.getTime());
                                configuration.getStringValue(replyPrefix + "/Text").setValue(reply.getText());
                        }
@@ -2082,7 +1868,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        configuration.getStringValue(sonePrefix + "/Friends/" + friendCounter + "/ID").setValue(null);
 
                        /* save albums. first, collect in a flat structure, top-level first. */
-                       List<Album> albums = sone.getAllAlbums();
+                       List<Album> albums = FluentIterable.from(sone.getAlbums()).transformAndConcat(Album.FLATTENER).toList();
 
                        int albumCounter = 0;
                        for (Album album : albums) {
@@ -2172,8 +1958,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        /* save Sone following times. */
                        soneCounter = 0;
                        synchronized (soneFollowingTimes) {
-                               for (Entry<Sone, Long> soneFollowingTime : soneFollowingTimes.entrySet()) {
-                                       configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey().getId());
+                               for (Entry<String, Long> soneFollowingTime : soneFollowingTimes.entrySet()) {
+                                       configuration.getStringValue("SoneFollowingTimes/" + soneCounter + "/Sone").setValue(soneFollowingTime.getKey());
                                        configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").setValue(soneFollowingTime.getValue());
                                        ++soneCounter;
                                }
@@ -2181,22 +1967,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        }
 
                        /* save known posts. */
-                       int postCounter = 0;
-                       synchronized (knownPosts) {
-                               for (String knownPostId : knownPosts) {
-                                       configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
-                               }
-                               configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
-                       }
-
-                       /* save known replies. */
-                       int replyCounter = 0;
-                       synchronized (knownReplies) {
-                               for (String knownReplyId : knownReplies) {
-                                       configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
-                               }
-                               configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
-                       }
+                       database.save();
 
                        /* save bookmarked posts. */
                        int bookmarkedPostCounter = 0;
@@ -2212,6 +1983,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
 
                } catch (ConfigurationException ce1) {
                        logger.log(Level.SEVERE, "Could not store configuration!", ce1);
+               } catch (DatabaseException de1) {
+                       logger.log(Level.SEVERE, "Could not save database!", de1);
                } finally {
                        synchronized (configuration) {
                                storingConfiguration = false;
@@ -2222,10 +1995,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        /**
         * Loads the configuration.
         */
-       @SuppressWarnings("unchecked")
        private void loadConfiguration() {
                /* create options. */
-               options.addIntegerOption("InsertionDelay", new DefaultOption<Integer>(60, new IntegerRangeValidator(0, Integer.MAX_VALUE), new OptionWatcher<Integer>() {
+               options.addIntegerOption("InsertionDelay", new DefaultOption<Integer>(60, new IntegerRangePredicate(0, Integer.MAX_VALUE), new OptionWatcher<Integer>() {
 
                        @Override
                        public void optionChanged(Option<Integer> option, Integer oldValue, Integer newValue) {
@@ -2233,13 +2005,13 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        }
 
                }));
-               options.addIntegerOption("PostsPerPage", new DefaultOption<Integer>(10, new IntegerRangeValidator(1, Integer.MAX_VALUE)));
-               options.addIntegerOption("ImagesPerPage", new DefaultOption<Integer>(9, new IntegerRangeValidator(1, Integer.MAX_VALUE)));
-               options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(400, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
-               options.addIntegerOption("PostCutOffLength", new DefaultOption<Integer>(200, new OrValidator<Integer>(new IntegerRangeValidator(50, Integer.MAX_VALUE), new EqualityValidator<Integer>(-1))));
+               options.addIntegerOption("PostsPerPage", new DefaultOption<Integer>(10, new IntegerRangePredicate(1, Integer.MAX_VALUE)));
+               options.addIntegerOption("ImagesPerPage", new DefaultOption<Integer>(9, new IntegerRangePredicate(1, Integer.MAX_VALUE)));
+               options.addIntegerOption("CharactersPerPost", new DefaultOption<Integer>(400, Predicates.<Integer> or(new IntegerRangePredicate(50, Integer.MAX_VALUE), Predicates.equalTo(-1))));
+               options.addIntegerOption("PostCutOffLength", new DefaultOption<Integer>(200, Predicates.<Integer> or(new IntegerRangePredicate(50, Integer.MAX_VALUE), Predicates.equalTo(-1))));
                options.addBooleanOption("RequireFullAccess", new DefaultOption<Boolean>(false));
-               options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75, new IntegerRangeValidator(0, 100)));
-               options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25, new IntegerRangeValidator(-100, 100)));
+               options.addIntegerOption("PositiveTrust", new DefaultOption<Integer>(75, new IntegerRangePredicate(0, 100)));
+               options.addIntegerOption("NegativeTrust", new DefaultOption<Integer>(-25, new IntegerRangePredicate(-100, 100)));
                options.addStringOption("TrustComment", new DefaultOption<String>("Set from Sone Web Interface"));
                options.addBooleanOption("ActivateFcpInterface", new DefaultOption<Boolean>(false, new OptionWatcher<Boolean>() {
 
@@ -2291,41 +2063,12 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                break;
                        }
                        long time = configuration.getLongValue("SoneFollowingTimes/" + soneCounter + "/Time").getValue(Long.MAX_VALUE);
-                       Sone followedSone = getSone(soneId);
-                       if (followedSone == null) {
-                               logger.log(Level.WARNING, String.format("Ignoring Sone with invalid ID: %s", soneId));
-                       } else {
-                               synchronized (soneFollowingTimes) {
-                                       soneFollowingTimes.put(getSone(soneId), time);
-                               }
+                       synchronized (soneFollowingTimes) {
+                               soneFollowingTimes.put(soneId, time);
                        }
                        ++soneCounter;
                }
 
-               /* load known posts. */
-               int postCounter = 0;
-               while (true) {
-                       String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null);
-                       if (knownPostId == null) {
-                               break;
-                       }
-                       synchronized (knownPosts) {
-                               knownPosts.add(knownPostId);
-                       }
-               }
-
-               /* load known replies. */
-               int replyCounter = 0;
-               while (true) {
-                       String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null);
-                       if (knownReplyId == null) {
-                               break;
-                       }
-                       synchronized (knownReplies) {
-                               knownReplies.add(knownReplyId);
-                       }
-               }
-
                /* load bookmarked posts. */
                int bookmarkedPostCounter = 0;
                while (true) {
@@ -2356,63 +2099,57 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        }
 
        /**
-        * Generate a Sone URI from the given URI and latest edition.
+        * Notifies the core that a new {@link OwnIdentity} was added.
         *
-        * @param uriString
-        *            The URI to derive the Sone URI from
-        * @return The derived URI
-        */
-       private static FreenetURI getSoneUri(String uriString) {
-               try {
-                       FreenetURI uri = new FreenetURI(uriString).setDocName("Sone").setMetaString(new String[0]);
-                       return uri;
-               } catch (MalformedURLException mue1) {
-                       logger.log(Level.WARNING, String.format("Could not create Sone URI from URI: %s", uriString), mue1);
-                       return null;
-               }
-       }
-
-       //
-       // INTERFACE IdentityListener
-       //
-
-       /**
-        * {@inheritDoc}
+        * @param ownIdentityAddedEvent
+        *            The event
         */
-       @Override
-       public void ownIdentityAdded(OwnIdentity ownIdentity) {
+       @Subscribe
+       public void ownIdentityAdded(OwnIdentityAddedEvent ownIdentityAddedEvent) {
+               OwnIdentity ownIdentity = ownIdentityAddedEvent.ownIdentity();
                logger.log(Level.FINEST, String.format("Adding OwnIdentity: %s", ownIdentity));
                if (ownIdentity.hasContext("Sone")) {
-                       trustedIdentities.put(ownIdentity, Collections.synchronizedSet(new HashSet<Identity>()));
                        addLocalSone(ownIdentity);
                }
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the core that an {@link OwnIdentity} was removed.
+        *
+        * @param ownIdentityRemovedEvent
+        *            The event
         */
-       @Override
-       public void ownIdentityRemoved(OwnIdentity ownIdentity) {
+       @Subscribe
+       public void ownIdentityRemoved(OwnIdentityRemovedEvent ownIdentityRemovedEvent) {
+               OwnIdentity ownIdentity = ownIdentityRemovedEvent.ownIdentity();
                logger.log(Level.FINEST, String.format("Removing OwnIdentity: %s", ownIdentity));
                trustedIdentities.remove(ownIdentity);
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the core that a new {@link Identity} was added.
+        *
+        * @param identityAddedEvent
+        *            The event
         */
-       @Override
-       public void identityAdded(OwnIdentity ownIdentity, Identity identity) {
+       @Subscribe
+       public void identityAdded(IdentityAddedEvent identityAddedEvent) {
+               Identity identity = identityAddedEvent.identity();
                logger.log(Level.FINEST, String.format("Adding Identity: %s", identity));
-               trustedIdentities.get(ownIdentity).add(identity);
+               trustedIdentities.get(identityAddedEvent.ownIdentity()).add(identity);
                addRemoteSone(identity);
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the core that an {@link Identity} was updated.
+        *
+        * @param identityUpdatedEvent
+        *            The event
         */
-       @Override
-       public void identityUpdated(OwnIdentity ownIdentity, final Identity identity) {
-               new Thread(new Runnable() {
+       @Subscribe
+       public void identityUpdated(IdentityUpdatedEvent identityUpdatedEvent) {
+               final Identity identity = identityUpdatedEvent.identity();
+               soneDownloaders.execute(new Runnable() {
 
                        @Override
                        @SuppressWarnings("synthetic-access")
@@ -2423,14 +2160,19 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                soneDownloader.addSone(sone);
                                soneDownloader.fetchSone(sone);
                        }
-               }).start();
+               });
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the core that an {@link Identity} was removed.
+        *
+        * @param identityRemovedEvent
+        *            The event
         */
-       @Override
-       public void identityRemoved(OwnIdentity ownIdentity, Identity identity) {
+       @Subscribe
+       public void identityRemoved(IdentityRemovedEvent identityRemovedEvent) {
+               OwnIdentity ownIdentity = identityRemovedEvent.ownIdentity();
+               Identity identity = identityRemovedEvent.identity();
                trustedIdentities.get(ownIdentity).remove(identity);
                boolean foundIdentity = false;
                for (Entry<OwnIdentity, Set<Identity>> trustedIdentity : trustedIdentities.entrySet()) {
@@ -2445,468 +2187,37 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        /* some local identity still trusts this identity, don’t remove. */
                        return;
                }
-               Sone sone = getSone(identity.getId(), false);
-               if (sone == null) {
+               Optional<Sone> sone = getSone(identity.getId());
+               if (!sone.isPresent()) {
                        /* TODO - we don’t have the Sone anymore. should this happen? */
                        return;
                }
-               synchronized (posts) {
-                       synchronized (knownPosts) {
-                               for (Post post : sone.getPosts()) {
-                                       posts.remove(post.getId());
-                                       coreListenerManager.firePostRemoved(post);
-                               }
-                       }
+               database.removePosts(sone.get());
+               for (Post post : sone.get().getPosts()) {
+                       eventBus.post(new PostRemovedEvent(post));
                }
-               synchronized (replies) {
-                       synchronized (knownReplies) {
-                               for (PostReply reply : sone.getReplies()) {
-                                       replies.remove(reply.getId());
-                                       coreListenerManager.fireReplyRemoved(reply);
-                               }
-                       }
+               database.removePostReplies(sone.get());
+               for (PostReply reply : sone.get().getReplies()) {
+                       eventBus.post(new PostReplyRemovedEvent(reply));
                }
-               synchronized (remoteSones) {
-                       remoteSones.remove(identity.getId());
+               synchronized (sones) {
+                       sones.remove(identity.getId());
                }
-               coreListenerManager.fireSoneRemoved(sone);
-       }
-
-       //
-       // INTERFACE UpdateListener
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void updateFound(Version version, long releaseTime, long latestEdition) {
-               coreListenerManager.fireUpdateFound(version, releaseTime, latestEdition);
+               eventBus.post(new SoneRemovedEvent(sone.get()));
        }
 
-       //
-       // INTERFACE ImageInsertListener
-       //
-
        /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void insertStarted(Sone sone) {
-               coreListenerManager.fireSoneInserting(sone);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void insertFinished(Sone sone, long insertDuration) {
-               coreListenerManager.fireSoneInserted(sone, insertDuration);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void insertAborted(Sone sone, Throwable cause) {
-               coreListenerManager.fireSoneInsertAborted(sone, cause);
-       }
-
-       //
-       // SONEINSERTLISTENER METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void imageInsertStarted(Image image) {
-               logger.log(Level.WARNING, String.format("Image insert started for %s...", image));
-               coreListenerManager.fireImageInsertStarted(image);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void imageInsertAborted(Image image) {
-               logger.log(Level.WARNING, String.format("Image insert aborted for %s.", image));
-               coreListenerManager.fireImageInsertAborted(image);
-       }
-
-       /**
-        * {@inheritDoc}
+        * Deletes the temporary image.
+        *
+        * @param imageInsertFinishedEvent
+        *            The event
         */
-       @Override
-       public void imageInsertFinished(Image image, FreenetURI key) {
-               logger.log(Level.WARNING, String.format("Image insert finished for %s: %s", image, key));
-               image.setKey(key.toString());
-               deleteTemporaryImage(image.getId());
+       @Subscribe
+       public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
+               logger.log(Level.WARNING, String.format("Image insert finished for %s: %s", imageInsertFinishedEvent.image(), imageInsertFinishedEvent.resultingUri()));
+               imageInsertFinishedEvent.image().setKey(imageInsertFinishedEvent.resultingUri().toString());
+               deleteTemporaryImage(imageInsertFinishedEvent.image().getId());
                touchConfiguration();
-               coreListenerManager.fireImageInsertFinished(image);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public void imageInsertFailed(Image image, Throwable cause) {
-               logger.log(Level.WARNING, String.format("Image insert failed for %s." + image), cause);
-               coreListenerManager.fireImageInsertFailed(image, cause);
-       }
-
-       /**
-        * Convenience interface for external classes that want to access the core’s
-        * configuration.
-        *
-        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
-        */
-       public static class Preferences {
-
-               /** The wrapped options. */
-               private final Options options;
-
-               /**
-                * Creates a new preferences object wrapped around the given options.
-                *
-                * @param options
-                *            The options to wrap
-                */
-               public Preferences(Options options) {
-                       this.options = options;
-               }
-
-               /**
-                * Returns the insertion delay.
-                *
-                * @return The insertion delay
-                */
-               public int getInsertionDelay() {
-                       return options.getIntegerOption("InsertionDelay").get();
-               }
-
-               /**
-                * Validates the given insertion delay.
-                *
-                * @param insertionDelay
-                *            The insertion delay to validate
-                * @return {@code true} if the given insertion delay was valid,
-                *         {@code false} otherwise
-                */
-               public boolean validateInsertionDelay(Integer insertionDelay) {
-                       return options.getIntegerOption("InsertionDelay").validate(insertionDelay);
-               }
-
-               /**
-                * Sets the insertion delay
-                *
-                * @param insertionDelay
-                *            The new insertion delay, or {@code null} to restore it to
-                *            the default value
-                * @return This preferences
-                */
-               public Preferences setInsertionDelay(Integer insertionDelay) {
-                       options.getIntegerOption("InsertionDelay").set(insertionDelay);
-                       return this;
-               }
-
-               /**
-                * Returns the number of posts to show per page.
-                *
-                * @return The number of posts to show per page
-                */
-               public int getPostsPerPage() {
-                       return options.getIntegerOption("PostsPerPage").get();
-               }
-
-               /**
-                * Validates the number of posts per page.
-                *
-                * @param postsPerPage
-                *            The number of posts per page
-                * @return {@code true} if the number of posts per page was valid,
-                *         {@code false} otherwise
-                */
-               public boolean validatePostsPerPage(Integer postsPerPage) {
-                       return options.getIntegerOption("PostsPerPage").validate(postsPerPage);
-               }
-
-               /**
-                * Sets the number of posts to show per page.
-                *
-                * @param postsPerPage
-                *            The number of posts to show per page
-                * @return This preferences object
-                */
-               public Preferences setPostsPerPage(Integer postsPerPage) {
-                       options.getIntegerOption("PostsPerPage").set(postsPerPage);
-                       return this;
-               }
-
-               /**
-                * Returns the number of images to show per page.
-                *
-                * @return The number of images to show per page
-                */
-               public int getImagesPerPage() {
-                       return options.getIntegerOption("ImagesPerPage").get();
-               }
-
-               /**
-                * Validates the number of images per page.
-                *
-                * @param imagesPerPage
-                *            The number of images per page
-                * @return {@code true} if the number of images per page was valid,
-                *         {@code false} otherwise
-                */
-               public boolean validateImagesPerPage(Integer imagesPerPage) {
-                       return options.getIntegerOption("ImagesPerPage").validate(imagesPerPage);
-               }
-
-               /**
-                * Sets the number of images per page.
-                *
-                * @param imagesPerPage
-                *            The number of images per page
-                * @return This preferences object
-                */
-               public Preferences setImagesPerPage(Integer imagesPerPage) {
-                       options.getIntegerOption("ImagesPerPage").set(imagesPerPage);
-                       return this;
-               }
-
-               /**
-                * Returns the number of characters per post, or <code>-1</code> if the
-                * posts should not be cut off.
-                *
-                * @return The numbers of characters per post
-                */
-               public int getCharactersPerPost() {
-                       return options.getIntegerOption("CharactersPerPost").get();
-               }
-
-               /**
-                * Validates the number of characters per post.
-                *
-                * @param charactersPerPost
-                *            The number of characters per post
-                * @return {@code true} if the number of characters per post was valid,
-                *         {@code false} otherwise
-                */
-               public boolean validateCharactersPerPost(Integer charactersPerPost) {
-                       return options.getIntegerOption("CharactersPerPost").validate(charactersPerPost);
-               }
-
-               /**
-                * Sets the number of characters per post.
-                *
-                * @param charactersPerPost
-                *            The number of characters per post, or <code>-1</code> to
-                *            not cut off the posts
-                * @return This preferences objects
-                */
-               public Preferences setCharactersPerPost(Integer charactersPerPost) {
-                       options.getIntegerOption("CharactersPerPost").set(charactersPerPost);
-                       return this;
-               }
-
-               /**
-                * Returns the number of characters the shortened post should have.
-                *
-                * @return The number of characters of the snippet
-                */
-               public int getPostCutOffLength() {
-                       return options.getIntegerOption("PostCutOffLength").get();
-               }
-
-               /**
-                * Validates the number of characters after which to cut off the post.
-                *
-                * @param postCutOffLength
-                *            The number of characters of the snippet
-                * @return {@code true} if the number of characters of the snippet is
-                *         valid, {@code false} otherwise
-                */
-               public boolean validatePostCutOffLength(Integer postCutOffLength) {
-                       return options.getIntegerOption("PostCutOffLength").validate(postCutOffLength);
-               }
-
-               /**
-                * Sets the number of characters the shortened post should have.
-                *
-                * @param postCutOffLength
-                *            The number of characters of the snippet
-                * @return This preferences
-                */
-               public Preferences setPostCutOffLength(Integer postCutOffLength) {
-                       options.getIntegerOption("PostCutOffLength").set(postCutOffLength);
-                       return this;
-               }
-
-               /**
-                * Returns whether Sone requires full access to be even visible.
-                *
-                * @return {@code true} if Sone requires full access, {@code false}
-                *         otherwise
-                */
-               public boolean isRequireFullAccess() {
-                       return options.getBooleanOption("RequireFullAccess").get();
-               }
-
-               /**
-                * Sets whether Sone requires full access to be even visible.
-                *
-                * @param requireFullAccess
-                *            {@code true} if Sone requires full access, {@code false}
-                *            otherwise
-                */
-               public void setRequireFullAccess(Boolean requireFullAccess) {
-                       options.getBooleanOption("RequireFullAccess").set(requireFullAccess);
-               }
-
-               /**
-                * Returns the positive trust.
-                *
-                * @return The positive trust
-                */
-               public int getPositiveTrust() {
-                       return options.getIntegerOption("PositiveTrust").get();
-               }
-
-               /**
-                * Validates the positive trust.
-                *
-                * @param positiveTrust
-                *            The positive trust to validate
-                * @return {@code true} if the positive trust was valid, {@code false}
-                *         otherwise
-                */
-               public boolean validatePositiveTrust(Integer positiveTrust) {
-                       return options.getIntegerOption("PositiveTrust").validate(positiveTrust);
-               }
-
-               /**
-                * Sets the positive trust.
-                *
-                * @param positiveTrust
-                *            The new positive trust, or {@code null} to restore it to
-                *            the default vlaue
-                * @return This preferences
-                */
-               public Preferences setPositiveTrust(Integer positiveTrust) {
-                       options.getIntegerOption("PositiveTrust").set(positiveTrust);
-                       return this;
-               }
-
-               /**
-                * Returns the negative trust.
-                *
-                * @return The negative trust
-                */
-               public int getNegativeTrust() {
-                       return options.getIntegerOption("NegativeTrust").get();
-               }
-
-               /**
-                * Validates the negative trust.
-                *
-                * @param negativeTrust
-                *            The negative trust to validate
-                * @return {@code true} if the negative trust was valid, {@code false}
-                *         otherwise
-                */
-               public boolean validateNegativeTrust(Integer negativeTrust) {
-                       return options.getIntegerOption("NegativeTrust").validate(negativeTrust);
-               }
-
-               /**
-                * Sets the negative trust.
-                *
-                * @param negativeTrust
-                *            The negative trust, or {@code null} to restore it to the
-                *            default value
-                * @return The preferences
-                */
-               public Preferences setNegativeTrust(Integer negativeTrust) {
-                       options.getIntegerOption("NegativeTrust").set(negativeTrust);
-                       return this;
-               }
-
-               /**
-                * Returns the trust comment. This is the comment that is set in the web
-                * of trust when a trust value is assigned to an identity.
-                *
-                * @return The trust comment
-                */
-               public String getTrustComment() {
-                       return options.getStringOption("TrustComment").get();
-               }
-
-               /**
-                * Sets the trust comment.
-                *
-                * @param trustComment
-                *            The trust comment, or {@code null} to restore it to the
-                *            default value
-                * @return This preferences
-                */
-               public Preferences setTrustComment(String trustComment) {
-                       options.getStringOption("TrustComment").set(trustComment);
-                       return this;
-               }
-
-               /**
-                * Returns whether the {@link FcpInterface FCP interface} is currently
-                * active.
-                *
-                * @see FcpInterface#setActive(boolean)
-                * @return {@code true} if the FCP interface is currently active,
-                *         {@code false} otherwise
-                */
-               public boolean isFcpInterfaceActive() {
-                       return options.getBooleanOption("ActivateFcpInterface").get();
-               }
-
-               /**
-                * Sets whether the {@link FcpInterface FCP interface} is currently
-                * active.
-                *
-                * @see FcpInterface#setActive(boolean)
-                * @param fcpInterfaceActive
-                *            {@code true} to activate the FCP interface, {@code false}
-                *            to deactivate the FCP interface
-                * @return This preferences object
-                */
-               public Preferences setFcpInterfaceActive(boolean fcpInterfaceActive) {
-                       options.getBooleanOption("ActivateFcpInterface").set(fcpInterfaceActive);
-                       return this;
-               }
-
-               /**
-                * Returns the action level for which full access to the FCP interface
-                * is required.
-                *
-                * @return The action level for which full access to the FCP interface
-                *         is required
-                */
-               public FullAccessRequired getFcpFullAccessRequired() {
-                       return FullAccessRequired.values()[options.getIntegerOption("FcpFullAccessRequired").get()];
-               }
-
-               /**
-                * Sets the action level for which full access to the FCP interface is
-                * required
-                *
-                * @param fcpFullAccessRequired
-                *            The action level
-                * @return This preferences
-                */
-               public Preferences setFcpFullAccessRequired(FullAccessRequired fcpFullAccessRequired) {
-                       options.getIntegerOption("FcpFullAccessRequired").set((fcpFullAccessRequired != null) ? fcpFullAccessRequired.ordinal() : null);
-                       return this;
-               }
-
        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/core/CoreListener.java b/src/main/java/net/pterodactylus/sone/core/CoreListener.java
deleted file mode 100644 (file)
index b56bff5..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Sone - CoreListener.java - Copyright © 2010–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import java.util.EventListener;
-
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.version.Version;
-
-/**
- * Listener interface for objects that want to be notified on certain
- * {@link Core} events, such es discovery of new data.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface CoreListener extends EventListener {
-
-       /**
-        * Notifies a listener that a new Sone has been discovered.
-        *
-        * @param sone
-        *            The new Sone
-        */
-       public void newSoneFound(Sone sone);
-
-       /**
-        * Notifies a listener that a new post has been found.
-        *
-        * @param post
-        *            The new post
-        */
-       public void newPostFound(Post post);
-
-       /**
-        * Notifies a listener that a new reply has been found.
-        *
-        * @param reply
-        *            The new reply
-        */
-       public void newReplyFound(PostReply reply);
-
-       /**
-        * Notifies a listener that the given Sone is now marked as known.
-        *
-        * @param sone
-        *            The known Sone
-        */
-       public void markSoneKnown(Sone sone);
-
-       /**
-        * Notifies a listener that the given post is now marked as known.
-        *
-        * @param post
-        *            The known post
-        */
-       public void markPostKnown(Post post);
-
-       /**
-        * Notifies a listener that the given reply is now marked as known.
-        *
-        * @param reply
-        *            The known reply
-        */
-       public void markReplyKnown(PostReply reply);
-
-       /**
-        * Notifies a listener that the given Sone was removed.
-        *
-        * @param sone
-        *            The removed Sone
-        */
-       public void soneRemoved(Sone sone);
-
-       /**
-        * Notifies a listener that the given post was removed.
-        *
-        * @param post
-        *            The removed post
-        */
-       public void postRemoved(Post post);
-
-       /**
-        * Notifies a listener that the given reply was removed.
-        *
-        * @param reply
-        *            The removed reply
-        */
-       public void replyRemoved(PostReply reply);
-
-       /**
-        * Notifies a listener when a Sone was locked.
-        *
-        * @param sone
-        *            The Sone that was locked
-        */
-       public void soneLocked(Sone sone);
-
-       /**
-        * Notifies a listener that a Sone was unlocked.
-        *
-        * @param sone
-        *            The Sone that was unlocked
-        */
-       public void soneUnlocked(Sone sone);
-
-       /**
-        * Notifies a listener that the insert of the given Sone has started.
-        *
-        * @see SoneInsertListener#insertStarted(Sone)
-        * @param sone
-        *            The Sone that is being inserted
-        */
-       public void soneInserting(Sone sone);
-
-       /**
-        * Notifies a listener that the insert of the given Sone has finished
-        * successfully.
-        *
-        * @see SoneInsertListener#insertFinished(Sone, long)
-        * @param sone
-        *            The Sone that has been inserted
-        * @param insertDuration
-        *            The insert duration (in milliseconds)
-        */
-       public void soneInserted(Sone sone, long insertDuration);
-
-       /**
-        * Notifies a listener that the insert of the given Sone was aborted.
-        *
-        * @see SoneInsertListener#insertAborted(Sone, Throwable)
-        * @param sone
-        *            The Sone that was inserted
-        * @param cause
-        *            The cause for the abortion (may be {@code null})
-        */
-       public void soneInsertAborted(Sone sone, Throwable cause);
-
-       /**
-        * Notifies a listener that a new version has been found.
-        *
-        * @param version
-        *            The version that was found
-        * @param releaseTime
-        *            The release time of the new version
-        * @param latestEdition
-        *            The latest edition of the Sone homepage
-        */
-       public void updateFound(Version version, long releaseTime, long latestEdition);
-
-       /**
-        * Notifies a listener that an image has started being inserted.
-        *
-        * @param image
-        *            The image that is now inserted
-        */
-       public void imageInsertStarted(Image image);
-
-       /**
-        * Notifies a listener that an image insert was aborted by the user.
-        *
-        * @param image
-        *            The image that is not inserted anymore
-        */
-       public void imageInsertAborted(Image image);
-
-       /**
-        * Notifies a listener that an image was successfully inserted.
-        *
-        * @param image
-        *            The image that was inserted
-        */
-       public void imageInsertFinished(Image image);
-
-       /**
-        * Notifies a listener that an image failed to be inserted.
-        *
-        * @param image
-        *            The image that could not be inserted
-        * @param cause
-        *            The reason for the failed insert
-        */
-       public void imageInsertFailed(Image image, Throwable cause);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/core/CoreListenerManager.java b/src/main/java/net/pterodactylus/sone/core/CoreListenerManager.java
deleted file mode 100644 (file)
index 0385698..0000000
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Sone - CoreListenerManager.java - Copyright © 2010–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import net.pterodactylus.sone.data.Image;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.event.AbstractListenerManager;
-import net.pterodactylus.util.version.Version;
-
-/**
- * Manager for {@link CoreListener}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class CoreListenerManager extends AbstractListenerManager<Core, CoreListener> {
-
-       /**
-        * Creates a new core listener manager.
-        *
-        * @param source
-        *            The Core
-        */
-       public CoreListenerManager(Core source) {
-               super(source);
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Notifies all listeners that a new Sone has been discovered.
-        *
-        * @see CoreListener#newSoneFound(Sone)
-        * @param sone
-        *            The discovered sone
-        */
-       void fireNewSoneFound(Sone sone) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.newSoneFound(sone);
-               }
-       }
-
-       /**
-        * Notifies all listeners that a new post has been found.
-        *
-        * @see CoreListener#newPostFound(Post)
-        * @param post
-        *            The new post
-        */
-       void fireNewPostFound(Post post) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.newPostFound(post);
-               }
-       }
-
-       /**
-        * Notifies all listeners that a new reply has been found.
-        *
-        * @see CoreListener#newReplyFound(PostReply)
-        * @param reply
-        *            The new reply
-        */
-       void fireNewReplyFound(PostReply reply) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.newReplyFound(reply);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the given Sone is now marked as known.
-        *
-        * @see CoreListener#markSoneKnown(Sone)
-        * @param sone
-        *            The known Sone
-        */
-       void fireMarkSoneKnown(Sone sone) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.markSoneKnown(sone);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the given post is now marked as known.
-        *
-        * @param post
-        *            The known post
-        */
-       void fireMarkPostKnown(Post post) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.markPostKnown(post);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the given reply is now marked as known.
-        *
-        * @param reply
-        *            The known reply
-        */
-       void fireMarkReplyKnown(PostReply reply) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.markReplyKnown(reply);
-               }
-       }
-
-       /**
-        * Notifies all listener that the given Sone was removed.
-        *
-        * @see CoreListener#soneRemoved(Sone)
-        * @param sone
-        *            The removed Sone
-        */
-       void fireSoneRemoved(Sone sone) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.soneRemoved(sone);
-               }
-       }
-
-       /**
-        * Notifies all listener that the given post was removed.
-        *
-        * @see CoreListener#postRemoved(Post)
-        * @param post
-        *            The removed post
-        */
-       void firePostRemoved(Post post) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.postRemoved(post);
-               }
-       }
-
-       /**
-        * Notifies all listener that the given reply was removed.
-        *
-        * @see CoreListener#replyRemoved(PostReply)
-        * @param reply
-        *            The removed reply
-        */
-       void fireReplyRemoved(PostReply reply) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.replyRemoved(reply);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the given Sone was locked.
-        *
-        * @see CoreListener#soneLocked(Sone)
-        * @param sone
-        *            The Sone that was locked
-        */
-       void fireSoneLocked(Sone sone) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.soneLocked(sone);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the given Sone was unlocked.
-        *
-        * @see CoreListener#soneUnlocked(Sone)
-        * @param sone
-        *            The Sone that was unlocked
-        */
-       void fireSoneUnlocked(Sone sone) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.soneUnlocked(sone);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the insert of the given Sone has started.
-        *
-        * @see SoneInsertListener#insertStarted(Sone)
-        * @param sone
-        *            The Sone being inserted
-        */
-       void fireSoneInserting(Sone sone) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.soneInserting(sone);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the insert of the given Sone has finished
-        * successfully.
-        *
-        * @see SoneInsertListener#insertFinished(Sone, long)
-        * @param sone
-        *            The Sone that was inserted
-        * @param insertDuration
-        *            The insert duration (in milliseconds)
-        */
-       void fireSoneInserted(Sone sone, long insertDuration) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.soneInserted(sone, insertDuration);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the insert of the given Sone was aborted.
-        *
-        * @see SoneInsertListener#insertStarted(Sone)
-        * @param sone
-        *            The Sone being inserted
-        * @param cause
-        *            The cause for the abortion (may be {@code null}
-        */
-       void fireSoneInsertAborted(Sone sone, Throwable cause) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.soneInsertAborted(sone, cause);
-               }
-       }
-
-       /**
-        * Notifies all listeners that a new version was found.
-        *
-        * @see CoreListener#updateFound(Version, long, long)
-        * @param version
-        *            The new version
-        * @param releaseTime
-        *            The release time of the new version
-        * @param latestEdition
-        *            The latest edition of the Sone homepage
-        */
-       void fireUpdateFound(Version version, long releaseTime, long latestEdition) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.updateFound(version, releaseTime, latestEdition);
-               }
-       }
-
-       /**
-        * Notifies all listeners that an image has started being inserted.
-        *
-        * @see CoreListener#imageInsertStarted(Image)
-        * @param image
-        *            The image that is now inserted
-        */
-       void fireImageInsertStarted(Image image) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.imageInsertStarted(image);
-               }
-       }
-
-       /**
-        * Notifies all listeners that an image insert was aborted by the user.
-        *
-        * @see CoreListener#imageInsertAborted(Image)
-        * @param image
-        *            The image that is not inserted anymore
-        */
-       void fireImageInsertAborted(Image image) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.imageInsertAborted(image);
-               }
-       }
-
-       /**
-        * Notifies all listeners that an image was successfully inserted.
-        *
-        * @see CoreListener#imageInsertFinished(Image)
-        * @param image
-        *            The image that was inserted
-        */
-       void fireImageInsertFinished(Image image) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.imageInsertFinished(image);
-               }
-       }
-
-       /**
-        * Notifies all listeners that an image failed to be inserted.
-        *
-        * @see CoreListener#imageInsertFailed(Image, Throwable)
-        * @param image
-        *            The image that could not be inserted
-        * @param cause
-        *            The cause of the failure
-        */
-       void fireImageInsertFailed(Image image, Throwable cause) {
-               for (CoreListener coreListener : getListeners()) {
-                       coreListener.imageInsertFailed(image, cause);
-               }
-       }
-
-}
index a8f3efa..e92cf8e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetInterface.java - Copyright © 2010–2012 David Roden
+ * Sone - FreenetInterface.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 package net.pterodactylus.sone.core;
 
 import java.net.MalformedURLException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
+import net.pterodactylus.sone.core.event.ImageInsertFailedEvent;
+import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
+import net.pterodactylus.sone.core.event.ImageInsertStartedEvent;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.TemporaryImage;
-import net.pterodactylus.util.collection.Pair;
 import net.pterodactylus.util.logging.Logging;
 
 import com.db4o.ObjectContainer;
+import com.google.common.eventbus.EventBus;
+import com.google.inject.Inject;
 
 import freenet.client.ClientMetadata;
 import freenet.client.FetchException;
@@ -65,6 +69,9 @@ public class FreenetInterface {
        /** The logger. */
        private static final Logger logger = Logging.getLogger(FreenetInterface.class);
 
+       /** The event bus. */
+       private final EventBus eventBus;
+
        /** The node to interact with. */
        private final Node node;
 
@@ -80,10 +87,14 @@ public class FreenetInterface {
        /**
         * Creates a new Freenet interface.
         *
+        * @param eventBus
+        *            The event bus
         * @param node
         *            The node to interact with
         */
-       public FreenetInterface(Node node) {
+       @Inject
+       public FreenetInterface(EventBus eventBus, Node node) {
+               this.eventBus = eventBus;
                this.node = node;
                this.client = node.clientCore.makeClient(RequestStarter.INTERACTIVE_PRIORITY_CLASS, false, true);
        }
@@ -99,13 +110,13 @@ public class FreenetInterface {
         *            The URI to fetch
         * @return The result of the fetch, or {@code null} if an error occured
         */
-       public Pair<FreenetURI, FetchResult> fetchUri(FreenetURI uri) {
+       public Fetched fetchUri(FreenetURI uri) {
                FetchResult fetchResult = null;
                FreenetURI currentUri = new FreenetURI(uri);
                while (true) {
                        try {
                                fetchResult = client.fetch(currentUri);
-                               return new Pair<FreenetURI, FetchResult>(currentUri, fetchResult);
+                               return new Fetched(currentUri, fetchResult);
                        } catch (FetchException fe1) {
                                if (fe1.getMode() == FetchException.PERMANENT_REDIRECT) {
                                        currentUri = fe1.newURI;
@@ -219,7 +230,8 @@ public class FreenetInterface {
                                }
                        };
                        soneUskCallbacks.put(sone.getId(), uskCallback);
-                       node.clientCore.uskManager.subscribe(USK.create(sone.getRequestUri()), uskCallback, (System.currentTimeMillis() - sone.getTime()) < 7 * 24 * 60 * 60 * 1000, (HighLevelSimpleClientImpl) client);
+                       boolean runBackgroundFetch = (System.currentTimeMillis() - sone.getTime()) < TimeUnit.DAYS.toMillis(7);
+                       node.clientCore.uskManager.subscribe(USK.create(sone.getRequestUri()), uskCallback, runBackgroundFetch, (HighLevelSimpleClientImpl) client);
                } catch (MalformedURLException mue1) {
                        logger.log(Level.WARNING, String.format("Could not subscribe USK “%s”!", sone.getRequestUri()), mue1);
                }
@@ -300,6 +312,56 @@ public class FreenetInterface {
        }
 
        /**
+        * Container for a fetched URI and the {@link FetchResult}.
+        *
+        * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+        */
+       public static class Fetched {
+
+               /** The fetched URI. */
+               private final FreenetURI freenetUri;
+
+               /** The fetch result. */
+               private final FetchResult fetchResult;
+
+               /**
+                * Creates a new fetched URI.
+                *
+                * @param freenetUri
+                *            The URI that was fetched
+                * @param fetchResult
+                *            The fetch result
+                */
+               public Fetched(FreenetURI freenetUri, FetchResult fetchResult) {
+                       this.freenetUri = freenetUri;
+                       this.fetchResult = fetchResult;
+               }
+
+               //
+               // ACCESSORS
+               //
+
+               /**
+                * Returns the fetched URI.
+                *
+                * @return The fetched URI
+                */
+               public FreenetURI getFreenetUri() {
+                       return freenetUri;
+               }
+
+               /**
+                * Returns the fetch result.
+                *
+                * @return The fetch result
+                */
+               public FetchResult getFetchResult() {
+                       return fetchResult;
+               }
+
+       }
+
+       /**
         * Callback for USK watcher events.
         *
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
@@ -324,9 +386,12 @@ public class FreenetInterface {
        }
 
        /**
-        * Insert token that can be used to add {@link ImageInsertListener}s and
-        * cancel a running insert.
+        * Insert token that can cancel a running insert and sends events.
         *
+        * @see ImageInsertAbortedEvent
+        * @see ImageInsertStartedEvent
+        * @see ImageInsertFailedEvent
+        * @see ImageInsertFinishedEvent
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
        public class InsertToken implements ClientPutCallback {
@@ -334,9 +399,6 @@ public class FreenetInterface {
                /** The image being inserted. */
                private final Image image;
 
-               /** The list of registered image insert listeners. */
-               private final List<ImageInsertListener> imageInsertListeners = Collections.synchronizedList(new ArrayList<ImageInsertListener>());
-
                /** The client putter. */
                private ClientPutter clientPutter;
 
@@ -354,30 +416,6 @@ public class FreenetInterface {
                }
 
                //
-               // LISTENER MANAGEMENT
-               //
-
-               /**
-                * Adds the given listener to the list of registered listener.
-                *
-                * @param imageInsertListener
-                *            The listener to add
-                */
-               public void addImageInsertListener(ImageInsertListener imageInsertListener) {
-                       imageInsertListeners.add(imageInsertListener);
-               }
-
-               /**
-                * Removes the given listener from the list of registered listener.
-                *
-                * @param imageInsertListener
-                *            The listener to remove
-                */
-               public void removeImageInsertListener(ImageInsertListener imageInsertListener) {
-                       imageInsertListeners.remove(imageInsertListener);
-               }
-
-               //
                // ACCESSORS
                //
 
@@ -385,15 +423,13 @@ public class FreenetInterface {
                 * Sets the client putter that is inserting the image. This will also
                 * signal all registered listeners that the image has started.
                 *
-                * @see ImageInsertListener#imageInsertStarted(Image)
                 * @param clientPutter
                 *            The client putter
                 */
+               @SuppressWarnings("synthetic-access")
                public void setClientPutter(ClientPutter clientPutter) {
                        this.clientPutter = clientPutter;
-                       for (ImageInsertListener imageInsertListener : imageInsertListeners) {
-                               imageInsertListener.imageInsertStarted(image);
-                       }
+                       eventBus.post(new ImageInsertStartedEvent(image));
                }
 
                //
@@ -402,15 +438,11 @@ public class FreenetInterface {
 
                /**
                 * Cancels the running insert.
-                *
-                * @see ImageInsertListener#imageInsertAborted(Image)
                 */
                @SuppressWarnings("synthetic-access")
                public void cancel() {
                        clientPutter.cancel(null, node.clientCore.clientContext);
-                       for (ImageInsertListener imageInsertListener : imageInsertListeners) {
-                               imageInsertListener.imageInsertAborted(image);
-                       }
+                       eventBus.post(new ImageInsertAbortedEvent(image));
                }
 
                //
@@ -429,13 +461,12 @@ public class FreenetInterface {
                 * {@inheritDoc}
                 */
                @Override
+               @SuppressWarnings("synthetic-access")
                public void onFailure(InsertException insertException, BaseClientPutter clientPutter, ObjectContainer objectContainer) {
-                       for (ImageInsertListener imageInsertListener : imageInsertListeners) {
-                               if ((insertException != null) && ("Cancelled by user".equals(insertException.getMessage()))) {
-                                       imageInsertListener.imageInsertAborted(image);
-                               } else {
-                                       imageInsertListener.imageInsertFailed(image, insertException);
-                               }
+                       if ((insertException != null) && ("Cancelled by user".equals(insertException.getMessage()))) {
+                               eventBus.post(new ImageInsertAbortedEvent(image));
+                       } else {
+                               eventBus.post(new ImageInsertFailedEvent(image, insertException));
                        }
                }
 
@@ -467,10 +498,9 @@ public class FreenetInterface {
                 * {@inheritDoc}
                 */
                @Override
+               @SuppressWarnings("synthetic-access")
                public void onSuccess(BaseClientPutter clientPutter, ObjectContainer objectContainer) {
-                       for (ImageInsertListener imageInsertListener : imageInsertListeners) {
-                               imageInsertListener.imageInsertFinished(image, resultingUri);
-                       }
+                       eventBus.post(new ImageInsertFinishedEvent(image, resultingUri));
                }
 
        }
diff --git a/src/main/java/net/pterodactylus/sone/core/ImageInsertListener.java b/src/main/java/net/pterodactylus/sone/core/ImageInsertListener.java
deleted file mode 100644 (file)
index ca1ab3e..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Sone - ImageInsertListener.java - Copyright © 2011–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import java.util.EventListener;
-
-import net.pterodactylus.sone.core.FreenetInterface.InsertToken;
-import net.pterodactylus.sone.data.Image;
-import freenet.keys.FreenetURI;
-
-/**
- * Listener interface for objects that want to be notified about the status of
- * an image insert.
- *
- * @see ImageInserter#insertImage(net.pterodactylus.sone.data.TemporaryImage,
- *      Image)
- * @see FreenetInterface#insertImage(net.pterodactylus.sone.data.TemporaryImage,
- *      Image, net.pterodactylus.sone.core.FreenetInterface.InsertToken)
- * @see InsertToken
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface ImageInsertListener extends EventListener {
-
-       /**
-        * Notifies a listener that the insert of the given image started.
-        *
-        * @param image
-        *            The image that is being inserted
-        */
-       public void imageInsertStarted(Image image);
-
-       /**
-        * Notifies a listener that the insert of the given image was aborted by the
-        * user.
-        *
-        * @param image
-        *            The image that is no longer being inserted
-        */
-       public void imageInsertAborted(Image image);
-
-       /**
-        * Notifies a listener that the given image was inserted successfully.
-        *
-        * @param image
-        *            The image that was inserted
-        * @param key
-        *            The final key of the image
-        */
-       public void imageInsertFinished(Image image, FreenetURI key);
-
-       /**
-        * Notifies a listener that the given image could not be inserted.
-        *
-        * @param image
-        *            The image that could not be inserted
-        * @param cause
-        *            The cause of the insertion failure
-        */
-       public void imageInsertFailed(Image image, Throwable cause);
-
-}
index 4fc7e89..791663f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageInserter.java - Copyright © 2011–2012 David Roden
+ * Sone - ImageInserter.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,9 @@
 
 package net.pterodactylus.sone.core;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -27,7 +30,6 @@ import net.pterodactylus.sone.core.FreenetInterface.InsertToken;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.TemporaryImage;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.validation.Validation;
 
 /**
  * The image inserter is responsible for inserting images using
@@ -42,9 +44,6 @@ public class ImageInserter {
        /** The logger. */
        private static final Logger logger = Logging.getLogger(ImageInserter.class);
 
-       /** The core. */
-       private final Core core;
-
        /** The freenet interface. */
        private final FreenetInterface freenetInterface;
 
@@ -54,19 +53,15 @@ public class ImageInserter {
        /**
         * Creates a new image inserter.
         *
-        * @param core
-        *            The Sone core
         * @param freenetInterface
         *            The freenet interface
         */
-       public ImageInserter(Core core, FreenetInterface freenetInterface) {
-               this.core = core;
+       public ImageInserter(FreenetInterface freenetInterface) {
                this.freenetInterface = freenetInterface;
        }
 
        /**
-        * Inserts the given image. The {@link #core} will automatically added as
-        * {@link ImageInsertListener} to the created {@link InsertToken}.
+        * Inserts the given image.
         *
         * @param temporaryImage
         *            The temporary image data
@@ -74,11 +69,12 @@ public class ImageInserter {
         *            The image
         */
        public void insertImage(TemporaryImage temporaryImage, Image image) {
-               Validation.begin().isNotNull("Temporary Image", temporaryImage).isNotNull("Image", image).check().isEqual("Image IDs", image.getId(), temporaryImage.getId()).check();
+               checkNotNull(temporaryImage, "temporaryImage must not be null");
+               checkNotNull(image, "image must not be null");
+               checkArgument(image.getId().equals(temporaryImage.getId()), "image IDs must match");
                try {
                        InsertToken insertToken = freenetInterface.new InsertToken(image);
                        insertTokens.put(image.getId(), insertToken);
-                       insertToken.addImageInsertListener(core);
                        freenetInterface.insertImage(temporaryImage, image, insertToken);
                } catch (SoneException se1) {
                        logger.log(Level.WARNING, "Could not insert image!", se1);
@@ -98,7 +94,6 @@ public class ImageInserter {
                        return;
                }
                insertToken.cancel();
-               insertToken.removeImageInsertListener(core);
        }
 
 }
index 0ea895a..c8e3589 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Options.java - Copyright © 2010–2012 David Roden
+ * Sone - Options.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,7 +21,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
-import net.pterodactylus.util.validation.Validator;
+import com.google.common.base.Predicate;
 
 /**
  * Stores various options that influence Sone’s behaviour.
@@ -68,9 +68,8 @@ public class Options {
                 *
                 * @param value
                 *            The value to validate
-                * @return {@code true} if this option does not have a {@link Validator}
-                *         , or the {@link Validator} validates this object, {@code
-                *         false} otherwise
+                * @return {@code true} if this option does not have a validator, or the
+                *         validator validates this object, {@code false} otherwise
                 */
                public boolean validate(T value);
 
@@ -127,7 +126,7 @@ public class Options {
                private volatile T value;
 
                /** The validator. */
-               private Validator<T> validator;
+               private Predicate<T> validator;
 
                /** The option watcher. */
                private final OptionWatcher<T> optionWatcher;
@@ -150,7 +149,7 @@ public class Options {
                 * @param validator
                 *            The validator for value validation (may be {@code null})
                 */
-               public DefaultOption(T defaultValue, Validator<T> validator) {
+               public DefaultOption(T defaultValue, Predicate<T> validator) {
                        this(defaultValue, validator, null);
                }
 
@@ -176,7 +175,7 @@ public class Options {
                 * @param optionWatcher
                 *            The option watcher (may be {@code null})
                 */
-               public DefaultOption(T defaultValue, Validator<T> validator, OptionWatcher<T> optionWatcher) {
+               public DefaultOption(T defaultValue, Predicate<T> validator, OptionWatcher<T> optionWatcher) {
                        this.defaultValue = defaultValue;
                        this.validator = validator;
                        this.optionWatcher = optionWatcher;
@@ -214,7 +213,7 @@ public class Options {
                 */
                @Override
                public boolean validate(T value) {
-                       return (validator == null) || (value == null) || validator.validate(value);
+                       return (validator == null) || (value == null) || validator.apply(value);
                }
 
                /**
@@ -222,7 +221,7 @@ public class Options {
                 */
                @Override
                public void set(T value) {
-                       if ((value != null) && (validator != null) && (!validator.validate(value))) {
+                       if ((value != null) && (validator != null) && (!validator.apply(value))) {
                                throw new IllegalArgumentException("New Value (" + value + ") could not be validated.");
                        }
                        T oldValue = this.value;
diff --git a/src/main/java/net/pterodactylus/sone/core/PostProvider.java b/src/main/java/net/pterodactylus/sone/core/PostProvider.java
deleted file mode 100644 (file)
index 6e46326..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Sone - PostProvider.java - Copyright © 2011–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import net.pterodactylus.sone.data.Post;
-
-/**
- * Interface for objects that can provide {@link Post}s by their ID.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface PostProvider {
-
-       /**
-        * Returns the post with the given ID, if it exists. If it does not exist
-        * and {@code create} is {@code false}, {@code null} is returned; otherwise,
-        * a new post with the given ID is created and returned.
-        *
-        * @param postId
-        *            The ID of the post to return
-        * @param create
-        *            {@code true} to create a new post if no post with the given ID
-        *            exists, {@code false} to return {@code null} instead
-        * @return The post with the given ID, or {@code null}
-        */
-       public Post getPost(String postId, boolean create);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/core/Preferences.java b/src/main/java/net/pterodactylus/sone/core/Preferences.java
new file mode 100644 (file)
index 0000000..16d9453
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ * Sone - Preferences.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import net.pterodactylus.sone.fcp.FcpInterface;
+import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
+
+/**
+ * Convenience interface for external classes that want to access the core’s
+ * configuration.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class Preferences {
+
+       /** The wrapped options. */
+       private final Options options;
+
+       /**
+        * Creates a new preferences object wrapped around the given options.
+        *
+        * @param options
+        *            The options to wrap
+        */
+       public Preferences(Options options) {
+               this.options = options;
+       }
+
+       /**
+        * Returns the insertion delay.
+        *
+        * @return The insertion delay
+        */
+       public int getInsertionDelay() {
+               return options.getIntegerOption("InsertionDelay").get();
+       }
+
+       /**
+        * Validates the given insertion delay.
+        *
+        * @param insertionDelay
+        *            The insertion delay to validate
+        * @return {@code true} if the given insertion delay was valid,
+        *         {@code false} otherwise
+        */
+       public boolean validateInsertionDelay(Integer insertionDelay) {
+               return options.getIntegerOption("InsertionDelay").validate(insertionDelay);
+       }
+
+       /**
+        * Sets the insertion delay
+        *
+        * @param insertionDelay
+        *            The new insertion delay, or {@code null} to restore it to
+        *            the default value
+        * @return This preferences
+        */
+       public Preferences setInsertionDelay(Integer insertionDelay) {
+               options.getIntegerOption("InsertionDelay").set(insertionDelay);
+               return this;
+       }
+
+       /**
+        * Returns the number of posts to show per page.
+        *
+        * @return The number of posts to show per page
+        */
+       public int getPostsPerPage() {
+               return options.getIntegerOption("PostsPerPage").get();
+       }
+
+       /**
+        * Validates the number of posts per page.
+        *
+        * @param postsPerPage
+        *            The number of posts per page
+        * @return {@code true} if the number of posts per page was valid,
+        *         {@code false} otherwise
+        */
+       public boolean validatePostsPerPage(Integer postsPerPage) {
+               return options.getIntegerOption("PostsPerPage").validate(postsPerPage);
+       }
+
+       /**
+        * Sets the number of posts to show per page.
+        *
+        * @param postsPerPage
+        *            The number of posts to show per page
+        * @return This preferences object
+        */
+       public Preferences setPostsPerPage(Integer postsPerPage) {
+               options.getIntegerOption("PostsPerPage").set(postsPerPage);
+               return this;
+       }
+
+       /**
+        * Returns the number of images to show per page.
+        *
+        * @return The number of images to show per page
+        */
+       public int getImagesPerPage() {
+               return options.getIntegerOption("ImagesPerPage").get();
+       }
+
+       /**
+        * Validates the number of images per page.
+        *
+        * @param imagesPerPage
+        *            The number of images per page
+        * @return {@code true} if the number of images per page was valid,
+        *         {@code false} otherwise
+        */
+       public boolean validateImagesPerPage(Integer imagesPerPage) {
+               return options.getIntegerOption("ImagesPerPage").validate(imagesPerPage);
+       }
+
+       /**
+        * Sets the number of images per page.
+        *
+        * @param imagesPerPage
+        *            The number of images per page
+        * @return This preferences object
+        */
+       public Preferences setImagesPerPage(Integer imagesPerPage) {
+               options.getIntegerOption("ImagesPerPage").set(imagesPerPage);
+               return this;
+       }
+
+       /**
+        * Returns the number of characters per post, or <code>-1</code> if the
+        * posts should not be cut off.
+        *
+        * @return The numbers of characters per post
+        */
+       public int getCharactersPerPost() {
+               return options.getIntegerOption("CharactersPerPost").get();
+       }
+
+       /**
+        * Validates the number of characters per post.
+        *
+        * @param charactersPerPost
+        *            The number of characters per post
+        * @return {@code true} if the number of characters per post was valid,
+        *         {@code false} otherwise
+        */
+       public boolean validateCharactersPerPost(Integer charactersPerPost) {
+               return options.getIntegerOption("CharactersPerPost").validate(charactersPerPost);
+       }
+
+       /**
+        * Sets the number of characters per post.
+        *
+        * @param charactersPerPost
+        *            The number of characters per post, or <code>-1</code> to
+        *            not cut off the posts
+        * @return This preferences objects
+        */
+       public Preferences setCharactersPerPost(Integer charactersPerPost) {
+               options.getIntegerOption("CharactersPerPost").set(charactersPerPost);
+               return this;
+       }
+
+       /**
+        * Returns the number of characters the shortened post should have.
+        *
+        * @return The number of characters of the snippet
+        */
+       public int getPostCutOffLength() {
+               return options.getIntegerOption("PostCutOffLength").get();
+       }
+
+       /**
+        * Validates the number of characters after which to cut off the post.
+        *
+        * @param postCutOffLength
+        *            The number of characters of the snippet
+        * @return {@code true} if the number of characters of the snippet is
+        *         valid, {@code false} otherwise
+        */
+       public boolean validatePostCutOffLength(Integer postCutOffLength) {
+               return options.getIntegerOption("PostCutOffLength").validate(postCutOffLength);
+       }
+
+       /**
+        * Sets the number of characters the shortened post should have.
+        *
+        * @param postCutOffLength
+        *            The number of characters of the snippet
+        * @return This preferences
+        */
+       public Preferences setPostCutOffLength(Integer postCutOffLength) {
+               options.getIntegerOption("PostCutOffLength").set(postCutOffLength);
+               return this;
+       }
+
+       /**
+        * Returns whether Sone requires full access to be even visible.
+        *
+        * @return {@code true} if Sone requires full access, {@code false}
+        *         otherwise
+        */
+       public boolean isRequireFullAccess() {
+               return options.getBooleanOption("RequireFullAccess").get();
+       }
+
+       /**
+        * Sets whether Sone requires full access to be even visible.
+        *
+        * @param requireFullAccess
+        *            {@code true} if Sone requires full access, {@code false}
+        *            otherwise
+        */
+       public void setRequireFullAccess(Boolean requireFullAccess) {
+               options.getBooleanOption("RequireFullAccess").set(requireFullAccess);
+       }
+
+       /**
+        * Returns the positive trust.
+        *
+        * @return The positive trust
+        */
+       public int getPositiveTrust() {
+               return options.getIntegerOption("PositiveTrust").get();
+       }
+
+       /**
+        * Validates the positive trust.
+        *
+        * @param positiveTrust
+        *            The positive trust to validate
+        * @return {@code true} if the positive trust was valid, {@code false}
+        *         otherwise
+        */
+       public boolean validatePositiveTrust(Integer positiveTrust) {
+               return options.getIntegerOption("PositiveTrust").validate(positiveTrust);
+       }
+
+       /**
+        * Sets the positive trust.
+        *
+        * @param positiveTrust
+        *            The new positive trust, or {@code null} to restore it to
+        *            the default vlaue
+        * @return This preferences
+        */
+       public Preferences setPositiveTrust(Integer positiveTrust) {
+               options.getIntegerOption("PositiveTrust").set(positiveTrust);
+               return this;
+       }
+
+       /**
+        * Returns the negative trust.
+        *
+        * @return The negative trust
+        */
+       public int getNegativeTrust() {
+               return options.getIntegerOption("NegativeTrust").get();
+       }
+
+       /**
+        * Validates the negative trust.
+        *
+        * @param negativeTrust
+        *            The negative trust to validate
+        * @return {@code true} if the negative trust was valid, {@code false}
+        *         otherwise
+        */
+       public boolean validateNegativeTrust(Integer negativeTrust) {
+               return options.getIntegerOption("NegativeTrust").validate(negativeTrust);
+       }
+
+       /**
+        * Sets the negative trust.
+        *
+        * @param negativeTrust
+        *            The negative trust, or {@code null} to restore it to the
+        *            default value
+        * @return The preferences
+        */
+       public Preferences setNegativeTrust(Integer negativeTrust) {
+               options.getIntegerOption("NegativeTrust").set(negativeTrust);
+               return this;
+       }
+
+       /**
+        * Returns the trust comment. This is the comment that is set in the web
+        * of trust when a trust value is assigned to an identity.
+        *
+        * @return The trust comment
+        */
+       public String getTrustComment() {
+               return options.getStringOption("TrustComment").get();
+       }
+
+       /**
+        * Sets the trust comment.
+        *
+        * @param trustComment
+        *            The trust comment, or {@code null} to restore it to the
+        *            default value
+        * @return This preferences
+        */
+       public Preferences setTrustComment(String trustComment) {
+               options.getStringOption("TrustComment").set(trustComment);
+               return this;
+       }
+
+       /**
+        * Returns whether the {@link FcpInterface FCP interface} is currently
+        * active.
+        *
+        * @see FcpInterface#setActive(boolean)
+        * @return {@code true} if the FCP interface is currently active,
+        *         {@code false} otherwise
+        */
+       public boolean isFcpInterfaceActive() {
+               return options.getBooleanOption("ActivateFcpInterface").get();
+       }
+
+       /**
+        * Sets whether the {@link FcpInterface FCP interface} is currently
+        * active.
+        *
+        * @see FcpInterface#setActive(boolean)
+        * @param fcpInterfaceActive
+        *            {@code true} to activate the FCP interface, {@code false}
+        *            to deactivate the FCP interface
+        * @return This preferences object
+        */
+       public Preferences setFcpInterfaceActive(boolean fcpInterfaceActive) {
+               options.getBooleanOption("ActivateFcpInterface").set(fcpInterfaceActive);
+               return this;
+       }
+
+       /**
+        * Returns the action level for which full access to the FCP interface
+        * is required.
+        *
+        * @return The action level for which full access to the FCP interface
+        *         is required
+        */
+       public FullAccessRequired getFcpFullAccessRequired() {
+               return FullAccessRequired.values()[options.getIntegerOption("FcpFullAccessRequired").get()];
+       }
+
+       /**
+        * Sets the action level for which full access to the FCP interface is
+        * required
+        *
+        * @param fcpFullAccessRequired
+        *            The action level
+        * @return This preferences
+        */
+       public Preferences setFcpFullAccessRequired(FullAccessRequired fcpFullAccessRequired) {
+               options.getIntegerOption("FcpFullAccessRequired").set((fcpFullAccessRequired != null) ? fcpFullAccessRequired.ordinal() : null);
+               return this;
+       }
+
+}
index 72ff008..e0ee363 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneDownloader.java - Copyright © 2010–2012 David Roden
+ * Sone - SoneDownloader.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@ import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import net.pterodactylus.sone.core.FreenetInterface.Fetched;
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Client;
 import net.pterodactylus.sone.data.Image;
@@ -34,7 +35,8 @@ import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
-import net.pterodactylus.util.collection.Pair;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.PostReplyBuilder;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
@@ -95,10 +97,9 @@ public class SoneDownloader extends AbstractService {
         *            The Sone to add
         */
        public void addSone(Sone sone) {
-               if (!sones.add(sone)) {
-                       freenetInterface.unregisterUsk(sone);
+               if (sones.add(sone)) {
+                       freenetInterface.registerUsk(sone, this);
                }
-               freenetInterface.registerUsk(sone, this);
        }
 
        /**
@@ -155,15 +156,16 @@ public class SoneDownloader extends AbstractService {
                FreenetURI requestUri = soneUri.setMetaString(new String[] { "sone.xml" });
                sone.setStatus(SoneStatus.downloading);
                try {
-                       Pair<FreenetURI, FetchResult> fetchResults = freenetInterface.fetchUri(requestUri);
+                       Fetched fetchResults = freenetInterface.fetchUri(requestUri);
                        if (fetchResults == null) {
                                /* TODO - mark Sone as bad. */
                                return null;
                        }
-                       logger.log(Level.FINEST, String.format("Got %d bytes back.", fetchResults.getRight().size()));
-                       Sone parsedSone = parseSone(sone, fetchResults.getRight(), fetchResults.getLeft());
+                       logger.log(Level.FINEST, String.format("Got %d bytes back.", fetchResults.getFetchResult().size()));
+                       Sone parsedSone = parseSone(sone, fetchResults.getFetchResult(), fetchResults.getFreenetUri());
                        if (parsedSone != null) {
                                if (!fetchOnly) {
+                                       parsedSone.setStatus((parsedSone.getTime() == 0) ? SoneStatus.unknown : SoneStatus.idle);
                                        core.updateSone(parsedSone);
                                        addSone(parsedSone);
                                }
@@ -236,7 +238,7 @@ public class SoneDownloader extends AbstractService {
                        return null;
                }
 
-               Sone sone = new Sone(originalSone.getId()).setIdentity(originalSone.getIdentity());
+               Sone sone = new Sone(originalSone.getId(), false).setIdentity(originalSone.getIdentity());
 
                SimpleXML soneXml;
                try {
@@ -372,11 +374,13 @@ public class SoneDownloader extends AbstractService {
                                        return null;
                                }
                                try {
-                                       Post post = core.getPost(postId).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText);
+                                       PostBuilder postBuilder = core.postBuilder();
+                                       /* TODO - parse time correctly. */
+                                       postBuilder.withId(postId).from(sone.getId()).withTime(Long.parseLong(postTime)).withText(postText);
                                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
-                                               post.setRecipient(core.getSone(postRecipientId));
+                                               postBuilder.to(postRecipientId);
                                        }
-                                       posts.add(post);
+                                       posts.add(postBuilder.build());
                                } catch (NumberFormatException nfe1) {
                                        /* TODO - mark Sone as bad. */
                                        logger.log(Level.WARNING, String.format("Downloaded post for Sone %s with invalid time: %s", sone, postTime));
@@ -403,7 +407,10 @@ public class SoneDownloader extends AbstractService {
                                        return null;
                                }
                                try {
-                                       replies.add(core.getReply(replyId).setSone(sone).setPost(core.getPost(replyPostId)).setTime(Long.parseLong(replyTime)).setText(replyText));
+                                       PostReplyBuilder postReplyBuilder = core.postReplyBuilder();
+                                       /* TODO - parse time correctly. */
+                                       postReplyBuilder.withId(replyId).from(sone.getId()).to(replyPostId).withTime(Long.parseLong(replyTime)).withText(replyText);
+                                       replies.add(postReplyBuilder.build());
                                } catch (NumberFormatException nfe1) {
                                        /* TODO - mark Sone as bad. */
                                        logger.log(Level.WARNING, String.format("Downloaded reply for Sone %s with invalid time: %s", sone, replyTime));
index 323fd66..f1af354 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneException.java - Copyright © 2010–2012 David Roden
+ * Sone - SoneException.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5cb8669..5c3f7cd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInsertException.java - Copyright © 2011–2012 David Roden
+ * Sone - SoneInsertException.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneInsertListener.java b/src/main/java/net/pterodactylus/sone/core/SoneInsertListener.java
deleted file mode 100644 (file)
index 6f99fe6..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Sone - SoneInsertListener.java - Copyright © 2011–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import java.util.EventListener;
-
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Listener for Sone insert events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface SoneInsertListener extends EventListener {
-
-       /**
-        * Notifies a listener that a Sone is now being inserted.
-        *
-        * @param sone
-        *            The Sone being inserted
-        */
-       public void insertStarted(Sone sone);
-
-       /**
-        * Notifies a listener that a Sone has been successfully inserted.
-        *
-        * @param sone
-        *            The Sone that was inserted
-        * @param insertDuration
-        *            The duration of the insert (in milliseconds)
-        */
-       public void insertFinished(Sone sone, long insertDuration);
-
-       /**
-        * Notifies a listener that the insert of the given Sone was aborted.
-        *
-        * @param sone
-        *            The Sone that was being inserted
-        * @param cause
-        *            The cause of the abortion (may be {@code null})
-        */
-       public void insertAborted(Sone sone, Throwable cause);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneInsertListenerManager.java b/src/main/java/net/pterodactylus/sone/core/SoneInsertListenerManager.java
deleted file mode 100644 (file)
index 6841e7f..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Sone - SoneInsertListenerManager.java - Copyright © 2011–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.event.AbstractListenerManager;
-
-/**
- * Manager for {@link SoneInsertListener}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class SoneInsertListenerManager extends AbstractListenerManager<Sone, SoneInsertListener> {
-
-       /**
-        * Creates a new Sone insert listener manager.
-        *
-        * @param sone
-        *            The sone being inserted
-        */
-       public SoneInsertListenerManager(Sone sone) {
-               super(sone);
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Notifies all listeners that the insert of the Sone has started.
-        *
-        * @see SoneInsertListener#insertStarted(Sone)
-        */
-       void fireInsertStarted() {
-               for (SoneInsertListener soneInsertListener : getListeners()) {
-                       soneInsertListener.insertStarted(getSource());
-               }
-       }
-
-       /**
-        * Notifies all listeners that the insert of the Sone has finished
-        * successfully.
-        *
-        * @see SoneInsertListener#insertFinished(Sone, long)
-        * @param insertDuration
-        *            The insert duration (in milliseconds)
-        */
-       void fireInsertFinished(long insertDuration) {
-               for (SoneInsertListener soneInsertListener : getListeners()) {
-                       soneInsertListener.insertFinished(getSource(), insertDuration);
-               }
-       }
-
-       /**
-        * Notifies all listeners that the insert of the Sone was aborted.
-        *
-        * @see SoneInsertListener#insertAborted(Sone, Throwable)
-        * @param cause
-        *            The cause of the abortion (may be {@code null}
-        */
-       void fireInsertAborted(Throwable cause) {
-               for (SoneInsertListener soneInsertListener : getListeners()) {
-                       soneInsertListener.insertAborted(getSource(), cause);
-               }
-       }
-
-}
index 534985a..8efcc73 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneInserter.java - Copyright © 2010–2012 David Roden
+ * Sone - SoneInserter.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,22 +20,22 @@ package net.pterodactylus.sone.core;
 import java.io.InputStreamReader;
 import java.io.StringWriter;
 import java.nio.charset.Charset;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import net.pterodactylus.sone.core.event.SoneInsertAbortedEvent;
+import net.pterodactylus.sone.core.event.SoneInsertedEvent;
+import net.pterodactylus.sone.core.event.SoneInsertingEvent;
+import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.SoneStatus;
 import net.pterodactylus.sone.freenet.StringBucket;
 import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.util.collection.ListBuilder;
-import net.pterodactylus.util.collection.ReverseComparator;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.service.AbstractService;
@@ -47,6 +47,11 @@ import net.pterodactylus.util.template.TemplateContextFactory;
 import net.pterodactylus.util.template.TemplateException;
 import net.pterodactylus.util.template.TemplateParser;
 import net.pterodactylus.util.template.XmlFilter;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Ordering;
+import com.google.common.eventbus.EventBus;
+
 import freenet.client.async.ManifestElement;
 import freenet.keys.FreenetURI;
 
@@ -78,15 +83,15 @@ public class SoneInserter extends AbstractService {
        /** The core. */
        private final Core core;
 
+       /** The event bus. */
+       private final EventBus eventBus;
+
        /** The Freenet interface. */
        private final FreenetInterface freenetInterface;
 
        /** The Sone to insert. */
        private final Sone sone;
 
-       /** The insert listener manager. */
-       private SoneInsertListenerManager soneInsertListenerManager;
-
        /** Whether a modification has been detected. */
        private volatile boolean modified = false;
 
@@ -98,41 +103,19 @@ public class SoneInserter extends AbstractService {
         *
         * @param core
         *            The core
+        * @param eventBus
+        *            The event bus
         * @param freenetInterface
         *            The freenet interface
         * @param sone
         *            The Sone to insert
         */
-       public SoneInserter(Core core, FreenetInterface freenetInterface, Sone sone) {
+       public SoneInserter(Core core, EventBus eventBus, FreenetInterface freenetInterface, Sone sone) {
                super("Sone Inserter for “" + sone.getName() + "”", false);
                this.core = core;
+               this.eventBus = eventBus;
                this.freenetInterface = freenetInterface;
                this.sone = sone;
-               this.soneInsertListenerManager = new SoneInsertListenerManager(sone);
-       }
-
-       //
-       // LISTENER MANAGEMENT
-       //
-
-       /**
-        * Adds a listener for Sone insert events.
-        *
-        * @param soneInsertListener
-        *            The Sone insert listener
-        */
-       public void addSoneInsertListener(SoneInsertListener soneInsertListener) {
-               soneInsertListenerManager.addListener(soneInsertListener);
-       }
-
-       /**
-        * Removes a listener for Sone insert events.
-        *
-        * @param soneInsertListener
-        *            The Sone insert listener
-        */
-       public void removeSoneInsertListener(SoneInsertListener soneInsertListener) {
-               soneInsertListenerManager.removeListener(soneInsertListener);
        }
 
        //
@@ -192,89 +175,91 @@ public class SoneInserter extends AbstractService {
                long lastModificationTime = 0;
                String lastInsertedFingerprint = lastInsertFingerprint;
                String lastFingerprint = "";
-               while (!shouldStop()) { try {
-                       /* check every seconds. */
-                       sleep(1000);
+               while (!shouldStop()) {
+                       try {
+                               /* check every seconds. */
+                               sleep(1000);
 
-                       /* don’t insert locked Sones. */
-                       if (core.isLocked(sone)) {
-                               /* trigger redetection when the Sone is unlocked. */
-                               synchronized (sone) {
-                                       modified = !sone.getFingerprint().equals(lastInsertedFingerprint);
+                               /* don’t insert locked Sones. */
+                               if (core.isLocked(sone)) {
+                                       /* trigger redetection when the Sone is unlocked. */
+                                       synchronized (sone) {
+                                               modified = !sone.getFingerprint().equals(lastInsertedFingerprint);
+                                       }
+                                       lastFingerprint = "";
+                                       lastModificationTime = 0;
+                                       continue;
                                }
-                               lastFingerprint = "";
-                               lastModificationTime = 0;
-                               continue;
-                       }
 
-                       InsertInformation insertInformation = null;
-                       synchronized (sone) {
-                               String fingerprint = sone.getFingerprint();
-                               if (!fingerprint.equals(lastFingerprint)) {
-                                       if (fingerprint.equals(lastInsertedFingerprint)) {
-                                               modified = false;
-                                               lastModificationTime = 0;
-                                               logger.log(Level.FINE, String.format("Sone %s has been reverted to last insert state.", sone));
-                                       } else {
-                                               lastModificationTime = System.currentTimeMillis();
-                                               modified = true;
-                                               logger.log(Level.FINE, String.format("Sone %s has been modified, waiting %d seconds before inserting.", sone.getName(), insertionDelay));
+                               InsertInformation insertInformation = null;
+                               synchronized (sone) {
+                                       String fingerprint = sone.getFingerprint();
+                                       if (!fingerprint.equals(lastFingerprint)) {
+                                               if (fingerprint.equals(lastInsertedFingerprint)) {
+                                                       modified = false;
+                                                       lastModificationTime = 0;
+                                                       logger.log(Level.FINE, String.format("Sone %s has been reverted to last insert state.", sone));
+                                               } else {
+                                                       lastModificationTime = System.currentTimeMillis();
+                                                       modified = true;
+                                                       logger.log(Level.FINE, String.format("Sone %s has been modified, waiting %d seconds before inserting.", sone.getName(), insertionDelay));
+                                               }
+                                               lastFingerprint = fingerprint;
+                                       }
+                                       if (modified && (lastModificationTime > 0) && ((System.currentTimeMillis() - lastModificationTime) > (insertionDelay * 1000))) {
+                                               lastInsertedFingerprint = fingerprint;
+                                               insertInformation = new InsertInformation(sone);
                                        }
-                                       lastFingerprint = fingerprint;
-                               }
-                               if (modified && (lastModificationTime > 0) && ((System.currentTimeMillis() - lastModificationTime) > (insertionDelay * 1000))) {
-                                       lastInsertedFingerprint = fingerprint;
-                                       insertInformation = new InsertInformation(sone);
                                }
-                       }
 
-                       if (insertInformation != null) {
-                               logger.log(Level.INFO, String.format("Inserting Sone “%s”…", sone.getName()));
-
-                               boolean success = false;
-                               try {
-                                       sone.setStatus(SoneStatus.inserting);
-                                       long insertTime = System.currentTimeMillis();
-                                       insertInformation.setTime(insertTime);
-                                       soneInsertListenerManager.fireInsertStarted();
-                                       FreenetURI finalUri = freenetInterface.insertDirectory(insertInformation.getInsertUri(), insertInformation.generateManifestEntries(), "index.html");
-                                       soneInsertListenerManager.fireInsertFinished(System.currentTimeMillis() - insertTime);
-                                       /* at this point we might already be stopped. */
-                                       if (shouldStop()) {
-                                               /* if so, bail out, don’t change anything. */
-                                               break;
+                               if (insertInformation != null) {
+                                       logger.log(Level.INFO, String.format("Inserting Sone “%s”…", sone.getName()));
+
+                                       boolean success = false;
+                                       try {
+                                               sone.setStatus(SoneStatus.inserting);
+                                               long insertTime = System.currentTimeMillis();
+                                               insertInformation.setTime(insertTime);
+                                               eventBus.post(new SoneInsertingEvent(sone));
+                                               FreenetURI finalUri = freenetInterface.insertDirectory(insertInformation.getInsertUri(), insertInformation.generateManifestEntries(), "index.html");
+                                               eventBus.post(new SoneInsertedEvent(sone, System.currentTimeMillis() - insertTime));
+                                               /* at this point we might already be stopped. */
+                                               if (shouldStop()) {
+                                                       /* if so, bail out, don’t change anything. */
+                                                       break;
+                                               }
+                                               sone.setTime(insertTime);
+                                               sone.setLatestEdition(finalUri.getEdition());
+                                               core.touchConfiguration();
+                                               success = true;
+                                               logger.log(Level.INFO, String.format("Inserted Sone “%s” at %s.", sone.getName(), finalUri));
+                                       } catch (SoneException se1) {
+                                               eventBus.post(new SoneInsertAbortedEvent(sone, se1));
+                                               logger.log(Level.WARNING, String.format("Could not insert Sone “%s”!", sone.getName()), se1);
+                                       } finally {
+                                               sone.setStatus(SoneStatus.idle);
                                        }
-                                       sone.setTime(insertTime);
-                                       sone.setLatestEdition(finalUri.getEdition());
-                                       core.touchConfiguration();
-                                       success = true;
-                                       logger.log(Level.INFO, String.format("Inserted Sone “%s” at %s.", sone.getName(), finalUri));
-                               } catch (SoneException se1) {
-                                       soneInsertListenerManager.fireInsertAborted(se1);
-                                       logger.log(Level.WARNING, String.format("Could not insert Sone “%s”!", sone.getName()), se1);
-                               } finally {
-                                       sone.setStatus(SoneStatus.idle);
-                               }
 
-                               /*
-                                * reset modification counter if Sone has not been modified
-                                * while it was inserted.
-                                */
-                               if (success) {
-                                       synchronized (sone) {
-                                               if (lastInsertedFingerprint.equals(sone.getFingerprint())) {
-                                                       logger.log(Level.FINE, String.format("Sone “%s” was not modified further, resetting counter…", sone));
-                                                       lastModificationTime = 0;
-                                                       lastInsertFingerprint = lastInsertedFingerprint;
-                                                       core.touchConfiguration();
-                                                       modified = false;
+                                       /*
+                                        * reset modification counter if Sone has not been modified
+                                        * while it was inserted.
+                                        */
+                                       if (success) {
+                                               synchronized (sone) {
+                                                       if (lastInsertedFingerprint.equals(sone.getFingerprint())) {
+                                                               logger.log(Level.FINE, String.format("Sone “%s” was not modified further, resetting counter…", sone));
+                                                               lastModificationTime = 0;
+                                                               lastInsertFingerprint = lastInsertedFingerprint;
+                                                               core.touchConfiguration();
+                                                               modified = false;
+                                                       }
                                                }
                                        }
                                }
+                       } catch (Throwable t1) {
+                               logger.log(Level.SEVERE, "SoneInserter threw an Exception!", t1);
                        }
-               } catch (Throwable t1) {
-                       logger.log(Level.SEVERE, "SoneInserter threw an Exception!", t1);
-               }}
+               }
        }
 
        /**
@@ -302,11 +287,11 @@ public class SoneInserter extends AbstractService {
                        soneProperties.put("requestUri", sone.getRequestUri());
                        soneProperties.put("insertUri", sone.getInsertUri());
                        soneProperties.put("profile", sone.getProfile());
-                       soneProperties.put("posts", new ListBuilder<Post>(new ArrayList<Post>(sone.getPosts())).sort(Post.TIME_COMPARATOR).get());
-                       soneProperties.put("replies", new ListBuilder<PostReply>(new ArrayList<PostReply>(sone.getReplies())).sort(new ReverseComparator<Reply<?>>(Reply.TIME_COMPARATOR)).get());
+                       soneProperties.put("posts", Ordering.from(Post.TIME_COMPARATOR).sortedCopy(sone.getPosts()));
+                       soneProperties.put("replies", Ordering.from(Reply.TIME_COMPARATOR).reverse().sortedCopy(sone.getReplies()));
                        soneProperties.put("likedPostIds", new HashSet<String>(sone.getLikedPostIds()));
                        soneProperties.put("likedReplyIds", new HashSet<String>(sone.getLikedReplyIds()));
-                       soneProperties.put("albums", sone.getAllAlbums());
+                       soneProperties.put("albums", FluentIterable.from(sone.getAlbums()).transformAndConcat(Album.FLATTENER).toList());
                }
 
                //
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneProvider.java b/src/main/java/net/pterodactylus/sone/core/SoneProvider.java
deleted file mode 100644 (file)
index 25f4d8b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Sone - SoneProvider.java - Copyright © 2011–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import net.pterodactylus.sone.data.Sone;
-
-/**
- * Interface for objects that can provide {@link Sone}s by their ID.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface SoneProvider {
-
-       /**
-        * Returns the Sone with the given ID, if it exists. If it does not exist
-        * and {@code create} is {@code false}, {@code null} is returned; otherwise,
-        * a new Sone with the given ID is created and returned.
-        *
-        * @param soneId
-        *            The ID of the Sone to return
-        * @param create
-        *            {@code true} to create a new Sone if no Sone with the given ID
-        *            exists, {@code false} to return {@code null} instead
-        * @return The Sone with the given ID, or {@code null}
-        */
-       public Sone getSone(String soneId, boolean create);
-
-}
index 0c1e64e..ed3d819 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneRescuer.java - Copyright © 2011–2012 David Roden
+ * Sone - SoneRescuer.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneUri.java b/src/main/java/net/pterodactylus/sone/core/SoneUri.java
new file mode 100644 (file)
index 0000000..6c5bf16
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - SoneUri.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import java.net.MalformedURLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.util.logging.Logging;
+import freenet.keys.FreenetURI;
+
+/**
+ * Helper class that creates {@link FreenetURI}s for Sone to insert to and
+ * request from.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneUri {
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(SoneUri.class);
+
+       /**
+        * Generate a Sone URI from the given URI.
+        *
+        * @param uri
+        *            The URI to derive the Sone URI from
+        * @return The derived URI
+        */
+       public static FreenetURI create(String uri) {
+               try {
+                       return new FreenetURI(uri).setDocName("Sone").setMetaString(new String[0]);
+               } catch (MalformedURLException mue1) {
+                       /* this should never happen. */
+                       logger.log(Level.WARNING, String.format("Could not create Sone URI from URI: %s", uri), mue1);
+                       return null;
+               }
+       }
+
+}
index 3f4d235..b1248ab 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UpdateChecker.java - Copyright © 2011–2012 David Roden
+ * Sone - UpdateChecker.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,12 +26,16 @@ import java.util.Properties;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import net.pterodactylus.sone.core.FreenetInterface.Fetched;
+import net.pterodactylus.sone.core.event.UpdateFoundEvent;
 import net.pterodactylus.sone.main.SonePlugin;
-import net.pterodactylus.util.collection.Pair;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.version.Version;
-import freenet.client.FetchResult;
+
+import com.google.common.eventbus.EventBus;
+import com.google.inject.Inject;
+
 import freenet.keys.FreenetURI;
 import freenet.support.api.Bucket;
 
@@ -49,14 +53,14 @@ public class UpdateChecker {
        private static final String SONE_HOMEPAGE = "USK@nwa8lHa271k2QvJ8aa0Ov7IHAV-DFOCFgmDt3X6BpCI,DuQSUZiI~agF8c-6tjsFFGuZ8eICrzWCILB60nT8KKo,AQACAAE/sone/";
 
        /** The current latest known edition. */
-       private static final int LATEST_EDITION = 55;
+       private static final int LATEST_EDITION = 56;
+
+       /** The event bus. */
+       private final EventBus eventBus;
 
        /** The Freenet interface. */
        private final FreenetInterface freenetInterface;
 
-       /** The update listener manager. */
-       private final UpdateListenerManager updateListenerManager = new UpdateListenerManager();
-
        /** The current URI of the homepage. */
        private FreenetURI currentUri;
 
@@ -72,38 +76,18 @@ public class UpdateChecker {
        /**
         * Creates a new update checker.
         *
+        * @param eventBus
+        *            The event bus
         * @param freenetInterface
         *            The freenet interface to use
         */
-       public UpdateChecker(FreenetInterface freenetInterface) {
+       @Inject
+       public UpdateChecker(EventBus eventBus, FreenetInterface freenetInterface) {
+               this.eventBus = eventBus;
                this.freenetInterface = freenetInterface;
        }
 
        //
-       // EVENT LISTENER MANAGEMENT
-       //
-
-       /**
-        * Adds the given listener to the list of registered listeners.
-        *
-        * @param updateListener
-        *            The listener to add
-        */
-       public void addUpdateListener(UpdateListener updateListener) {
-               updateListenerManager.addListener(updateListener);
-       }
-
-       /**
-        * Removes the given listener from the list of registered listeners.
-        *
-        * @param updateListener
-        *            The listener to remove
-        */
-       public void removeUpdateListener(UpdateListener updateListener) {
-               updateListenerManager.removeListener(updateListener);
-       }
-
-       //
        // ACCESSORS
        //
 
@@ -168,12 +152,12 @@ public class UpdateChecker {
                        public void editionFound(FreenetURI uri, long edition, boolean newKnownGood, boolean newSlot) {
                                logger.log(Level.FINEST, String.format("Found update for %s: %d, %s, %s", uri, edition, newKnownGood, newSlot));
                                if (newKnownGood || newSlot) {
-                                       Pair<FreenetURI, FetchResult> uriResult = freenetInterface.fetchUri(uri.setMetaString(new String[] { "sone.properties" }));
+                                       Fetched uriResult = freenetInterface.fetchUri(uri.setMetaString(new String[] { "sone.properties" }));
                                        if (uriResult == null) {
                                                logger.log(Level.WARNING, String.format("Could not fetch properties of latest homepage: %s", uri));
                                                return;
                                        }
-                                       Bucket resultBucket = uriResult.getRight().asBucket();
+                                       Bucket resultBucket = uriResult.getFetchResult().asBucket();
                                        try {
                                                parseProperties(resultBucket.getInputStream(), edition);
                                                latestEdition = edition;
@@ -202,8 +186,7 @@ public class UpdateChecker {
         * Parses the properties of the latest version and fires events, if
         * necessary.
         *
-        * @see UpdateListener#updateFound(Version, long, long)
-        * @see UpdateListenerManager#fireUpdateFound(Version, long, long)
+        * @see UpdateFoundEvent
         * @param propertiesInputStream
         *            The input stream to parse
         * @param edition
@@ -241,7 +224,7 @@ public class UpdateChecker {
                        currentLatestVersion = version;
                        latestVersionDate = releaseTime;
                        logger.log(Level.INFO, String.format("Found new version: %s (%tc)", version, new Date(releaseTime)));
-                       updateListenerManager.fireUpdateFound(version, releaseTime, edition);
+                       eventBus.post(new UpdateFoundEvent(version, releaseTime, edition));
                }
        }
 
diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateListener.java b/src/main/java/net/pterodactylus/sone/core/UpdateListener.java
deleted file mode 100644 (file)
index 2d52f16..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Sone - UpdateListener.java - Copyright © 2011–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import java.util.EventListener;
-
-import net.pterodactylus.util.version.Version;
-
-/**
- * Listener interface for {@link UpdateChecker} events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface UpdateListener extends EventListener {
-
-       /**
-        * Notifies a listener that a newer version than the current version was
-        * found.
-        *
-        * @param version
-        *            The version that was found
-        * @param releaseTime
-        *            The release time of the version
-        * @param latestEdition
-        *            The latest edition of the Sone homepage
-        */
-       public void updateFound(Version version, long releaseTime, long latestEdition);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/core/UpdateListenerManager.java b/src/main/java/net/pterodactylus/sone/core/UpdateListenerManager.java
deleted file mode 100644 (file)
index 31fdbed..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Sone - UpdateListenerManager.java - Copyright © 2011–2012 David Roden
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package net.pterodactylus.sone.core;
-
-import net.pterodactylus.util.event.AbstractListenerManager;
-import net.pterodactylus.util.version.Version;
-
-/**
- * Listener manager for {@link UpdateListener} events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class UpdateListenerManager extends AbstractListenerManager<Void, UpdateListener> {
-
-       /**
-        * Creates a new update listener manager.
-        */
-       public UpdateListenerManager() {
-               super(null);
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Notifies all listeners that a new version has been found.
-        *
-        * @param version
-        *            The new version
-        * @param releaseTime
-        *            The release time of the new version
-        * @param latestEdition
-        *            The latest edition of the Sone homepage
-        */
-       void fireUpdateFound(Version version, long releaseTime, long latestEdition) {
-               for (UpdateListener updateListener : getListeners()) {
-                       updateListener.updateFound(version, releaseTime, latestEdition);
-               }
-       }
-
-}
index bdfc1ae..d449835 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustUpdater.java - Copyright © 2012 David Roden
+ * Sone - WebOfTrustUpdater.java - Copyright © 2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.core;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.logging.Level;
@@ -31,7 +33,8 @@ import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
 import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.service.AbstractService;
-import net.pterodactylus.util.validation.Validation;
+
+import com.google.inject.Inject;
 
 /**
  * Updates WebOfTrust identity data in a background thread because communicating
@@ -60,6 +63,7 @@ public class WebOfTrustUpdater extends AbstractService {
         * @param webOfTrustConnector
         *            The web of trust connector
         */
+       @Inject
        public WebOfTrustUpdater(WebOfTrustConnector webOfTrustConnector) {
                super("Trust Updater");
                this.webOfTrustConnector = webOfTrustConnector;
@@ -290,7 +294,6 @@ public class WebOfTrustUpdater extends AbstractService {
                 *
                 * @return {@code true} if this job finished successfully, {@code false}
                 *         otherwise
-                *
                 * @see WebOfTrustUpdater#stop()
                 */
                @SuppressWarnings("synthetic-access")
@@ -471,9 +474,8 @@ public class WebOfTrustUpdater extends AbstractService {
                 */
                @SuppressWarnings("synthetic-access")
                public WebOfTrustContextUpdateJob(OwnIdentity ownIdentity, String context) {
-                       Validation.begin().isNotNull("OwnIdentity", ownIdentity).isNotNull("Context", context).check();
-                       this.ownIdentity = ownIdentity;
-                       this.context = context;
+                       this.ownIdentity = checkNotNull(ownIdentity, "ownIdentity must not be null");
+                       this.context = checkNotNull(context, "context must not be null");
                }
 
                //
diff --git a/src/main/java/net/pterodactylus/sone/core/event/ImageEvent.java b/src/main/java/net/pterodactylus/sone/core/event/ImageEvent.java
new file mode 100644 (file)
index 0000000..2af6408
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - ImageEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Image;
+
+/**
+ * Base class for {@link Image} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class ImageEvent {
+
+       /** The image this event is about. */
+       private final Image image;
+
+       /**
+        * Creates a new image event.
+        *
+        * @param image
+        *            The image this event is about
+        */
+       protected ImageEvent(Image image) {
+               this.image = image;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the image this event is about.
+        *
+        * @return The image this event is about
+        */
+       public Image image() {
+               return image;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/ImageInsertAbortedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/ImageInsertAbortedEvent.java
new file mode 100644 (file)
index 0000000..2431cf6
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - ImageInsertAbortedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Image;
+
+/**
+ * Event that signals that an {@link Image} insert is aborted.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ImageInsertAbortedEvent extends ImageEvent {
+
+       /**
+        * Creates a new “image insert aborted” event.
+        *
+        * @param image
+        *            The image whose insert aborted
+        */
+       public ImageInsertAbortedEvent(Image image) {
+               super(image);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/ImageInsertFailedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/ImageInsertFailedEvent.java
new file mode 100644 (file)
index 0000000..7800537
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Sone - ImageInsertFailedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Image;
+
+/**
+ * Event that signals that an {@link Image} insert has failed.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ImageInsertFailedEvent extends ImageEvent {
+
+       /** The cause of the insert failure. */
+       private final Throwable cause;
+
+       /**
+        * Creates a new “image insert failed” event.
+        *
+        * @param image
+        *            The image whose insert failed
+        * @param cause
+        *            The cause of the insert failure
+        */
+       public ImageInsertFailedEvent(Image image, Throwable cause) {
+               super(image);
+               this.cause = cause;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the cause of the insert failure.
+        *
+        * @return The cause of the insert failure
+        */
+       public Throwable cause() {
+               return cause;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/ImageInsertFinishedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/ImageInsertFinishedEvent.java
new file mode 100644 (file)
index 0000000..53023b0
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Sone - ImageInsertFinishedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Image;
+import freenet.keys.FreenetURI;
+
+/**
+ * Event that signals that an {@link Image} insert is finished.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ImageInsertFinishedEvent extends ImageEvent {
+
+       /** The URI of the image. */
+       private final FreenetURI resultingUri;
+
+       /**
+        * Creates a new “image insert finished” event.
+        *
+        * @param image
+        *            The image whose insert finished
+        * @param resultingUri
+        *            The resulting URI of the image
+        */
+       public ImageInsertFinishedEvent(Image image, FreenetURI resultingUri) {
+               super(image);
+               this.resultingUri = resultingUri;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the URI of the image.
+        *
+        * @return The URI of the image
+        */
+       public FreenetURI resultingUri() {
+               return resultingUri;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/ImageInsertStartedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/ImageInsertStartedEvent.java
new file mode 100644 (file)
index 0000000..3b60277
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - ImageInsertStartedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Image;
+
+/**
+ * Event that signals that an {@link Image} is not being inserted.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ImageInsertStartedEvent extends ImageEvent {
+
+       /**
+        * Creates a new “image is inserted” event.
+        *
+        * @param image
+        *            The image that is being inserted
+        */
+       public ImageInsertStartedEvent(Image image) {
+               super(image);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/MarkPostKnownEvent.java b/src/main/java/net/pterodactylus/sone/core/event/MarkPostKnownEvent.java
new file mode 100644 (file)
index 0000000..b8faac9
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Sone - MarkPostKnownEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Post;
+
+/**
+ * Event that signals that a {@link Post} has been marked as
+ * {@link Post#isKnown() known}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MarkPostKnownEvent extends PostEvent {
+
+       /**
+        * Creates a new “post marked known” event.
+        *
+        * @param post
+        *            The post that was marked as known
+        */
+       public MarkPostKnownEvent(Post post) {
+               super(post);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/MarkPostReplyKnownEvent.java b/src/main/java/net/pterodactylus/sone/core/event/MarkPostReplyKnownEvent.java
new file mode 100644 (file)
index 0000000..e49fb31
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Sone - MarkPostReplyKnownEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.PostReply;
+
+/**
+ * Event that signals that a {@link PostReply} has been marked as
+ * {@link PostReply#isKnown() known}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MarkPostReplyKnownEvent extends PostReplyEvent {
+
+       /**
+        * Creates a new “post reply marked known” event.
+        *
+        * @param postReply
+        *            The post reply that was marked as known
+        */
+       public MarkPostReplyKnownEvent(PostReply postReply) {
+               super(postReply);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/MarkSoneKnownEvent.java b/src/main/java/net/pterodactylus/sone/core/event/MarkSoneKnownEvent.java
new file mode 100644 (file)
index 0000000..48a5109
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Sone - MarkSoneKnownEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a {@link Sone} has been marked as
+ * {@link Sone#isKnown() known}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MarkSoneKnownEvent extends SoneEvent {
+
+       /**
+        * Creates a new “Sone marked known” event.
+        *
+        * @param sone
+        *            The Sone that was marked as known
+        */
+       public MarkSoneKnownEvent(Sone sone) {
+               super(sone);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/NewPostFoundEvent.java b/src/main/java/net/pterodactylus/sone/core/event/NewPostFoundEvent.java
new file mode 100644 (file)
index 0000000..a7529a8
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - NewPostFoundEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Post;
+
+/**
+ * Event that signals that a new post was found.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class NewPostFoundEvent extends PostEvent {
+
+       /**
+        * Creates a new “new post found” event.
+        *
+        * @param post
+        *            The post that was found
+        */
+       public NewPostFoundEvent(Post post) {
+               super(post);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.java b/src/main/java/net/pterodactylus/sone/core/event/NewPostReplyFoundEvent.java
new file mode 100644 (file)
index 0000000..fb52bcc
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - NewPostReplyFoundEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.PostReply;
+
+/**
+ * Event that signals that a new {@link PostReply} was found.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class NewPostReplyFoundEvent extends PostReplyEvent {
+
+       /**
+        * Creates a new “new post found” event.
+        *
+        * @param postReply
+        *            The post reply that was found
+        */
+       public NewPostReplyFoundEvent(PostReply postReply) {
+               super(postReply);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/NewSoneFoundEvent.java b/src/main/java/net/pterodactylus/sone/core/event/NewSoneFoundEvent.java
new file mode 100644 (file)
index 0000000..32c6b71
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - NewSoneFoundEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a new remote Sone was found.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class NewSoneFoundEvent extends SoneEvent {
+
+       /**
+        * Creates a new “new Sone found” event.
+        *
+        * @param sone
+        *            The Sone that was found
+        */
+       public NewSoneFoundEvent(Sone sone) {
+               super(sone);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/PostEvent.java b/src/main/java/net/pterodactylus/sone/core/event/PostEvent.java
new file mode 100644 (file)
index 0000000..aa47170
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - PostEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Post;
+
+/**
+ * Base class for post events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostEvent {
+
+       /** The post the event is about. */
+       private final Post post;
+
+       /**
+        * Creates a new post event.
+        *
+        * @param post
+        *            The post the event is about
+        */
+       protected PostEvent(Post post) {
+               this.post = post;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the post the event is about.
+        *
+        * @return The post the event is about
+        */
+       public Post post() {
+               return post;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/PostRemovedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/PostRemovedEvent.java
new file mode 100644 (file)
index 0000000..7e07df7
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - PostRemovedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Post;
+
+/**
+ * Event that signals that a {@link Post} was removed.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostRemovedEvent extends PostEvent {
+
+       /**
+        * Creates a new “post removed” event.
+        *
+        * @param post
+        *            The post that was removed
+        */
+       public PostRemovedEvent(Post post) {
+               super(post);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/PostReplyEvent.java b/src/main/java/net/pterodactylus/sone/core/event/PostReplyEvent.java
new file mode 100644 (file)
index 0000000..b5e9a1f
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - PostReplyEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.PostReply;
+
+/**
+ * Base class for {@link PostReply} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostReplyEvent {
+
+       /** The post reply the event is about. */
+       private final PostReply postReply;
+
+       /**
+        * Creates a new post reply event.
+        *
+        * @param postReply
+        *            The post reply the event is about
+        */
+       protected PostReplyEvent(PostReply postReply) {
+               this.postReply = postReply;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the post reply the event is about.
+        *
+        * @return The post reply the event is about
+        */
+       public PostReply postReply() {
+               return postReply;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/PostReplyRemovedEvent.java
new file mode 100644 (file)
index 0000000..1ed4f9b
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - PostReplyRemovedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.PostReply;
+
+/**
+ * Event that signals that a {@link PostReply} was removed.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostReplyRemovedEvent extends PostReplyEvent {
+
+       /**
+        * Creates a new “post reply removed” event.
+        *
+        * @param postReply
+        *            The post reply that was removed
+        */
+       public PostReplyRemovedEvent(PostReply postReply) {
+               super(postReply);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/SoneEvent.java b/src/main/java/net/pterodactylus/sone/core/event/SoneEvent.java
new file mode 100644 (file)
index 0000000..68691ed
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - SoneEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Base class for Sone events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class SoneEvent {
+
+       /** The Sone this event is about. */
+       private final Sone sone;
+
+       /**
+        * Creates a new Sone event.
+        *
+        * @param sone
+        *            The Sone this event is about
+        */
+       protected SoneEvent(Sone sone) {
+               this.sone = sone;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the Sone this event is about.
+        *
+        * @return The Sone this event is about
+        */
+       public Sone sone() {
+               return sone;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/SoneInsertAbortedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/SoneInsertAbortedEvent.java
new file mode 100644 (file)
index 0000000..5884839
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Sone - SoneInsertAbortedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a {@link Sone} insert was aborted.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneInsertAbortedEvent extends SoneEvent {
+
+       /** The cause of the abortion. */
+       private final Throwable cause;
+
+       /**
+        * Creates a new “Sone was inserted” event.
+        *
+        * @param sone
+        *            The Sone that was inserted
+        * @param cause
+        *            The cause of the abortion
+        */
+       public SoneInsertAbortedEvent(Sone sone, Throwable cause) {
+               super(sone);
+               this.cause = cause;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the cause of the abortion.
+        *
+        * @return The cause of the abortion (may be {@code null})
+        */
+       public Throwable cause() {
+               return cause;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/SoneInsertedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/SoneInsertedEvent.java
new file mode 100644 (file)
index 0000000..8a4280d
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Sone - SoneInsertedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a {@link Sone} was inserted.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneInsertedEvent extends SoneEvent {
+
+       /** The duration of the insert. */
+       private final long insertDuration;
+
+       /**
+        * Creates a new “Sone was inserted” event.
+        *
+        * @param sone
+        *            The Sone that was inserted
+        * @param insertDuration
+        *            The duration of the insert (in milliseconds)
+        */
+       public SoneInsertedEvent(Sone sone, long insertDuration) {
+               super(sone);
+               this.insertDuration = insertDuration;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the duration of the insert.
+        *
+        * @return The duration of the insert (in milliseconds)
+        */
+       public long insertDuration() {
+               return insertDuration;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/SoneInsertingEvent.java b/src/main/java/net/pterodactylus/sone/core/event/SoneInsertingEvent.java
new file mode 100644 (file)
index 0000000..37effe9
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - SoneInsertingEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a {@link Sone} is now being inserted.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneInsertingEvent extends SoneEvent {
+
+       /**
+        * Creates a new “Sone is being inserted” event.
+        *
+        * @param sone
+        *            The Sone that is being inserted
+        */
+       public SoneInsertingEvent(Sone sone) {
+               super(sone);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/SoneLockedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/SoneLockedEvent.java
new file mode 100644 (file)
index 0000000..a707aca
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Sone - SoneLockedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a {@link Sone} was locked. Only
+ * {@link Sone#isLocal() local Sones} can be locked.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneLockedEvent extends SoneEvent {
+
+       /**
+        * Creates a new “Sone locked” event.
+        *
+        * @param sone
+        *            The Sone that was locked
+        */
+       public SoneLockedEvent(Sone sone) {
+               super(sone);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/SoneRemovedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/SoneRemovedEvent.java
new file mode 100644 (file)
index 0000000..5b2e5d8
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - SoneRemovedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a {@link Sone} was removed.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneRemovedEvent extends SoneEvent {
+
+       /**
+        * Creates a new “Sone removed” event.
+        *
+        * @param sone
+        *            The Sone that was removed
+        */
+       public SoneRemovedEvent(Sone sone) {
+               super(sone);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/SoneUnlockedEvent.java b/src/main/java/net/pterodactylus/sone/core/event/SoneUnlockedEvent.java
new file mode 100644 (file)
index 0000000..86100b4
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Sone - SoneUnlockedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Event that signals that a {@link Sone} was unlocked. Only
+ * {@link Sone#isLocal() local Sones} can be locked.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneUnlockedEvent extends SoneEvent {
+
+       /**
+        * Creates a new “Sone unlocked” event.
+        *
+        * @param sone
+        *            The Sone that was unlocked
+        */
+       public SoneUnlockedEvent(Sone sone) {
+               super(sone);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/event/UpdateFoundEvent.java b/src/main/java/net/pterodactylus/sone/core/event/UpdateFoundEvent.java
new file mode 100644 (file)
index 0000000..2e7b341
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Sone - UpdateFoundEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core.event;
+
+import net.pterodactylus.util.version.Version;
+
+/**
+ * Event that signals that an update for Sone was found.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UpdateFoundEvent {
+
+       /** The version that was found. */
+       private final Version version;
+
+       /** The time the update was released. */
+       private final long releaseTime;
+
+       /** The latest edition of the update page. */
+       private final long latestEdition;
+
+       /**
+        * Creates a new “update found” event.
+        *
+        * @param version
+        *            The version of the update
+        * @param releaseTime
+        *            The release time of the update
+        * @param latestEdition
+        *            The latest edition of the update page
+        */
+       public UpdateFoundEvent(Version version, long releaseTime, long latestEdition) {
+               this.version = version;
+               this.releaseTime = releaseTime;
+               this.latestEdition = latestEdition;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the version of the update.
+        *
+        * @return The version of the update
+        */
+       public Version version() {
+               return version;
+       }
+
+       /**
+        * Returns the release time of the update.
+        *
+        * @return The releae time of the update (in milliseconds since Jan 1, 1970
+        *         UTC)
+        */
+       public long releaseTime() {
+               return releaseTime;
+       }
+
+       /**
+        * Returns the latest edition of the update page.
+        *
+        * @return The latest edition of the update page
+        */
+       public long latestEdition() {
+               return latestEdition;
+       }
+
+}
index 44b37b7..5375d98 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Album.java - Copyright © 2011–2012 David Roden
+ * Sone - Album.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.data;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -24,11 +28,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import net.pterodactylus.util.collection.IterableWrapper;
-import net.pterodactylus.util.collection.filter.NotNullFilter;
-import net.pterodactylus.util.collection.mapper.Mapper;
-import net.pterodactylus.util.object.Default;
-import net.pterodactylus.util.validation.Validation;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 
 /**
  * Container for images that can also contain nested {@link Album}s.
@@ -46,6 +53,20 @@ public class Album implements Fingerprintable {
                }
        };
 
+       /** Function that flattens the given album and all albums beneath it. */
+       public static final Function<Album, List<Album>> FLATTENER = new Function<Album, List<Album>>() {
+
+               @Override
+               public List<Album> apply(Album album) {
+                       List<Album> albums = new ArrayList<Album>();
+                       albums.add(album);
+                       for (Album subAlbum : album.getAlbums()) {
+                               albums.addAll(FluentIterable.from(ImmutableList.of(subAlbum)).transformAndConcat(FLATTENER).toList());
+                       }
+                       return albums;
+               }
+       };
+
        /** The ID of this album. */
        private final String id;
 
@@ -87,8 +108,7 @@ public class Album implements Fingerprintable {
         *            The ID of the album
         */
        public Album(String id) {
-               Validation.begin().isNotNull("Album ID", id).check();
-               this.id = id;
+               this.id = checkNotNull(id, "id must not be null");
        }
 
        //
@@ -122,7 +142,8 @@ public class Album implements Fingerprintable {
         * @return This album
         */
        public Album setSone(Sone sone) {
-               Validation.begin().isNotNull("New Album Owner", sone).isEither("Old Album Owner", this.sone, null, sone).check();
+               checkNotNull(sone, "sone must not be null");
+               checkState((this.sone == null) || (this.sone.equals(sone)), "album owner must not already be set to some other Sone");
                this.sone = sone;
                return this;
        }
@@ -143,7 +164,9 @@ public class Album implements Fingerprintable {
         *            The album to add
         */
        public void addAlbum(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.sone, sone).isEither("Old Album Parent", this.parent, null, album.parent).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
+               checkState((this.parent == null) || (this.parent.equals(album.parent)), "album must not already be set to some other Sone");
                album.setParent(this);
                if (!albums.contains(album)) {
                        albums.add(album);
@@ -157,7 +180,9 @@ public class Album implements Fingerprintable {
         *            The album to remove
         */
        public void removeAlbum(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.sone, sone).isEqual("Album Parent", album.parent, this).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.sone.equals(sone), "album must belong this album’s Sone");
+               checkArgument(equals(album.parent), "album must belong to this album");
                albums.remove(album);
                album.removeParent();
        }
@@ -172,7 +197,9 @@ public class Album implements Fingerprintable {
         *         <code>null</code> if the album did not change its place
         */
        public Album moveAlbumUp(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.sone, sone).isEqual("Album Parent", album.parent, this).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.sone.equals(sone), "album must belong to the same Sone as this album");
+               checkArgument(equals(album.parent), "album must belong to this album");
                int oldIndex = albums.indexOf(album);
                if (oldIndex <= 0) {
                        return null;
@@ -192,7 +219,9 @@ public class Album implements Fingerprintable {
         *         <code>null</code> if the album did not change its place
         */
        public Album moveAlbumDown(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.sone, sone).isEqual("Album Parent", album.parent, this).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.sone.equals(sone), "album must belong to the same Sone as this album");
+               checkArgument(equals(album.parent), "album must belong to this album");
                int oldIndex = albums.indexOf(album);
                if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
                        return null;
@@ -208,15 +237,14 @@ public class Album implements Fingerprintable {
         * @return The images in this album
         */
        public List<Image> getImages() {
-               return IterableWrapper.wrap(imageIds).map(new Mapper<String, Image>() {
+               return new ArrayList<Image>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
 
                        @Override
                        @SuppressWarnings("synthetic-access")
-                       public Image map(String imageId) {
+                       public Image apply(String imageId) {
                                return images.get(imageId);
                        }
-
-               }).filter(new NotNullFilter()).list();
+               }), Predicates.notNull()));
        }
 
        /**
@@ -226,7 +254,9 @@ public class Album implements Fingerprintable {
         *            The image to add
         */
        public void addImage(Image image) {
-               Validation.begin().isNotNull("Image", image).check().isNotNull("Image Owner", image.getSone()).check().isEqual("Image Owner", image.getSone(), sone).check();
+               checkNotNull(image, "image must not be null");
+               checkNotNull(image.getSone(), "image must have an owner");
+               checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
                if (image.getAlbum() != null) {
                        image.getAlbum().removeImage(image);
                }
@@ -247,7 +277,9 @@ public class Album implements Fingerprintable {
         *            The image to remove
         */
        public void removeImage(Image image) {
-               Validation.begin().isNotNull("Image", image).check().isEqual("Image Owner", image.getSone(), sone).check();
+               checkNotNull(image, "image must not be null");
+               checkNotNull(image.getSone(), "image must have an owner");
+               checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
                imageIds.remove(image.getId());
                images.remove(image.getId());
                if (image.getId().equals(albumImage)) {
@@ -269,7 +301,10 @@ public class Album implements Fingerprintable {
         *         <code>null</code> if the image did not change its place
         */
        public Image moveImageUp(Image image) {
-               Validation.begin().isNotNull("Image", image).check().isEqual("Image Album", image.getAlbum(), this).isEqual("Album Owner", image.getAlbum().getSone(), sone).check();
+               checkNotNull(image, "image must not be null");
+               checkNotNull(image.getSone(), "image must have an owner");
+               checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
+               checkArgument(image.getAlbum().equals(this), "image must belong to this album");
                int oldIndex = imageIds.indexOf(image.getId());
                if (oldIndex <= 0) {
                        return null;
@@ -289,7 +324,10 @@ public class Album implements Fingerprintable {
         *         <code>null</code> if the image did not change its place
         */
        public Image moveImageDown(Image image) {
-               Validation.begin().isNotNull("Image", image).check().isEqual("Image Album", image.getAlbum(), this).isEqual("Album Owner", image.getAlbum().getSone(), sone).check();
+               checkNotNull(image, "image must not be null");
+               checkNotNull(image.getSone(), "image must have an owner");
+               checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
+               checkArgument(image.getAlbum().equals(this), "image must belong to this album");
                int oldIndex = imageIds.indexOf(image.getId());
                if ((oldIndex == -1) || (oldIndex >= (imageIds.size() - 1))) {
                        return null;
@@ -309,7 +347,7 @@ public class Album implements Fingerprintable {
                if (albumImage == null) {
                        return null;
                }
-               return Default.forNull(images.get(albumImage), images.values().iterator().next());
+               return Optional.fromNullable(images.get(albumImage)).or(images.values().iterator().next());
        }
 
        /**
@@ -351,8 +389,7 @@ public class Album implements Fingerprintable {
         * @return This album
         */
        protected Album setParent(Album parent) {
-               Validation.begin().isNotNull("Album Parent", parent).check();
-               this.parent = parent;
+               this.parent = checkNotNull(parent, "parent must not be null");
                return this;
        }
 
@@ -383,8 +420,7 @@ public class Album implements Fingerprintable {
         * @return This album
         */
        public Album setTitle(String title) {
-               Validation.begin().isNotNull("Album Title", title).check();
-               this.title = title;
+               this.title = checkNotNull(title, "title must not be null");
                return this;
        }
 
@@ -405,8 +441,7 @@ public class Album implements Fingerprintable {
         * @return This album
         */
        public Album setDescription(String description) {
-               Validation.begin().isNotNull("Album Description", description).check();
-               this.description = description;
+               this.description = checkNotNull(description, "description must not be null");
                return this;
        }
 
@@ -419,33 +454,33 @@ public class Album implements Fingerprintable {
         */
        @Override
        public String getFingerprint() {
-               StringBuilder fingerprint = new StringBuilder();
-               fingerprint.append("Album(");
-               fingerprint.append("ID(").append(id).append(')');
-               fingerprint.append("Title(").append(title).append(')');
-               fingerprint.append("Description(").append(description).append(')');
+               Hasher hash = Hashing.sha256().newHasher();
+               hash.putString("Album(");
+               hash.putString("ID(").putString(id).putString(")");
+               hash.putString("Title(").putString(title).putString(")");
+               hash.putString("Description(").putString(description).putString(")");
                if (albumImage != null) {
-                       fingerprint.append("AlbumImage(").append(albumImage).append(')');
+                       hash.putString("AlbumImage(").putString(albumImage).putString(")");
                }
 
                /* add nested albums. */
-               fingerprint.append("Albums(");
+               hash.putString("Albums(");
                for (Album album : albums) {
-                       fingerprint.append(album.getFingerprint());
+                       hash.putString(album.getFingerprint());
                }
-               fingerprint.append(')');
+               hash.putString(")");
 
                /* add images. */
-               fingerprint.append("Images(");
+               hash.putString("Images(");
                for (Image image : getImages()) {
                        if (image.isInserted()) {
-                               fingerprint.append(image.getFingerprint());
+                               hash.putString(image.getFingerprint());
                        }
                }
-               fingerprint.append(')');
+               hash.putString(")");
 
-               fingerprint.append(')');
-               return fingerprint.toString();
+               hash.putString(")");
+               return hash.hash().toString();
        }
 
        //
index 93b8dbf..b77b9f2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Client.java - Copyright © 2010–2012 David Roden
+ * Sone - Client.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 96e6bc0..2cf34ed 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Fingerprintable.java - Copyright © 2011–2012 David Roden
+ * Sone - Fingerprintable.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 25a36e3..952d540 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Image.java - Copyright © 2011–2012 David Roden
+ * Sone - Image.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.data;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
 import java.util.UUID;
 
-import net.pterodactylus.util.validation.Validation;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 
 /**
  * Container for image metadata.
@@ -70,8 +75,7 @@ public class Image implements Fingerprintable {
         *            The ID of the image
         */
        public Image(String id) {
-               Validation.begin().isNotNull("Image ID", id).check();
-               this.id = id;
+               this.id = checkNotNull(id, "id must not be null");
        }
 
        //
@@ -105,7 +109,8 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setSone(Sone sone) {
-               Validation.begin().isNotNull("New Image Owner", sone).isEither("Old Image Owner", this.sone, null, sone).check();
+               checkNotNull(sone, "sone must not be null");
+               checkArgument((this.sone == null) || this.sone.equals(sone), "sone must not already be set to another sone");
                this.sone = sone;
                return this;
        }
@@ -128,7 +133,8 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setAlbum(Album album) {
-               Validation.begin().isNotNull("New Album", album).check().isEqual("Album Owner and Image Owner", album.getSone(), getSone()).check();
+               checkNotNull(album, "album must not be null");
+               checkNotNull(album.getSone().equals(getSone()), "album must belong to the same Sone as this image");
                this.album = album;
                return this;
        }
@@ -151,7 +157,8 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setKey(String key) {
-               Validation.begin().isNotNull("New Image Key", key).isEither("Old Image Key", this.key, null, key).check();
+               checkNotNull(key, "key must not be null");
+               checkState((this.key == null) || this.key.equals(key), "key must not be already set to another key");
                this.key = key;
                return this;
        }
@@ -187,7 +194,8 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setCreationTime(long creationTime) {
-               Validation.begin().isGreater("New Image Creation Time", creationTime, 0).isEither("Old Image Creation Time", this.creationTime, 0L, creationTime).check();
+               checkArgument(creationTime > 0, "creationTime must be > 0");
+               checkState((this.creationTime == 0) || (this.creationTime == creationTime), "creationTime must not already be set");
                this.creationTime = creationTime;
                return this;
        }
@@ -210,7 +218,8 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setWidth(int width) {
-               Validation.begin().isGreater("New Image Width", width, 0).isEither("Old Image Width", this.width, 0, width).check();
+               checkArgument(width > 0, "width must be > 0");
+               checkState((this.width == 0) || (this.width == width), "width must not already be set to another width");
                this.width = width;
                return this;
        }
@@ -233,7 +242,8 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setHeight(int height) {
-               Validation.begin().isGreater("New Image Height", height, 0).isEither("Old Image Height", this.height, 0, height).check();
+               checkArgument(height > 0, "height must be > 0");
+               checkState((this.height == 0) || (this.height == height), "height must not already be set to another height");
                this.height = height;
                return this;
        }
@@ -255,8 +265,7 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setTitle(String title) {
-               Validation.begin().isNotNull("Image Title", title).check();
-               this.title = title;
+               this.title = checkNotNull(title, "title must not be null");
                return this;
        }
 
@@ -277,8 +286,7 @@ public class Image implements Fingerprintable {
         * @return This image
         */
        public Image setDescription(String description) {
-               Validation.begin().isNotNull("Image Description", description).check();
-               this.description = description;
+               this.description = checkNotNull(description, "description must not be null");
                return this;
        }
 
@@ -291,13 +299,13 @@ public class Image implements Fingerprintable {
         */
        @Override
        public String getFingerprint() {
-               StringBuilder fingerprint = new StringBuilder();
-               fingerprint.append("Image(");
-               fingerprint.append("ID(").append(id).append(')');
-               fingerprint.append("Title(").append(title).append(')');
-               fingerprint.append("Description(").append(description).append(')');
-               fingerprint.append(')');
-               return fingerprint.toString();
+               Hasher hash = Hashing.sha256().newHasher();
+               hash.putString("Image(");
+               hash.putString("ID(").putString(id).putString(")");
+               hash.putString("Title(").putString(title).putString(")");
+               hash.putString("Description(").putString(description).putString(")");
+               hash.putString(")");
+               return hash.hash().toString();
        }
 
        //
index f171475..6523e9b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Post.java - Copyright © 2010–2012 David Roden
+ * Sone - Post.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,7 +19,8 @@ package net.pterodactylus.sone.data;
 
 import java.util.Comparator;
 
-import net.pterodactylus.util.collection.filter.Filter;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
 
 /**
  * A post is a short message that a user writes in his Sone to let other users
@@ -40,10 +41,10 @@ public interface Post {
        };
 
        /** Filter for posts with timestamps from the future. */
-       public static final Filter<Post> FUTURE_POSTS_FILTER = new Filter<Post>() {
+       public static final Predicate<Post> FUTURE_POSTS_FILTER = new Predicate<Post>() {
 
                @Override
-               public boolean filterObject(Post post) {
+               public boolean apply(Post post) {
                        return post.getTime() <= System.currentTimeMillis();
                }
 
@@ -68,29 +69,23 @@ public interface Post {
        public Sone getSone();
 
        /**
-        * Sets the Sone of this post.
+        * Returns the ID of the recipient {@link Sone}, or
+        * {@link Optional#absent()} if this post does not have a recipient.
         *
-        * @param sone
-        *            The Sone of this post
-        * @return This post (for method chaining)
+        * @return The ID of the recipient, or {@link Optional#absent()}
         */
-       public Post setSone(Sone sone);
+       public Optional<String> getRecipientId();
 
        /**
-        * Returns the recipient of this post, if any.
+        * Returns the recipient of this post, if any. As this method can return
+        * {@link Optional#absent()} if the post has a recipient which has not yet
+        * been loaded, it is recommended to use {@link #hasRecipient()} to check
+        * for the presence of a recipient.
         *
-        * @return The recipient of this post, or {@code null}
+        * @return The recipient of this post, or {@link Optional#absent()} if there
+        *         is no recipient
         */
-       public Sone getRecipient();
-
-       /**
-        * Sets the recipient of this post.
-        *
-        * @param recipient
-        *            The recipient of this post, or {@code null}
-        * @return This post (for method chaining)
-        */
-       public Post setRecipient(Sone recipient);
+       public Optional<Sone> getRecipient();
 
        /**
         * Returns the time of the post.
@@ -100,15 +95,6 @@ public interface Post {
        public long getTime();
 
        /**
-        * Sets the time of this post.
-        *
-        * @param time
-        *            The time of this post (in milliseconds since Jan 1, 1970 UTC)
-        * @return This post (for method chaining)
-        */
-       public Post setTime(long time);
-
-       /**
         * Returns the text of the post.
         *
         * @return The text of the post
@@ -116,15 +102,6 @@ public interface Post {
        public String getText();
 
        /**
-        * Sets the text of this post.
-        *
-        * @param text
-        *            The text of this post
-        * @return This post (for method chaining)
-        */
-       public Post setText(String text);
-
-       /**
         * Returns whether this post is known.
         *
         * @return {@code true} if this post is known, {@code false} otherwise
index 0f63be1..1e870b9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostReply.java - Copyright © 2010–2012 David Roden
+ * Sone - PostReply.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,8 @@
 
 package net.pterodactylus.sone.data;
 
-import java.util.UUID;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
 
 /**
  * A reply is like a {@link Post} but can never be posted on its own, it always
@@ -25,93 +26,32 @@ import java.util.UUID;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class PostReply extends Reply<PostReply> {
-
-       /** The Post this reply refers to. */
-       private volatile Post post;
+public interface PostReply extends Reply<PostReply> {
 
        /**
-        * Creates a new reply.
-        *
-        * @param id
-        *            The ID of the reply
+        * Filter that selects {@link PostReply}s that have a
+        * {@link Optional#isPresent() present} {@link #getPost() post}.
         */
-       public PostReply(String id) {
-               this(id, null, null, 0, null);
-       }
+       public static final Predicate<PostReply> HAS_POST_FILTER = new Predicate<PostReply>() {
 
-       /**
-        * Creates a new reply.
-        *
-        * @param sone
-        *            The sone that posted the reply
-        * @param post
-        *            The post to reply to
-        * @param text
-        *            The text of the reply
-        */
-       public PostReply(Sone sone, Post post, String text) {
-               this(sone, post, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new reply-
-        *
-        * @param sone
-        *            The sone that posted the reply
-        * @param post
-        *            The post to reply to
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        */
-       public PostReply(Sone sone, Post post, long time, String text) {
-               this(UUID.randomUUID().toString(), sone, post, time, text);
-       }
+               @Override
+               public boolean apply(PostReply postReply) {
+                       return postReply.getPost().isPresent();
+               }
+       };
 
        /**
-        * Creates a new reply-
+        * Returns the ID of the post this reply refers to.
         *
-        * @param sone
-        *            The sone that posted the reply
-        * @param id
-        *            The ID of the reply
-        * @param post
-        *            The post to reply to
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
+        * @return The ID of the post this reply refers to
         */
-       public PostReply(String id, Sone sone, Post post, long time, String text) {
-               super(id, sone, time, text);
-               this.post = post;
-       }
-
-       //
-       // ACCESSORS
-       //
+       public String getPostId();
 
        /**
         * Returns the post this reply refers to.
         *
         * @return The post this reply refers to
         */
-       public Post getPost() {
-               return post;
-       }
-
-       /**
-        * Sets the post this reply refers to.
-        *
-        * @param post
-        *            The post this reply refers to
-        * @return This reply (for method chaining)
-        */
-       public PostReply setPost(Post post) {
-               this.post = post;
-               return this;
-       }
+       public Optional<Post> getPost();
 
 }
index 6936409..5a4fde0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Profile.java - Copyright © 2010–2012 David Roden
+ * Sone - Profile.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.data;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 
-import net.pterodactylus.util.validation.Validation;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 
 /**
  * A profile stores personal information about a {@link Sone}. All information
@@ -237,7 +242,7 @@ public class Profile implements Fingerprintable {
                        this.avatar = null;
                        return this;
                }
-               Validation.begin().isEqual("Image Owner", avatar.getSone(), sone).check();
+               checkArgument(avatar.getSone().equals(sone), "avatar must belong to Sone");
                this.avatar = avatar.getId();
                return this;
        }
@@ -283,7 +288,7 @@ public class Profile implements Fingerprintable {
         *         field with the given ID
         */
        public Field getFieldById(String fieldId) {
-               Validation.begin().isNotNull("Field ID", fieldId).check();
+               checkNotNull(fieldId, "fieldId must not be null");
                for (Field field : fields) {
                        if (field.getId().equals(fieldId)) {
                                return field;
@@ -319,7 +324,9 @@ public class Profile implements Fingerprintable {
         *             if the name is not valid
         */
        public Field addField(String fieldName) throws IllegalArgumentException {
-               Validation.begin().isNotNull("Field Name", fieldName).check().isGreater("Field Name Length", fieldName.length(), 0).isNull("Field Name Unique", getFieldByName(fieldName)).check();
+               checkNotNull(fieldName, "fieldName must not be null");
+               checkArgument(fieldName.length() > 0, "fieldName must not be empty");
+               checkState(getFieldByName(fieldName) == null, "fieldName must be unique");
                @SuppressWarnings("synthetic-access")
                Field field = new Field().setName(fieldName);
                fields.add(field);
@@ -335,7 +342,9 @@ public class Profile implements Fingerprintable {
         *            The field to move up
         */
        public void moveFieldUp(Field field) {
-               Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).isGreater("Field Index", getFieldIndex(field), 0).check();
+               checkNotNull(field, "field must not be null");
+               checkArgument(hasField(field), "field must belong to this profile");
+               checkArgument(getFieldIndex(field) > 0, "field index must be > 0");
                int fieldIndex = getFieldIndex(field);
                fields.remove(field);
                fields.add(fieldIndex - 1, field);
@@ -350,7 +359,9 @@ public class Profile implements Fingerprintable {
         *            The field to move down
         */
        public void moveFieldDown(Field field) {
-               Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).isLess("Field Index", getFieldIndex(field), fields.size() - 1).check();
+               checkNotNull(field, "field must not be null");
+               checkArgument(hasField(field), "field must belong to this profile");
+               checkArgument(getFieldIndex(field) < fields.size() - 1, "field index must be < " + (fields.size() - 1));
                int fieldIndex = getFieldIndex(field);
                fields.remove(field);
                fields.add(fieldIndex + 1, field);
@@ -363,7 +374,8 @@ public class Profile implements Fingerprintable {
         *            The field to remove
         */
        public void removeField(Field field) {
-               Validation.begin().isNotNull("Field", field).check().is("Field Existing", hasField(field)).check();
+               checkNotNull(field, "field must not be null");
+               checkArgument(hasField(field), "field must belong to this profile");
                fields.remove(field);
        }
 
@@ -392,37 +404,37 @@ public class Profile implements Fingerprintable {
         */
        @Override
        public String getFingerprint() {
-               StringBuilder fingerprint = new StringBuilder();
-               fingerprint.append("Profile(");
+               Hasher hash = Hashing.sha256().newHasher();
+               hash.putString("Profile(");
                if (firstName != null) {
-                       fingerprint.append("FirstName(").append(firstName).append(')');
+                       hash.putString("FirstName(").putString(firstName).putString(")");
                }
                if (middleName != null) {
-                       fingerprint.append("MiddleName(").append(middleName).append(')');
+                       hash.putString("MiddleName(").putString(middleName).putString(")");
                }
                if (lastName != null) {
-                       fingerprint.append("LastName(").append(lastName).append(')');
+                       hash.putString("LastName(").putString(lastName).putString(")");
                }
                if (birthDay != null) {
-                       fingerprint.append("BirthDay(").append(birthDay).append(')');
+                       hash.putString("BirthDay(").putInt(birthDay).putString(")");
                }
                if (birthMonth != null) {
-                       fingerprint.append("BirthMonth(").append(birthMonth).append(')');
+                       hash.putString("BirthMonth(").putInt(birthMonth).putString(")");
                }
                if (birthYear != null) {
-                       fingerprint.append("BirthYear(").append(birthYear).append(')');
+                       hash.putString("BirthYear(").putInt(birthYear).putString(")");
                }
                if (avatar != null) {
-                       fingerprint.append("Avatar(").append(avatar).append(')');
+                       hash.putString("Avatar(").putString(avatar).putString(")");
                }
-               fingerprint.append("ContactInformation(");
+               hash.putString("ContactInformation(");
                for (Field field : fields) {
-                       fingerprint.append(field.getName()).append('(').append(field.getValue()).append(')');
+                       hash.putString(field.getName()).putString("(").putString(field.getValue()).putString(")");
                }
-               fingerprint.append(")");
-               fingerprint.append(")");
+               hash.putString(")");
+               hash.putString(")");
 
-               return fingerprint.toString();
+               return hash.hash().toString();
        }
 
        /**
@@ -455,8 +467,7 @@ public class Profile implements Fingerprintable {
                 *            The ID of the field
                 */
                private Field(String id) {
-                       Validation.begin().isNotNull("Field ID", id).check();
-                       this.id = id;
+                       this.id = checkNotNull(id, "id must not be null");
                }
 
                /**
@@ -487,7 +498,8 @@ public class Profile implements Fingerprintable {
                 * @return This field
                 */
                public Field setName(String name) {
-                       Validation.begin().isNotNull("Field Name", name).check().is("Field Unique", (getFieldByName(name) == null) || equals(getFieldByName(name))).check();
+                       checkNotNull(name, "name must not be null");
+                       checkArgument(getFieldByName(name) == null, "name must be unique");
                        this.name = name;
                        return this;
                }
index be60b88..a686023 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Reply.java - Copyright © 2011–2012 David Roden
+ * Sone - Reply.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 package net.pterodactylus.sone.data;
 
 import java.util.Comparator;
-import java.util.UUID;
 
-import net.pterodactylus.util.collection.filter.Filter;
+import com.google.common.base.Predicate;
 
 /**
- * Abstract base class for all replies.
+ * Defines methods common for all replies.
  *
  * @param <T>
  *            The type of the reply
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public abstract class Reply<T extends Reply<T>> {
+public interface Reply<T extends Reply<T>> {
 
        /** Comparator that sorts replies ascending by time. */
-       public static final Comparator<Reply<?>> TIME_COMPARATOR = new Comparator<Reply<?>>() {
+       public static final Comparator<? super Reply<?>> TIME_COMPARATOR = new Comparator<Reply<?>>() {
 
                /**
                 * {@inheritDoc}
@@ -45,159 +44,52 @@ public abstract class Reply<T extends Reply<T>> {
        };
 
        /** Filter for replies with timestamps from the future. */
-       public static final Filter<Reply<?>> FUTURE_REPLY_FILTER = new Filter<Reply<?>>() {
+       public static final Predicate<Reply<?>> FUTURE_REPLY_FILTER = new Predicate<Reply<?>>() {
 
                /**
                 * {@inheritDoc}
                 */
                @Override
-               public boolean filterObject(Reply<?> reply) {
+               public boolean apply(Reply<?> reply) {
                        return reply.getTime() <= System.currentTimeMillis();
                }
 
        };
 
-       /** The ID of the reply. */
-       private final String id;
-
-       /** The Sone that created this reply. */
-       private volatile Sone sone;
-
-       /** The time of the reply. */
-       private volatile long time;
-
-       /** The text of the reply. */
-       private volatile String text;
-
-       /** Whether the reply is known. */
-       private volatile boolean known;
-
-       /**
-        * Creates a new reply with the given ID.
-        *
-        * @param id
-        *            The ID of the reply
-        */
-       protected Reply(String id) {
-               this(id, null, 0, null);
-       }
-
-       /**
-        * Creates a new reply with a new random ID.
-        *
-        * @param sone
-        *            The Sone of the reply
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        */
-       protected Reply(Sone sone, long time, String text) {
-               this(UUID.randomUUID().toString(), sone, time, text);
-       }
-
-       /**
-        * Creates a new reply.
-        *
-        * @param id
-        *            The ID of the reply
-        * @param sone
-        *            The Sone of the reply
-        * @param time
-        *            The time of the reply
-        * @param text
-        *            The text of the reply
-        */
-       protected Reply(String id, Sone sone, long time, String text) {
-               this.id = id;
-               this.sone = sone;
-               this.time = time;
-               this.text = text;
-       }
-
        /**
         * Returns the ID of the reply.
         *
         * @return The ID of the reply
         */
-       public String getId() {
-               return id;
-       }
+       public String getId();
 
        /**
         * Returns the Sone that posted this reply.
         *
         * @return The Sone that posted this reply
         */
-       public Sone getSone() {
-               return sone;
-       }
-
-       /**
-        * Sets the Sone that posted this reply.
-        *
-        * @param sone
-        *            The Sone that posted this reply
-        * @return This reply (for method chaining)
-        */
-       @SuppressWarnings("unchecked")
-       public T setSone(Sone sone) {
-               this.sone = sone;
-               return (T) this;
-       }
+       public Sone getSone();
 
        /**
         * Returns the time of the reply.
         *
         * @return The time of the reply (in milliseconds since Jan 1, 1970 UTC)
         */
-       public long getTime() {
-               return time;
-       }
-
-       /**
-        * Sets the time of this reply.
-        *
-        * @param time
-        *            The time of this reply (in milliseconds since Jan 1, 1970 UTC)
-        * @return This reply (for method chaining)
-        */
-       @SuppressWarnings("unchecked")
-       public T setTime(long time) {
-               this.time = time;
-               return (T) this;
-       }
+       public long getTime();
 
        /**
         * Returns the text of the reply.
         *
         * @return The text of the reply
         */
-       public String getText() {
-               return text;
-       }
-
-       /**
-        * Sets the text of this reply.
-        *
-        * @param text
-        *            The text of this reply
-        * @return This reply (for method chaining)
-        */
-       @SuppressWarnings("unchecked")
-       public T setText(String text) {
-               this.text = text;
-               return (T) this;
-       }
+       public String getText();
 
        /**
         * Returns whether this reply is known.
         *
         * @return {@code true} if this reply is known, {@code false} otherwise
         */
-       public boolean isKnown() {
-               return known;
-       }
+       public boolean isKnown();
 
        /**
         * Sets whether this reply is known.
@@ -206,42 +98,6 @@ public abstract class Reply<T extends Reply<T>> {
         *            {@code true} if this reply is known, {@code false} otherwise
         * @return This reply
         */
-       @SuppressWarnings("unchecked")
-       public T setKnown(boolean known) {
-               this.known = known;
-               return (T) this;
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof Reply<?>)) {
-                       return false;
-               }
-               Reply<?> reply = (Reply<?>) object;
-               return reply.id.equals(id);
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public String toString() {
-               return getClass().getName() + "[id=" + id + ",sone=" + sone + ",time=" + time + ",text=" + text + "]";
-       }
+       public T setKnown(boolean known);
 
 }
index 99d65ec..4f4fd98 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Sone.java - Copyright © 2010–2012 David Roden
+ * Sone - Sone.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.data;
 
+import static com.google.common.base.Preconditions.*;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -28,20 +30,23 @@ import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.core.Options;
 import net.pterodactylus.sone.freenet.wot.Identity;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.template.SoneAccessor;
-import net.pterodactylus.util.collection.filter.Filter;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.validation.Validation;
+
 import freenet.keys.FreenetURI;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+
 /**
  * A Sone defines everything about a user: her profile, her status updates, her
  * replies, her likes and dislikes, etc.
- * <p>
+ * <p/>
  * Operations that modify the Sone need to synchronize on the Sone in question.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
@@ -106,9 +111,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
        };
 
-       /**
-        * Comparator that sorts Sones by last activity (least recent active first).
-        */
+       /** Comparator that sorts Sones by last activity (least recent active first). */
        public static final Comparator<Sone> LAST_ACTIVITY_COMPARATOR = new Comparator<Sone>() {
 
                @Override
@@ -142,29 +145,29 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        };
 
        /** Filter to remove Sones that have not been downloaded. */
-       public static final Filter<Sone> EMPTY_SONE_FILTER = new Filter<Sone>() {
+       public static final Predicate<Sone> EMPTY_SONE_FILTER = new Predicate<Sone>() {
 
                @Override
-               public boolean filterObject(Sone sone) {
+               public boolean apply(Sone sone) {
                        return sone.getTime() != 0;
                }
        };
 
-       /** Filter that matches all {@link Core#isLocalSone(Sone) local Sones}. */
-       public static final Filter<Sone> LOCAL_SONE_FILTER = new Filter<Sone>() {
+       /** Filter that matches all {@link Sone#isLocal() local Sones}. */
+       public static final Predicate<Sone> LOCAL_SONE_FILTER = new Predicate<Sone>() {
 
                @Override
-               public boolean filterObject(Sone sone) {
+               public boolean apply(Sone sone) {
                        return sone.getIdentity() instanceof OwnIdentity;
                }
 
        };
 
        /** Filter that matches Sones that have at least one album. */
-       public static final Filter<Sone> HAS_ALBUM_FILTER = new Filter<Sone>() {
+       public static final Predicate<Sone> HAS_ALBUM_FILTER = new Predicate<Sone>() {
 
                @Override
-               public boolean filterObject(Sone sone) {
+               public boolean apply(Sone sone) {
                        return !sone.getAlbums().isEmpty();
                }
        };
@@ -175,6 +178,9 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        /** The ID of this Sone. */
        private final String id;
 
+       /** Whether the Sone is local. */
+       private final boolean local;
+
        /** The identity of this Sone. */
        private Identity identity;
 
@@ -222,16 +228,19 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        private final List<Album> albums = new CopyOnWriteArrayList<Album>();
 
        /** Sone-specific options. */
-       private final Options options = new Options();
+       private Options options = new Options();
 
        /**
         * Creates a new Sone.
         *
         * @param id
-        *            The ID of the Sone
+        *              The ID of the Sone
+        * @param local
+        *              {@code true} if the Sone is a local Sone, {@code false} otherwise
         */
-       public Sone(String id) {
+       public Sone(String id, boolean local) {
                this.id = id;
+               this.local = local;
        }
 
        //
@@ -261,10 +270,10 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * identity has to match this Sone’s {@link #getId()}.
         *
         * @param identity
-        *            The identity of this Sone
+        *              The identity of this Sone
         * @return This Sone (for method chaining)
         * @throws IllegalArgumentException
-        *             if the ID of the identity does not match this Sone’s ID
+        *              if the ID of the identity does not match this Sone’s ID
         */
        public Sone setIdentity(Identity identity) throws IllegalArgumentException {
                if (!identity.getId().equals(id)) {
@@ -284,6 +293,15 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
+        * Returns whether this Sone is a local Sone.
+        *
+        * @return {@code true} if this Sone is a local Sone, {@code false} otherwise
+        */
+       public boolean isLocal() {
+               return local;
+       }
+
+       /**
         * Returns the request URI of this Sone.
         *
         * @return The request URI of this Sone
@@ -296,7 +314,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the request URI of this Sone.
         *
         * @param requestUri
-        *            The request URI of this Sone
+        *              The request URI of this Sone
         * @return This Sone (for method chaining)
         */
        public Sone setRequestUri(FreenetURI requestUri) {
@@ -324,7 +342,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the insert URI of this Sone.
         *
         * @param insertUri
-        *            The insert URI of this Sone
+        *              The insert URI of this Sone
         * @return This Sone (for method chaining)
         */
        public Sone setInsertUri(FreenetURI insertUri) {
@@ -350,11 +368,11 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
        /**
         * Sets the latest edition of this Sone. If the given latest edition is not
-        * greater than the current latest edition, the latest edition of this Sone
-        * is not changed.
+        * greater than the current latest edition, the latest edition of this Sone is
+        * not changed.
         *
         * @param latestEdition
-        *            The latest edition of this Sone
+        *              The latest edition of this Sone
         */
        public void setLatestEdition(long latestEdition) {
                if (!(latestEdition > this.latestEdition)) {
@@ -377,7 +395,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the time of the last inserted update of this Sone.
         *
         * @param time
-        *            The time of the update (in milliseconds since Jan 1, 1970 UTC)
+        *              The time of the update (in milliseconds since Jan 1, 1970 UTC)
         * @return This Sone (for method chaining)
         */
        public Sone setTime(long time) {
@@ -398,21 +416,20 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the new status of this Sone.
         *
         * @param status
-        *            The new status of this Sone
+        *              The new status of this Sone
         * @return This Sone
         * @throws IllegalArgumentException
-        *             if {@code status} is {@code null}
+        *              if {@code status} is {@code null}
         */
        public Sone setStatus(SoneStatus status) {
-               Validation.begin().isNotNull("Sone Status", status).check();
-               this.status = status;
+               this.status = checkNotNull(status, "status must not be null");
                return this;
        }
 
        /**
-        * Returns a copy of the profile. If you want to update values in the
-        * profile of this Sone, update the values in the returned {@link Profile}
-        * and use {@link #setProfile(Profile)} to change the profile in this Sone.
+        * Returns a copy of the profile. If you want to update values in the profile
+        * of this Sone, update the values in the returned {@link Profile} and use
+        * {@link #setProfile(Profile)} to change the profile in this Sone.
         *
         * @return A copy of the profile
         */
@@ -421,12 +438,12 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
-        * Sets the profile of this Sone. A copy of the given profile is stored so
-        * that subsequent modifications of the given profile are not reflected in
-        * this Sone!
+        * Sets the profile of this Sone. A copy of the given profile is stored so that
+        * subsequent modifications of the given profile are not reflected in this
+        * Sone!
         *
         * @param profile
-        *            The profile to set
+        *              The profile to set
         */
        public void setProfile(Profile profile) {
                this.profile = new Profile(profile);
@@ -445,7 +462,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the client used by this Sone.
         *
         * @param client
-        *            The client used by this Sone, or {@code null}
+        *              The client used by this Sone, or {@code null}
         * @return This Sone (for method chaining)
         */
        public Sone setClient(Client client) {
@@ -466,7 +483,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets whether this Sone is known.
         *
         * @param known
-        *            {@code true} if this Sone is known, {@code false} otherwise
+        *              {@code true} if this Sone is known, {@code false} otherwise
         * @return This Sone
         */
        public Sone setKnown(boolean known) {
@@ -488,9 +505,9 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Returns whether this Sone has the given Sone as a friend Sone.
         *
         * @param friendSoneId
-        *            The ID of the Sone to check for
-        * @return {@code true} if this Sone has the given Sone as a friend,
-        *         {@code false} otherwise
+        *              The ID of the Sone to check for
+        * @return {@code true} if this Sone has the given Sone as a friend, {@code
+        *         false} otherwise
         */
        public boolean hasFriend(String friendSoneId) {
                return friendSones.contains(friendSoneId);
@@ -500,7 +517,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Adds the given Sone as a friend Sone.
         *
         * @param friendSone
-        *            The friend Sone to add
+        *              The friend Sone to add
         * @return This Sone (for method chaining)
         */
        public Sone addFriend(String friendSone) {
@@ -514,7 +531,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Removes the given Sone as a friend Sone.
         *
         * @param friendSoneId
-        *            The ID of the friend Sone to remove
+        *              The ID of the friend Sone to remove
         * @return This Sone (for method chaining)
         */
        public Sone removeFriend(String friendSoneId) {
@@ -540,7 +557,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets all posts of this Sone at once.
         *
         * @param posts
-        *            The new (and only) posts of this Sone
+        *              The new (and only) posts of this Sone
         * @return This Sone (for method chaining)
         */
        public Sone setPosts(Collection<Post> posts) {
@@ -552,11 +569,11 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
-        * Adds the given post to this Sone. The post will not be added if its
-        * {@link Post#getSone() Sone} is not this Sone.
+        * Adds the given post to this Sone. The post will not be added if its {@link
+        * Post#getSone() Sone} is not this Sone.
         *
         * @param post
-        *            The post to add
+        *              The post to add
         */
        public void addPost(Post post) {
                if (post.getSone().equals(this) && posts.add(post)) {
@@ -568,7 +585,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Removes the given post from this Sone.
         *
         * @param post
-        *            The post to remove
+        *              The post to remove
         */
        public void removePost(Post post) {
                if (post.getSone().equals(this)) {
@@ -589,7 +606,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets all replies of this Sone at once.
         *
         * @param replies
-        *            The new (and only) replies of this Sone
+        *              The new (and only) replies of this Sone
         * @return This Sone (for method chaining)
         */
        public Sone setReplies(Collection<PostReply> replies) {
@@ -603,7 +620,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * nothing is added to this Sone.
         *
         * @param reply
-        *            The reply to add
+        *              The reply to add
         */
        public void addReply(PostReply reply) {
                if (reply.getSone().equals(this)) {
@@ -615,7 +632,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Removes a reply from this Sone.
         *
         * @param reply
-        *            The reply to remove
+        *              The reply to remove
         */
        public void removeReply(PostReply reply) {
                if (reply.getSone().equals(this)) {
@@ -636,7 +653,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the IDs of all liked posts.
         *
         * @param likedPostIds
-        *            All liked posts’ IDs
+        *              All liked posts’ IDs
         * @return This Sone (for method chaining)
         */
        public Sone setLikePostIds(Set<String> likedPostIds) {
@@ -649,7 +666,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Checks whether the given post ID is liked by this Sone.
         *
         * @param postId
-        *            The ID of the post
+        *              The ID of the post
         * @return {@code true} if this Sone likes the given post, {@code false}
         *         otherwise
         */
@@ -661,7 +678,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Adds the given post ID to the list of posts this Sone likes.
         *
         * @param postId
-        *            The ID of the post
+        *              The ID of the post
         * @return This Sone (for method chaining)
         */
        public Sone addLikedPostId(String postId) {
@@ -673,7 +690,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Removes the given post ID from the list of posts this Sone likes.
         *
         * @param postId
-        *            The ID of the post
+        *              The ID of the post
         * @return This Sone (for method chaining)
         */
        public Sone removeLikedPostId(String postId) {
@@ -694,7 +711,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the IDs of all liked replies.
         *
         * @param likedReplyIds
-        *            All liked replies’ IDs
+        *              All liked replies’ IDs
         * @return This Sone (for method chaining)
         */
        public Sone setLikeReplyIds(Set<String> likedReplyIds) {
@@ -707,7 +724,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Checks whether the given reply ID is liked by this Sone.
         *
         * @param replyId
-        *            The ID of the reply
+        *              The ID of the reply
         * @return {@code true} if this Sone likes the given reply, {@code false}
         *         otherwise
         */
@@ -719,7 +736,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Adds the given reply ID to the list of replies this Sone likes.
         *
         * @param replyId
-        *            The ID of the reply
+        *              The ID of the reply
         * @return This Sone (for method chaining)
         */
        public Sone addLikedReplyId(String replyId) {
@@ -731,7 +748,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Removes the given post ID from the list of replies this Sone likes.
         *
         * @param replyId
-        *            The ID of the reply
+        *              The ID of the reply
         * @return This Sone (for method chaining)
         */
        public Sone removeLikedReplyId(String replyId) {
@@ -749,35 +766,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
-        * Returns a flattened list of all albums of this Sone. The resulting list
-        * contains parent albums before child albums so that the resulting list can
-        * be parsed in a single pass.
-        *
-        * @return The flattened albums
-        */
-       public List<Album> getAllAlbums() {
-               List<Album> flatAlbums = new ArrayList<Album>();
-               flatAlbums.addAll(albums);
-               int lastAlbumIndex = 0;
-               while (lastAlbumIndex < flatAlbums.size()) {
-                       int previousAlbumCount = flatAlbums.size();
-                       for (Album album : new ArrayList<Album>(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) {
-                               flatAlbums.addAll(album.getAlbums());
-                       }
-                       lastAlbumIndex = previousAlbumCount;
-               }
-               return flatAlbums;
-       }
-
-       /**
-        * Returns all images of a Sone. Images of a album are inserted into this
-        * list before images of all child albums.
+        * Returns all images of a Sone. Images of a album are inserted into this list
+        * before images of all child albums.
         *
         * @return The list of all images
         */
        public List<Image> getAllImages() {
                List<Image> allImages = new ArrayList<Image>();
-               for (Album album : getAllAlbums()) {
+               for (Album album : FluentIterable.from(getAlbums()).transformAndConcat(Album.FLATTENER).toList()) {
                        allImages.addAll(album.getImages());
                }
                return allImages;
@@ -787,10 +783,11 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Adds an album to this Sone.
         *
         * @param album
-        *            The album to add
+        *              The album to add
         */
        public void addAlbum(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
                if (!albums.contains(album)) {
                        albums.add(album);
                }
@@ -800,10 +797,10 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Sets the albums of this Sone.
         *
         * @param albums
-        *            The albums of this Sone
+        *              The albums of this Sone
         */
        public void setAlbums(Collection<? extends Album> albums) {
-               Validation.begin().isNotNull("Albums", albums).check();
+               checkNotNull(albums, "albums must not be null");
                this.albums.clear();
                for (Album album : albums) {
                        addAlbum(album);
@@ -814,24 +811,27 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * Removes an album from this Sone.
         *
         * @param album
-        *            The album to remove
+        *              The album to remove
         */
        public void removeAlbum(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
                albums.remove(album);
        }
 
        /**
-        * Moves the given album up in this album’s albums. If the album is already
-        * the first album, nothing happens.
+        * Moves the given album up in this album’s albums. If the album is already the
+        * first album, nothing happens.
         *
         * @param album
-        *            The album to move up
+        *              The album to move up
         * @return The album that the given album swapped the place with, or
         *         <code>null</code> if the album did not change its place
         */
        public Album moveAlbumUp(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
+               checkArgument(album.getParent() == null, "album must not have a parent");
                int oldIndex = albums.indexOf(album);
                if (oldIndex <= 0) {
                        return null;
@@ -842,16 +842,18 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
-        * Moves the given album down in this album’s albums. If the album is
-        * already the last album, nothing happens.
+        * Moves the given album down in this album’s albums. If the album is already
+        * the last album, nothing happens.
         *
         * @param album
-        *            The album to move down
+        *              The album to move down
         * @return The album that the given album swapped the place with, or
         *         <code>null</code> if the album did not change its place
         */
        public Album moveAlbumDown(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check();
+               checkNotNull(album, "album must not be null");
+               checkArgument(album.getSone().equals(this), "album must belong to this Sone");
+               checkArgument(album.getParent() == null, "album must not have a parent");
                int oldIndex = albums.indexOf(album);
                if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
                        return null;
@@ -870,64 +872,71 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                return options;
        }
 
+       /**
+        * Sets the options of this Sone.
+        *
+        * @param options
+        *              The options of this Sone
+        */
+       /* TODO - remove this method again, maybe add an option provider */
+       public void setOptions(Options options) {
+               this.options = options;
+       }
+
        //
        // FINGERPRINTABLE METHODS
        //
 
-       /**
-        * {@inheritDoc}
-        */
+       /** {@inheritDoc} */
        @Override
        public synchronized String getFingerprint() {
-               StringBuilder fingerprint = new StringBuilder();
-               fingerprint.append(profile.getFingerprint());
+               Hasher hash = Hashing.sha256().newHasher();
+               hash.putString(profile.getFingerprint());
 
-               fingerprint.append("Posts(");
+               hash.putString("Posts(");
                for (Post post : getPosts()) {
-                       fingerprint.append("Post(").append(post.getId()).append(')');
+                       hash.putString("Post(").putString(post.getId()).putString(")");
                }
-               fingerprint.append(")");
+               hash.putString(")");
 
                List<PostReply> replies = new ArrayList<PostReply>(getReplies());
                Collections.sort(replies, Reply.TIME_COMPARATOR);
-               fingerprint.append("Replies(");
+               hash.putString("Replies(");
                for (PostReply reply : replies) {
-                       fingerprint.append("Reply(").append(reply.getId()).append(')');
+                       hash.putString("Reply(").putString(reply.getId()).putString(")");
                }
-               fingerprint.append(')');
+               hash.putString(")");
 
                List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
                Collections.sort(likedPostIds);
-               fingerprint.append("LikedPosts(");
+               hash.putString("LikedPosts(");
                for (String likedPostId : likedPostIds) {
-                       fingerprint.append("Post(").append(likedPostId).append(')');
+                       hash.putString("Post(").putString(likedPostId).putString(")");
                }
-               fingerprint.append(')');
+               hash.putString(")");
 
                List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
                Collections.sort(likedReplyIds);
-               fingerprint.append("LikedReplies(");
+               hash.putString("LikedReplies(");
                for (String likedReplyId : likedReplyIds) {
-                       fingerprint.append("Reply(").append(likedReplyId).append(')');
+                       hash.putString("Reply(").putString(likedReplyId).putString(")");
                }
-               fingerprint.append(')');
+               hash.putString(")");
 
-               fingerprint.append("Albums(");
+               hash.putString("Albums(");
                for (Album album : albums) {
-                       fingerprint.append(album.getFingerprint());
+                       hash.putString(album.getFingerprint());
                }
-               fingerprint.append(')');
+               hash.putString(")");
 
-               return fingerprint.toString();
+               return hash.hash().toString();
        }
 
        //
        // INTERFACE Comparable<Sone>
        //
 
-       /**
-        * {@inheritDoc}
-        */
+       /** {@inheritDoc} */
        @Override
        public int compareTo(Sone sone) {
                return NICE_NAME_COMPARATOR.compare(this, sone);
@@ -937,17 +946,13 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        // OBJECT METHODS
        //
 
-       /**
-        * {@inheritDoc}
-        */
+       /** {@inheritDoc} */
        @Override
        public int hashCode() {
                return id.hashCode();
        }
 
-       /**
-        * {@inheritDoc}
-        */
+       /** {@inheritDoc} */
        @Override
        public boolean equals(Object object) {
                if (!(object instanceof Sone)) {
@@ -956,9 +961,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                return ((Sone) object).id.equals(id);
        }
 
-       /**
-        * {@inheritDoc}
-        */
+       /** {@inheritDoc} */
        @Override
        public String toString() {
                return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]";
index 5e7c78d..5c657ac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TemporaryImage.java - Copyright © 2011–2012 David Roden
+ * Sone - TemporaryImage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.data;
 
-import java.util.UUID;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
-import net.pterodactylus.util.validation.Validation;
+import java.util.UUID;
 
 /**
  * A temporary image stores an uploaded image in memory until it has been
@@ -82,7 +83,8 @@ public class TemporaryImage {
         * @return This temporary image
         */
        public TemporaryImage setMimeType(String mimeType) {
-               Validation.begin().isNotNull("MIME Type", mimeType).isNull("Previous MIME Type", this.mimeType).check();
+               checkNotNull(mimeType, "mimeType must not be null");
+               checkState(this.mimeType == null, "mime type must not already be set");
                this.mimeType = mimeType;
                return this;
        }
@@ -105,7 +107,8 @@ public class TemporaryImage {
         * @return This temporary image
         */
        public TemporaryImage setImageData(byte[] imageData) {
-               Validation.begin().isNotNull("Image Data", imageData).isNull("Previous Image Data", this.imageData).check();
+               checkNotNull(imageData, "imageData must not be null");
+               checkState(this.imageData == null, "image data must not already be set");
                this.imageData = imageData;
                return this;
        }
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/AbstractPostBuilder.java b/src/main/java/net/pterodactylus/sone/data/impl/AbstractPostBuilder.java
new file mode 100644 (file)
index 0000000..3a56dc9
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Sone - AbstractPostBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import org.apache.commons.lang.StringUtils;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.SoneProvider;
+
+/**
+ * Abstract {@link PostBuilder} implementation. It stores the state of the new
+ * post and performs validation, you only need to implement {@link #build()}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class AbstractPostBuilder implements PostBuilder {
+
+       /** The Sone provider for the created posts. */
+       protected final SoneProvider soneProvider;
+
+       /** Wether to create a post with a random ID. */
+       protected boolean randomId;
+
+       /** The ID of the post. */
+       protected String id;
+
+       /** The sender of the post. */
+       protected String senderId;
+
+       /** Whether to use the current time when creating the post. */
+       protected boolean currentTime;
+
+       /** The time of the post. */
+       protected long time;
+
+       /** The text of the post. */
+       protected String text;
+
+       /** The (optional) recipient of the post. */
+       protected String recipientId;
+
+       /**
+        * Creates a new abstract post builder.
+        *
+        * @param soneProvider
+        *            The Sone provider
+        */
+       public AbstractPostBuilder(SoneProvider soneProvider) {
+               this.soneProvider = soneProvider;
+       }
+
+       //
+       // POSTBUILDER METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder copyPost(Post post) {
+               this.randomId = false;
+               this.id = post.getId();
+               this.senderId = post.getSone().getId();
+               this.currentTime = false;
+               this.time = post.getTime();
+               this.text = post.getText();
+               this.recipientId = post.getRecipientId().orNull();
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder randomId() {
+               randomId = true;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder withId(String id) {
+               this.id = id;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder from(String senderId) {
+               this.senderId = senderId;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder currentTime() {
+               currentTime = true;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder withTime(long time) {
+               this.time = time;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder withText(String text) {
+               this.text = text;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder to(String recipientId) {
+               this.recipientId = recipientId;
+               return this;
+       }
+
+       //
+       // PROTECTED METHODS
+       //
+
+       /**
+        * Validates the state of this post builder.
+        *
+        * @throws IllegalStateException
+        *             if the state is not valid for building a new post
+        */
+       protected void validate() throws IllegalStateException {
+               checkState((randomId && (id == null)) || (!randomId && (id != null)), "exactly one of random ID or custom ID must be set");
+               checkState(senderId != null, "sender must not be null");
+               checkState((currentTime && (time == 0)) || (!currentTime && (time > 0)), "one of current time or custom time must be set");
+               checkState(!StringUtils.isBlank(text), "text must not be empty");
+               checkState((recipientId == null) || !recipientId.equals(senderId), "sender and recipient must not be the same");
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/AbstractPostReplyBuilder.java b/src/main/java/net/pterodactylus/sone/data/impl/AbstractPostReplyBuilder.java
new file mode 100644 (file)
index 0000000..952a94e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Sone - AbstractPostReplyBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import org.apache.commons.lang.StringUtils;
+
+import net.pterodactylus.sone.database.PostReplyBuilder;
+
+/**
+ * Abstract {@link PostReplyBuilder} implementation. It stores the state of the
+ * new post and performs validation, implementations only need to implement
+ * {@link #build()}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class AbstractPostReplyBuilder extends AbstractReplyBuilder<PostReplyBuilder> implements PostReplyBuilder {
+
+       /** The ID of the post the created reply refers to. */
+       protected String postId;
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostReplyBuilder to(String postId) {
+               this.postId = postId;
+               return this;
+       }
+
+       //
+       // PROTECTED METHODS
+       //
+
+       /**
+        * Validates the state of this post reply builder.
+        *
+        * @throws IllegalStateException
+        *             if the state is not valid for building a new post reply
+        */
+       protected void validate() throws IllegalStateException {
+               checkState((randomId && (id == null)) || (!randomId && (id != null)), "either random ID nor custom ID must be set");
+               checkState(senderId != null, "sender must not be null");
+               checkState((currentTime && (time == 0)) || (!currentTime && (time >= 0)), "either current time or custom time must be set");
+               checkState(!StringUtils.isBlank(text), "text must not be empty");
+               checkState(postId != null, "post must not be null");
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/AbstractReplyBuilder.java b/src/main/java/net/pterodactylus/sone/data/impl/AbstractReplyBuilder.java
new file mode 100644 (file)
index 0000000..2a68a13
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Sone - ReplyBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.database.ReplyBuilder;
+
+/**
+ * Abstract implementation of a {@link ReplyBuilder}.
+ *
+ * @param <B>
+ *            The interface implemented and exposed by the builder
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class AbstractReplyBuilder<B extends ReplyBuilder<B>> implements ReplyBuilder<B> {
+
+       /** Whether to use a random ID for the reply. */
+       protected boolean randomId;
+
+       /** The ID of the reply. */
+       protected String id;
+
+       /** The sender of the reply. */
+       protected String senderId;
+
+       /** Whether to use the current time when creating the reply. */
+       protected boolean currentTime;
+
+       /** The time of the reply. */
+       protected long time;
+
+       /** The text of the reply. */
+       protected String text;
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B randomId() {
+               this.randomId = true;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B withId(String id) {
+               this.id = id;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B from(String senderId) {
+               this.senderId = senderId;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B currentTime() {
+               this.currentTime = true;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B withTime(long time) {
+               this.time = time;
+               return (B) this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public B withText(String text) {
+               this.text = text;
+               return (B) this;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostBuilderFactory.java
new file mode 100644 (file)
index 0000000..4df8897
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Sone - DefaultPostBuilderFactory.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.PostBuilderFactory;
+import net.pterodactylus.sone.database.SoneProvider;
+
+import com.google.inject.Inject;
+
+/**
+ * {@link PostBuilderFactory} implementation that creates
+ * {@link PostBuilderImpl}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DefaultPostBuilderFactory implements PostBuilderFactory {
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /**
+        * Creates a new default post builder factory.
+        *
+        * @param soneProvider
+        *            The Sone provider
+        */
+       @Inject
+       public DefaultPostBuilderFactory(SoneProvider soneProvider) {
+               this.soneProvider = soneProvider;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostBuilder newPostBuilder() {
+               return new PostBuilderImpl(soneProvider);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java b/src/main/java/net/pterodactylus/sone/data/impl/DefaultPostReplyBuilderFactory.java
new file mode 100644 (file)
index 0000000..e326304
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Sone - DefaultPostReplyBuilderFactory.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.PostReplyBuilder;
+import net.pterodactylus.sone.database.PostReplyBuilderFactory;
+import net.pterodactylus.sone.database.SoneProvider;
+
+import com.google.inject.Inject;
+
+/**
+ * {@link PostReplyBuilderFactory} that creates {@link PostReplyBuilderImpl}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DefaultPostReplyBuilderFactory implements PostReplyBuilderFactory {
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /** The post provider. */
+       private final PostProvider postProvider;
+
+       /**
+        * Creates a new default post reply builder factory.
+        *
+        * @param soneProvider
+        *            The Sone provider
+        * @param postProvider
+        *            The post provider
+        */
+       @Inject
+       public DefaultPostReplyBuilderFactory(SoneProvider soneProvider, PostProvider postProvider) {
+               this.soneProvider = soneProvider;
+               this.postProvider = postProvider;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostReplyBuilder newPostReplyBuilder() {
+               return new PostReplyBuilderImpl(soneProvider, postProvider);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostBuilderImpl.java
new file mode 100644 (file)
index 0000000..88a2d71
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Sone - PostBuilderImpl.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import java.util.UUID;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.SoneProvider;
+
+/**
+ * {@link PostBuilder} implementation that creates {@link PostImpl} objects.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostBuilderImpl extends AbstractPostBuilder {
+
+       /**
+        * Creates a new post builder.
+        *
+        * @param soneProvider
+        *            The Sone provider
+        */
+       public PostBuilderImpl(SoneProvider soneProvider) {
+               super(soneProvider);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Post build() {
+               validate();
+               return new PostImpl(soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, recipientId, currentTime ? System.currentTimeMillis() : time, text);
+       }
+
+}
index 2269738..2d25715 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostImpl.java - Copyright © 2010–2012 David Roden
+ * Sone - PostImpl.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,9 @@ import java.util.UUID;
 
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.SoneProvider;
+
+import com.google.common.base.Optional;
 
 /**
  * A post is a short message that a user writes in his Sone to let other users
@@ -30,20 +33,23 @@ import net.pterodactylus.sone.data.Sone;
  */
 public class PostImpl implements Post {
 
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
        /** The GUID of the post. */
        private final UUID id;
 
-       /** The Sone this post belongs to. */
-       private volatile Sone sone;
+       /** The ID of the owning Sone. */
+       private final String soneId;
 
-       /** The Sone of the recipient. */
-       private volatile Sone recipient;
+       /** The ID of the recipient Sone. */
+       private final String recipientId;
 
        /** The time of the post (in milliseconds since Jan 1, 1970 UTC). */
-       private volatile long time;
+       private final long time;
 
        /** The text of the post. */
-       private volatile String text;
+       private final String text;
 
        /** Whether the post is known. */
        private volatile boolean known;
@@ -51,54 +57,24 @@ public class PostImpl implements Post {
        /**
         * Creates a new post.
         *
+        * @param soneProvider
+        *            The Sone provider
         * @param id
         *            The ID of the post
-        */
-       public PostImpl(String id) {
-               this(id, null, 0, null);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param sone
-        *            The Sone this post belongs to
-        * @param text
-        *            The text of the post
-        */
-       public PostImpl(Sone sone, String text) {
-               this(sone, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param sone
-        *            The Sone this post belongs to
-        * @param time
-        *            The time of the post (in milliseconds since Jan 1, 1970 UTC)
-        * @param text
-        *            The text of the post
-        */
-       public PostImpl(Sone sone, long time, String text) {
-               this(UUID.randomUUID().toString(), sone, time, text);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param id
-        *            The ID of the post
-        * @param sone
-        *            The Sone this post belongs to
+        * @param soneId
+        *            The ID of the Sone this post belongs to
+        * @param recipientId
+        *            The ID of the recipient of the post
         * @param time
         *            The time of the post (in milliseconds since Jan 1, 1970 UTC)
         * @param text
         *            The text of the post
         */
-       public PostImpl(String id, Sone sone, long time, String text) {
+       public PostImpl(SoneProvider soneProvider, String id, String soneId, String recipientId, long time, String text) {
+               this.soneProvider = soneProvider;
                this.id = UUID.fromString(id);
-               this.sone = sone;
+               this.soneId = soneId;
+               this.recipientId = recipientId;
                this.time = time;
                this.text = text;
        }
@@ -120,35 +96,23 @@ public class PostImpl implements Post {
         */
        @Override
        public Sone getSone() {
-               return sone;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public PostImpl setSone(Sone sone) {
-               this.sone = sone;
-               return this;
+               return soneProvider.getSone(soneId).get();
        }
 
        /**
-        * {@inheritDoc}
+        * {@inheritDocs}
         */
        @Override
-       public Sone getRecipient() {
-               return recipient;
+       public Optional<String> getRecipientId() {
+               return Optional.fromNullable(recipientId);
        }
 
        /**
         * {@inheritDoc}
         */
        @Override
-       public PostImpl setRecipient(Sone recipient) {
-               if (!sone.equals(recipient)) {
-                       this.recipient = recipient;
-               }
-               return this;
+       public Optional<Sone> getRecipient() {
+               return soneProvider.getSone(recipientId);
        }
 
        /**
@@ -163,15 +127,6 @@ public class PostImpl implements Post {
         * {@inheritDoc}
         */
        @Override
-       public PostImpl setTime(long time) {
-               this.time = time;
-               return this;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
        public String getText() {
                return text;
        }
@@ -180,15 +135,6 @@ public class PostImpl implements Post {
         * {@inheritDoc}
         */
        @Override
-       public PostImpl setText(String text) {
-               this.text = text;
-               return this;
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
        public boolean isKnown() {
                return known;
        }
@@ -231,7 +177,7 @@ public class PostImpl implements Post {
         */
        @Override
        public String toString() {
-               return getClass().getName() + "[id=" + id + ",sone=" + sone + ",time=" + time + ",text=" + text + "]";
+               return String.format("%s[id=%s,sone=%s,recipient=%s,time=%d,text=%s]", getClass().getName(), id, soneId, recipientId, time, text);
        }
 
 }
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostReplyBuilderImpl.java
new file mode 100644 (file)
index 0000000..dac84ad
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Sone - PostReplyBuilderImpl.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.UUID;
+
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.PostReplyBuilder;
+import net.pterodactylus.sone.database.SoneProvider;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * {@link PostReplyBuilder} implementation that creates {@link PostReplyImpl}
+ * objects.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostReplyBuilderImpl extends AbstractPostReplyBuilder {
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /** The post provider. */
+       private final PostProvider postProvider;
+
+       /**
+        * Creates a new post reply builder.
+        *
+        * @param soneProvider
+        *            The Sone provider
+        * @param postProvider
+        *            The post provider
+        */
+       public PostReplyBuilderImpl(SoneProvider soneProvider, PostProvider postProvider) {
+               this.soneProvider = soneProvider;
+               this.postProvider = postProvider;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public PostReply build() {
+               checkState((randomId && (id == null)) || (!randomId && (id != null)), "either random ID nor custom ID must be set");
+               checkState(senderId != null, "sender must not be null");
+               checkState((currentTime && (time == 0)) || (!currentTime && (time >= 0)), "either current time or custom time must be set");
+               checkState(!StringUtils.isBlank(text), "text must not be empty");
+               checkState(postId != null, "post must not be null");
+
+               /* create new post reply. */
+               return new PostReplyImpl(soneProvider, postProvider, randomId ? UUID.randomUUID().toString() : id, senderId, currentTime ? System.currentTimeMillis() : time, text, postId);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/PostReplyImpl.java
new file mode 100644 (file)
index 0000000..30badf7
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Sone - PostReplyImpl.java - Copyright © 2010–2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.SoneProvider;
+
+import com.google.common.base.Optional;
+
+/**
+ * Simple {@link PostReply} implementation.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PostReplyImpl extends ReplyImpl<PostReply> implements PostReply {
+
+       /** The post provider. */
+       private final PostProvider postProvider;
+
+       /** The Post this reply refers to. */
+       private final String postId;
+
+       /**
+        * Creates a new reply.
+        *
+        * @param soneProvider
+        *            The Sone provider
+        * @param postProvider
+        *            The post provider
+        * @param id
+        *            The ID of the reply
+        * @param soneId
+        *            The ID of the Sone of the reply
+        * @param time
+        *            The time of the reply
+        * @param text
+        *            The text of the reply
+        * @param postId
+        *            The ID of the post this reply refers to
+        */
+       public PostReplyImpl(SoneProvider soneProvider, PostProvider postProvider, String id, String soneId, long time, String text, String postId) {
+               super(soneProvider, id, soneId, time, text);
+               this.postProvider = postProvider;
+               this.postId = postId;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public String getPostId() {
+               return postId;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Optional<Post> getPost() {
+               return postProvider.getPost(postId);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/ReplyImpl.java
new file mode 100644 (file)
index 0000000..a67081f
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Sone - ReplyImpl.java - Copyright © 2011–2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.data.impl;
+
+import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.SoneProvider;
+
+/**
+ * Abstract base class for all replies.
+ *
+ * @param <T>
+ *            The type of the reply
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class ReplyImpl<T extends Reply<T>> implements Reply<T> {
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /** The ID of the reply. */
+       private final String id;
+
+       /** The Sone that created this reply. */
+       private final String soneId;
+
+       /** The time of the reply. */
+       private final long time;
+
+       /** The text of the reply. */
+       private final String text;
+
+       /** Whether the reply is known. */
+       private volatile boolean known;
+
+       /**
+        * Creates a new reply.
+        *
+        * @param soneProvider
+        *            The Sone provider
+        * @param id
+        *            The ID of the reply
+        * @param soneId
+        *            The ID of the Sone of the reply
+        * @param time
+        *            The time of the reply
+        * @param text
+        *            The text of the reply
+        */
+       protected ReplyImpl(SoneProvider soneProvider, String id, String soneId, long time, String text) {
+               this.soneProvider = soneProvider;
+               this.id = id;
+               this.soneId = soneId;
+               this.time = time;
+               this.text = text;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone getSone() {
+               return soneProvider.getSone(soneId).get();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public long getTime() {
+               return time;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getText() {
+               return text;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isKnown() {
+               return known;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       @SuppressWarnings("unchecked")
+       public T setKnown(boolean known) {
+               this.known = known;
+               return (T) this;
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean equals(Object object) {
+               if (!(object instanceof Reply<?>)) {
+                       return false;
+               }
+               Reply<?> reply = (Reply<?>) object;
+               return reply.getId().equals(id);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String toString() {
+               return String.format("%s[id=%s,sone=%s,time=%d,text=%s]", getClass().getName(), id, soneId, time, text);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/Database.java b/src/main/java/net/pterodactylus/sone/database/Database.java
new file mode 100644 (file)
index 0000000..c93c061
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - Database.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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 and adds some methods
+ * necessary for lifecycle management.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Database extends Service, PostDatabase, PostReplyDatabase {
+
+       /**
+        * 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/DatabaseException.java b/src/main/java/net/pterodactylus/sone/database/DatabaseException.java
new file mode 100644 (file)
index 0000000..43d3e77
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Sone - DatabaseException.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+/**
+ * Exception that signals a database error.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DatabaseException extends Exception {
+
+       /**
+        * Creates a new database exception.
+        */
+       public DatabaseException() {
+               super();
+       }
+
+       /**
+        * Creates a new database exception.
+        *
+        * @param message
+        *            The message of the exception
+        */
+       public DatabaseException(String message) {
+               super(message);
+       }
+
+       /**
+        * Creates a new database exception.
+        *
+        * @param cause
+        *            The cause of the exception
+        */
+       public DatabaseException(Throwable cause) {
+               super(cause);
+       }
+
+       /**
+        * Creates a new database exception.
+        *
+        * @param message
+        *            The message of the exception
+        * @param cause
+        *            The cause of the exception
+        */
+       public DatabaseException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostBuilder.java b/src/main/java/net/pterodactylus/sone/database/PostBuilder.java
new file mode 100644 (file)
index 0000000..449147a
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ * Sone - PostBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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
new file mode 100644 (file)
index 0000000..b89ae28
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Sone - PostBuilderFactory.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+/**
+ * Factory for {@link PostBuilder}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostBuilderFactory {
+
+       /**
+        * Creates a new post builder.
+        *
+        * @return A new post builder
+        */
+       public PostBuilder newPostBuilder();
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostDatabase.java b/src/main/java/net/pterodactylus/sone/database/PostDatabase.java
new file mode 100644 (file)
index 0000000..40e6290
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - PostDatabase.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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
new file mode 100644 (file)
index 0000000..740373e
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Sone - PostProvider.java - Copyright © 2011–2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+import java.util.Collection;
+
+import net.pterodactylus.sone.data.Post;
+
+import com.google.common.base.Optional;
+
+/**
+ * Interface for objects that can provide {@link Post}s by their ID.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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
new file mode 100644 (file)
index 0000000..c031443
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Sone - PostReplyBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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
new file mode 100644 (file)
index 0000000..7fd4ae1
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Sone - PostReplyBuilderFactory.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+/**
+ * Factory for {@link PostReplyBuilder}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostReplyBuilderFactory {
+
+       /**
+        * Creates a new post reply builder.
+        *
+        * @return A new post reply builder
+        */
+       public PostReplyBuilder newPostReplyBuilder();
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java b/src/main/java/net/pterodactylus/sone/database/PostReplyDatabase.java
new file mode 100644 (file)
index 0000000..c9a809f
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sone - PostReplyDatabase.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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
new file mode 100644 (file)
index 0000000..e186e5b
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Sone - PostReplyProvider.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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
new file mode 100644 (file)
index 0000000..a3cefb3
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Sone - PostReplyStore.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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);
+
+       /**
+        * Stores the given post replies as exclusive collection of post replies for
+        * the given Sone. This will remove all other post replies from this Sone!
+        *
+        * @param sone
+        *            The Sone to store all post replies for
+        * @param postReplies
+        *            The post replies of the Sone
+        * @throws IllegalArgumentException
+        *             if one of the replies does not belong to the given Sone
+        */
+       public void storePostReplies(Sone sone, Collection<PostReply> postReplies) throws IllegalArgumentException;
+
+       /**
+        * Removes the given post reply from this store.
+        *
+        * @param postReply
+        *            The post reply to remove
+        */
+       public void removePostReply(PostReply postReply);
+
+       /**
+        * Removes all post replies of the given Sone.
+        *
+        * @param sone
+        *            The Sone to remove all post replies for
+        */
+       public void removePostReplies(Sone sone);
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/PostStore.java b/src/main/java/net/pterodactylus/sone/database/PostStore.java
new file mode 100644 (file)
index 0000000..9c2ca42
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Sone - PostStore.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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);
+
+       /**
+        * Stores the given posts as all posts of a single {@link Sone}. This method
+        * will removed all other posts from the Sone!
+        *
+        * @param sone
+        *            The Sone to store the posts for
+        * @param posts
+        *            The posts to store
+        * @throws IllegalArgumentException
+        *             if posts do not all belong to the same Sone
+        */
+       public void storePosts(Sone sone, Collection<Post> posts) throws IllegalArgumentException;
+
+       /**
+        * Removes all posts of the given {@link Sone}
+        *
+        * @param sone
+        *            The Sone to remove all posts for
+        */
+       public void removePosts(Sone sone);
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/ReplyBuilder.java b/src/main/java/net/pterodactylus/sone/database/ReplyBuilder.java
new file mode 100644 (file)
index 0000000..d83e7ce
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Sone - ReplyBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.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/SoneProvider.java b/src/main/java/net/pterodactylus/sone/database/SoneProvider.java
new file mode 100644 (file)
index 0000000..993804f
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Sone - SoneProvider.java - Copyright © 2011–2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database;
+
+import java.util.Collection;
+
+import net.pterodactylus.sone.data.Sone;
+
+import com.google.common.base.Optional;
+
+/**
+ * Interface for objects that can provide {@link Sone}s by their ID.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface SoneProvider {
+
+       /**
+        * 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
+        */
+       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/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java
new file mode 100644 (file)
index 0000000..3f30d11
--- /dev/null
@@ -0,0 +1,654 @@
+/*
+ * Sone - MemoryDatabase.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database.memory;
+
+import static com.google.common.base.Preconditions.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.database.DatabaseException;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.PostDatabase;
+import net.pterodactylus.sone.database.PostReplyBuilder;
+import net.pterodactylus.sone.database.SoneProvider;
+import net.pterodactylus.util.config.Configuration;
+import net.pterodactylus.util.config.ConfigurationException;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.SortedSetMultimap;
+import com.google.common.collect.TreeMultimap;
+import com.google.common.util.concurrent.AbstractService;
+import com.google.inject.Inject;
+
+/**
+ * Memory-based {@link PostDatabase} implementation.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MemoryDatabase extends AbstractService implements Database {
+
+       /** The lock. */
+       private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /** The configuration. */
+       private final Configuration configuration;
+
+       /** All posts by their ID. */
+       private final Map<String, Post> allPosts = new HashMap<String, Post>();
+
+       /** All posts by their Sones. */
+       private final Map<String, Collection<Post>> sonePosts = new HashMap<String, Collection<Post>>();
+
+       /** All posts by their recipient. */
+       private final Map<String, Collection<Post>> recipientPosts = new HashMap<String, Collection<Post>>();
+
+       /** Whether posts are known. */
+       private final Set<String> knownPosts = new HashSet<String>();
+
+       /** All post replies by their ID. */
+       private final Map<String, PostReply> allPostReplies = new HashMap<String, PostReply>();
+
+       /** Replies sorted by Sone. */
+       private final SortedSetMultimap<String, PostReply> sonePostReplies = TreeMultimap.create(new Comparator<String>() {
+
+               @Override
+               public int compare(String leftString, String rightString) {
+                       return leftString.compareTo(rightString);
+               }
+       }, PostReply.TIME_COMPARATOR);
+
+       /** Replies by post. */
+       private final Map<String, SortedSet<PostReply>> postReplies = new HashMap<String, SortedSet<PostReply>>();
+
+       /** Whether post replies are known. */
+       private final Set<String> knownPostReplies = new HashSet<String>();
+
+       /**
+        * Creates a new memory database.
+        *
+        * @param soneProvider
+        *              The Sone provider
+        * @param configuration
+        *              The configuration for loading and saving elements
+        */
+       @Inject
+       public MemoryDatabase(SoneProvider soneProvider, Configuration configuration) {
+               this.soneProvider = soneProvider;
+               this.configuration = configuration;
+       }
+
+       //
+       // DATABASE METHODS
+       //
+
+       /**
+        * Saves the database.
+        *
+        * @throws DatabaseException
+        *              if an error occurs while saving
+        */
+       @Override
+       public void save() throws DatabaseException {
+               saveKnownPosts();
+               saveKnownPostReplies();
+       }
+
+       //
+       // SERVICE METHODS
+       //
+
+       /** {@inheritDocs} */
+       @Override
+       protected void doStart() {
+               loadKnownPosts();
+               loadKnownPostReplies();
+               notifyStarted();
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       protected void doStop() {
+               try {
+                       save();
+                       notifyStopped();
+               } catch (DatabaseException de1) {
+                       notifyFailed(de1);
+               }
+       }
+
+       //
+       // POSTPROVIDER METHODS
+       //
+
+       /** {@inheritDocs} */
+       @Override
+       public Optional<Post> getPost(String postId) {
+               lock.readLock().lock();
+               try {
+                       return Optional.fromNullable(allPosts.get(postId));
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public Collection<Post> getPosts(String soneId) {
+               return new HashSet<Post>(getPostsFrom(soneId));
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public Collection<Post> getDirectedPosts(String recipientId) {
+               lock.readLock().lock();
+               try {
+                       Collection<Post> posts = recipientPosts.get(recipientId);
+                       return (posts == null) ? Collections.<Post>emptySet() : new HashSet<Post>(posts);
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       //
+       // POSTBUILDERFACTORY METHODS
+       //
+
+       /** {@inheritDocs} */
+       @Override
+       public PostBuilder newPostBuilder() {
+               return new MemoryPostBuilder(this, soneProvider);
+       }
+
+       //
+       // POSTSTORE METHODS
+       //
+
+       /** {@inheritDocs} */
+       @Override
+       public void storePost(Post post) {
+               checkNotNull(post, "post must not be null");
+               lock.writeLock().lock();
+               try {
+                       allPosts.put(post.getId(), post);
+                       getPostsFrom(post.getSone().getId()).add(post);
+                       if (post.getRecipientId().isPresent()) {
+                               getPostsTo(post.getRecipientId().get()).add(post);
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public void removePost(Post post) {
+               checkNotNull(post, "post must not be null");
+               lock.writeLock().lock();
+               try {
+                       allPosts.remove(post.getId());
+                       getPostsFrom(post.getSone().getId()).remove(post);
+                       if (post.getRecipientId().isPresent()) {
+                               getPostsTo(post.getRecipientId().get()).remove(post);
+                       }
+                       post.getSone().removePost(post);
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public void storePosts(Sone sone, Collection<Post> posts) throws IllegalArgumentException {
+               checkNotNull(sone, "sone must not be null");
+               /* verify that all posts are from the same Sone. */
+               for (Post post : posts) {
+                       if (!sone.equals(post.getSone())) {
+                               throw new IllegalArgumentException(String.format("Post from different Sone found: %s", post));
+                       }
+               }
+
+               lock.writeLock().lock();
+               try {
+                       /* remove all posts by the Sone. */
+                       getPostsFrom(sone.getId()).clear();
+                       for (Post post : posts) {
+                               allPosts.remove(post.getId());
+                               if (post.getRecipientId().isPresent()) {
+                                       getPostsTo(post.getRecipientId().get()).remove(post);
+                               }
+                       }
+
+                       /* add new posts. */
+                       getPostsFrom(sone.getId()).addAll(posts);
+                       for (Post post : posts) {
+                               allPosts.put(post.getId(), post);
+                               if (post.getRecipientId().isPresent()) {
+                                       getPostsTo(post.getRecipientId().get()).add(post);
+                               }
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public void removePosts(Sone sone) {
+               checkNotNull(sone, "sone must not be null");
+               lock.writeLock().lock();
+               try {
+                       /* remove all posts by the Sone. */
+                       getPostsFrom(sone.getId()).clear();
+                       for (Post post : sone.getPosts()) {
+                               allPosts.remove(post.getId());
+                               if (post.getRecipientId().isPresent()) {
+                                       getPostsTo(post.getRecipientId().get()).remove(post);
+                               }
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       //
+       // POSTREPLYPROVIDER METHODS
+       //
+
+       /** {@inheritDocs} */
+       @Override
+       public Optional<PostReply> getPostReply(String id) {
+               lock.readLock().lock();
+               try {
+                       return Optional.fromNullable(allPostReplies.get(id));
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public List<PostReply> getReplies(String postId) {
+               lock.readLock().lock();
+               try {
+                       if (!postReplies.containsKey(postId)) {
+                               return Collections.emptyList();
+                       }
+                       return new ArrayList<PostReply>(postReplies.get(postId));
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       //
+       // POSTREPLYBUILDERFACTORY METHODS
+       //
+
+       /** {@inheritDocs} */
+       @Override
+       public PostReplyBuilder newPostReplyBuilder() {
+               return new MemoryPostReplyBuilder(this, soneProvider);
+       }
+
+       //
+       // POSTREPLYSTORE METHODS
+       //
+
+       /** {@inheritDocs} */
+       @Override
+       public void storePostReply(PostReply postReply) {
+               lock.writeLock().lock();
+               try {
+                       allPostReplies.put(postReply.getId(), postReply);
+                       if (postReplies.containsKey(postReply.getPostId())) {
+                               postReplies.get(postReply.getPostId()).add(postReply);
+                       } else {
+                               TreeSet<PostReply> replies = new TreeSet<PostReply>(Reply.TIME_COMPARATOR);
+                               replies.add(postReply);
+                               postReplies.put(postReply.getPostId(), replies);
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public void storePostReplies(Sone sone, Collection<PostReply> postReplies) {
+               checkNotNull(sone, "sone must not be null");
+               /* verify that all posts are from the same Sone. */
+               for (PostReply postReply : postReplies) {
+                       if (!sone.equals(postReply.getSone())) {
+                               throw new IllegalArgumentException(String.format("PostReply from different Sone found: %s", postReply));
+                       }
+               }
+
+               lock.writeLock().lock();
+               try {
+                       /* remove all post replies of the Sone. */
+                       for (PostReply postReply : getRepliesFrom(sone.getId())) {
+                               removePostReply(postReply);
+                       }
+                       for (PostReply postReply : postReplies) {
+                               allPostReplies.put(postReply.getId(), postReply);
+                               sonePostReplies.put(postReply.getSone().getId(), postReply);
+                               if (this.postReplies.containsKey(postReply.getPostId())) {
+                                       this.postReplies.get(postReply.getPostId()).add(postReply);
+                               } else {
+                                       TreeSet<PostReply> replies = new TreeSet<PostReply>(Reply.TIME_COMPARATOR);
+                                       replies.add(postReply);
+                                       this.postReplies.put(postReply.getPostId(), replies);
+                               }
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public void removePostReply(PostReply postReply) {
+               lock.writeLock().lock();
+               try {
+                       allPostReplies.remove(postReply.getId());
+                       if (postReplies.containsKey(postReply.getPostId())) {
+                               postReplies.get(postReply.getPostId()).remove(postReply);
+                               if (postReplies.get(postReply.getPostId()).isEmpty()) {
+                                       postReplies.remove(postReply.getPostId());
+                               }
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /** {@inheritDocs} */
+       @Override
+       public void removePostReplies(Sone sone) {
+               checkNotNull(sone, "sone must not be null");
+
+               lock.writeLock().lock();
+               try {
+                       for (PostReply postReply : sone.getReplies()) {
+                               removePostReply(postReply);
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       //
+       // PACKAGE-PRIVATE METHODS
+       //
+
+       /**
+        * Returns whether the given post is known.
+        *
+        * @param post
+        *              The post
+        * @return {@code true} if the post is known, {@code false} otherwise
+        */
+       boolean isPostKnown(Post post) {
+               lock.readLock().lock();
+               try {
+                       return knownPosts.contains(post.getId());
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       /**
+        * Sets whether the given post is known.
+        *
+        * @param post
+        *              The post
+        * @param known
+        *              {@code true} if the post is known, {@code false} otherwise
+        */
+       void setPostKnown(Post post, boolean known) {
+               lock.writeLock().lock();
+               try {
+                       if (known) {
+                               knownPosts.add(post.getId());
+                       } else {
+                               knownPosts.remove(post.getId());
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /**
+        * Returns whether the given post reply is known.
+        *
+        * @param postReply
+        *              The post reply
+        * @return {@code true} if the given post reply is known, {@code false}
+        *         otherwise
+        */
+       boolean isPostReplyKnown(PostReply postReply) {
+               lock.readLock().lock();
+               try {
+                       return knownPostReplies.contains(postReply.getId());
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       /**
+        * Sets whether the given post reply is known.
+        *
+        * @param postReply
+        *              The post reply
+        * @param known
+        *              {@code true} if the post reply is known, {@code false} otherwise
+        */
+       void setPostReplyKnown(PostReply postReply, boolean known) {
+               lock.writeLock().lock();
+               try {
+                       if (known) {
+                               knownPostReplies.add(postReply.getId());
+                       } else {
+                               knownPostReplies.remove(postReply.getId());
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Gets all posts for the given Sone, creating a new collection if there is
+        * none yet.
+        *
+        * @param soneId
+        *              The ID of the Sone to get the posts for
+        * @return All posts
+        */
+       private Collection<Post> getPostsFrom(String soneId) {
+               Collection<Post> posts = null;
+               lock.readLock().lock();
+               try {
+                       posts = sonePosts.get(soneId);
+               } finally {
+                       lock.readLock().unlock();
+               }
+               if (posts != null) {
+                       return posts;
+               }
+
+               posts = new HashSet<Post>();
+               lock.writeLock().lock();
+               try {
+                       sonePosts.put(soneId, posts);
+               } finally {
+                       lock.writeLock().unlock();
+               }
+
+               return posts;
+       }
+
+       /**
+        * Gets all posts that are directed the given Sone, creating a new collection
+        * if there is none yet.
+        *
+        * @param recipientId
+        *              The ID of the Sone to get the posts for
+        * @return All posts
+        */
+       private Collection<Post> getPostsTo(String recipientId) {
+               Collection<Post> posts = null;
+               lock.readLock().lock();
+               try {
+                       posts = recipientPosts.get(recipientId);
+               } finally {
+                       lock.readLock().unlock();
+               }
+               if (posts != null) {
+                       return posts;
+               }
+
+               posts = new HashSet<Post>();
+               lock.writeLock().lock();
+               try {
+                       recipientPosts.put(recipientId, posts);
+               } finally {
+                       lock.writeLock().unlock();
+               }
+
+               return posts;
+       }
+
+       /** Loads the known posts. */
+       private void loadKnownPosts() {
+               lock.writeLock().lock();
+               try {
+                       int postCounter = 0;
+                       while (true) {
+                               String knownPostId = configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").getValue(null);
+                               if (knownPostId == null) {
+                                       break;
+                               }
+                               knownPosts.add(knownPostId);
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /**
+        * Saves the known posts to the configuration.
+        *
+        * @throws DatabaseException
+        *              if a configuration error occurs
+        */
+       private void saveKnownPosts() throws DatabaseException {
+               lock.readLock().lock();
+               try {
+                       int postCounter = 0;
+                       for (String knownPostId : knownPosts) {
+                               configuration.getStringValue("KnownPosts/" + postCounter++ + "/ID").setValue(knownPostId);
+                       }
+                       configuration.getStringValue("KnownPosts/" + postCounter + "/ID").setValue(null);
+               } catch (ConfigurationException ce1) {
+                       throw new DatabaseException("Could not save database.", ce1);
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       /**
+        * Returns all replies by the given Sone.
+        *
+        * @param id
+        *              The ID of the Sone
+        * @return The post replies of the Sone, sorted by time (newest first)
+        */
+       private Collection<PostReply> getRepliesFrom(String id) {
+               lock.readLock().lock();
+               try {
+                       if (sonePostReplies.containsKey(id)) {
+                               return Collections.unmodifiableCollection(sonePostReplies.get(id));
+                       }
+                       return Collections.emptySet();
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+       /** Loads the known post replies. */
+       private void loadKnownPostReplies() {
+               lock.writeLock().lock();
+               try {
+                       int replyCounter = 0;
+                       while (true) {
+                               String knownReplyId = configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").getValue(null);
+                               if (knownReplyId == null) {
+                                       break;
+                               }
+                               knownPostReplies.add(knownReplyId);
+                       }
+               } finally {
+                       lock.writeLock().unlock();
+               }
+       }
+
+       /**
+        * Saves the known post replies to the configuration.
+        *
+        * @throws DatabaseException
+        *              if a configuration error occurs
+        */
+       private void saveKnownPostReplies() throws DatabaseException {
+               lock.readLock().lock();
+               try {
+                       int replyCounter = 0;
+                       for (String knownReplyId : knownPostReplies) {
+                               configuration.getStringValue("KnownReplies/" + replyCounter++ + "/ID").setValue(knownReplyId);
+                       }
+                       configuration.getStringValue("KnownReplies/" + replyCounter + "/ID").setValue(null);
+               } catch (ConfigurationException ce1) {
+                       throw new DatabaseException("Could not save database.", ce1);
+               } finally {
+                       lock.readLock().unlock();
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPost.java
new file mode 100644 (file)
index 0000000..22fa7e6
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Sone - PostImpl.java - Copyright © 2010–2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database.memory;
+
+import java.util.UUID;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.SoneProvider;
+
+import com.google.common.base.Optional;
+
+/**
+ * A post is a short message that a user writes in his Sone to let other users
+ * know what is going on.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+class MemoryPost implements Post {
+
+       /** The post database. */
+       private final MemoryDatabase postDatabase;
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /** The GUID of the post. */
+       private final UUID id;
+
+       /** The ID of the owning Sone. */
+       private final String soneId;
+
+       /** The ID of the recipient Sone. */
+       private final String recipientId;
+
+       /** The time of the post (in milliseconds since Jan 1, 1970 UTC). */
+       private final long time;
+
+       /** The text of the post. */
+       private final String text;
+
+       /**
+        * Creates a new post.
+        *
+        * @param postDatabase
+        *            The post database
+        * @param soneProvider
+        *            The Sone provider
+        * @param id
+        *            The ID of the post
+        * @param soneId
+        *            The ID of the Sone this post belongs to
+        * @param recipientId
+        *            The ID of the recipient of the post
+        * @param time
+        *            The time of the post (in milliseconds since Jan 1, 1970 UTC)
+        * @param text
+        *            The text of the post
+        */
+       public MemoryPost(MemoryDatabase postDatabase, SoneProvider soneProvider, String id, String soneId, String recipientId, long time, String text) {
+               this.postDatabase = postDatabase;
+               this.soneProvider = soneProvider;
+               this.id = UUID.fromString(id);
+               this.soneId = soneId;
+               this.recipientId = recipientId;
+               this.time = time;
+               this.text = text;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getId() {
+               return id.toString();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone getSone() {
+               return soneProvider.getSone(soneId).get();
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public Optional<String> getRecipientId() {
+               return Optional.fromNullable(recipientId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Optional<Sone> getRecipient() {
+               return soneProvider.getSone(recipientId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public long getTime() {
+               return time;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getText() {
+               return text;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isKnown() {
+               return postDatabase.isPostKnown(this);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public MemoryPost setKnown(boolean known) {
+               postDatabase.setPostKnown(this, known);
+               return this;
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean equals(Object object) {
+               if (!(object instanceof MemoryPost)) {
+                       return false;
+               }
+               MemoryPost post = (MemoryPost) object;
+               return post.id.equals(id);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String toString() {
+               return String.format("%s[id=%s,sone=%s,recipient=%s,time=%d,text=%s]", getClass().getName(), id, soneId, recipientId, time, text);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostBuilder.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostBuilder.java
new file mode 100644 (file)
index 0000000..d0b2c9f
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Sone - MemoryPostBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database.memory;
+
+import java.util.UUID;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.impl.AbstractPostBuilder;
+import net.pterodactylus.sone.database.PostBuilder;
+import net.pterodactylus.sone.database.SoneProvider;
+
+/**
+ * {@link PostBuilder} implementation that creates a {@link MemoryPost}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+class MemoryPostBuilder extends AbstractPostBuilder {
+
+       /** The database. */
+       private final MemoryDatabase database;
+
+       /**
+        * Creates a new memory post builder.
+        *
+        * @param memoryDatabase
+        *            The database
+        * @param soneProvider
+        *            The Sone provider
+        */
+       public MemoryPostBuilder(MemoryDatabase memoryDatabase, SoneProvider soneProvider) {
+               super(soneProvider);
+               database = memoryDatabase;
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public Post build() throws IllegalStateException {
+               validate();
+               Post post = new MemoryPost(database, soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, recipientId, currentTime ? System.currentTimeMillis() : time, text);
+               post.setKnown(database.isPostKnown(post));
+               return post;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReply.java
new file mode 100644 (file)
index 0000000..a6686ca
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Sone - MemoryPostReply.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database.memory;
+
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.SoneProvider;
+
+import com.google.common.base.Optional;
+
+/**
+ * Memory-based {@link PostReply} implementation.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+class MemoryPostReply implements PostReply {
+
+       /** The database. */
+       private final MemoryDatabase database;
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /** The ID of the post reply. */
+       private final String id;
+
+       /** The ID of the owning Sone. */
+       private final String soneId;
+
+       /** The time of the post reply. */
+       private final long time;
+
+       /** The text of the post reply. */
+       private final String text;
+
+       /** The ID of the post this post reply refers to. */
+       private final String postId;
+
+       /**
+        * Creates a new memory-based {@link PostReply} implementation.
+        *
+        * @param database
+        *            The database
+        * @param soneProvider
+        *            The Sone provider
+        * @param id
+        *            The ID of the post reply
+        * @param soneId
+        *            The ID of the owning Sone
+        * @param time
+        *            The time of the post reply
+        * @param text
+        *            The text of the post reply
+        * @param postId
+        *            The ID of the post this post reply refers to
+        */
+       public MemoryPostReply(MemoryDatabase database, SoneProvider soneProvider, String id, String soneId, long time, String text, String postId) {
+               this.database = database;
+               this.soneProvider = soneProvider;
+               this.id = id;
+               this.soneId = soneId;
+               this.time = time;
+               this.text = text;
+               this.postId = postId;
+       }
+
+       //
+       // REPLY METHODS
+       //
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public Sone getSone() {
+               return soneProvider.getSone(soneId).get();
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public long getTime() {
+               return time;
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public String getText() {
+               return text;
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public boolean isKnown() {
+               return database.isPostReplyKnown(this);
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public PostReply setKnown(boolean known) {
+               database.setPostReplyKnown(this, known);
+               return this;
+       }
+
+       //
+       // POSTREPLY METHODS
+       //
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public String getPostId() {
+               return postId;
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public Optional<Post> getPost() {
+               return database.getPost(postId);
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public boolean equals(Object object) {
+               if (!(object instanceof MemoryPostReply)) {
+                       return false;
+               }
+               MemoryPostReply memoryPostReply = (MemoryPostReply) object;
+               return memoryPostReply.id.equals(id);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReplyBuilder.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryPostReplyBuilder.java
new file mode 100644 (file)
index 0000000..32ab1e0
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Sone - MemoryPostReplyBuilder.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.database.memory;
+
+import java.util.UUID;
+
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.impl.AbstractPostReplyBuilder;
+import net.pterodactylus.sone.database.PostReplyBuilder;
+import net.pterodactylus.sone.database.SoneProvider;
+
+/**
+ * {@link PostReplyBuilder} implementation that creates {@link MemoryPostReply}
+ * objects.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+class MemoryPostReplyBuilder extends AbstractPostReplyBuilder {
+
+       /** The database. */
+       private final MemoryDatabase database;
+
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /**
+        * Creates a new {@link MemoryPostReply} builder.
+        *
+        * @param database
+        *            The database
+        * @param soneProvider
+        *            The Sone provider
+        */
+       public MemoryPostReplyBuilder(MemoryDatabase database, SoneProvider soneProvider) {
+               this.database = database;
+               this.soneProvider = soneProvider;
+       }
+
+       /**
+        * {@inheritDocs}
+        */
+       @Override
+       public PostReply build() throws IllegalStateException {
+               validate();
+
+               PostReply postReply = new MemoryPostReply(database, soneProvider, randomId ? UUID.randomUUID().toString() : id, senderId, currentTime ? System.currentTimeMillis() : time, text, postId);
+               postReply.setKnown(database.isPostReplyKnown(postReply));
+               return postReply;
+       }
+
+}
index 1944570..d71ffc4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FcpInterface.java - Copyright © 2011–2012 David Roden
+ * Sone - AbstractSoneCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,7 +32,10 @@ import net.pterodactylus.sone.freenet.fcp.AbstractCommand;
 import net.pterodactylus.sone.freenet.fcp.Command;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
 import net.pterodactylus.sone.template.SoneAccessor;
-import net.pterodactylus.util.collection.filter.Filters;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Collections2;
+
 import freenet.node.FSParseException;
 import freenet.support.SimpleFieldSet;
 
@@ -131,7 +134,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         *             or if the Sone ID is invalid
         */
        protected Sone getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly) throws FcpException {
-               return getSone(simpleFieldSet, parameterName, localOnly, true);
+               return getSone(simpleFieldSet, parameterName, localOnly, true).get();
        }
 
        /**
@@ -155,13 +158,13 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         *             or if {@code mandatory} is {@code true} and the Sone ID is
         *             invalid
         */
-       protected Sone getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly, boolean mandatory) throws FcpException {
+       protected Optional<Sone> getSone(SimpleFieldSet simpleFieldSet, String parameterName, boolean localOnly, boolean mandatory) throws FcpException {
                String soneId = simpleFieldSet.get(parameterName);
                if (mandatory && (soneId == null)) {
                        throw new FcpException("Could not load Sone ID from “" + parameterName + "”.");
                }
-               Sone sone = localOnly ? core.getLocalSone(soneId, false) : core.getSone(soneId, false);
-               if (mandatory && (sone == null)) {
+               Optional<Sone> sone = core.getSone(soneId);
+               if ((mandatory && !sone.isPresent()) || (mandatory && sone.isPresent() && (localOnly && !sone.get().isLocal()))) {
                        throw new FcpException("Could not load Sone from “" + soneId + "”.");
                }
                return sone;
@@ -183,11 +186,11 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
        protected Post getPost(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
                try {
                        String postId = simpleFieldSet.getString(parameterName);
-                       Post post = core.getPost(postId, false);
-                       if (post == null) {
+                       Optional<Post> post = core.getPost(postId);
+                       if (!post.isPresent()) {
                                throw new FcpException("Could not load post from “" + postId + "”.");
                        }
-                       return post;
+                       return post.get();
                } catch (FSParseException fspe1) {
                        throw new FcpException("Could not post ID from “" + parameterName + "”.", fspe1);
                }
@@ -209,11 +212,11 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
        protected PostReply getReply(SimpleFieldSet simpleFieldSet, String parameterName) throws FcpException {
                try {
                        String replyId = simpleFieldSet.getString(parameterName);
-                       PostReply reply = core.getReply(replyId, false);
-                       if (reply == null) {
+                       Optional<PostReply> reply = core.getPostReply(replyId);
+                       if (!reply.isPresent()) {
                                throw new FcpException("Could not load reply from “" + replyId + "”.");
                        }
-                       return reply;
+                       return reply.get();
                } catch (FSParseException fspe1) {
                        throw new FcpException("Could not reply ID from “" + parameterName + "”.", fspe1);
                }
@@ -233,14 +236,14 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         *            such as if the Sone is followed by the local Sone
         * @return The simple field set containing the given Sone
         */
-       protected static SimpleFieldSet encodeSone(Sone sone, String prefix, Sone localSone) {
+       protected static SimpleFieldSet encodeSone(Sone sone, String prefix, Optional<Sone> localSone) {
                SimpleFieldSetBuilder soneBuilder = new SimpleFieldSetBuilder();
 
                soneBuilder.put(prefix + "Name", sone.getName());
                soneBuilder.put(prefix + "NiceName", SoneAccessor.getNiceName(sone));
                soneBuilder.put(prefix + "LastUpdated", sone.getTime());
-               if (localSone != null) {
-                       soneBuilder.put(prefix + "Followed", String.valueOf(localSone.hasFriend(sone.getId())));
+               if (localSone.isPresent()) {
+                       soneBuilder.put(prefix + "Followed", String.valueOf(localSone.get().hasFriend(sone.getId())));
                }
                Profile profile = sone.getProfile();
                soneBuilder.put(prefix + "Field.Count", profile.getFields().size());
@@ -298,15 +301,15 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
 
                postBuilder.put(prefix + "ID", post.getId());
                postBuilder.put(prefix + "Sone", post.getSone().getId());
-               if (post.getRecipient() != null) {
-                       postBuilder.put(prefix + "Recipient", post.getRecipient().getId());
+               if (post.getRecipientId().isPresent()) {
+                       postBuilder.put(prefix + "Recipient", post.getRecipientId().get());
                }
                postBuilder.put(prefix + "Time", post.getTime());
                postBuilder.put(prefix + "Text", encodeString(post.getText()));
                postBuilder.put(encodeLikes(core.getLikes(post), prefix + "Likes."));
 
                if (includeReplies) {
-                       List<PostReply> replies = core.getReplies(post);
+                       List<PostReply> replies = core.getReplies(post.getId());
                        postBuilder.put(encodeReplies(replies, prefix));
                }
 
@@ -335,7 +338,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                        String postPrefix = prefix + postIndex++;
                        postBuilder.put(encodePost(post, postPrefix + ".", includeReplies));
                        if (includeReplies) {
-                               postBuilder.put(encodeReplies(Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLY_FILTER), postPrefix + "."));
+                               postBuilder.put(encodeReplies(Collections2.filter(core.getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER), postPrefix + "."));
                        }
                }
 
index a37dc46..f6c5dd9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreatePostCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - CreatePostCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.fcp;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
@@ -57,7 +59,7 @@ public class CreatePostCommand extends AbstractSoneCommand {
                if (sone.equals(recipient)) {
                        return new ErrorResponse("Sone and Recipient must not be the same.");
                }
-               Post post = getCore().createPost(sone, recipient, text);
+               Post post = getCore().createPost(sone, Optional.fromNullable(recipient), text);
                return new Response("PostCreated", new SimpleFieldSetBuilder().put("Post", post.getId()).get());
        }
 
index 84bedc7..fe4bee8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateReplyCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - CreateReplyCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 54f01c8..00d5018 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeletePostCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - DeletePostCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -48,7 +48,7 @@ public class DeletePostCommand extends AbstractSoneCommand {
        @Override
        public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
                Post post = getPost(parameters, "Post");
-               if (!getCore().isLocalSone(post.getSone())) {
+               if (!post.getSone().isLocal()) {
                        return new ErrorResponse(401, "Not allowed.");
                }
                return new Response("PostDeleted", new SimpleFieldSetBuilder().get());
index daed26d..e9c0926 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplyCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - DeleteReplyCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -48,7 +48,7 @@ public class DeleteReplyCommand extends AbstractSoneCommand {
        @Override
        public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
                PostReply reply = getReply(parameters, "Reply");
-               if (!getCore().isLocalSone(reply.getSone())) {
+               if (!reply.getSone().isLocal()) {
                        return new ErrorResponse(401, "Not allowed.");
                }
                return new Response("ReplyDeleted", new SimpleFieldSetBuilder().get());
index b3222be..02d84cc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FcpInterface.java - Copyright © 2011–2012 David Roden
+ * Sone - FcpInterface.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.fcp;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -27,9 +29,10 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.freenet.fcp.Command.AccessType;
 import net.pterodactylus.sone.freenet.fcp.Command.ErrorResponse;
 import net.pterodactylus.sone.freenet.fcp.Command.Response;
-import net.pterodactylus.sone.freenet.fcp.FcpException;
 import net.pterodactylus.util.logging.Logging;
-import net.pterodactylus.util.validation.Validation;
+
+import com.google.inject.Inject;
+
 import freenet.pluginmanager.FredPluginFCP;
 import freenet.pluginmanager.PluginNotFoundException;
 import freenet.pluginmanager.PluginReplySender;
@@ -80,6 +83,7 @@ public class FcpInterface {
         * @param core
         *            The core
         */
+       @Inject
        public FcpInterface(Core core) {
                commands.put("Version", new VersionCommand(core));
                commands.put("GetLocalSones", new GetLocalSonesCommand(core));
@@ -88,6 +92,8 @@ public class FcpInterface {
                commands.put("GetPost", new GetPostCommand(core));
                commands.put("GetPosts", new GetPostsCommand(core));
                commands.put("GetPostFeed", new GetPostFeedCommand(core));
+               commands.put("LockSone", new LockSoneCommand(core));
+               commands.put("UnlockSone", new UnlockSoneCommand(core));
                commands.put("LikePost", new LikePostCommand(core));
                commands.put("LikeReply", new LikeReplyCommand(core));
                commands.put("CreatePost", new CreatePostCommand(core));
@@ -119,8 +125,7 @@ public class FcpInterface {
         *            The action level for which full FCP access is required
         */
        public void setFullAccessRequired(FullAccessRequired fullAccessRequired) {
-               Validation.begin().isNotNull("FullAccessRequired", fullAccessRequired).check();
-               this.fullAccessRequired = fullAccessRequired;
+               this.fullAccessRequired = checkNotNull(fullAccessRequired, "fullAccessRequired must not be null");
        }
 
        //
@@ -172,9 +177,9 @@ public class FcpInterface {
                        try {
                                Response response = command.execute(parameters, data, AccessType.values()[accessType]);
                                sendReply(pluginReplySender, identifier, response);
-                       } catch (FcpException fe1) {
+                       } catch (Exception e1) {
                                logger.log(Level.WARNING, "Could not process FCP command “%s”.", command);
-                               sendReply(pluginReplySender, identifier, new ErrorResponse("Error executing command: " + fe1.getMessage()));
+                               sendReply(pluginReplySender, identifier, new ErrorResponse("Error executing command: " + e1.getMessage()));
                        }
                } catch (PluginNotFoundException pnfe1) {
                        logger.log(Level.WARNING, "Could not find destination plugin: " + pluginReplySender);
index 4619004..b8684b0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetLocalSonesCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - GetLocalSonesCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@
 package net.pterodactylus.sone.fcp;
 
 import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
 import freenet.support.api.Bucket;
 
@@ -44,7 +43,7 @@ public class GetLocalSonesCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) {
                return new Response("ListLocalSones", encodeSones(getCore().getLocalSones(), "LocalSones."));
        }
 
index e15d7d6..c503f5b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - GetPostCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 781bed2..b22d356 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostFeedCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - GetPostFeedCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 package net.pterodactylus.sone.fcp;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
-import net.pterodactylus.util.collection.filter.Filters;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Collections2;
+
 import freenet.support.SimpleFieldSet;
 import freenet.support.api.Bucket;
 
@@ -58,16 +61,17 @@ public class GetPostFeedCommand extends AbstractSoneCommand {
                int startPost = getInt(parameters, "StartPost", 0);
                int maxPosts = getInt(parameters, "MaxPosts", -1);
 
-               Set<Post> allPosts = new HashSet<Post>();
+               Collection<Post> allPosts = new HashSet<Post>();
                allPosts.addAll(sone.getPosts());
                for (String friendSoneId : sone.getFriends()) {
-                       if (!getCore().hasSone(friendSoneId)) {
+                       Optional<Sone> friendSone = getCore().getSone(friendSoneId);
+                       if (!friendSone.isPresent()) {
                                continue;
                        }
-                       allPosts.addAll(getCore().getSone(friendSoneId, false).getPosts());
+                       allPosts.addAll(friendSone.get().getPosts());
                }
-               allPosts.addAll(getCore().getDirectedPosts(sone));
-               allPosts = Filters.filteredSet(allPosts, Post.FUTURE_POSTS_FILTER);
+               allPosts.addAll(getCore().getDirectedPosts(sone.getId()));
+               allPosts = Collections2.filter(allPosts, Post.FUTURE_POSTS_FILTER);
 
                List<Post> sortedPosts = new ArrayList<Post>(allPosts);
                Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
index 36d2912..1947718 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostsCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - GetPostsCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c71ea43..e3ec1b1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetSoneCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - GetSoneCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,6 +21,9 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Profile;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.fcp.FcpException;
+
+import com.google.common.base.Optional;
+
 import freenet.support.SimpleFieldSet;
 import freenet.support.api.Bucket;
 
@@ -48,7 +51,7 @@ public class GetSoneCommand extends AbstractSoneCommand {
        @Override
        public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
                Sone sone = getSone(parameters, "Sone", false);
-               Sone localSone = getSone(parameters, "LocalSone", false, false);
+               Optional<Sone> localSone = getSone(parameters, "LocalSone", false, false);
                return new Response("Sone", encodeSone(sone, "", localSone));
        }
 
index 5c9c117..9ff8587 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetSonesCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - GetSonesCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,7 +23,6 @@ import java.util.List;
 
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.sone.freenet.fcp.FcpException;
 import freenet.support.SimpleFieldSet;
 import freenet.support.api.Bucket;
 
@@ -48,7 +47,7 @@ public class GetSonesCommand extends AbstractSoneCommand {
         * {@inheritDoc}
         */
        @Override
-       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) {
                int startSone = getInt(parameters, "StartSone", 0);
                int maxSones = getInt(parameters, "MaxSones", -1);
                List<Sone> sones = new ArrayList<Sone>(getCore().getSones());
index d13271a..20d251c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikePostCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - LikePostCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index cc989ab..a462f08 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikeReplyCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - LikeReplyCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/fcp/LockSoneCommand.java b/src/main/java/net/pterodactylus/sone/fcp/LockSoneCommand.java
new file mode 100644 (file)
index 0000000..5cad8ca
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Sone - LockSoneCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.fcp;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
+import net.pterodactylus.sone.freenet.fcp.FcpException;
+
+import freenet.support.SimpleFieldSet;
+import freenet.support.api.Bucket;
+
+import com.google.common.base.Optional;
+
+/**
+ * Implements the “LockSone” FCP command. If a valid local Sone was given as
+ * parameter “Sone,” this command will always lock the Sone and reply with
+ * “SoneLocked.”
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class LockSoneCommand extends AbstractSoneCommand {
+
+       /**
+        * Creates a new “LockSone” FCP command.
+        *
+        * @param core
+        *              The core to operate on
+        */
+       public LockSoneCommand(Core core) {
+               super(core, true);
+       }
+
+       //
+       // COMMAND METHODS
+       //
+
+       @Override
+       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+               Optional<Sone> sone = getSone(parameters, "Sone", true, true);
+               getCore().lockSone(sone.get());
+               return new Response("SoneLocked", new SimpleFieldSetBuilder().put("Sone", sone.get().getId()).get());
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/fcp/UnlockSoneCommand.java b/src/main/java/net/pterodactylus/sone/fcp/UnlockSoneCommand.java
new file mode 100644 (file)
index 0000000..7ecfada
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Sone - LockSoneCommand.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.fcp;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
+import net.pterodactylus.sone.freenet.fcp.FcpException;
+
+import freenet.support.SimpleFieldSet;
+import freenet.support.api.Bucket;
+
+import com.google.common.base.Optional;
+
+/**
+ * Implements the “UnlockSone” FCP command. If a valid local Sone was given as
+ * parameter “Sone,” this command will always unlock the Sone and reply with
+ * “SoneUnlocked.”
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UnlockSoneCommand extends AbstractSoneCommand {
+
+       /**
+        * Creates a new “LockSone” FCP command.
+        *
+        * @param core
+        *              The core to operate on
+        */
+       public UnlockSoneCommand(Core core) {
+               super(core, true);
+       }
+
+       //
+       // COMMAND METHODS
+       //
+
+       @Override
+       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException {
+               Optional<Sone> sone = getSone(parameters, "Sone", true, true);
+               getCore().unlockSone(sone.get());
+               return new Response("SoneUnlocked", new SimpleFieldSetBuilder().put("Sone", sone.get().getId()).get());
+       }
+
+}
index bc2adb2..c482f24 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - VersionCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - VersionCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 7fffd5c..36ac8cd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - L10nFilter.java - Copyright © 2010–2012 David Roden
+ * Sone - L10nFilter.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 94a2690..1e95389 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010–2012 David Roden
+ * Sone - PluginStoreConfigurationBackend.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5507b0d..a256edb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SimpleFieldSetBuilder.java - Copyright © 2011–2012 David Roden
+ * Sone - SimpleFieldSetBuilder.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,7 +17,8 @@
 
 package net.pterodactylus.sone.freenet;
 
-import net.pterodactylus.util.validation.Validation;
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import freenet.support.SimpleFieldSet;
 
 /**
@@ -46,8 +47,7 @@ public class SimpleFieldSetBuilder {
         *            The simple field set to build
         */
        public SimpleFieldSetBuilder(SimpleFieldSet simpleFieldSet) {
-               Validation.begin().isNotNull("Simple Field Set", simpleFieldSet).check();
-               this.simpleFieldSet = simpleFieldSet;
+               this.simpleFieldSet = checkNotNull(simpleFieldSet, "simpleFieldSet must not be null");
        }
 
        /**
index 8f6b27f..c96a3cd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - StringBucket.java - Copyright © 2010–2012 David Roden
+ * Sone - StringBucket.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,7 +18,6 @@
 package net.pterodactylus.sone.freenet;
 
 import java.io.ByteArrayInputStream;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.Charset;
@@ -84,7 +83,7 @@ public class StringBucket implements Bucket {
         * {@inheritDoc}
         */
        @Override
-       public InputStream getInputStream() throws IOException {
+       public InputStream getInputStream() {
                return new ByteArrayInputStream(string.getBytes(encoding));
        }
 
@@ -100,7 +99,7 @@ public class StringBucket implements Bucket {
         * {@inheritDoc}
         */
        @Override
-       public OutputStream getOutputStream() throws IOException {
+       public OutputStream getOutputStream() {
                return null;
        }
 
index b73fb1d..fa5a372 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AbstractCommand.java - Copyright © 2011–2012 David Roden
+ * Sone - AbstractCommand.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e3b55a9..32df6d8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Command.java - Copyright © 2011–2012 David Roden
+ * Sone - Command.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 22bb269..e40438c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FcpException.java - Copyright © 2011–2012 David Roden
+ * Sone - FcpException.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListener.java
deleted file mode 100644 (file)
index 76a5497..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Sone - ConnectorListener.java - Copyright © 2010–2012 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.freenet.plugin;
-
-import java.util.EventListener;
-
-import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
-
-/**
- * Interface for objects that want to be notified if a {@link PluginConnector}
- * receives a reply from a plugin. As a connection listener is always
- * {@link PluginConnector#addConnectorListener(String, String, ConnectorListener)
- * added} for a specific plugin, it will always be notified for replies from the
- * correct plugin (unless you register the same listener for multiple
- * plugins—which you subsequently should not do).
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface ConnectorListener extends EventListener {
-
-       /**
-        * A reply was received from the plugin this connection listener was added
-        * for.
-        *
-        * @param pluginConnector
-        *            The plugin connector that received the reply
-        * @param fields
-        *            The fields of the reply
-        * @param data
-        *            The data of the reply (may be null)
-        */
-       public void receivedReply(PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListenerManager.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/ConnectorListenerManager.java
deleted file mode 100644 (file)
index 742a514..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Sone - ConnectorListenerManager.java - Copyright © 2010–2012 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.freenet.plugin;
-
-import net.pterodactylus.util.event.AbstractListenerManager;
-import freenet.support.SimpleFieldSet;
-import freenet.support.api.Bucket;
-
-/**
- * Manages {@link ConnectorListener}s and fire events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class ConnectorListenerManager extends AbstractListenerManager<PluginConnector, ConnectorListener> {
-
-       /**
-        * Creates a new manager for {@link ConnectorListener}s.
-        *
-        * @param pluginConnector
-        *            The plugin connector that is the source for all events
-        */
-       public ConnectorListenerManager(PluginConnector pluginConnector) {
-               super(pluginConnector);
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Notifies all registered listeners that a reply from the plugin was
-        * received.
-        *
-        * @param fields
-        *            The fields of the reply
-        * @param data
-        *            The data of the reply (may be null)
-        */
-       public void fireReceivedReply(SimpleFieldSet fields, Bucket data) {
-               for (ConnectorListener connectorListener : getListeners()) {
-                       connectorListener.receivedReply(getSource(), fields, data);
-               }
-       }
-
-}
index 9145cf5..47330e8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginConnector.java - Copyright © 2010–2012 David Roden
+ * Sone - PluginConnector.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.freenet.plugin;
 
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
+import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent;
+
+import com.google.common.eventbus.EventBus;
+import com.google.inject.Inject;
 
-import net.pterodactylus.util.collection.Pair;
 import freenet.pluginmanager.FredPluginTalker;
 import freenet.pluginmanager.PluginNotFoundException;
 import freenet.pluginmanager.PluginRespirator;
@@ -37,55 +37,27 @@ import freenet.support.api.Bucket;
  */
 public class PluginConnector implements FredPluginTalker {
 
+       /** The event bus. */
+       private final EventBus eventBus;
+
        /** The plugin respirator. */
        private final PluginRespirator pluginRespirator;
 
-       /** Connector listener managers for all plugin connections. */
-       private final Map<Pair<String, String>, ConnectorListenerManager> connectorListenerManagers = Collections.synchronizedMap(new HashMap<Pair<String, String>, ConnectorListenerManager>());
-
        /**
         * Creates a new plugin connector.
         *
+        * @param eventBus
+        *            The event bus
         * @param pluginRespirator
         *            The plugin respirator
         */
-       public PluginConnector(PluginRespirator pluginRespirator) {
+       @Inject
+       public PluginConnector(EventBus eventBus, PluginRespirator pluginRespirator) {
+               this.eventBus = eventBus;
                this.pluginRespirator = pluginRespirator;
        }
 
        //
-       // LISTENER MANAGEMENT
-       //
-
-       /**
-        * Adds a connection listener for the given plugin connection.
-        *
-        * @param pluginName
-        *            The name of the plugin
-        * @param identifier
-        *            The identifier of the connection
-        * @param connectorListener
-        *            The listener to add
-        */
-       public void addConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) {
-               getConnectorListenerManager(pluginName, identifier).addListener(connectorListener);
-       }
-
-       /**
-        * Removes a connection listener for the given plugin connection.
-        *
-        * @param pluginName
-        *            The name of the plugin
-        * @param identifier
-        *            The identifier of the connection
-        * @param connectorListener
-        *            The listener to remove
-        */
-       public void removeConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) {
-               getConnectorListenerManager(pluginName, identifier).removeListener(connectorListener);
-       }
-
-       //
        // ACTIONS
        //
 
@@ -128,43 +100,6 @@ public class PluginConnector implements FredPluginTalker {
        //
 
        /**
-        * Returns the connection listener manager for the given plugin connection,
-        * creating a new one if none does exist yet.
-        *
-        * @param pluginName
-        *            The name of the plugin
-        * @param identifier
-        *            The identifier of the connection
-        * @return The connection listener manager
-        */
-       private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier) {
-               return getConnectorListenerManager(pluginName, identifier, true);
-       }
-
-       /**
-        * Returns the connection listener manager for the given plugin connection,
-        * optionally creating a new one if none does exist yet.
-        *
-        * @param pluginName
-        *            The name of the plugin
-        * @param identifier
-        *            The identifier of the connection
-        * @param create
-        *            {@code true} to create a new manager if there is none,
-        *            {@code false} to return {@code null} in that case
-        * @return The connection listener manager, or {@code null} if none existed
-        *         and {@code create} is {@code false}
-        */
-       private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier, boolean create) {
-               ConnectorListenerManager connectorListenerManager = connectorListenerManagers.get(new Pair<String, String>(pluginName, identifier));
-               if (create && (connectorListenerManager == null)) {
-                       connectorListenerManager = new ConnectorListenerManager(this);
-                       connectorListenerManagers.put(new Pair<String, String>(pluginName, identifier), connectorListenerManager);
-               }
-               return connectorListenerManager;
-       }
-
-       /**
         * Returns the plugin talker for the given plugin connection.
         *
         * @param pluginName
@@ -192,12 +127,7 @@ public class PluginConnector implements FredPluginTalker {
         */
        @Override
        public void onReply(String pluginName, String identifier, SimpleFieldSet params, Bucket data) {
-               ConnectorListenerManager connectorListenerManager = getConnectorListenerManager(pluginName, identifier, false);
-               if (connectorListenerManager == null) {
-                       /* we don’t care about events for this plugin. */
-                       return;
-               }
-               connectorListenerManager.fireReceivedReply(params, data);
+               eventBus.post(new ReceivedReplyEvent(this, pluginName, identifier, params, data));
        }
 
 }
index 08810b3..fd3dec7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PluginException.java - Copyright © 2010–2012 David Roden
+ * Sone - PluginException.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java
new file mode 100644 (file)
index 0000000..89a8df3
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Sone - ReceivedReplyEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.plugin.event;
+
+import net.pterodactylus.sone.freenet.plugin.PluginConnector;
+import freenet.support.SimpleFieldSet;
+import freenet.support.api.Bucket;
+
+/**
+ * Event that signals that a plugin reply was received.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ReceivedReplyEvent {
+
+       /** The connector that received the reply. */
+       private final PluginConnector pluginConnector;
+
+       /** The name of the plugin that sent the reply. */
+       private final String pluginName;
+
+       /** The identifier of the initial request. */
+       private final String identifier;
+
+       /** The fields containing the reply. */
+       private final SimpleFieldSet fieldSet;
+
+       /** The optional reply data. */
+       private final Bucket data;
+
+       /**
+        * Creates a new “reply received” event.
+        *
+        * @param pluginConnector
+        *            The connector that received the event
+        * @param pluginName
+        *            The name of the plugin that sent the reply
+        * @param identifier
+        *            The identifier of the initial request
+        * @param fieldSet
+        *            The fields containing the reply
+        * @param data
+        *            The optional data of the reply
+        */
+       public ReceivedReplyEvent(PluginConnector pluginConnector, String pluginName, String identifier, SimpleFieldSet fieldSet, Bucket data) {
+               this.pluginConnector = pluginConnector;
+               this.pluginName = pluginName;
+               this.identifier = identifier;
+               this.fieldSet = fieldSet;
+               this.data = data;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the plugin connector that received the reply.
+        *
+        * @return The plugin connector that received the reply
+        */
+       public PluginConnector pluginConnector() {
+               return pluginConnector;
+       }
+
+       /**
+        * Returns the name of the plugin that sent the reply.
+        *
+        * @return The name of the plugin that sent the reply
+        */
+       public String pluginName() {
+               return pluginName;
+       }
+
+       /**
+        * Returns the identifier of the initial request.
+        *
+        * @return The identifier of the initial request
+        */
+       public String identifier() {
+               return identifier;
+       }
+
+       /**
+        * Returns the fields containing the reply.
+        *
+        * @return The fields containing the reply
+        */
+       public SimpleFieldSet fieldSet() {
+               return fieldSet;
+       }
+
+       /**
+        * Returns the optional data of the reply.
+        *
+        * @return The optional data of the reply (may be {@code null})
+        */
+       public Bucket data() {
+               return data;
+       }
+
+}
index 2ef33c3..0cfb055 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultIdentity.java - Copyright © 2010–2012 David Roden
+ * Sone - DefaultIdentity.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -208,7 +208,7 @@ public class DefaultIdentity implements Identity {
         */
        @Override
        public int hashCode() {
-               return id.hashCode();
+               return getId().hashCode();
        }
 
        /**
@@ -216,11 +216,11 @@ public class DefaultIdentity implements Identity {
         */
        @Override
        public boolean equals(Object object) {
-               if (!(object instanceof DefaultIdentity)) {
+               if (!(object instanceof Identity)) {
                        return false;
                }
-               DefaultIdentity identity = (DefaultIdentity) object;
-               return identity.id.equals(id);
+               Identity identity = (Identity) object;
+               return identity.getId().equals(getId());
        }
 
        /**
index 451dd5f..4f87b97 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DefaultOwnIdentity.java - Copyright © 2010–2012 David Roden
+ * Sone - DefaultOwnIdentity.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2849da9..bc594f8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Identity.java - Copyright © 2010–2012 David Roden
+ * Sone - Identity.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListener.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListener.java
deleted file mode 100644 (file)
index 51e1437..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Sone - IdentityListener.java - Copyright © 2010–2012 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.freenet.wot;
-
-import java.util.EventListener;
-
-/**
- * Listener interface for {@link IdentityManager} events.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public interface IdentityListener extends EventListener {
-
-       /**
-        * Notifies a listener that an {@link OwnIdentity} that was not known on the
-        * previous check is available.
-        *
-        * @param ownIdentity
-        *            The new own identity
-        */
-       public void ownIdentityAdded(OwnIdentity ownIdentity);
-
-       /**
-        * Notifies a listener that an {@link OwnIdentity} that was available during
-        * the last check has gone away.
-        *
-        * @param ownIdentity
-        *            The disappeared own identity
-        */
-       public void ownIdentityRemoved(OwnIdentity ownIdentity);
-
-       /**
-        * Notifies a listener that a new identity was discovered.
-        *
-        * @param ownIdentity
-        *            The own identity at the root of the trust tree
-        * @param identity
-        *            The new identity
-        */
-       public void identityAdded(OwnIdentity ownIdentity, Identity identity);
-
-       /**
-        * Notifies a listener that some properties of the identity have changed.
-        *
-        * @param ownIdentity
-        *            The own identity at the root of the trust tree
-        * @param identity
-        *            The updated identity
-        */
-       public void identityUpdated(OwnIdentity ownIdentity, Identity identity);
-
-       /**
-        * Notifies a listener that an identity has gone away.
-        *
-        * @param ownIdentity
-        *            The own identity at the root of the trust tree
-        * @param identity
-        *            The disappeared identity
-        */
-       public void identityRemoved(OwnIdentity ownIdentity, Identity identity);
-
-}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListenerManager.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityListenerManager.java
deleted file mode 100644 (file)
index 50808a2..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Sone - IdentityListenerManager.java - Copyright © 2010–2012 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.freenet.wot;
-
-import net.pterodactylus.util.event.AbstractListenerManager;
-
-/**
- * Manager for {@link IdentityListener}s.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
-public class IdentityListenerManager extends AbstractListenerManager<IdentityManager, IdentityListener> {
-
-       /**
-        * Creates a new identity listener manager.
-        */
-       public IdentityListenerManager() {
-               super(null);
-       }
-
-       //
-       // ACTIONS
-       //
-
-       /**
-        * Notifies all listeners that an {@link OwnIdentity} that was not known on
-        * the previous check is available.
-        *
-        * @see IdentityListener#ownIdentityAdded(OwnIdentity)
-        * @param ownIdentity
-        *            The new own identity
-        */
-       public void fireOwnIdentityAdded(OwnIdentity ownIdentity) {
-               for (IdentityListener identityListener : getListeners()) {
-                       identityListener.ownIdentityAdded(ownIdentity);
-               }
-       }
-
-       /**
-        * Notifies all listeners that an {@link OwnIdentity} that was available
-        * during the last check has gone away.
-        *
-        * @see IdentityListener#ownIdentityRemoved(OwnIdentity)
-        * @param ownIdentity
-        *            The disappeared own identity
-        */
-       public void fireOwnIdentityRemoved(OwnIdentity ownIdentity) {
-               for (IdentityListener identityListener : getListeners()) {
-                       identityListener.ownIdentityRemoved(ownIdentity);
-               }
-       }
-
-       /**
-        * Notifies all listeners that a new identity was discovered.
-        *
-        * @see IdentityListener#identityAdded(OwnIdentity, Identity)
-        * @param ownIdentity
-        *            The own identity at the root of the trust tree
-        * @param identity
-        *            The new identity
-        */
-       public void fireIdentityAdded(OwnIdentity ownIdentity, Identity identity) {
-               for (IdentityListener identityListener : getListeners()) {
-                       identityListener.identityAdded(ownIdentity, identity);
-               }
-       }
-
-       /**
-        * Notifies all listeners that some properties of the identity have changed.
-        *
-        * @see IdentityListener#identityUpdated(OwnIdentity, Identity)
-        * @param ownIdentity
-        *            The own identity at the root of the trust tree
-        * @param identity
-        *            The updated identity
-        */
-       public void fireIdentityUpdated(OwnIdentity ownIdentity, Identity identity) {
-               for (IdentityListener identityListener : getListeners()) {
-                       identityListener.identityUpdated(ownIdentity, identity);
-               }
-       }
-
-       /**
-        * Notifies all listeners that an identity has gone away.
-        *
-        * @see IdentityListener#identityRemoved(OwnIdentity, Identity)
-        * @param ownIdentity
-        *            The own identity at the root of the trust tree
-        * @param identity
-        *            The disappeared identity
-        */
-       public void fireIdentityRemoved(OwnIdentity ownIdentity, Identity identity) {
-               for (IdentityListener identityListener : getListeners()) {
-                       identityListener.identityRemoved(ownIdentity, identity);
-               }
-       }
-
-}
index 568a637..de36ca1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityManager.java - Copyright © 2010–2012 David Roden
+ * Sone - IdentityManager.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,16 +27,25 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.freenet.plugin.PluginException;
+import net.pterodactylus.sone.freenet.wot.event.IdentityAddedEvent;
+import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent;
+import net.pterodactylus.sone.freenet.wot.event.IdentityUpdatedEvent;
+import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent;
+import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.service.AbstractService;
 
+import com.google.common.eventbus.EventBus;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
 /**
  * The identity manager takes care of loading and storing identities, their
  * contexts, and properties. It does so in a way that does not expose errors via
  * exceptions but it only logs them and tries to return sensible defaults.
  * <p>
  * It is also responsible for polling identities from the Web of Trust plugin
- * and notifying registered {@link IdentityListener}s when {@link Identity}s and
+ * and sending events to the {@link EventBus} when {@link Identity}s and
  * {@link OwnIdentity}s are discovered or disappearing.
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
@@ -52,8 +61,8 @@ public class IdentityManager extends AbstractService {
        /** The logger. */
        private static final Logger logger = Logging.getLogger(IdentityManager.class);
 
-       /** The event manager. */
-       private final IdentityListenerManager identityListenerManager = new IdentityListenerManager();
+       /** The event bus. */
+       private final EventBus eventBus;
 
        /** The Web of Trust connector. */
        private final WebOfTrustConnector webOfTrustConnector;
@@ -71,43 +80,23 @@ public class IdentityManager extends AbstractService {
        /**
         * Creates a new identity manager.
         *
+        * @param eventBus
+        *            The event bus
         * @param webOfTrustConnector
         *            The Web of Trust connector
         * @param context
         *            The context to focus on (may be {@code null} to ignore
         *            contexts)
         */
-       public IdentityManager(WebOfTrustConnector webOfTrustConnector, String context) {
+       @Inject
+       public IdentityManager(EventBus eventBus, WebOfTrustConnector webOfTrustConnector, @Named("WebOfTrustContext") String context) {
                super("Sone Identity Manager", false);
+               this.eventBus = eventBus;
                this.webOfTrustConnector = webOfTrustConnector;
                this.context = context;
        }
 
        //
-       // LISTENER MANAGEMENT
-       //
-
-       /**
-        * Adds a listener for identity events.
-        *
-        * @param identityListener
-        *            The listener to add
-        */
-       public void addIdentityListener(IdentityListener identityListener) {
-               identityListenerManager.addListener(identityListener);
-       }
-
-       /**
-        * Removes a listener for identity events.
-        *
-        * @param identityListener
-        *            The listener to remove
-        */
-       public void removeIdentityListener(IdentityListener identityListener) {
-               identityListenerManager.removeListener(identityListener);
-       }
-
-       //
        // ACCESSORS
        //
 
@@ -190,7 +179,10 @@ public class IdentityManager extends AbstractService {
                                        Map<String, Identity> identities = new HashMap<String, Identity>();
                                        currentIdentities.put(ownIdentity, identities);
 
-                                       /* if the context doesn’t match, skip getting trusted identities. */
+                                       /*
+                                        * if the context doesn’t match, skip getting trusted
+                                        * identities.
+                                        */
                                        if ((context != null) && !ownIdentity.hasContext(context)) {
                                                continue;
                                        }
@@ -218,7 +210,7 @@ public class IdentityManager extends AbstractService {
                                        /* find new identities. */
                                        for (Identity currentIdentity : currentIdentities.get(ownIdentity).values()) {
                                                if (!oldIdentities.containsKey(ownIdentity) || !oldIdentities.get(ownIdentity).containsKey(currentIdentity.getId())) {
-                                                       identityListenerManager.fireIdentityAdded(ownIdentity, currentIdentity);
+                                                       eventBus.post(new IdentityAddedEvent(ownIdentity, currentIdentity));
                                                }
                                        }
 
@@ -226,7 +218,7 @@ public class IdentityManager extends AbstractService {
                                        if (oldIdentities.containsKey(ownIdentity)) {
                                                for (Identity oldIdentity : oldIdentities.get(ownIdentity).values()) {
                                                        if (!currentIdentities.get(ownIdentity).containsKey(oldIdentity.getId())) {
-                                                               identityListenerManager.fireIdentityRemoved(ownIdentity, oldIdentity);
+                                                               eventBus.post(new IdentityRemovedEvent(ownIdentity, oldIdentity));
                                                        }
                                                }
 
@@ -239,12 +231,12 @@ public class IdentityManager extends AbstractService {
                                                        Set<String> oldContexts = oldIdentity.getContexts();
                                                        Set<String> newContexts = newIdentity.getContexts();
                                                        if (oldContexts.size() != newContexts.size()) {
-                                                               identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                               eventBus.post(new IdentityUpdatedEvent(ownIdentity, newIdentity));
                                                                continue;
                                                        }
                                                        for (String oldContext : oldContexts) {
                                                                if (!newContexts.contains(oldContext)) {
-                                                                       identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                                       eventBus.post(new IdentityUpdatedEvent(ownIdentity, newIdentity));
                                                                        break;
                                                                }
                                                        }
@@ -259,12 +251,12 @@ public class IdentityManager extends AbstractService {
                                                        Map<String, String> oldProperties = oldIdentity.getProperties();
                                                        Map<String, String> newProperties = newIdentity.getProperties();
                                                        if (oldProperties.size() != newProperties.size()) {
-                                                               identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                               eventBus.post(new IdentityUpdatedEvent(ownIdentity, newIdentity));
                                                                continue;
                                                        }
                                                        for (Entry<String, String> oldProperty : oldProperties.entrySet()) {
                                                                if (!newProperties.containsKey(oldProperty.getKey()) || !newProperties.get(oldProperty.getKey()).equals(oldProperty.getValue())) {
-                                                                       identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                                       eventBus.post(new IdentityUpdatedEvent(ownIdentity, newIdentity));
                                                                        break;
                                                                }
                                                        }
@@ -299,7 +291,7 @@ public class IdentityManager extends AbstractService {
                        for (OwnIdentity oldOwnIdentity : currentOwnIdentities.values()) {
                                OwnIdentity newOwnIdentity = newOwnIdentities.get(oldOwnIdentity.getId());
                                if ((newOwnIdentity == null) || ((context != null) && oldOwnIdentity.hasContext(context) && !newOwnIdentity.hasContext(context))) {
-                                       identityListenerManager.fireOwnIdentityRemoved(new DefaultOwnIdentity(oldOwnIdentity));
+                                       eventBus.post(new OwnIdentityRemovedEvent(new DefaultOwnIdentity(oldOwnIdentity)));
                                }
                        }
 
@@ -307,7 +299,7 @@ public class IdentityManager extends AbstractService {
                        for (OwnIdentity currentOwnIdentity : newOwnIdentities.values()) {
                                OwnIdentity oldOwnIdentity = currentOwnIdentities.get(currentOwnIdentity.getId());
                                if (((oldOwnIdentity == null) && ((context == null) || currentOwnIdentity.hasContext(context))) || ((oldOwnIdentity != null) && (context != null) && (!oldOwnIdentity.hasContext(context) && currentOwnIdentity.hasContext(context)))) {
-                                       identityListenerManager.fireOwnIdentityAdded(new DefaultOwnIdentity(currentOwnIdentity));
+                                       eventBus.post(new OwnIdentityAddedEvent(new DefaultOwnIdentity(currentOwnIdentity)));
                                }
                        }
 
index 4272669..6fc7044 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OwnIdentity.java - Copyright © 2010–2012 David Roden
+ * Sone - OwnIdentity.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9b355b5..6fa37d0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Trust.java - Copyright © 2010–2012 David Roden
+ * Sone - Trust.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c4e8d89..07628e3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustConnector.java - Copyright © 2010–2012 David Roden
+ * Sone - WebOfTrustConnector.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,11 +25,16 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import net.pterodactylus.sone.freenet.plugin.ConnectorListener;
 import net.pterodactylus.sone.freenet.plugin.PluginConnector;
 import net.pterodactylus.sone.freenet.plugin.PluginException;
+import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
+
+import com.google.common.collect.MapMaker;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+
 import freenet.support.SimpleFieldSet;
 import freenet.support.api.Bucket;
 
@@ -52,6 +57,9 @@ public class WebOfTrustConnector {
        /** The plugin connector. */
        private final PluginConnector pluginConnector;
 
+       /** Map for replies. */
+       private final Map<PluginIdentifier, Reply> replies = new MapMaker().makeMap();
+
        /**
         * Creates a new Web of Trust connector that uses the given plugin
         * connector.
@@ -59,6 +67,7 @@ public class WebOfTrustConnector {
         * @param pluginConnector
         *            The plugin connector
         */
+       @Inject
        public WebOfTrustConnector(PluginConnector pluginConnector) {
                this.pluginConnector = pluginConnector;
        }
@@ -380,24 +389,12 @@ public class WebOfTrustConnector {
         *             if the request could not be sent
         */
        private Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException {
-               final String identifier = "FCP-Command-" + System.currentTimeMillis() + "-" + counter.getAndIncrement();
-               final Reply reply = new Reply();
+               String identifier = "FCP-Command-" + System.currentTimeMillis() + "-" + counter.getAndIncrement();
+               Reply reply = new Reply();
+               PluginIdentifier pluginIdentifier = new PluginIdentifier(WOT_PLUGIN_NAME, identifier);
+               replies.put(pluginIdentifier, reply);
+
                logger.log(Level.FINE, String.format("Sending FCP Request: %s", fields.get("Message")));
-               ConnectorListener connectorListener = new ConnectorListener() {
-
-                       @Override
-                       @SuppressWarnings("synthetic-access")
-                       public void receivedReply(PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data) {
-                               String messageName = fields.get("Message");
-                               logger.log(Level.FINEST, String.format("Received Reply from Plugin: %s", messageName));
-                               synchronized (reply) {
-                                       reply.setFields(fields);
-                                       reply.setData(data);
-                                       reply.notify();
-                               }
-                       }
-               };
-               pluginConnector.addConnectorListener(WOT_PLUGIN_NAME, identifier, connectorListener);
                synchronized (reply) {
                        try {
                                pluginConnector.sendRequest(WOT_PLUGIN_NAME, identifier, fields, data);
@@ -409,7 +406,7 @@ public class WebOfTrustConnector {
                                        }
                                }
                        } finally {
-                               pluginConnector.removeConnectorListener(WOT_PLUGIN_NAME, identifier, connectorListener);
+                               replies.remove(pluginIdentifier);
                        }
                }
                logger.log(Level.FINEST, String.format("Received FCP Response for %s: %s", fields.get("Message"), (reply.getFields() != null) ? reply.getFields().get("Message") : null));
@@ -420,6 +417,27 @@ public class WebOfTrustConnector {
        }
 
        /**
+        * Notifies the connector that a plugin reply was received.
+        *
+        * @param receivedReplyEvent
+        *            The event
+        */
+       @Subscribe
+       public void receivedReply(ReceivedReplyEvent receivedReplyEvent) {
+               PluginIdentifier pluginIdentifier = new PluginIdentifier(receivedReplyEvent.pluginName(), receivedReplyEvent.identifier());
+               Reply reply = replies.remove(pluginIdentifier);
+               if (reply == null) {
+                       return;
+               }
+               logger.log(Level.FINEST, String.format("Received Reply from Plugin: %s", receivedReplyEvent.fieldSet().get("Message")));
+               synchronized (reply) {
+                       reply.setFields(receivedReplyEvent.fieldSet());
+                       reply.setData(receivedReplyEvent.data());
+                       reply.notify();
+               }
+       }
+
+       /**
         * Container for the data of the reply from a plugin.
         *
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
@@ -554,4 +572,57 @@ public class WebOfTrustConnector {
 
        }
 
+       /**
+        * Container for identifying plugins. Plugins are identified by their plugin
+        * name and their unique identifier.
+        *
+        * @author <a href="mailto:d.roden@xplosion.de">David Roden</a>
+        */
+       private static class PluginIdentifier {
+
+               /** The plugin name. */
+               private final String pluginName;
+
+               /** The plugin identifier. */
+               private final String identifier;
+
+               /**
+                * Creates a new plugin identifier.
+                *
+                * @param pluginName
+                *            The name of the plugin
+                * @param identifier
+                *            The identifier of the plugin
+                */
+               public PluginIdentifier(String pluginName, String identifier) {
+                       this.pluginName = pluginName;
+                       this.identifier = identifier;
+               }
+
+               //
+               // OBJECT METHODS
+               //
+
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public int hashCode() {
+                       return pluginName.hashCode() ^ identifier.hashCode();
+               }
+
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public boolean equals(Object object) {
+                       if (!(object instanceof PluginIdentifier)) {
+                               return false;
+                       }
+                       PluginIdentifier pluginIdentifier = (PluginIdentifier) object;
+                       return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier);
+               }
+
+       }
+
 }
index 35d65af..2b154fc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebOfTrustException.java - Copyright © 2010–2012 David Roden
+ * Sone - WebOfTrustException.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java
new file mode 100644 (file)
index 0000000..69f17ef
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sone - IdentityAddedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event;
+
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+/**
+ * Event that signals that an {@link Identity} was added.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IdentityAddedEvent extends IdentityEvent {
+
+       /**
+        * Creates a new “identity added” event.
+        *
+        * @param ownIdentity
+        *            The own identity that added the identity
+        * @param identity
+        *            The identity that was added
+        */
+       public IdentityAddedEvent(OwnIdentity ownIdentity, Identity identity) {
+               super(ownIdentity, identity);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java
new file mode 100644 (file)
index 0000000..2727226
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Sone - IdentityEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event;
+
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+/**
+ * Base class for {@link Identity} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class IdentityEvent {
+
+       /** The own identity this event relates to. */
+       private final OwnIdentity ownIdentity;
+
+       /** The identity this event is about. */
+       private final Identity identity;
+
+       /**
+        * Creates a new identity-based event.
+        *
+        * @param ownIdentity
+        *            The own identity that relates to the identity
+        * @param identity
+        *            The identity this event is about
+        */
+       protected IdentityEvent(OwnIdentity ownIdentity, Identity identity) {
+               this.ownIdentity = ownIdentity;
+               this.identity = identity;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the own identity this event relates to.
+        *
+        * @return The own identity this event relates to
+        */
+       public OwnIdentity ownIdentity() {
+               return ownIdentity;
+       }
+
+       /**
+        * Returns the identity this event is about.
+        *
+        * @return The identity this event is about
+        */
+       public Identity identity() {
+               return identity;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java
new file mode 100644 (file)
index 0000000..d5671cc
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sone - IdentityRemovedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event;
+
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+/**
+ * Event that signals that an {@link Identity} was removed.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IdentityRemovedEvent extends IdentityEvent {
+
+       /**
+        * Creates a new “identity removed” event.
+        *
+        * @param ownIdentity
+        *            The own identity that removed the identity
+        * @param identity
+        *            The identity that was removed
+        */
+       public IdentityRemovedEvent(OwnIdentity ownIdentity, Identity identity) {
+               super(ownIdentity, identity);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java
new file mode 100644 (file)
index 0000000..96ed694
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sone - IdentityUpdatedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event;
+
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+/**
+ * Event that signals that an {@link Identity} was updated.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IdentityUpdatedEvent extends IdentityEvent {
+
+       /**
+        * Creates a new “identity updated” event.
+        *
+        * @param ownIdentity
+        *            The own identity that tracks the identity
+        * @param identity
+        *            The identity that was updated
+        */
+       public IdentityUpdatedEvent(OwnIdentity ownIdentity, Identity identity) {
+               super(ownIdentity, identity);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java
new file mode 100644 (file)
index 0000000..aacf2e8
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - OwnIdentityAddedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event;
+
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+/**
+ * Event that signals that an {@link OwnIdentity} was added.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class OwnIdentityAddedEvent extends OwnIdentityEvent {
+
+       /**
+        * Creates new “own identity added” event.
+        *
+        * @param ownIdentity
+        *            The own identity that was added
+        */
+       public OwnIdentityAddedEvent(OwnIdentity ownIdentity) {
+               super(ownIdentity);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java
new file mode 100644 (file)
index 0000000..97179e8
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sone - OwnIdentityEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event;
+
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+/**
+ * Base class for {@link OwnIdentity} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class OwnIdentityEvent {
+
+       /** The own identity this event is about. */
+       private final OwnIdentity ownIdentity;
+
+       /**
+        * Creates a new own identity-based event.
+        *
+        * @param ownIdentity
+        *            The own identity this event is about
+        */
+       protected OwnIdentityEvent(OwnIdentity ownIdentity) {
+               this.ownIdentity = ownIdentity;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the own identity this event is about.
+        *
+        * @return The own identity this event is about
+        */
+       public OwnIdentity ownIdentity() {
+               return ownIdentity;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java
new file mode 100644 (file)
index 0000000..437bd6d
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.freenet.wot.event;
+
+import net.pterodactylus.sone.freenet.wot.OwnIdentity;
+
+/**
+ * Event that signals that an {@link OwnIdentity} was removed.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class OwnIdentityRemovedEvent extends OwnIdentityEvent {
+
+       /**
+        * Creates a new “own identity removed” event.
+        *
+        * @param ownIdentity
+        *            The own identity that was removed
+        */
+       public OwnIdentityRemovedEvent(OwnIdentity ownIdentity) {
+               super(ownIdentity);
+       }
+
+}
index 5b28b85..9626c57 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SonePlugin.java - Copyright © 2010–2012 David Roden
+ * Sone - SonePlugin.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -25,6 +25,12 @@ import java.util.logging.Logger;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.core.FreenetInterface;
 import net.pterodactylus.sone.core.WebOfTrustUpdater;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.database.PostBuilderFactory;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.PostReplyBuilderFactory;
+import net.pterodactylus.sone.database.SoneProvider;
+import net.pterodactylus.sone.database.memory.MemoryDatabase;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
 import net.pterodactylus.sone.freenet.plugin.PluginConnector;
@@ -37,9 +43,23 @@ import net.pterodactylus.util.config.MapConfigurationBackend;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.logging.LoggingListener;
 import net.pterodactylus.util.version.Version;
+
+import com.google.common.eventbus.EventBus;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.name.Names;
+import com.google.inject.spi.InjectionListener;
+import com.google.inject.spi.TypeEncounter;
+import com.google.inject.spi.TypeListener;
+
 import freenet.client.async.DatabaseDisabledException;
 import freenet.l10n.BaseL10n.LANGUAGE;
 import freenet.l10n.PluginL10n;
+import freenet.node.Node;
 import freenet.pluginmanager.FredPlugin;
 import freenet.pluginmanager.FredPluginBaseL10n;
 import freenet.pluginmanager.FredPluginFCP;
@@ -84,7 +104,7 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        }
 
        /** The version. */
-       public static final Version VERSION = new Version(0, 8, 4);
+       public static final Version VERSION = new Version(0, 8, 5);
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(SonePlugin.class);
@@ -107,9 +127,6 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
        /** The web of trust connector. */
        private WebOfTrustConnector webOfTrustConnector;
 
-       /** The identity manager. */
-       private IdentityManager identityManager;
-
        //
        // ACCESSORS
        //
@@ -178,33 +195,72 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                        }
                }
 
-               boolean startupFailed = true;
-               try {
-                       /* create freenet interface. */
-                       FreenetInterface freenetInterface = new FreenetInterface(pluginRespirator.getNode());
+               final Configuration startConfiguration = oldConfiguration;
+               final EventBus eventBus = new EventBus();
+
+               /* Freenet injector configuration. */
+               AbstractModule freenetModule = new AbstractModule() {
+
+                       @Override
+                       @SuppressWarnings("synthetic-access")
+                       protected void configure() {
+                               bind(PluginRespirator.class).toInstance(SonePlugin.this.pluginRespirator);
+                               bind(Node.class).toInstance(SonePlugin.this.pluginRespirator.getNode());
+                       }
+               };
+               /* Sone injector configuration. */
+               AbstractModule soneModule = new AbstractModule() {
 
-                       /* create web of trust connector. */
-                       PluginConnector pluginConnector = new PluginConnector(pluginRespirator);
-                       webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
-                       identityManager = new IdentityManager(webOfTrustConnector, "Sone");
+                       @Override
+                       protected void configure() {
+                               bind(Core.class).in(Singleton.class);
+                               bind(MemoryDatabase.class).in(Singleton.class);
+                               bind(EventBus.class).toInstance(eventBus);
+                               bind(Configuration.class).toInstance(startConfiguration);
+                               bind(FreenetInterface.class).in(Singleton.class);
+                               bind(PluginConnector.class).in(Singleton.class);
+                               bind(WebOfTrustConnector.class).in(Singleton.class);
+                               bind(WebOfTrustUpdater.class).in(Singleton.class);
+                               bind(IdentityManager.class).in(Singleton.class);
+                               bind(String.class).annotatedWith(Names.named("WebOfTrustContext")).toInstance("Sone");
+                               bind(SonePlugin.class).toInstance(SonePlugin.this);
+                               bind(FcpInterface.class).in(Singleton.class);
+                               bind(Database.class).to(MemoryDatabase.class);
+                               bind(PostBuilderFactory.class).to(MemoryDatabase.class);
+                               bind(PostReplyBuilderFactory.class).to(MemoryDatabase.class);
+                               bind(SoneProvider.class).to(Core.class).in(Singleton.class);
+                               bind(PostProvider.class).to(MemoryDatabase.class);
+                               bindListener(Matchers.any(), new TypeListener() {
+
+                                       @Override
+                                       public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> typeEncounter) {
+                                               typeEncounter.register(new InjectionListener<I>() {
+
+                                                       @Override
+                                                       public void afterInjection(I injectee) {
+                                                               eventBus.register(injectee);
+                                                       }
+                                               });
+                                       }
+                               });
+                       }
 
-                       /* create trust updater. */
-                       WebOfTrustUpdater trustUpdater = new WebOfTrustUpdater(webOfTrustConnector);
-                       trustUpdater.init();
+               };
+               Injector injector = Guice.createInjector(freenetModule, soneModule);
+               core = injector.getInstance(Core.class);
 
-                       /* create core. */
-                       core = new Core(oldConfiguration, freenetInterface, identityManager, trustUpdater);
+               /* create web of trust connector. */
+               webOfTrustConnector = injector.getInstance(WebOfTrustConnector.class);
 
-                       /* create the web interface. */
-                       webInterface = new WebInterface(this);
-                       core.addCoreListener(webInterface);
+               /* create FCP interface. */
+               fcpInterface = injector.getInstance(FcpInterface.class);
+               core.setFcpInterface(fcpInterface);
 
-                       /* create FCP interface. */
-                       fcpInterface = new FcpInterface(core);
-                       core.setFcpInterface(fcpInterface);
+               /* create the web interface. */
+               webInterface = injector.getInstance(WebInterface.class);
 
-                       /* create the identity manager. */
-                       identityManager.addIdentityListener(core);
+               boolean startupFailed = true;
+               try {
 
                        /* start core! */
                        core.start();
@@ -215,7 +271,6 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                        webInterface.start();
                        webInterface.setFirstStart(firstStart);
                        webInterface.setNewConfig(newConfig);
-                       identityManager.start();
                        startupFailed = false;
                } finally {
                        if (startupFailed) {
@@ -241,9 +296,6 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                        /* stop the core. */
                        core.stop();
 
-                       /* stop the identity manager. */
-                       identityManager.stop();
-
                        /* stop the web of trust connector. */
                        webOfTrustConnector.stop();
                } catch (Throwable t1) {
index f1343ad..9d1203b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ListNotification.java - Copyright © 2010–2012 David Roden
+ * Sone - ListNotification.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -141,7 +141,9 @@ public class ListNotification<T> extends TemplateNotification {
         *            The element to remove
         */
        public void remove(T element) {
-               elements.remove(element);
+               while (elements.remove(element)) {
+                       /* do nothing, just remove all instances of the element. */
+               }
                if (elements.isEmpty()) {
                        dismiss();
                }
index 6efee27..f431bb7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ListNotificationFilters.java - Copyright © 2010–2012 David Roden
+ * Sone - ListNotificationFilters.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.notify;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -28,7 +30,8 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.freenet.wot.Trust;
 import net.pterodactylus.util.notify.Notification;
-import net.pterodactylus.util.validation.Validation;
+
+import com.google.common.base.Optional;
 
 /**
  * Filter for {@link ListNotification}s.
@@ -218,7 +221,7 @@ public class ListNotificationFilters {
         *         otherwise
         */
        public static boolean isPostVisible(Sone sone, Post post) {
-               Validation.begin().isNotNull("Post", post).check();
+               checkNotNull(post, "post must not be null");
                Sone postSone = post.getSone();
                if (postSone == null) {
                        return false;
@@ -240,9 +243,8 @@ public class ListNotificationFilters {
                                 * received trust values. to prevent this we simply assume that
                                 * posts are visible if there is no trust.
                                 */
-                               return true;
                        }
-                       if ((!postSone.equals(sone)) && !sone.hasFriend(postSone.getId()) && !sone.equals(post.getRecipient())) {
+                       if ((!postSone.equals(sone)) && !sone.hasFriend(postSone.getId()) && !sone.getId().equals(post.getRecipientId().orNull())) {
                                return false;
                        }
                }
@@ -280,12 +282,12 @@ public class ListNotificationFilters {
         *         otherwise
         */
        public static boolean isReplyVisible(Sone sone, PostReply reply) {
-               Validation.begin().isNotNull("Reply", reply).check();
-               Post post = reply.getPost();
-               if (post == null) {
+               checkNotNull(reply, "reply must not be null");
+               Optional<Post> post = reply.getPost();
+               if (!post.isPresent()) {
                        return false;
                }
-               if (!isPostVisible(sone, post)) {
+               if (!isPostVisible(sone, post.get())) {
                        return false;
                }
                if (reply.getTime() > System.currentTimeMillis()) {
index e22e00b..96891e5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AlbumAccessor.java - Copyright © 2011–2012 David Roden
+ * Sone - AlbumAccessor.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index afa22ec..12b4bc2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CollectionAccessor.java - Copyright © 2010–2012 David Roden
+ * Sone - CollectionAccessor.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0282156..a3f8db6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CssClassNameFilter.java - Copyright © 2010–2012 David Roden
+ * Sone - CssClassNameFilter.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index dab01cc..d070fe9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPagePlugin.java - Copyright © 2010–2012 David Roden
+ * Sone - GetPagePlugin.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 8fadc6c..0d888bc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - HttpRequestAccessor.java - Copyright © 2011–2012 David Roden
+ * Sone - HttpRequestAccessor.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4fbe74c..c08a0a2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IdentityAccessor.java - Copyright © 2010–2012 David Roden
+ * Sone - IdentityAccessor.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ec7f360..3ac684c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageAccessor.java - Copyright © 2011–2012 David Roden
+ * Sone - ImageAccessor.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 06a11d7..7e3723c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageLinkFilter.java - Copyright © 2011–2012 David Roden
+ * Sone - ImageLinkFilter.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,13 +24,14 @@ import java.util.Map;
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.util.number.Numbers;
-import net.pterodactylus.util.object.Default;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.template.TemplateContextFactory;
 import net.pterodactylus.util.template.TemplateParser;
 
+import com.google.common.base.Optional;
+
 /**
  * Template filter that turns an {@link Image} into an HTML &lt;img&gt; tag,
  * using some parameters to influence parameters of the image.
@@ -105,8 +106,8 @@ public class ImageLinkFilter implements Filter {
                        linkTemplateContext.set("width", (int) (imageWidth * scale + 0.5));
                        linkTemplateContext.set("height", (int) (imageHeight * scale + 0.5));
                }
-               linkTemplateContext.set("alt", Default.forNull(title, image.getDescription()));
-               linkTemplateContext.set("title", Default.forNull(title, image.getTitle()));
+               linkTemplateContext.set("alt", Optional.fromNullable(title).or(image.getDescription()));
+               linkTemplateContext.set("title", Optional.fromNullable(title).or(image.getTitle()));
 
                StringWriter stringWriter = new StringWriter();
                linkTemplate.render(linkTemplateContext, stringWriter);
index 2dc963f..9b8cd7e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - JavascriptFilter.java - Copyright © 2011–2012 David Roden
+ * Sone - JavascriptFilter.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,10 +19,11 @@ package net.pterodactylus.sone.template;
 
 import java.util.Map;
 
-import net.pterodactylus.util.number.Hex;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.io.BaseEncoding;
+
 /**
  * Escapes double quotes, backslashes, carriage returns and line feeds, and
  * additionally encloses a given string with double quotes to make it possible
@@ -56,7 +57,7 @@ public class JavascriptFilter implements Filter {
                                javascriptString.append('\\');
                                javascriptString.append(c);
                        } else if (c < 32) {
-                               javascriptString.append("\\x").append(Hex.toHex((byte) c));
+                               javascriptString.append("\\x").append(BaseEncoding.base16().lowerCase().encode(new byte[] { (byte) c }));
                        } else {
                                javascriptString.append(c);
                        }
index 75eff8b..8833a2d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ParserFilter.java - Copyright © 2011–2012 David Roden
+ * Sone - ParserFilter.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,7 +20,9 @@ package net.pterodactylus.sone.template;
 import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
 import java.io.Writer;
+import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -92,7 +94,7 @@ public class ParserFilter implements Filter {
                int cutOffLength = Numbers.safeParseInteger(parameters.get("cut-off-length"), Numbers.safeParseInteger(templateContext.get(String.valueOf(parameters.get("cut-off-length"))), length));
                Object sone = parameters.get("sone");
                if (sone instanceof String) {
-                       sone = core.getSone((String) sone, false);
+                       sone = core.getSone((String) sone);
                }
                FreenetRequest request = (FreenetRequest) templateContext.get("request");
                SoneTextParserContext context = new SoneTextParserContext(request, (Sone) sone);
@@ -213,7 +215,12 @@ public class ParserFilter implements Filter {
         *            The part to render
         */
        private void render(Writer writer, LinkPart linkPart) {
-               renderLink(writer, "/?_CHECKED_HTTP_=" + linkPart.getLink(), linkPart.getText(), linkPart.getTitle(), "internet");
+               try {
+                       renderLink(writer, "/external-link/?_CHECKED_HTTP_=" + URLEncoder.encode(linkPart.getLink(), "UTF-8"), linkPart.getText(), linkPart.getTitle(), "internet");
+               } catch (UnsupportedEncodingException uee1) {
+                       /* not possible for UTF-8. */
+                       throw new RuntimeException("The JVM does not support UTF-8 encoding!", uee1);
+               }
        }
 
        /**
index d05b074..d593b51 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostAccessor.java - Copyright © 2010–2012 David Roden
+ * Sone - PostAccessor.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -21,10 +21,11 @@ import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
-import net.pterodactylus.util.collection.filter.Filters;
 import net.pterodactylus.util.template.ReflectionAccessor;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.collect.Collections2;
+
 /**
  * Accessor for {@link Post} objects that adds additional properties:
  * <dl>
@@ -56,7 +57,7 @@ public class PostAccessor extends ReflectionAccessor {
        public Object get(TemplateContext templateContext, Object object, String member) {
                Post post = (Post) object;
                if ("replies".equals(member)) {
-                       return Filters.filteredList(core.getReplies(post), Reply.FUTURE_REPLY_FILTER);
+                       return Collections2.filter(core.getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER);
                } else if (member.equals("likes")) {
                        return core.getLikes(post);
                } else if (member.equals("liked")) {
index f004967..30c0a52 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ProfileAccessor.java - Copyright © 2011–2012 David Roden
+ * Sone - ProfileAccessor.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -70,7 +70,7 @@ public class ProfileAccessor extends ReflectionAccessor {
                                return null;
                        }
                        Sone remoteSone = profile.getSone();
-                       if (core.isLocalSone(remoteSone)) {
+                       if (remoteSone.isLocal()) {
                                /* always show your own avatars. */
                                return avatarId;
                        }
index 4faa045..b497a94 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyAccessor.java - Copyright © 2010–2012 David Roden
+ * Sone - ReplyAccessor.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ff4b64e..bcf73d8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ReplyGroupFilter.java - Copyright © 2010–2012 David Roden
+ * Sone - ReplyGroupFilter.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -29,6 +29,8 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.template.Filter;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+
 /**
  * {@link Filter} implementation that groups replies by the post the are in
  * reply to, returning a map with the post as key and the list of replies as
@@ -48,17 +50,21 @@ public class ReplyGroupFilter implements Filter {
                Map<Post, Set<Sone>> postSones = new HashMap<Post, Set<Sone>>();
                Map<Post, Set<PostReply>> postReplies = new HashMap<Post, Set<PostReply>>();
                for (PostReply reply : allReplies) {
-                       Post post = reply.getPost();
-                       Set<Sone> sones = postSones.get(post);
+                       /*
+                        * All replies from a new-reply notification have posts,
+                        * ListNotificationFilters takes care of that.
+                        */
+                       Optional<Post> post = reply.getPost();
+                       Set<Sone> sones = postSones.get(post.get());
                        if (sones == null) {
                                sones = new HashSet<Sone>();
-                               postSones.put(post, sones);
+                               postSones.put(post.get(), sones);
                        }
                        sones.add(reply.getSone());
-                       Set<PostReply> replies = postReplies.get(post);
+                       Set<PostReply> replies = postReplies.get(post.get());
                        if (replies == null) {
                                replies = new HashSet<PostReply>();
-                               postReplies.put(post, replies);
+                               postReplies.put(post.get(), replies);
                        }
                        replies.add(reply);
                }
index 3a89021..0d4f556 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - RequestChangeFilter.java - Copyright © 2010–2012 David Roden
+ * Sone - RequestChangeFilter.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index eca8654..c54fc0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneAccessor.java - Copyright © 2010–2012 David Roden
+ * Sone - SoneAccessor.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -76,8 +76,6 @@ public class SoneAccessor extends ReflectionAccessor {
                Sone sone = (Sone) object;
                if (member.equals("niceName")) {
                        return getNiceName(sone);
-               } else if (member.equals("local")) {
-                       return core.isLocalSone(sone);
                } else if (member.equals("friend")) {
                        Sone currentSone = (Sone) templateContext.get("currentSone");
                        return (currentSone != null) && currentSone.hasFriend(sone.getId());
index 005eb1f..913e058 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SubstringFilter.java - Copyright © 2010–2012 David Roden
+ * Sone - SubstringFilter.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5097cb2..7ba7a10 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustAccessor.java - Copyright © 2010–2012 David Roden
+ * Sone - TrustAccessor.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e3ddd59..fb5551d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UniqueElementFilter.java - Copyright © 2011–2012 David Roden
+ * Sone - UniqueElementFilter.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index c87a97b..077e1fd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnknownDateFilter.java - Copyright © 2011–2012 David Roden
+ * Sone - UnknownDateFilter.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index cfd2f63..c667f38 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetLinkPart.java - Copyright © 2011–2012 David Roden
+ * Sone - FreenetLinkPart.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 202b9db..f531bab 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LinkPart.java - Copyright © 2011–2012 David Roden
+ * Sone - LinkPart.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3a80db8..2a7700a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Parser.java - Copyright © 2010–2012 David Roden
+ * Sone - Parser.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d044d1b..4734906 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ParserContext.java - Copyright © 2010–2012 David Roden
+ * Sone - ParserContext.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 79c59dc..4c1a188 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - Part.java - Copyright © 2010–2012 David Roden
+ * Sone - Part.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a8a7e85..7d8acd6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PartContainer.java - Copyright © 2010–2012 David Roden
+ * Sone - PartContainer.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2c29ee2..aa6441b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PlainTextPart.java - Copyright © 2011–2012 David Roden
+ * Sone - PlainTextPart.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6241b7a..f7177c9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PostLinkPart.java - Copyright © 2011–2012 David Roden
+ * Sone - PostPart.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 37f098b..a7b418b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneLinkPart.java - Copyright © 2011–2012 David Roden
+ * Sone - SonePart.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index baa3dc9..6d81c0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetLinkParser.java - Copyright © 2010–2012 David Roden
+ * Sone - SoneTextParser.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,12 +26,15 @@ import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import net.pterodactylus.sone.core.PostProvider;
-import net.pterodactylus.sone.core.SoneProvider;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.PostProvider;
+import net.pterodactylus.sone.database.SoneProvider;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
+
+import com.google.common.base.Optional;
+
 import freenet.keys.FreenetURI;
 
 /**
@@ -55,28 +58,50 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
        private enum LinkType {
 
                /** Link is a KSK. */
-               KSK,
+               KSK("KSK@"),
 
                /** Link is a CHK. */
-               CHK,
+               CHK("CHK@"),
 
                /** Link is an SSK. */
-               SSK,
+               SSK("SSK@"),
 
                /** Link is a USK. */
-               USK,
+               USK("USK@"),
 
                /** Link is HTTP. */
-               HTTP,
+               HTTP("http://"),
 
                /** Link is HTTPS. */
-               HTTPS,
+               HTTPS("https://"),
 
                /** Link is a Sone. */
-               SONE,
+               SONE("sone://"),
 
                /** Link is a post. */
-               POST,
+               POST("post://");
+
+               /** The scheme identifying this link type. */
+               private final String scheme;
+
+               /**
+                * Creates a new link type identified by the given scheme.
+                *
+                * @param scheme
+                *            The scheme of the link type
+                */
+               private LinkType(String scheme) {
+                       this.scheme = scheme;
+               }
+
+               /**
+                * Returns the scheme of this link type.
+                *
+                * @return The scheme of this link type
+                */
+               public String getScheme() {
+                       return scheme;
+               }
 
        }
 
@@ -200,18 +225,32 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        }
                                        lineComplete = false;
 
+                                       Matcher matcher = whitespacePattern.matcher(line);
+                                       int nextSpace = matcher.find(0) ? matcher.start() : line.length();
+                                       String link = line.substring(0, nextSpace);
+                                       String name = link;
+                                       logger.log(Level.FINER, String.format("Found link: %s", link));
+                                       logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
+
+                                       /* 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;
+                                       }
+
                                        if (linkType == LinkType.SONE) {
                                                if (line.length() >= (7 + 43)) {
                                                        String soneId = line.substring(7, 50);
-                                                       Sone sone = soneProvider.getSone(soneId, false);
-                                                       if (sone == null) {
+                                                       Optional<Sone> sone = soneProvider.getSone(soneId);
+                                                       if (!sone.isPresent()) {
                                                                /*
                                                                 * don’t use create=true above, we don’t want
                                                                 * the empty shell.
                                                                 */
-                                                               sone = new Sone(soneId);
+                                                               sone = Optional.fromNullable(new Sone(soneId, false));
                                                        }
-                                                       parts.add(new SonePart(sone));
+                                                       parts.add(new SonePart(sone.get()));
                                                        line = line.substring(50);
                                                } else {
                                                        parts.add(new PlainTextPart(line));
@@ -222,9 +261,9 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        if (linkType == LinkType.POST) {
                                                if (line.length() >= (7 + 36)) {
                                                        String postId = line.substring(7, 43);
-                                                       Post post = postProvider.getPost(postId, false);
-                                                       if ((post != null) && (post.getSone() != null)) {
-                                                               parts.add(new PostPart(post));
+                                                       Optional<Post> post = postProvider.getPost(postId);
+                                                       if (post.isPresent()) {
+                                                               parts.add(new PostPart(post.get()));
                                                        } else {
                                                                parts.add(new PlainTextPart(line.substring(0, 43)));
                                                        }
@@ -235,12 +274,6 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                                }
                                                continue;
                                        }
-                                       Matcher matcher = whitespacePattern.matcher(line);
-                                       int nextSpace = matcher.find(0) ? matcher.start() : line.length();
-                                       String link = line.substring(0, nextSpace);
-                                       String name = link;
-                                       logger.log(Level.FINER, String.format("Found link: %s", link));
-                                       logger.log(Level.FINEST, String.format("CHK: %d, SSK: %d, USK: %d", nextChk, nextSsk, nextUsk));
 
                                        if ((linkType == LinkType.KSK) || (linkType == LinkType.CHK) || (linkType == LinkType.SSK) || (linkType == LinkType.USK)) {
                                                FreenetURI uri;
index e1a6eb3..ba00916 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTextParserContext.java - Copyright © 2011–2012 David Roden
+ * Sone - SoneTextParserContext.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index dfd9627..f07a000 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TextFilter.java - Copyright © 2011–2012 David Roden
+ * Sone - TextFilter.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/java/net/pterodactylus/sone/utils/IntegerRangePredicate.java b/src/main/java/net/pterodactylus/sone/utils/IntegerRangePredicate.java
new file mode 100644 (file)
index 0000000..db6a841
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Sone - IntegerRangePredicate.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.utils;
+
+import com.google.common.base.Predicate;
+
+/**
+ * {@link Predicate} that verifies that an {@link Integer} value is not
+ * {@code null} and is between a lower and an upper bound. Both bounds are
+ * inclusive.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IntegerRangePredicate implements Predicate<Integer> {
+
+       /** The lower bound. */
+       private final int lowerBound;
+
+       /** The upper bound. */
+       private final int upperBound;
+
+       /**
+        * Creates a new integer range predicate.
+        *
+        * @param lowerBound
+        *            The lower bound
+        * @param upperBound
+        *            The upper bound
+        */
+       public IntegerRangePredicate(int lowerBound, int upperBound) {
+               this.lowerBound = lowerBound;
+               this.upperBound = upperBound;
+       }
+
+       //
+       // PREDICATE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean apply(Integer value) {
+               return (value != null) && (value >= lowerBound) && (value <= upperBound);
+       }
+
+}
index 2e9780b..6a6bc63 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - AboutPage.java - Copyright © 2010–2012 David Roden
+ * Sone - AboutPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0c9bbae..042a683 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarkPage.java - Copyright © 2011–2012 David Roden
+ * Sone - BookmarkPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 95dfd65..10b6dc3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarksPage.java - Copyright © 2011–2012 David Roden
+ * Sone - BookmarksPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
 package net.pterodactylus.sone.web;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -25,12 +26,13 @@ import java.util.Set;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.collection.Pagination;
-import net.pterodactylus.util.collection.filter.Filter;
-import net.pterodactylus.util.collection.filter.Filters;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+
 /**
  * Page that lets the user browse all his bookmarked posts.
  *
@@ -61,10 +63,10 @@ public class BookmarksPage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                Set<Post> allPosts = webInterface.getCore().getBookmarkedPosts();
-               Set<Post> loadedPosts = Filters.filteredSet(allPosts, new Filter<Post>() {
+               Collection<Post> loadedPosts = Collections2.filter(allPosts, new Predicate<Post>() {
 
                        @Override
-                       public boolean filterObject(Post post) {
+                       public boolean apply(Post post) {
                                return post.getSone() != null;
                        }
                });
index 1e83a0e..95ca0da 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateAlbumPage.java - Copyright © 2011–2012 David Roden
+ * Sone - CreateAlbumPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6b374dc..83913b4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreatePostPage.java - Copyright © 2010–2012 David Roden
+ * Sone - CreatePostPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.text.TextFilter;
@@ -65,7 +67,7 @@ public class CreatePostPage extends SoneTemplatePage {
                                if (sender == null) {
                                        sender = currentSone;
                                }
-                               Sone recipient = webInterface.getCore().getSone(recipientId, false);
+                               Optional<Sone> recipient = webInterface.getCore().getSone(recipientId);
                                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
                                webInterface.getCore().createPost(sender, recipient, System.currentTimeMillis(), text);
                                throw new RedirectException(returnPage);
index 82e4c51..c8979c1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateReplyPage.java - Copyright © 2010–2012 David Roden
+ * Sone - CreateReplyPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.text.TextFilter;
@@ -58,7 +60,10 @@ public class CreateReplyPage extends SoneTemplatePage {
                String text = request.getHttpRequest().getPartAsStringFailsafe("text", 65536).trim();
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
-                       Post post = webInterface.getCore().getPost(postId);
+                       Optional<Post> post = webInterface.getCore().getPost(postId);
+                       if (!post.isPresent()) {
+                               throw new RedirectException("noPermission.html");
+                       }
                        if (text.length() > 0) {
                                String senderId = request.getHttpRequest().getPartAsStringFailsafe("sender", 43);
                                Sone sender = webInterface.getCore().getLocalSone(senderId, false);
@@ -66,7 +71,7 @@ public class CreateReplyPage extends SoneTemplatePage {
                                        sender = getCurrentSone(request.getToadletContext());
                                }
                                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
-                               webInterface.getCore().createReply(sender, post, text);
+                               webInterface.getCore().createReply(sender, post.get(), text);
                                throw new RedirectException(returnPage);
                        }
                        templateContext.set("errorTextEmpty", true);
index a4c6379..aea76c4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateSonePage.java - Copyright © 2010–2012 David Roden
+ * Sone - CreateSonePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 2f2b766..3dfb8b4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteAlbumPage.java - Copyright © 2011–2012 David Roden
+ * Sone - DeleteAlbumPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -54,7 +54,7 @@ public class DeleteAlbumPage extends SoneTemplatePage {
                        if (album == null) {
                                throw new RedirectException("invalid.html");
                        }
-                       if (!webInterface.getCore().isLocalSone(album.getSone())) {
+                       if (!album.getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("abortDelete")) {
index 3bbaf3d..77f3ab8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteImagePage.java - Copyright © 2011–2012 David Roden
+ * Sone - DeleteImagePage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -57,7 +57,7 @@ public class DeleteImagePage extends SoneTemplatePage {
                if (image == null) {
                        throw new RedirectException("invalid.html");
                }
-               if (!webInterface.getCore().isLocalSone(image.getSone())) {
+               if (!image.getSone().isLocal()) {
                        throw new RedirectException("noPermission.html");
                }
                if (request.getMethod() == Method.POST) {
index 6689061..6ef86f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeletePostPage.java - Copyright © 2010–2012 David Roden
+ * Sone - DeletePostPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.template.Template;
@@ -55,19 +57,22 @@ public class DeletePostPage extends SoneTemplatePage {
                if (request.getMethod() == Method.GET) {
                        String postId = request.getHttpRequest().getParam("post");
                        String returnPage = request.getHttpRequest().getParam("returnPage");
-                       Post post = webInterface.getCore().getPost(postId);
-                       templateContext.set("post", post);
+                       Optional<Post> post = webInterface.getCore().getPost(postId);
+                       if (!post.isPresent()) {
+                               throw new RedirectException("noPermission.html");
+                       }
+                       templateContext.set("post", post.get());
                        templateContext.set("returnPage", returnPage);
                        return;
                } else if (request.getMethod() == Method.POST) {
                        String postId = request.getHttpRequest().getPartAsStringFailsafe("post", 36);
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
-                       Post post = webInterface.getCore().getPost(postId);
-                       if (!webInterface.getCore().isLocalSone(post.getSone())) {
+                       Optional<Post> post = webInterface.getCore().getPost(postId);
+                       if (!post.isPresent() || !post.get().getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
-                               webInterface.getCore().deletePost(post);
+                               webInterface.getCore().deletePost(post.get());
                                throw new RedirectException(returnPage);
                        } else if (request.getHttpRequest().isPartSet("abortDelete")) {
                                throw new RedirectException(returnPage);
index 5754b2f..852cc91 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteProfileFieldPage.java - Copyright © 2011–2012 David Roden
+ * Sone - DeleteProfileFieldPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 900acd9..f7ca132 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplyPage.java - Copyright © 2010–2012 David Roden
+ * Sone - DeleteReplyPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,6 +23,8 @@ import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.web.Method;
 
+import com.google.common.base.Optional;
+
 /**
  * This page lets the user delete a reply.
  *
@@ -53,14 +55,14 @@ public class DeleteReplyPage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                String replyId = request.getHttpRequest().getPartAsStringFailsafe("reply", 36);
-               PostReply reply = webInterface.getCore().getReply(replyId);
+               Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
-                       if (!webInterface.getCore().isLocalSone(reply.getSone())) {
+                       if (!reply.get().getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
-                               webInterface.getCore().deleteReply(reply);
+                               webInterface.getCore().deleteReply(reply.get());
                                throw new RedirectException(returnPage);
                        } else if (request.getHttpRequest().isPartSet("abortDelete")) {
                                throw new RedirectException(returnPage);
index a5bdd2f..39d3fb3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteSonePage.java - Copyright © 2010–2012 David Roden
+ * Sone - DeleteSonePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 763271c..40efa5d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DismissNotificationPage.java - Copyright © 2010–2012 David Roden
+ * Sone - DismissNotificationPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index e4fd52d..c8879a0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DistrustPage.java - Copyright © 2011–2012 David Roden
+ * Sone - DistrustPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -59,9 +61,9 @@ public class DistrustPage extends SoneTemplatePage {
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        Sone currentSone = getCurrentSone(request.getToadletContext());
-                       Sone sone = webInterface.getCore().getSone(identity, false);
-                       if (sone != null) {
-                               webInterface.getCore().distrustSone(currentSone, sone);
+                       Optional<Sone> sone = webInterface.getCore().getSone(identity);
+                       if (sone.isPresent()) {
+                               webInterface.getCore().distrustSone(currentSone, sone.get());
                        }
                        throw new RedirectException(returnPage);
                }
index d028f8d..8ef03e3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditAlbumPage.java - Copyright © 2011–2012 David Roden
+ * Sone - EditAlbumPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -57,7 +57,7 @@ public class EditAlbumPage extends SoneTemplatePage {
                        if (album == null) {
                                throw new RedirectException("invalid.html");
                        }
-                       if (!webInterface.getCore().isLocalSone(album.getSone())) {
+                       if (!album.getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) {
index b0297a8..75872eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * FreenetSone - WebInterface.java - Copyright © 2010–2012 David Roden
+ * Sone - EditImagePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -60,12 +60,12 @@ public class EditImagePage extends SoneTemplatePage {
                        if (image == null) {
                                throw new RedirectException("invalid.html");
                        }
-                       if (!webInterface.getCore().isLocalSone(image.getSone())) {
+                       if (!image.getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveLeft", 4))) {
                                image.getAlbum().moveImageUp(image);
-                       } else  if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveRight", 4))) {
+                       } else if ("true".equals(request.getHttpRequest().getPartAsStringFailsafe("moveRight", 4))) {
                                image.getAlbum().moveImageDown(image);
                        } else {
                                String title = request.getHttpRequest().getPartAsStringFailsafe("title", 100).trim();
index 9958a2d..fe9d78f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditProfileFieldPage.java - Copyright © 2011–2012 David Roden
+ * Sone - EditProfileFieldPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a1b759b..c79a1e9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditProfilePage.java - Copyright © 2010–2012 David Roden
+ * Sone - EditProfilePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -77,7 +77,7 @@ public class EditProfilePage extends SoneTemplatePage {
                                birthDay = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-day", 256).trim());
                                birthMonth = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-month", 256).trim());
                                birthYear = Numbers.safeParseInteger(request.getHttpRequest().getPartAsStringFailsafe("birth-year", 256).trim());
-                               avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatar-id", 36);
+                               avatarId = request.getHttpRequest().getPartAsStringFailsafe("avatarId", 36);
                                profile.setFirstName(firstName.length() > 0 ? firstName : null);
                                profile.setMiddleName(middleName.length() > 0 ? middleName : null);
                                profile.setLastName(lastName.length() > 0 ? lastName : null);
@@ -139,7 +139,7 @@ public class EditProfilePage extends SoneTemplatePage {
                templateContext.set("birthDay", birthDay);
                templateContext.set("birthMonth", birthMonth);
                templateContext.set("birthYear", birthYear);
-               templateContext.set("avatar-id", avatarId);
+               templateContext.set("avatarId", avatarId);
                templateContext.set("fields", fields);
        }
 
index 661ddd3..11a680c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FollowSonePage.java - Copyright © 2010–2012 David Roden
+ * Sone - FollowSonePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.template.Template;
@@ -55,9 +57,10 @@ public class FollowSonePage extends SoneTemplatePage {
                        Sone currentSone = getCurrentSone(request.getToadletContext());
                        String soneIds = request.getHttpRequest().getPartAsStringFailsafe("sone", 1200);
                        for (String soneId : soneIds.split("[ ,]+")) {
-                               if (webInterface.getCore().hasSone(soneId)) {
+                               Optional<Sone> sone = webInterface.getCore().getSone(soneId);
+                               if (sone.isPresent()) {
                                        webInterface.getCore().followSone(currentSone, soneId);
-                                       webInterface.getCore().markSoneKnown(webInterface.getCore().getSone(soneId));
+                                       webInterface.getCore().markSoneKnown(sone.get());
                                }
                        }
                        throw new RedirectException(returnPage);
index a16c7db..442ce7d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetImagePage.java - Copyright © 2011–2012 David Roden
+ * Sone - GetImagePage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index da48ec5..37268ec 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ImageBrowserPage.java - Copyright © 2011–2012 David Roden
+ * Sone - ImageBrowserPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,6 +22,9 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Sone;
@@ -77,9 +80,9 @@ public class ImageBrowserPage extends SoneTemplatePage {
                }
                String soneId = request.getHttpRequest().getParam("sone", null);
                if (soneId != null) {
-                       Sone sone = webInterface.getCore().getSone(soneId, false);
+                       Optional<Sone> sone = webInterface.getCore().getSone(soneId);
                        templateContext.set("soneRequested", true);
-                       templateContext.set("sone", sone);
+                       templateContext.set("sone", sone.orNull());
                        return;
                }
                String mode = request.getHttpRequest().getParam("mode", null);
@@ -87,7 +90,7 @@ public class ImageBrowserPage extends SoneTemplatePage {
                        templateContext.set("galleryRequested", true);
                        List<Album> albums = new ArrayList<Album>();
                        for (Sone sone : webInterface.getCore().getSones()) {
-                               albums.addAll(sone.getAllAlbums());
+                               albums.addAll(FluentIterable.from(sone.getAlbums()).transformAndConcat(Album.FLATTENER).toList());
                        }
                        Collections.sort(albums, Album.TITLE_COMPARATOR);
                        Pagination<Album> albumPagination = new Pagination<Album>(albums, 12).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
index 8a5d1b5..e1a52b9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - IndexPage.java - Copyright © 2010–2012 David Roden
+ * Sone - IndexPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
 package net.pterodactylus.sone.web;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -26,12 +27,14 @@ import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.collection.Pagination;
-import net.pterodactylus.util.collection.filter.Filter;
-import net.pterodactylus.util.collection.filter.Filters;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+
 /**
  * The index page shows the main page of Sone. This page will contain the posts
  * of all friends of the current user.
@@ -61,31 +64,33 @@ public class IndexPage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                final Sone currentSone = getCurrentSone(request.getToadletContext());
-               List<Post> allPosts = new ArrayList<Post>();
+               Collection<Post> allPosts = new ArrayList<Post>();
                allPosts.addAll(currentSone.getPosts());
                for (String friendSoneId : currentSone.getFriends()) {
-                       if (!webInterface.getCore().hasSone(friendSoneId)) {
+                       Optional<Sone> friendSone = webInterface.getCore().getSone(friendSoneId);
+                       if (!friendSone.isPresent()) {
                                continue;
                        }
-                       allPosts.addAll(webInterface.getCore().getSone(friendSoneId).getPosts());
+                       allPosts.addAll(friendSone.get().getPosts());
                }
                for (Sone sone : webInterface.getCore().getSones()) {
                        for (Post post : sone.getPosts()) {
-                               if (currentSone.equals(post.getRecipient()) && !allPosts.contains(post)) {
+                               if (currentSone.equals(post.getRecipient().orNull()) && !allPosts.contains(post)) {
                                        allPosts.add(post);
                                }
                        }
                }
-               allPosts = Filters.filteredList(allPosts, new Filter<Post>() {
+               allPosts = Collections2.filter(allPosts, new Predicate<Post>() {
 
                        @Override
-                       public boolean filterObject(Post post) {
+                       public boolean apply(Post post) {
                                return ListNotificationFilters.isPostVisible(currentSone, post);
                        }
                });
-               allPosts = Filters.filteredList(allPosts, Post.FUTURE_POSTS_FILTER);
-               Collections.sort(allPosts, Post.TIME_COMPARATOR);
-               Pagination<Post> pagination = new Pagination<Post>(allPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
+               allPosts = Collections2.filter(allPosts, Post.FUTURE_POSTS_FILTER);
+               List<Post> sortedPosts = new ArrayList<Post>(allPosts);
+               Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
+               Pagination<Post> pagination = new Pagination<Post>(sortedPosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
                templateContext.set("pagination", pagination);
                templateContext.set("posts", pagination.getItems());
        }
index b40515a..dd1b1fc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - KnownSonesPage.java - Copyright © 2010–2012 David Roden
+ * Sone - KnownSonesPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 package net.pterodactylus.sone.web;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.collection.Pagination;
-import net.pterodactylus.util.collection.ReverseComparator;
-import net.pterodactylus.util.collection.filter.Filter;
-import net.pterodactylus.util.collection.filter.Filters;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Ordering;
+
 /**
  * This page shows all known Sones.
  *
@@ -67,70 +70,77 @@ public class KnownSonesPage extends SoneTemplatePage {
                templateContext.set("order", (sortOrder != null) ? sortOrder : "asc");
                templateContext.set("filter", filter);
                final Sone currentSone = getCurrentSone(request.getToadletContext(), false);
-               List<Sone> knownSones = Filters.filteredList(new ArrayList<Sone>(webInterface.getCore().getSones()), Sone.EMPTY_SONE_FILTER);
+               Collection<Sone> knownSones = Collections2.filter(webInterface.getCore().getSones(), Sone.EMPTY_SONE_FILTER);
                if ((currentSone != null) && "followed".equals(filter)) {
-                       knownSones = Filters.filteredList(knownSones, new Filter<Sone>() {
+                       knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
 
                                @Override
-                               public boolean filterObject(Sone sone) {
+                               public boolean apply(Sone sone) {
                                        return currentSone.hasFriend(sone.getId());
                                }
                        });
                } else if ((currentSone != null) && "not-followed".equals(filter)) {
-                       knownSones = Filters.filteredList(knownSones, new Filter<Sone>() {
+                       knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
 
                                @Override
-                               public boolean filterObject(Sone sone) {
+                               public boolean apply(Sone sone) {
                                        return !currentSone.hasFriend(sone.getId());
                                }
                        });
                } else if ("new".equals(filter)) {
-                       knownSones = Filters.filteredList(knownSones, new Filter<Sone>() {
+                       knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
+
                                /**
                                 * {@inheritDoc}
                                 */
                                @Override
-                               public boolean filterObject(Sone sone) {
+                               public boolean apply(Sone sone) {
                                        return !sone.isKnown();
                                }
                        });
                } else if ("not-new".equals(filter)) {
-                       knownSones = Filters.filteredList(knownSones, new Filter<Sone>() {
+                       knownSones = Collections2.filter(knownSones, new Predicate<Sone>() {
+
                                /**
                                 * {@inheritDoc}
                                 */
                                @Override
-                               public boolean filterObject(Sone sone) {
+                               public boolean apply(Sone sone) {
                                        return sone.isKnown();
                                }
                        });
+               } else if ("own".equals(filter)) {
+                       knownSones = Collections2.filter(knownSones, Sone.LOCAL_SONE_FILTER);
+               } else if ("not-own".equals(filter)) {
+                       knownSones = Collections2.filter(knownSones, Predicates.not(Sone.LOCAL_SONE_FILTER));
                }
+               List<Sone> sortedSones = new ArrayList<Sone>(knownSones);
                if ("activity".equals(sortField)) {
                        if ("asc".equals(sortOrder)) {
-                               Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.LAST_ACTIVITY_COMPARATOR));
+                               Collections.sort(sortedSones, Ordering.from(Sone.LAST_ACTIVITY_COMPARATOR).reverse());
                        } else {
-                               Collections.sort(knownSones, Sone.LAST_ACTIVITY_COMPARATOR);
+                               Collections.sort(sortedSones, Sone.LAST_ACTIVITY_COMPARATOR);
                        }
                } else if ("posts".equals(sortField)) {
                        if ("asc".equals(sortOrder)) {
-                               Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.POST_COUNT_COMPARATOR));
+                               Collections.sort(sortedSones, Ordering.from(Sone.POST_COUNT_COMPARATOR).reverse());
                        } else {
-                               Collections.sort(knownSones, Sone.POST_COUNT_COMPARATOR);
+                               Collections.sort(sortedSones, Sone.POST_COUNT_COMPARATOR);
                        }
                } else if ("images".equals(sortField)) {
                        if ("asc".equals(sortOrder)) {
-                               Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.IMAGE_COUNT_COMPARATOR));
+                               Collections.sort(sortedSones, Ordering.from(Sone.IMAGE_COUNT_COMPARATOR).reverse());
                        } else {
-                               Collections.sort(knownSones, Sone.IMAGE_COUNT_COMPARATOR);
+                               Collections.sort(sortedSones, Sone.IMAGE_COUNT_COMPARATOR);
                        }
                } else {
                        if ("desc".equals(sortOrder)) {
-                               Collections.sort(knownSones, new ReverseComparator<Sone>(Sone.NICE_NAME_COMPARATOR));
+                               Collections.sort(sortedSones, Ordering.from(Sone.NICE_NAME_COMPARATOR).reverse());
                        } else {
-                               Collections.sort(knownSones, Sone.NICE_NAME_COMPARATOR);
+                               Collections.sort(sortedSones, Sone.NICE_NAME_COMPARATOR);
                        }
                }
-               Pagination<Sone> sonePagination = new Pagination<Sone>(knownSones, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
+               Pagination<Sone> sonePagination = new Pagination<Sone>(sortedSones, 25).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("page"), 0));
                templateContext.set("pagination", sonePagination);
                templateContext.set("knownSones", sonePagination.getItems());
        }
index 960f32b..7f08e65 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikePage.java - Copyright © 2010–2012 David Roden
+ * Sone - LikePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 662e833..604b176 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSonePage.java - Copyright © 2010–2012 David Roden
+ * Sone - LockSonePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index a2fb9bc..a837bd1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LoginPage.java - Copyright © 2010–2012 David Roden
+ * Sone - LoginPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 9b712c6..bc4c56d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LogoutPage.java - Copyright © 2010–2012 David Roden
+ * Sone - LogoutPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 4f8e071..66ac635 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkAsKnownPage.java - Copyright © 2011–2012 David Roden
+ * Sone - MarkAsKnownPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -27,6 +27,8 @@ import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+
 /**
  * Page that lets the user mark a number of {@link Sone}s, {@link Post}s, or
  * {@link Reply Replie}s as known.
@@ -65,23 +67,23 @@ public class MarkAsKnownPage extends SoneTemplatePage {
                for (StringTokenizer idTokenizer = new StringTokenizer(ids); idTokenizer.hasMoreTokens();) {
                        String id = idTokenizer.nextToken();
                        if (type.equals("post")) {
-                               Post post = webInterface.getCore().getPost(id, false);
-                               if (post == null) {
+                               Optional<Post> post = webInterface.getCore().getPost(id);
+                               if (!post.isPresent()) {
                                        continue;
                                }
-                               webInterface.getCore().markPostKnown(post);
+                               webInterface.getCore().markPostKnown(post.get());
                        } else if (type.equals("reply")) {
-                               PostReply reply = webInterface.getCore().getReply(id, false);
-                               if (reply == null) {
+                               Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
+                               if (!reply.isPresent()) {
                                        continue;
                                }
-                               webInterface.getCore().markReplyKnown(reply);
+                               webInterface.getCore().markReplyKnown(reply.get());
                        } else if (type.equals("sone")) {
-                               Sone sone = webInterface.getCore().getSone(id, false);
-                               if (sone == null) {
+                               Optional<Sone> sone = webInterface.getCore().getSone(id);
+                               if (!sone.isPresent()) {
                                        continue;
                                }
-                               webInterface.getCore().markSoneKnown(sone);
+                               webInterface.getCore().markSoneKnown(sone.get());
                        }
                }
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
index c7281e3..71200d8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - NewPage.java - Copyright © 2012 David Roden
+ * Sone - NewPage.java - Copyright © 2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,9 +19,12 @@ package net.pterodactylus.sone.web;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import com.google.common.collect.Collections2;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.notify.ListNotificationFilters;
@@ -57,16 +60,16 @@ public class NewPage extends SoneTemplatePage {
        //
 
        /**
-        * {@inherit}
+        * {@inheritDoc}
         */
        @Override
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
 
                /* collect new elements from notifications. */
-               Set<Post> posts = webInterface.getNewPosts();
-               for (PostReply reply : webInterface.getNewReplies()) {
-                       posts.add(reply.getPost());
+               Set<Post> posts = new HashSet<Post>(webInterface.getNewPosts());
+               for (PostReply reply : Collections2.filter(webInterface.getNewReplies(), PostReply.HAS_POST_FILTER)) {
+                       posts.add(reply.getPost().get());
                }
 
                /* filter and sort them. */
index c770da3..094a9cf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - OptionsPage.java - Copyright © 2010–2012 David Roden
+ * Sone - OptionsPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,7 +20,7 @@ package net.pterodactylus.sone.web;
 import java.util.ArrayList;
 import java.util.List;
 
-import net.pterodactylus.sone.core.Core.Preferences;
+import net.pterodactylus.sone.core.Preferences;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.data.Sone.ShowCustomAvatars;
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
index 083c68b..6888277 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - RescuePage.java - Copyright © 2011–2012 David Roden
+ * Sone - RescuePage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0f12abf..6c9e15c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SearchPage.java - Copyright © 2010–2012 David Roden
+ * Sone - SearchPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,6 +24,7 @@ import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -34,18 +35,7 @@ import net.pterodactylus.sone.data.Profile.Field;
 import net.pterodactylus.sone.data.Reply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.cache.Cache;
-import net.pterodactylus.util.cache.CacheException;
-import net.pterodactylus.util.cache.CacheItem;
-import net.pterodactylus.util.cache.DefaultCacheItem;
-import net.pterodactylus.util.cache.MemoryCache;
-import net.pterodactylus.util.cache.ValueRetriever;
 import net.pterodactylus.util.collection.Pagination;
-import net.pterodactylus.util.collection.TimedMap;
-import net.pterodactylus.util.collection.filter.Filter;
-import net.pterodactylus.util.collection.filter.Filters;
-import net.pterodactylus.util.collection.mapper.Mapper;
-import net.pterodactylus.util.collection.mapper.Mappers;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
@@ -53,6 +43,16 @@ import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.text.StringEscaper;
 import net.pterodactylus.util.text.TextException;
 
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Ordering;
+
 /**
  * This page lets the user search for posts and replies that contain certain
  * words.
@@ -65,19 +65,18 @@ public class SearchPage extends SoneTemplatePage {
        private static final Logger logger = Logging.getLogger(SearchPage.class);
 
        /** Short-term cache. */
-       private final Cache<List<Phrase>, Set<Hit<Post>>> hitCache = new MemoryCache<List<Phrase>, Set<Hit<Post>>>(new ValueRetriever<List<Phrase>, Set<Hit<Post>>>() {
+       private final LoadingCache<List<Phrase>, Set<Hit<Post>>> hitCache = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<List<Phrase>, Set<Hit<Post>>>() {
 
                @Override
                @SuppressWarnings("synthetic-access")
-               public CacheItem<Set<Hit<Post>>> retrieve(List<Phrase> phrases) throws CacheException {
+               public Set<Hit<Post>> load(List<Phrase> phrases) {
                        Set<Post> posts = new HashSet<Post>();
                        for (Sone sone : webInterface.getCore().getSones()) {
                                posts.addAll(sone.getPosts());
                        }
-                       return new DefaultCacheItem<Set<Hit<Post>>>(getHits(Filters.filteredSet(posts, Post.FUTURE_POSTS_FILTER), phrases, new PostStringGenerator()));
+                       return getHits(Collections2.filter(posts, Post.FUTURE_POSTS_FILTER), phrases, new PostStringGenerator());
                }
-
-       }, new TimedMap<List<Phrase>, CacheItem<Set<Hit<Post>>>>(300000));
+       });
 
        /**
         * Creates a new search page.
@@ -99,6 +98,7 @@ public class SearchPage extends SoneTemplatePage {
         * {@inheritDoc}
         */
        @Override
+       @SuppressWarnings("synthetic-access")
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                String query = request.getHttpRequest().getParam("query").trim();
@@ -131,31 +131,22 @@ public class SearchPage extends SoneTemplatePage {
                        redirectIfNotNull(getImageId(phrase), "imageBrowser.html?image=");
                }
 
-               Set<Sone> sones = webInterface.getCore().getSones();
-               Set<Hit<Sone>> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR);
+               Collection<Sone> sones = webInterface.getCore().getSones();
+               Collection<Hit<Sone>> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR);
 
-               Set<Hit<Post>> postHits;
-               try {
-                       postHits = hitCache.get(phrases);
-               } catch (CacheException ce1) {
-                       /* should never happen. */
-                       logger.log(Level.SEVERE, "Could not get search results from cache!", ce1);
-                       postHits = Collections.emptySet();
-               }
+               Collection<Hit<Post>> postHits = hitCache.getUnchecked(phrases);
 
                /* now filter. */
-               soneHits = Filters.filteredSet(soneHits, Hit.POSITIVE_FILTER);
-               postHits = Filters.filteredSet(postHits, Hit.POSITIVE_FILTER);
+               soneHits = Collections2.filter(soneHits, Hit.POSITIVE_FILTER);
+               postHits = Collections2.filter(postHits, Hit.POSITIVE_FILTER);
 
                /* now sort. */
-               List<Hit<Sone>> sortedSoneHits = new ArrayList<Hit<Sone>>(soneHits);
-               Collections.sort(sortedSoneHits, Hit.DESCENDING_COMPARATOR);
-               List<Hit<Post>> sortedPostHits = new ArrayList<Hit<Post>>(postHits);
-               Collections.sort(sortedPostHits, Hit.DESCENDING_COMPARATOR);
+               List<Hit<Sone>> sortedSoneHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(soneHits);
+               List<Hit<Post>> sortedPostHits = Ordering.from(Hit.DESCENDING_COMPARATOR).sortedCopy(postHits);
 
                /* extract Sones and posts. */
-               List<Sone> resultSones = Mappers.mappedList(sortedSoneHits, new HitMapper<Sone>());
-               List<Post> resultPosts = Mappers.mappedList(sortedPostHits, new HitMapper<Post>());
+               List<Sone> resultSones = FluentIterable.from(sortedSoneHits).transform(new HitMapper<Sone>()).toList();
+               List<Post> resultPosts = FluentIterable.from(sortedPostHits).transform(new HitMapper<Post>()).toList();
 
                /* pagination. */
                Pagination<Sone> sonePagination = new Pagination<Sone>(resultSones, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("sonePage"), 0));
@@ -319,7 +310,7 @@ public class SearchPage extends SoneTemplatePage {
         */
        private String getSoneId(String phrase) {
                String soneId = phrase.startsWith("sone://") ? phrase.substring(7) : phrase;
-               return (webInterface.getCore().getSone(soneId, false) != null) ? soneId : null;
+               return (webInterface.getCore().getSone(soneId).isPresent()) ? soneId : null;
        }
 
        /**
@@ -332,7 +323,7 @@ public class SearchPage extends SoneTemplatePage {
         */
        private String getPostId(String phrase) {
                String postId = phrase.startsWith("post://") ? phrase.substring(7) : phrase;
-               return (webInterface.getCore().getPost(postId, false) != null) ? postId : null;
+               return (webInterface.getCore().getPost(postId).isPresent()) ? postId : null;
        }
 
        /**
@@ -346,7 +337,11 @@ public class SearchPage extends SoneTemplatePage {
         */
        private String getReplyPostId(String phrase) {
                String replyId = phrase.startsWith("reply://") ? phrase.substring(8) : phrase;
-               return (webInterface.getCore().getReply(replyId, false) != null) ? webInterface.getCore().getReply(replyId, false).getPost().getId() : null;
+               Optional<PostReply> postReply = webInterface.getCore().getPostReply(replyId);
+               if (!postReply.isPresent()) {
+                       return null;
+               }
+               return postReply.get().getPostId();
        }
 
        /**
@@ -469,10 +464,10 @@ public class SearchPage extends SoneTemplatePage {
                public String generateString(Post post) {
                        StringBuilder postString = new StringBuilder();
                        postString.append(post.getText());
-                       if (post.getRecipient() != null) {
-                               postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(post.getRecipient()));
+                       if (post.getRecipient().isPresent()) {
+                               postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(post.getRecipient().get()));
                        }
-                       for (PostReply reply : Filters.filteredList(webInterface.getCore().getReplies(post), Reply.FUTURE_REPLY_FILTER)) {
+                       for (PostReply reply : Collections2.filter(webInterface.getCore().getReplies(post.getId()), Reply.FUTURE_REPLY_FILTER)) {
                                postString.append(' ').append(SoneStringGenerator.NAME_GENERATOR.generateString(reply.getSone()));
                                postString.append(' ').append(reply.getText());
                        }
@@ -582,10 +577,10 @@ public class SearchPage extends SoneTemplatePage {
        private static class Hit<T> {
 
                /** Filter for {@link Hit}s with a score of more than 0. */
-               public static final Filter<Hit<?>> POSITIVE_FILTER = new Filter<Hit<?>>() {
+               public static final Predicate<Hit<?>> POSITIVE_FILTER = new Predicate<Hit<?>>() {
 
                        @Override
-                       public boolean filterObject(Hit<?> hit) {
+                       public boolean apply(Hit<?> hit) {
                                return hit.getScore() > 0;
                        }
 
@@ -647,13 +642,13 @@ public class SearchPage extends SoneTemplatePage {
         *            The type of the object to extract
         * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
         */
-       public static class HitMapper<T> implements Mapper<Hit<T>, T> {
+       private static class HitMapper<T> implements Function<Hit<T>, T> {
 
                /**
                 * {@inheritDoc}
                 */
                @Override
-               public T map(Hit<T> input) {
+               public T apply(Hit<T> input) {
                        return input.getObject();
                }
 
index 4ef434f..7dfdfe7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTemplatePage.java - Copyright © 2010–2012 David Roden
+ * Sone - SoneTemplatePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,12 +30,13 @@ import net.pterodactylus.sone.main.SonePlugin;
 import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.sone.web.page.FreenetTemplatePage;
-import net.pterodactylus.util.collection.ListBuilder;
-import net.pterodactylus.util.collection.MapBuilder;
 import net.pterodactylus.util.notify.Notification;
-import net.pterodactylus.util.object.HashCode;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContext;
 import freenet.support.api.HTTPRequest;
@@ -217,7 +218,7 @@ public class SoneTemplatePage extends FreenetTemplatePage {
         */
        @Override
        protected List<Map<String, String>> getAdditionalLinkNodes(FreenetRequest request) {
-               return new ListBuilder<Map<String, String>>().add(new MapBuilder<String, String>().put("rel", "search").put("type", "application/opensearchdescription+xml").put("title", "Sone").put("href", "http://" + request.getHttpRequest().getHeader("host") + "/Sone/OpenSearch.xml").get()).get();
+               return ImmutableList.<Map<String, String>> builder().add(ImmutableMap.<String, String> builder().put("rel", "search").put("type", "application/opensearchdescription+xml").put("title", "Sone").put("href", "http://" + request.getHttpRequest().getHeader("host") + "/Sone/OpenSearch.xml").build()).build();
        }
 
        /**
@@ -265,7 +266,7 @@ public class SoneTemplatePage extends FreenetTemplatePage {
                List<Notification> notifications = ListNotificationFilters.filterNotifications(webInterface.getNotifications().getNotifications(), currentSone);
                Collections.sort(notifications, Notification.CREATED_TIME_SORTER);
                templateContext.set("notifications", notifications);
-               templateContext.set("notificationHash", HashCode.hashCode(notifications));
+               templateContext.set("notificationHash", notifications.hashCode());
        }
 
        /**
index 3723fac..39fd1de 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustPage.java - Copyright © 2011–2012 David Roden
+ * Sone - TrustPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -59,9 +61,9 @@ public class TrustPage extends SoneTemplatePage {
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        Sone currentSone = getCurrentSone(request.getToadletContext());
-                       Sone sone = webInterface.getCore().getSone(identity, false);
-                       if (sone != null) {
-                               webInterface.getCore().trustSone(currentSone, sone);
+                       Optional<Sone> sone = webInterface.getCore().getSone(identity);
+                       if (sone.isPresent()) {
+                               webInterface.getCore().trustSone(currentSone, sone.get());
                        }
                        throw new RedirectException(returnPage);
                }
index 5e8a2a8..b568be1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnbookmarkPage.java - Copyright © 2011–2012 David Roden
+ * Sone - UnbookmarkPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 97ba4cd..4813239 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnfollowSonePage.java - Copyright © 2010–2012 David Roden
+ * Sone - UnfollowSonePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index d7e9acb..9254a42 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlikePage.java - Copyright © 2010–2012 David Roden
+ * Sone - UnlikePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 3d2370c..ed92246 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlockSonePage.java - Copyright © 2010–2012 David Roden
+ * Sone - UnlockSonePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 29e17e7..d68d5ca 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UntrustPage.java - Copyright © 2011–2012 David Roden
+ * Sone - UntrustPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -59,9 +61,9 @@ public class UntrustPage extends SoneTemplatePage {
                        String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                        String identity = request.getHttpRequest().getPartAsStringFailsafe("sone", 44);
                        Sone currentSone = getCurrentSone(request.getToadletContext());
-                       Sone sone = webInterface.getCore().getSone(identity, false);
-                       if (sone != null) {
-                               webInterface.getCore().untrustSone(currentSone, sone);
+                       Optional<Sone> sone = webInterface.getCore().getSone(identity);
+                       if (sone.isPresent()) {
+                               webInterface.getCore().untrustSone(currentSone, sone.get());
                        }
                        throw new RedirectException(returnPage);
                }
index 559a8ff..2800f29 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UploadImagePage.java - Copyright © 2011–2012 David Roden
+ * Sone - UploadImagePage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,11 +36,13 @@ import net.pterodactylus.sone.data.TemporaryImage;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.io.Closer;
-import net.pterodactylus.util.io.StreamCopier;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 import net.pterodactylus.util.web.Method;
+
+import com.google.common.io.ByteStreams;
+
 import freenet.support.api.Bucket;
 import freenet.support.api.HTTPUploadedFile;
 
@@ -99,7 +101,7 @@ public class UploadImagePage extends SoneTemplatePage {
                                imageInputStream = fileBucket.getInputStream();
                                /* TODO - check length */
                                imageDataOutputStream = new ByteArrayOutputStream((int) fileBucket.size());
-                               StreamCopier.copy(imageInputStream, imageDataOutputStream);
+                               ByteStreams.copy(imageInputStream, imageDataOutputStream);
                        } catch (IOException ioe1) {
                                logger.log(Level.WARNING, "Could not read uploaded image!", ioe1);
                                return;
index 98b2603..8adf46d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ViewPostPage.java - Copyright © 2010–2012 David Roden
+ * Sone - ViewPostPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.web;
 
 import java.net.URI;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -54,11 +56,11 @@ public class ViewPostPage extends SoneTemplatePage {
        @Override
        protected String getPageTitle(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
-               Post post = webInterface.getCore().getPost(postId, false);
+               Optional<Post> post = webInterface.getCore().getPost(postId);
                String title = "";
-               if ((post != null) && (post.getSone() != null)) {
-                       title = post.getText().substring(0, Math.min(20, post.getText().length())) + "…";
-                       title += " - " + SoneAccessor.getNiceName(post.getSone()) + " - ";
+               if (post.isPresent()) {
+                       title = post.get().getText().substring(0, Math.min(20, post.get().getText().length())) + "…";
+                       title += " - " + SoneAccessor.getNiceName(post.get().getSone()) + " - ";
                }
                title += webInterface.getL10n().getString("Page.ViewPost.Title");
                return title;
@@ -72,8 +74,8 @@ public class ViewPostPage extends SoneTemplatePage {
                super.processTemplate(request, templateContext);
                String postId = request.getHttpRequest().getParam("post");
                boolean raw = request.getHttpRequest().getParam("raw").equals("true");
-               Post post = webInterface.getCore().getPost(postId);
-               templateContext.set("post", post);
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               templateContext.set("post", post.orNull());
                templateContext.set("raw", raw);
        }
 
index 2df911c..a94b4af 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - ViewSonePage.java - Copyright © 2010–2012 David Roden
+ * Sone - ViewSonePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -36,6 +36,8 @@ import net.pterodactylus.util.number.Numbers;
 import net.pterodactylus.util.template.Template;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.base.Optional;
+
 /**
  * Lets the user browser another Sone.
  *
@@ -65,9 +67,9 @@ public class ViewSonePage extends SoneTemplatePage {
        @Override
        protected String getPageTitle(FreenetRequest request) {
                String soneId = request.getHttpRequest().getParam("sone");
-               Sone sone = webInterface.getCore().getSone(soneId, false);
-               if ((sone != null) && (sone.getTime() > 0)) {
-                       String soneName = SoneAccessor.getNiceName(sone);
+               Optional<Sone> sone = webInterface.getCore().getSone(soneId);
+               if (sone.isPresent()) {
+                       String soneName = SoneAccessor.getNiceName(sone.get());
                        return soneName + " - " + webInterface.getL10n().getString("Page.ViewSone.Title");
                }
                return webInterface.getL10n().getString("Page.ViewSone.Page.TitleWithoutSone");
@@ -80,26 +82,26 @@ public class ViewSonePage extends SoneTemplatePage {
        protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
                super.processTemplate(request, templateContext);
                String soneId = request.getHttpRequest().getParam("sone");
-               Sone sone = webInterface.getCore().getSone(soneId, false);
-               templateContext.set("sone", sone);
+               Optional<Sone> sone = webInterface.getCore().getSone(soneId);
+               templateContext.set("sone", sone.orNull());
                templateContext.set("soneId", soneId);
-               if (sone == null) {
+               if (!sone.isPresent()) {
                        return;
                }
-               List<Post> sonePosts = sone.getPosts();
-               sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone));
+               List<Post> sonePosts = sone.get().getPosts();
+               sonePosts.addAll(webInterface.getCore().getDirectedPosts(sone.get().getId()));
                Collections.sort(sonePosts, Post.TIME_COMPARATOR);
                Pagination<Post> postPagination = new Pagination<Post>(sonePosts, webInterface.getCore().getPreferences().getPostsPerPage()).setPage(Numbers.safeParseInteger(request.getHttpRequest().getParam("postPage"), 0));
                templateContext.set("postPagination", postPagination);
                templateContext.set("posts", postPagination.getItems());
-               Set<PostReply> replies = sone.getReplies();
+               Set<PostReply> replies = sone.get().getReplies();
                final Map<Post, List<PostReply>> repliedPosts = new HashMap<Post, List<PostReply>>();
                for (PostReply reply : replies) {
-                       Post post = reply.getPost();
-                       if (repliedPosts.containsKey(post) || sone.equals(post.getSone()) || (sone.equals(post.getRecipient()))) {
+                       Optional<Post> post = reply.getPost();
+                       if (!post.isPresent() || repliedPosts.containsKey(post.get()) || sone.get().equals(post.get().getSone()) || (sone.get().getId().equals(post.get().getRecipientId().orNull()))) {
                                continue;
                        }
-                       repliedPosts.put(post, webInterface.getCore().getReplies(post));
+                       repliedPosts.put(post.get(), webInterface.getCore().getReplies(post.get().getId()));
                }
                List<Post> posts = new ArrayList<Post>(repliedPosts.keySet());
                Collections.sort(posts, new Comparator<Post>() {
index 00f8cf9..593193c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - WebInterface.java - Copyright © 2010–2012 David Roden
+ * Sone - WebInterface.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,11 +32,33 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import net.pterodactylus.sone.core.Core;
-import net.pterodactylus.sone.core.CoreListener;
+import net.pterodactylus.sone.core.event.ImageInsertAbortedEvent;
+import net.pterodactylus.sone.core.event.ImageInsertFailedEvent;
+import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
+import net.pterodactylus.sone.core.event.ImageInsertStartedEvent;
+import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
+import net.pterodactylus.sone.core.event.MarkPostReplyKnownEvent;
+import net.pterodactylus.sone.core.event.MarkSoneKnownEvent;
+import net.pterodactylus.sone.core.event.NewPostFoundEvent;
+import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent;
+import net.pterodactylus.sone.core.event.NewSoneFoundEvent;
+import net.pterodactylus.sone.core.event.PostRemovedEvent;
+import net.pterodactylus.sone.core.event.PostReplyRemovedEvent;
+import net.pterodactylus.sone.core.event.SoneInsertAbortedEvent;
+import net.pterodactylus.sone.core.event.SoneInsertedEvent;
+import net.pterodactylus.sone.core.event.SoneInsertingEvent;
+import net.pterodactylus.sone.core.event.SoneLockedEvent;
+import net.pterodactylus.sone.core.event.SoneRemovedEvent;
+import net.pterodactylus.sone.core.event.SoneUnlockedEvent;
+import net.pterodactylus.sone.core.event.UpdateFoundEvent;
 import net.pterodactylus.sone.data.Album;
 import net.pterodactylus.sone.data.Image;
 import net.pterodactylus.sone.data.Post;
@@ -103,8 +125,6 @@ import net.pterodactylus.sone.web.ajax.UntrustAjaxPage;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.sone.web.page.PageToadlet;
 import net.pterodactylus.sone.web.page.PageToadletFactory;
-import net.pterodactylus.util.collection.SetBuilder;
-import net.pterodactylus.util.collection.filter.Filters;
 import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.NotificationManager;
@@ -126,11 +146,15 @@ import net.pterodactylus.util.template.TemplateContextFactory;
 import net.pterodactylus.util.template.TemplateParser;
 import net.pterodactylus.util.template.TemplateProvider;
 import net.pterodactylus.util.template.XmlFilter;
-import net.pterodactylus.util.thread.Ticker;
-import net.pterodactylus.util.version.Version;
 import net.pterodactylus.util.web.RedirectPage;
 import net.pterodactylus.util.web.StaticPage;
 import net.pterodactylus.util.web.TemplatePage;
+
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.eventbus.Subscribe;
+import com.google.inject.Inject;
+
 import freenet.clients.http.SessionManager;
 import freenet.clients.http.SessionManager.Session;
 import freenet.clients.http.ToadletContainer;
@@ -144,7 +168,7 @@ import freenet.support.api.HTTPRequest;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class WebInterface implements CoreListener {
+public class WebInterface {
 
        /** The logger. */
        private static final Logger logger = Logging.getLogger(WebInterface.class);
@@ -192,7 +216,7 @@ public class WebInterface implements CoreListener {
        private final Map<Sone, TemplateNotification> soneInsertNotifications = new HashMap<Sone, TemplateNotification>();
 
        /** Sone locked notification ticker objects. */
-       private final Map<Sone, Object> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, Object>());
+       private final Map<Sone, ScheduledFuture<?>> lockedSonesTickerObjects = Collections.synchronizedMap(new HashMap<Sone, ScheduledFuture<?>>());
 
        /** The “Sone locked” notification. */
        private final ListNotification<Sone> lockedSonesNotification;
@@ -209,12 +233,16 @@ public class WebInterface implements CoreListener {
        /** The “image insert failed” notification. */
        private final ListNotification<Image> imageInsertFailedNotification;
 
+       /** Scheduled executor for time-based notifications. */
+       private final ScheduledExecutorService ticker = Executors.newScheduledThreadPool(1);
+
        /**
         * Creates a new web interface.
         *
         * @param sonePlugin
         *            The Sone plugin
         */
+       @Inject
        public WebInterface(SonePlugin sonePlugin) {
                this.sonePlugin = sonePlugin;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
@@ -372,7 +400,7 @@ public class WebInterface implements CoreListener {
         *         currently logged in
         */
        public Sone getCurrentSone(ToadletContext toadletContext, boolean create) {
-               Set<Sone> localSones = getCore().getLocalSones();
+               Collection<Sone> localSones = getCore().getLocalSones();
                if (localSones.size() == 1) {
                        return localSones.iterator().next();
                }
@@ -458,7 +486,7 @@ public class WebInterface implements CoreListener {
         * @return The new posts
         */
        public Set<Post> getNewPosts() {
-               return new SetBuilder<Post>().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).get();
+               return ImmutableSet.<Post> builder().addAll(newPostNotification.getElements()).addAll(localPostNotification.getElements()).build();
        }
 
        /**
@@ -468,7 +496,7 @@ public class WebInterface implements CoreListener {
         * @return The new replies
         */
        public Set<PostReply> getNewReplies() {
-               return new SetBuilder<PostReply>().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).get();
+               return ImmutableSet.<PostReply> builder().addAll(newReplyNotification.getElements()).addAll(localReplyNotification.getElements()).build();
        }
 
        /**
@@ -532,17 +560,17 @@ public class WebInterface implements CoreListener {
                final TemplateNotification startupNotification = new TemplateNotification("startup-notification", startupNotificationTemplate);
                notificationManager.addNotification(startupNotification);
 
-               Ticker.getInstance().registerEvent(System.currentTimeMillis() + (120 * 1000), new Runnable() {
+               ticker.schedule(new Runnable() {
 
                        @Override
                        public void run() {
                                startupNotification.dismiss();
                        }
-               }, "Sone Startup Notification Remover");
+               }, 2, TimeUnit.MINUTES);
 
                Template wotMissingNotificationTemplate = TemplateParser.parse(createReader("/templates/notify/wotMissingNotification.html"));
                final TemplateNotification wotMissingNotification = new TemplateNotification("wot-missing-notification", wotMissingNotificationTemplate);
-               Ticker.getInstance().registerEvent(System.currentTimeMillis() + (15 * 1000), new Runnable() {
+               ticker.scheduleAtFixedRate(new Runnable() {
 
                        @Override
                        @SuppressWarnings("synthetic-access")
@@ -552,10 +580,9 @@ public class WebInterface implements CoreListener {
                                } else {
                                        notificationManager.addNotification(wotMissingNotification);
                                }
-                               Ticker.getInstance().registerEvent(System.currentTimeMillis() + (15 * 1000), this, "Sone WoT Connector Checker");
                        }
 
-               }, "Sone WoT Connector Checker");
+               }, 15, 15, TimeUnit.SECONDS);
        }
 
        /**
@@ -563,7 +590,7 @@ public class WebInterface implements CoreListener {
         */
        public void stop() {
                unregisterToadlets();
-               Ticker.getInstance().stop();
+               ticker.shutdownNow();
        }
 
        //
@@ -725,15 +752,15 @@ public class WebInterface implements CoreListener {
        }
 
        /**
-        * Returns all {@link Core#isLocalSone(Sone) local Sone}s that are
-        * referenced by {@link SonePart}s in the given text (after parsing it using
+        * Returns all {@link Sone#isLocal() local Sone}s that are referenced by
+        * {@link SonePart}s in the given text (after parsing it using
         * {@link SoneTextParser}).
         *
         * @param text
         *            The text to parse
         * @return All mentioned local Sones
         */
-       private Set<Sone> getMentionedSones(String text) {
+       private Collection<Sone> getMentionedSones(String text) {
                /* we need no context to find mentioned Sones. */
                Set<Sone> mentionedSones = new HashSet<Sone>();
                try {
@@ -745,7 +772,7 @@ public class WebInterface implements CoreListener {
                } catch (IOException ioe1) {
                        logger.log(Level.WARNING, String.format("Could not parse post text: %s", text), ioe1);
                }
-               return Filters.filteredSet(mentionedSones, Sone.LOCAL_SONE_FILTER);
+               return Collections2.filter(mentionedSones, Sone.LOCAL_SONE_FILTER);
        }
 
        /**
@@ -770,26 +797,33 @@ public class WebInterface implements CoreListener {
        }
 
        //
-       // CORELISTENER METHODS
+       // EVENT HANDLERS
        //
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a new {@link Sone} was found.
+        *
+        * @param newSoneFoundEvent
+        *            The event
         */
-       @Override
-       public void newSoneFound(Sone sone) {
-               newSoneNotification.add(sone);
+       @Subscribe
+       public void newSoneFound(NewSoneFoundEvent newSoneFoundEvent) {
+               newSoneNotification.add(newSoneFoundEvent.sone());
                if (!hasFirstStartNotification()) {
                        notificationManager.addNotification(newSoneNotification);
                }
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a new {@link Post} was found.
+        *
+        * @param newPostFoundEvent
+        *            The event
         */
-       @Override
-       public void newPostFound(Post post) {
-               boolean isLocal = getCore().isLocalSone(post.getSone());
+       @Subscribe
+       public void newPostFound(NewPostFoundEvent newPostFoundEvent) {
+               Post post = newPostFoundEvent.post();
+               boolean isLocal = post.getSone().isLocal();
                if (isLocal) {
                        localPostNotification.add(post);
                } else {
@@ -807,11 +841,15 @@ public class WebInterface implements CoreListener {
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a new {@link PostReply} was found.
+        *
+        * @param newPostReplyFoundEvent
+        *            The event
         */
-       @Override
-       public void newReplyFound(PostReply reply) {
-               boolean isLocal = getCore().isLocalSone(reply.getSone());
+       @Subscribe
+       public void newReplyFound(NewPostReplyFoundEvent newPostReplyFoundEvent) {
+               PostReply reply = newPostReplyFoundEvent.postReply();
+               boolean isLocal = reply.getSone().isLocal();
                if (isLocal) {
                        localReplyNotification.add(reply);
                } else {
@@ -819,8 +857,8 @@ public class WebInterface implements CoreListener {
                }
                if (!hasFirstStartNotification()) {
                        notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
-                       if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && (reply.getPost().getSone() != null) && (reply.getTime() <= System.currentTimeMillis())) {
-                               mentionNotification.add(reply.getPost());
+                       if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && reply.getPost().isPresent() && (reply.getTime() <= System.currentTimeMillis())) {
+                               mentionNotification.add(reply.getPost().get());
                                notificationManager.addNotification(mentionNotification);
                        }
                } else {
@@ -829,179 +867,228 @@ public class WebInterface implements CoreListener {
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link Sone} was marked as known.
+        *
+        * @param markSoneKnownEvent
+        *            The event
         */
-       @Override
-       public void markSoneKnown(Sone sone) {
-               newSoneNotification.remove(sone);
+       @Subscribe
+       public void markSoneKnown(MarkSoneKnownEvent markSoneKnownEvent) {
+               newSoneNotification.remove(markSoneKnownEvent.sone());
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link Post} was marked as known.
+        *
+        * @param markPostKnownEvent
+        *            The event
         */
-       @Override
-       public void markPostKnown(Post post) {
-               newPostNotification.remove(post);
-               localPostNotification.remove(post);
-               mentionNotification.remove(post);
+       @Subscribe
+       public void markPostKnown(MarkPostKnownEvent markPostKnownEvent) {
+               newPostNotification.remove(markPostKnownEvent.post());
+               localPostNotification.remove(markPostKnownEvent.post());
+               mentionNotification.remove(markPostKnownEvent.post());
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link PostReply} was marked as known.
+        *
+        * @param markPostReplyKnownEvent
+        *            The event
         */
-       @Override
-       public void markReplyKnown(PostReply reply) {
-               newReplyNotification.remove(reply);
-               localReplyNotification.remove(reply);
-               mentionNotification.remove(reply.getPost());
+       @Subscribe
+       public void markReplyKnown(MarkPostReplyKnownEvent markPostReplyKnownEvent) {
+               newReplyNotification.remove(markPostReplyKnownEvent.postReply());
+               localReplyNotification.remove(markPostReplyKnownEvent.postReply());
+               mentionNotification.remove(markPostReplyKnownEvent.postReply().getPost().get());
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link Sone} was removed.
+        *
+        * @param soneRemovedEvent
+        *            The event
         */
-       @Override
-       public void soneRemoved(Sone sone) {
-               newSoneNotification.remove(sone);
+       @Subscribe
+       public void soneRemoved(SoneRemovedEvent soneRemovedEvent) {
+               newSoneNotification.remove(soneRemovedEvent.sone());
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link Post} was removed.
+        *
+        * @param postRemovedEvent
+        *            The event
         */
-       @Override
-       public void postRemoved(Post post) {
-               newPostNotification.remove(post);
-               localPostNotification.remove(post);
-               mentionNotification.remove(post);
+       @Subscribe
+       public void postRemoved(PostRemovedEvent postRemovedEvent) {
+               newPostNotification.remove(postRemovedEvent.post());
+               localPostNotification.remove(postRemovedEvent.post());
+               mentionNotification.remove(postRemovedEvent.post());
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link PostReply} was removed.
+        *
+        * @param postReplyRemovedEvent
+        *            The event
         */
-       @Override
-       public void replyRemoved(PostReply reply) {
+       @Subscribe
+       public void replyRemoved(PostReplyRemovedEvent postReplyRemovedEvent) {
+               PostReply reply = postReplyRemovedEvent.postReply();
                newReplyNotification.remove(reply);
                localReplyNotification.remove(reply);
-               if (!getMentionedSones(reply.getText()).isEmpty()) {
+               if (!getMentionedSones(reply.getText()).isEmpty() && reply.getPost().isPresent()) {
                        boolean isMentioned = false;
-                       for (PostReply existingReply : getCore().getReplies(reply.getPost())) {
+                       for (PostReply existingReply : getCore().getReplies(reply.getPostId())) {
                                isMentioned |= !reply.isKnown() && !getMentionedSones(existingReply.getText()).isEmpty();
                        }
                        if (!isMentioned) {
-                               mentionNotification.remove(reply.getPost());
+                               mentionNotification.remove(reply.getPost().get());
                        }
                }
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a Sone was locked.
+        *
+        * @param soneLockedEvent
+        *            The event
         */
-       @Override
-       public void soneLocked(final Sone sone) {
-               Object tickerObject = Ticker.getInstance().registerEvent(System.currentTimeMillis() + (5 * 60) * 1000, new Runnable() {
+       @Subscribe
+       public void soneLocked(SoneLockedEvent soneLockedEvent) {
+               final Sone sone = soneLockedEvent.sone();
+               ScheduledFuture<?> tickerObject = ticker.schedule(new Runnable() {
 
                        @Override
                        @SuppressWarnings("synthetic-access")
                        public void run() {
                                lockedSonesNotification.add(sone);
-                               lockedSonesTickerObjects.remove(sone);
                                notificationManager.addNotification(lockedSonesNotification);
                        }
-               }, "Sone Locked Notification");
+               }, 5, TimeUnit.MINUTES);
                lockedSonesTickerObjects.put(sone, tickerObject);
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a Sone was unlocked.
+        *
+        * @param soneUnlockedEvent
+        *            The event
         */
-       @Override
-       public void soneUnlocked(Sone sone) {
-               lockedSonesNotification.remove(sone);
-               Ticker.getInstance().deregisterEvent(lockedSonesTickerObjects.remove(sone));
+       @Subscribe
+       public void soneUnlocked(SoneUnlockedEvent soneUnlockedEvent) {
+               lockedSonesNotification.remove(soneUnlockedEvent.sone());
+               lockedSonesTickerObjects.remove(soneUnlockedEvent.sone()).cancel(false);
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link Sone} is being inserted.
+        *
+        * @param soneInsertingEvent
+        *            The event
         */
-       @Override
-       public void soneInserting(Sone sone) {
-               TemplateNotification soneInsertNotification = getSoneInsertNotification(sone);
+       @Subscribe
+       public void soneInserting(SoneInsertingEvent soneInsertingEvent) {
+               TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertingEvent.sone());
                soneInsertNotification.set("soneStatus", "inserting");
-               if (sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").get()) {
+               if (soneInsertingEvent.sone().getOptions().getBooleanOption("EnableSoneInsertNotifications").get()) {
                        notificationManager.addNotification(soneInsertNotification);
                }
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link Sone} was inserted.
+        *
+        * @param soneInsertedEvent
+        *            The event
         */
-       @Override
-       public void soneInserted(Sone sone, long insertDuration) {
-               TemplateNotification soneInsertNotification = getSoneInsertNotification(sone);
+       @Subscribe
+       public void soneInserted(SoneInsertedEvent soneInsertedEvent) {
+               TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertedEvent.sone());
                soneInsertNotification.set("soneStatus", "inserted");
-               soneInsertNotification.set("insertDuration", insertDuration / 1000);
-               if (sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").get()) {
+               soneInsertNotification.set("insertDuration", soneInsertedEvent.insertDuration() / 1000);
+               if (soneInsertedEvent.sone().getOptions().getBooleanOption("EnableSoneInsertNotifications").get()) {
                        notificationManager.addNotification(soneInsertNotification);
                }
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a {@link Sone} insert was aborted.
+        *
+        * @param soneInsertAbortedEvent
+        *            The event
         */
-       @Override
-       public void soneInsertAborted(Sone sone, Throwable cause) {
-               TemplateNotification soneInsertNotification = getSoneInsertNotification(sone);
+       @Subscribe
+       public void soneInsertAborted(SoneInsertAbortedEvent soneInsertAbortedEvent) {
+               TemplateNotification soneInsertNotification = getSoneInsertNotification(soneInsertAbortedEvent.sone());
                soneInsertNotification.set("soneStatus", "insert-aborted");
-               soneInsertNotification.set("insert-error", cause);
-               if (sone.getOptions().getBooleanOption("EnableSoneInsertNotifications").get()) {
+               soneInsertNotification.set("insert-error", soneInsertAbortedEvent.cause());
+               if (soneInsertAbortedEvent.sone().getOptions().getBooleanOption("EnableSoneInsertNotifications").get()) {
                        notificationManager.addNotification(soneInsertNotification);
                }
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that a new Sone version was found.
+        *
+        * @param updateFoundEvent
+        *            The event
         */
-       @Override
-       public void updateFound(Version version, long releaseTime, long latestEdition) {
-               newVersionNotification.getTemplateContext().set("latestVersion", version);
-               newVersionNotification.getTemplateContext().set("latestEdition", latestEdition);
-               newVersionNotification.getTemplateContext().set("releaseTime", releaseTime);
+       @Subscribe
+       public void updateFound(UpdateFoundEvent updateFoundEvent) {
+               newVersionNotification.getTemplateContext().set("latestVersion", updateFoundEvent.version());
+               newVersionNotification.getTemplateContext().set("latestEdition", updateFoundEvent.latestEdition());
+               newVersionNotification.getTemplateContext().set("releaseTime", updateFoundEvent.releaseTime());
                notificationManager.addNotification(newVersionNotification);
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that an image insert was started
+        *
+        * @param imageInsertStartedEvent
+        *            The event
         */
-       @Override
-       public void imageInsertStarted(Image image) {
-               insertingImagesNotification.add(image);
+       @Subscribe
+       public void imageInsertStarted(ImageInsertStartedEvent imageInsertStartedEvent) {
+               insertingImagesNotification.add(imageInsertStartedEvent.image());
                notificationManager.addNotification(insertingImagesNotification);
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that an {@link Image} insert was aborted.
+        *
+        * @param imageInsertAbortedEvent
+        *            The event
         */
-       @Override
-       public void imageInsertAborted(Image image) {
-               insertingImagesNotification.remove(image);
+       @Subscribe
+       public void imageInsertAborted(ImageInsertAbortedEvent imageInsertAbortedEvent) {
+               insertingImagesNotification.remove(imageInsertAbortedEvent.image());
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that an {@link Image} insert is finished.
+        *
+        * @param imageInsertFinishedEvent
+        *            The event
         */
-       @Override
-       public void imageInsertFinished(Image image) {
-               insertingImagesNotification.remove(image);
-               insertedImagesNotification.add(image);
+       @Subscribe
+       public void imageInsertFinished(ImageInsertFinishedEvent imageInsertFinishedEvent) {
+               insertingImagesNotification.remove(imageInsertFinishedEvent.image());
+               insertedImagesNotification.add(imageInsertFinishedEvent.image());
                notificationManager.addNotification(insertedImagesNotification);
        }
 
        /**
-        * {@inheritDoc}
+        * Notifies the web interface that an {@link Image} insert has failed.
+        *
+        * @param imageInsertFailedEvent
+        *            The event
         */
-       @Override
-       public void imageInsertFailed(Image image, Throwable cause) {
-               insertingImagesNotification.remove(image);
-               imageInsertFailedNotification.add(image);
+       @Subscribe
+       public void imageInsertFailed(ImageInsertFailedEvent imageInsertFailedEvent) {
+               insertingImagesNotification.remove(imageInsertFailedEvent.image());
+               imageInsertFailedNotification.add(imageInsertFailedEvent.image());
                notificationManager.addNotification(imageInsertFailedNotification);
        }
 
index a684b0f..1005381 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - BookmarkAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - BookmarkAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 53a5a51..4c278c6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreatePostAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - CreatePostAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -24,6 +24,8 @@ import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonObject;
 
+import com.google.common.base.Optional;
+
 /**
  * AJAX handler that creates a new post.
  *
@@ -51,7 +53,7 @@ public class CreatePostAjaxPage extends JsonPage {
                        return createErrorJsonObject("auth-required");
                }
                String recipientId = request.getHttpRequest().getParam("recipient");
-               Sone recipient = webInterface.getCore().getSone(recipientId, false);
+               Optional<Sone> recipient = webInterface.getCore().getSone(recipientId);
                String senderId = request.getHttpRequest().getParam("sender");
                Sone sender = webInterface.getCore().getLocalSone(senderId, false);
                if (sender == null) {
@@ -63,7 +65,7 @@ public class CreatePostAjaxPage extends JsonPage {
                }
                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
                Post newPost = webInterface.getCore().createPost(sender, recipient, text);
-               return createSuccessJsonObject().put("postId", newPost.getId()).put("sone", sender.getId()).put("recipient", (newPost.getRecipient() != null) ? newPost.getRecipient().getId() : null);
+               return createSuccessJsonObject().put("postId", newPost.getId()).put("sone", sender.getId()).put("recipient", newPost.getRecipientId().orNull());
        }
 
 }
index 20857f3..8a35185 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - CreateReplyAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - CreateReplyAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
@@ -58,12 +60,12 @@ public class CreateReplyAjaxPage extends JsonPage {
                if (sender == null) {
                        sender = getCurrentSone(request.getToadletContext());
                }
-               Post post = webInterface.getCore().getPost(postId);
-               if ((post == null) || (post.getSone() == null)) {
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               if (!post.isPresent()) {
                        return createErrorJsonObject("invalid-post-id");
                }
                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
-               PostReply reply = webInterface.getCore().createReply(sender, post, text);
+               PostReply reply = webInterface.getCore().createReply(sender, post.get(), text);
                return createSuccessJsonObject().put("reply", reply.getId()).put("sone", sender.getId());
        }
 
index ea2310c..71b0016 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeletePostAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - DeletePostAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -49,14 +51,14 @@ public class DeletePostAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
-               Post post = webInterface.getCore().getPost(postId, false);
-               if ((post == null) || (post.getSone() == null)) {
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               if (!post.isPresent()) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               if (!webInterface.getCore().isLocalSone(post.getSone())) {
+               if (!post.get().getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
-               webInterface.getCore().deletePost(post);
+               webInterface.getCore().deletePost(post.get());
                return createSuccessJsonObject();
        }
 
index 20fe841..7874f83 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteProfileFieldAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - DeleteProfileFieldAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 0442117..2aa217d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DeleteReplyAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - DeleteReplyAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -49,14 +51,14 @@ public class DeleteReplyAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
-               PostReply reply = webInterface.getCore().getReply(replyId);
-               if (reply == null) {
+               Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
+               if (!reply.isPresent()) {
                        return createErrorJsonObject("invalid-reply-id");
                }
-               if (!webInterface.getCore().isLocalSone(reply.getSone())) {
+               if (!reply.get().getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
-               webInterface.getCore().deleteReply(reply);
+               webInterface.getCore().deleteReply(reply.get());
                return createSuccessJsonObject();
        }
 
index bd5d83c..595696f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DismissNotificationAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - DismissNotificationAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6e72a5a..843b088 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - DistrustAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - DistrustAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -51,11 +53,11 @@ public class DistrustAjaxPage extends JsonPage {
                        return createErrorJsonObject("auth-required");
                }
                String soneId = request.getHttpRequest().getParam("sone");
-               Sone sone = webInterface.getCore().getSone(soneId, false);
-               if (sone == null) {
+               Optional<Sone> sone = webInterface.getCore().getSone(soneId);
+               if (!sone.isPresent()) {
                        return createErrorJsonObject("invalid-sone-id");
                }
-               webInterface.getCore().distrustSone(currentSone, sone);
+               webInterface.getCore().distrustSone(currentSone, sone.get());
                return createSuccessJsonObject().put("trustValue", webInterface.getCore().getPreferences().getNegativeTrust());
        }
 
index 92e1e48..8d436ea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditAlbumAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - EditAlbumAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -54,7 +54,7 @@ public class EditAlbumAjaxPage extends JsonPage {
                if (album == null) {
                        return createErrorJsonObject("invalid-album-id");
                }
-               if (!webInterface.getCore().isLocalSone(album.getSone())) {
+               if (!album.getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
                if ("true".equals(request.getHttpRequest().getParam("moveLeft"))) {
index 42f4285..378b03b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditImageAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - EditImageAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -22,10 +22,11 @@ import net.pterodactylus.sone.template.ParserFilter;
 import net.pterodactylus.sone.text.TextFilter;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.collection.MapBuilder;
 import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.template.TemplateContext;
 
+import com.google.common.collect.ImmutableMap;
+
 /**
  * Page that stores a user’s image modifications.
  *
@@ -63,7 +64,7 @@ public class EditImageAjaxPage extends JsonPage {
                if (image == null) {
                        return createErrorJsonObject("invalid-image-id");
                }
-               if (!webInterface.getCore().isLocalSone(image.getSone())) {
+               if (!image.getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
                if ("true".equals(request.getHttpRequest().getParam("moveLeft"))) {
@@ -80,7 +81,7 @@ public class EditImageAjaxPage extends JsonPage {
                String description = request.getHttpRequest().getParam("description").trim();
                image.setTitle(title).setDescription(TextFilter.filter(request.getHttpRequest().getHeader("host"), description));
                webInterface.getCore().touchConfiguration();
-               return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", (String) parserFilter.format(new TemplateContext(), image.getDescription(), new MapBuilder<String, Object>().put("sone", image.getSone()).get()));
+               return createSuccessJsonObject().put("imageId", image.getId()).put("title", image.getTitle()).put("description", image.getDescription()).put("parsedDescription", (String) parserFilter.format(new TemplateContext(), image.getDescription(), ImmutableMap.<String, Object> builder().put("sone", image.getSone()).build()));
        }
 
 }
index 16572cf..8a47ffe 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - EditProfileFieldAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - EditProfileFieldAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ce0fd06..071f8e9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FollowSoneAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - FollowSoneAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
@@ -45,7 +47,8 @@ public class FollowSoneAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String soneId = request.getHttpRequest().getParam("sone");
-               if (!webInterface.getCore().hasSone(soneId)) {
+               Optional<Sone> sone = webInterface.getCore().getSone(soneId);
+               if (!sone.isPresent()) {
                        return createErrorJsonObject("invalid-sone-id");
                }
                Sone currentSone = getCurrentSone(request.getToadletContext());
@@ -53,7 +56,7 @@ public class FollowSoneAjaxPage extends JsonPage {
                        return createErrorJsonObject("auth-required");
                }
                webInterface.getCore().followSone(currentSone, soneId);
-               webInterface.getCore().markSoneKnown(webInterface.getCore().getSone(soneId));
+               webInterface.getCore().markSoneKnown(sone.get());
                return createSuccessJsonObject();
        }
 
index 293b4b0..1a5286f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetLikesAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - GetLikesAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -31,6 +31,8 @@ import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonArray;
 import net.pterodactylus.util.json.JsonObject;
 
+import com.google.common.base.Optional;
+
 /**
  * AJAX page that retrieves the number of “likes” a {@link Post} has.
  *
@@ -63,12 +65,18 @@ public class GetLikesAjaxPage extends JsonPage {
                        return createErrorJsonObject("invalid-" + type + "-id");
                }
                if ("post".equals(type)) {
-                       Post post = webInterface.getCore().getPost(id);
-                       Set<Sone> sones = webInterface.getCore().getLikes(post);
+                       Optional<Post> post = webInterface.getCore().getPost(id);
+                       if (!post.isPresent()) {
+                               return createErrorJsonObject("invalid-post-id");
+                       }
+                       Set<Sone> sones = webInterface.getCore().getLikes(post.get());
                        return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
                } else if ("reply".equals(type)) {
-                       PostReply reply = webInterface.getCore().getReply(id);
-                       Set<Sone> sones = webInterface.getCore().getLikes(reply);
+                       Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
+                       if (!reply.isPresent()) {
+                               return createErrorJsonObject("invalid-reply-id");
+                       }
+                       Set<Sone> sones = webInterface.getCore().getLikes(reply.get());
                        return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
                }
                return createErrorJsonObject("invalid-type");
index 98a204b..6573dda 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetNotificationsAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - GetNotificationsAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -32,7 +32,6 @@ import net.pterodactylus.util.json.JsonArray;
 import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.notify.Notification;
 import net.pterodactylus.util.notify.TemplateNotification;
-import net.pterodactylus.util.object.HashCode;
 import net.pterodactylus.util.template.TemplateContext;
 
 /**
@@ -81,12 +80,11 @@ public class GetNotificationsAjaxPage extends JsonPage {
                Collection<Notification> notifications = webInterface.getNotifications().getNotifications();
                List<Notification> filteredNotifications = ListNotificationFilters.filterNotifications(notifications, currentSone);
                Collections.sort(filteredNotifications, Notification.CREATED_TIME_SORTER);
-               int notificationHash = HashCode.hashCode(filteredNotifications);
                JsonArray jsonNotifications = new JsonArray();
                for (Notification notification : filteredNotifications) {
                        jsonNotifications.add(createJsonNotification(request, notification));
                }
-               return createSuccessJsonObject().put("notificationHash", notificationHash).put("notifications", jsonNotifications).put("options", createJsonOptions(currentSone));
+               return createSuccessJsonObject().put("notificationHash", filteredNotifications.hashCode()).put("notifications", jsonNotifications).put("options", createJsonOptions(currentSone));
        }
 
        //
index 3d4633c..8eeedc9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetPostAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - GetPostAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.web.ajax;
 
 import java.io.StringWriter;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -59,11 +61,11 @@ public class GetPostAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String postId = request.getHttpRequest().getParam("post");
-               Post post = webInterface.getCore().getPost(postId, false);
-               if (post == null) {
+               Optional<Post> post = webInterface.getCore().getPost(postId);
+               if (!post.isPresent()) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               return createSuccessJsonObject().put("post", createJsonPost(request, post, getCurrentSone(request.getToadletContext())));
+               return createSuccessJsonObject().put("post", createJsonPost(request, post.get(), getCurrentSone(request.getToadletContext())));
        }
 
        /**
@@ -94,7 +96,7 @@ public class GetPostAjaxPage extends JsonPage {
                JsonObject jsonPost = new JsonObject();
                jsonPost.put("id", post.getId());
                jsonPost.put("sone", post.getSone().getId());
-               jsonPost.put("recipient", (post.getRecipient() == null) ? null : post.getRecipient().getId());
+               jsonPost.put("recipient", post.getRecipientId().orNull());
                jsonPost.put("time", post.getTime());
                StringWriter stringWriter = new StringWriter();
                TemplateContext templateContext = webInterface.getTemplateContextFactory().createTemplateContext();
index 38bf090..b617608 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetReplyAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - GetReplyAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,8 @@ package net.pterodactylus.sone.web.ajax;
 
 import java.io.StringWriter;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -62,11 +64,11 @@ public class GetReplyAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String replyId = request.getHttpRequest().getParam("reply");
-               PostReply reply = webInterface.getCore().getReply(replyId);
-               if ((reply == null) || (reply.getSone() == null)) {
+               Optional<PostReply> reply = webInterface.getCore().getPostReply(replyId);
+               if (!reply.isPresent()) {
                        return createErrorJsonObject("invalid-reply-id");
                }
-               return createSuccessJsonObject().put("reply", createJsonReply(request, reply, getCurrentSone(request.getToadletContext())));
+               return createSuccessJsonObject().put("reply", createJsonReply(request, reply.get(), getCurrentSone(request.getToadletContext())));
        }
 
        /**
@@ -95,7 +97,7 @@ public class GetReplyAjaxPage extends JsonPage {
        private JsonObject createJsonReply(FreenetRequest request, PostReply reply, Sone currentSone) {
                JsonObject jsonReply = new JsonObject();
                jsonReply.put("id", reply.getId());
-               jsonReply.put("postId", reply.getPost().getId());
+               jsonReply.put("postId", reply.getPostId());
                jsonReply.put("soneId", reply.getSone().getId());
                jsonReply.put("time", reply.getTime());
                StringWriter stringWriter = new StringWriter();
index 4c61f89..48b25d6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetStatusAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - GetStatusAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.web.ajax;
 
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
@@ -32,12 +33,12 @@ import net.pterodactylus.sone.notify.ListNotificationFilters;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
-import net.pterodactylus.util.collection.filter.Filter;
-import net.pterodactylus.util.collection.filter.Filters;
 import net.pterodactylus.util.json.JsonArray;
 import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.notify.Notification;
-import net.pterodactylus.util.object.HashCode;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 
 /**
  * The “get status” AJAX handler returns all information that is necessary to
@@ -73,7 +74,7 @@ public class GetStatusAjaxPage extends JsonPage {
                        String[] soneIds = loadSoneIds.split(",");
                        for (String soneId : soneIds) {
                                /* just add it, we skip null further down. */
-                               sones.add(webInterface.getCore().getSone(soneId, false));
+                               sones.add(webInterface.getCore().getSone(soneId).orNull());
                        }
                }
                JsonArray jsonSones = new JsonArray();
@@ -87,14 +88,13 @@ public class GetStatusAjaxPage extends JsonPage {
                /* load notifications. */
                List<Notification> notifications = ListNotificationFilters.filterNotifications(webInterface.getNotifications().getNotifications(), currentSone);
                Collections.sort(notifications, Notification.CREATED_TIME_SORTER);
-               int notificationHash = HashCode.hashCode(notifications);
                /* load new posts. */
-               Set<Post> newPosts = webInterface.getNewPosts();
+               Collection<Post> newPosts = webInterface.getNewPosts();
                if (currentSone != null) {
-                       newPosts = Filters.filteredSet(newPosts, new Filter<Post>() {
+                       newPosts = Collections2.filter(newPosts, new Predicate<Post>() {
 
                                @Override
-                               public boolean filterObject(Post post) {
+                               public boolean apply(Post post) {
                                        return ListNotificationFilters.isPostVisible(currentSone, post);
                                }
 
@@ -105,40 +105,34 @@ public class GetStatusAjaxPage extends JsonPage {
                        JsonObject jsonPost = new JsonObject();
                        jsonPost.put("id", post.getId());
                        jsonPost.put("sone", post.getSone().getId());
-                       jsonPost.put("recipient", (post.getRecipient() != null) ? post.getRecipient().getId() : null);
+                       jsonPost.put("recipient", post.getRecipientId().orNull());
                        jsonPost.put("time", post.getTime());
                        jsonPosts.add(jsonPost);
                }
                /* load new replies. */
-               Set<PostReply> newReplies = webInterface.getNewReplies();
+               Collection<PostReply> newReplies = webInterface.getNewReplies();
                if (currentSone != null) {
-                       newReplies = Filters.filteredSet(newReplies, new Filter<PostReply>() {
+                       newReplies = Collections2.filter(newReplies, new Predicate<PostReply>() {
 
                                @Override
-                               public boolean filterObject(PostReply reply) {
+                               public boolean apply(PostReply reply) {
                                        return ListNotificationFilters.isReplyVisible(currentSone, reply);
                                }
 
                        });
                }
                /* remove replies to unknown posts. */
-               newReplies = Filters.filteredSet(newReplies, new Filter<PostReply>() {
-
-                       @Override
-                       public boolean filterObject(PostReply reply) {
-                               return (reply.getPost() != null) && (reply.getPost().getSone() != null);
-                       }
-               });
+               newReplies = Collections2.filter(newReplies, PostReply.HAS_POST_FILTER);
                JsonArray jsonReplies = new JsonArray();
                for (PostReply reply : newReplies) {
                        JsonObject jsonReply = new JsonObject();
                        jsonReply.put("id", reply.getId());
                        jsonReply.put("sone", reply.getSone().getId());
-                       jsonReply.put("post", reply.getPost().getId());
-                       jsonReply.put("postSone", reply.getPost().getSone().getId());
+                       jsonReply.put("post", reply.getPostId());
+                       jsonReply.put("postSone", reply.getPost().get().getSone().getId());
                        jsonReplies.add(jsonReply);
                }
-               return createSuccessJsonObject().put("loggedIn", currentSone != null).put("options", createJsonOptions(currentSone)).put("sones", jsonSones).put("notificationHash", notificationHash).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
+               return createSuccessJsonObject().put("loggedIn", currentSone != null).put("options", createJsonOptions(currentSone)).put("sones", jsonSones).put("notificationHash", notifications.hashCode()).put("newPosts", jsonPosts).put("newReplies", jsonReplies);
        }
 
        /**
index f6bc05d..b0d9dac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetTimesAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - GetTimesAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify it under
  * the terms of the GNU General Public License as published by the Free Software
@@ -20,13 +20,15 @@ package net.pterodactylus.sone.web.ajax;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.concurrent.TimeUnit;
 
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.PostReply;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonObject;
-import net.pterodactylus.util.number.Digits;
+
+import com.google.common.base.Optional;
 
 /**
  * Ajax page that returns a formatted, relative timestamp for replies or posts.
@@ -58,16 +60,16 @@ public class GetTimesAjaxPage extends JsonPage {
                if (allIds.length() > 0) {
                        String[] ids = allIds.split(",");
                        for (String id : ids) {
-                               Post post = webInterface.getCore().getPost(id, false);
-                               if (post == null) {
+                               Optional<Post> post = webInterface.getCore().getPost(id);
+                               if (!post.isPresent()) {
                                        continue;
                                }
                                JsonObject postTime = new JsonObject();
-                               Time time = getTime(post.getTime());
+                               Time time = getTime(post.get().getTime());
                                postTime.put("timeText", time.getText());
-                               postTime.put("refreshTime", time.getRefresh() / Time.SECOND);
+                               postTime.put("refreshTime", TimeUnit.MILLISECONDS.toSeconds(time.getRefresh()));
                                synchronized (dateFormat) {
-                                       postTime.put("tooltip", dateFormat.format(new Date(post.getTime())));
+                                       postTime.put("tooltip", dateFormat.format(new Date(post.get().getTime())));
                                }
                                postTimes.put(id, postTime);
                        }
@@ -77,16 +79,16 @@ public class GetTimesAjaxPage extends JsonPage {
                if (allIds.length() > 0) {
                        String[] ids = allIds.split(",");
                        for (String id : ids) {
-                               PostReply reply = webInterface.getCore().getReply(id, false);
-                               if (reply == null) {
+                               Optional<PostReply> reply = webInterface.getCore().getPostReply(id);
+                               if (!reply.isPresent()) {
                                        continue;
                                }
                                JsonObject replyTime = new JsonObject();
-                               Time time = getTime(reply.getTime());
+                               Time time = getTime(reply.get().getTime());
                                replyTime.put("timeText", time.getText());
-                               replyTime.put("refreshTime", time.getRefresh() / Time.SECOND);
+                               replyTime.put("refreshTime", TimeUnit.MILLISECONDS.toSeconds(time.getRefresh()));
                                synchronized (dateFormat) {
-                                       replyTime.put("tooltip", dateFormat.format(new Date(reply.getTime())));
+                                       replyTime.put("tooltip", dateFormat.format(new Date(reply.get().getTime())));
                                }
                                replyTimes.put(id, replyTime);
                        }
@@ -140,59 +142,59 @@ public class GetTimesAjaxPage extends JsonPage {
         */
        public static Time getTime(WebInterface webInterface, long time) {
                if (time == 0) {
-                       return new Time(webInterface.getL10n().getString("View.Sone.Text.UnknownDate"), 12 * Time.HOUR);
+                       return new Time(webInterface.getL10n().getString("View.Sone.Text.UnknownDate"), TimeUnit.HOURS.toMillis(12));
                }
                long age = System.currentTimeMillis() - time;
                String text;
                long refresh;
                if (age < 0) {
                        text = webInterface.getL10n().getDefaultString("View.Time.InTheFuture");
-                       refresh = 5 * Time.MINUTE;
-               } else if (age < 20 * Time.SECOND) {
+                       refresh = TimeUnit.MINUTES.toMillis(5);
+               } else if (age < TimeUnit.SECONDS.toMillis(20)) {
                        text = webInterface.getL10n().getDefaultString("View.Time.AFewSecondsAgo");
-                       refresh = 10 * Time.SECOND;
-               } else if (age < 45 * Time.SECOND) {
+                       refresh = TimeUnit.SECONDS.toMillis(10);
+               } else if (age < TimeUnit.SECONDS.toMillis(45)) {
                        text = webInterface.getL10n().getString("View.Time.HalfAMinuteAgo");
-                       refresh = 20 * Time.SECOND;
-               } else if (age < 90 * Time.SECOND) {
+                       refresh = TimeUnit.SECONDS.toMillis(20);
+               } else if (age < TimeUnit.SECONDS.toMillis(90)) {
                        text = webInterface.getL10n().getString("View.Time.AMinuteAgo");
-                       refresh = Time.MINUTE;
-               } else if (age < 30 * Time.MINUTE) {
-                       text = webInterface.getL10n().getString("View.Time.XMinutesAgo", "min", String.valueOf((int) (Digits.round(age, Time.MINUTE) / Time.MINUTE)));
-                       refresh = 1 * Time.MINUTE;
-               } else if (age < 45 * Time.MINUTE) {
+                       refresh = TimeUnit.MINUTES.toMillis(1);
+               } else if (age < TimeUnit.MINUTES.toMillis(30)) {
+                       text = webInterface.getL10n().getString("View.Time.XMinutesAgo", "min", String.valueOf(TimeUnit.MILLISECONDS.toMinutes(age + TimeUnit.SECONDS.toMillis(30))));
+                       refresh = TimeUnit.MINUTES.toMillis(1);
+               } else if (age < TimeUnit.MINUTES.toMillis(45)) {
                        text = webInterface.getL10n().getString("View.Time.HalfAnHourAgo");
-                       refresh = 10 * Time.MINUTE;
-               } else if (age < 90 * Time.MINUTE) {
+                       refresh = TimeUnit.MINUTES.toMillis(10);
+               } else if (age < TimeUnit.MINUTES.toMillis(90)) {
                        text = webInterface.getL10n().getString("View.Time.AnHourAgo");
-                       refresh = Time.HOUR;
-               } else if (age < 21 * Time.HOUR) {
-                       text = webInterface.getL10n().getString("View.Time.XHoursAgo", "hour", String.valueOf((int) (Digits.round(age, Time.HOUR) / Time.HOUR)));
-                       refresh = Time.HOUR;
-               } else if (age < 42 * Time.HOUR) {
+                       refresh = TimeUnit.HOURS.toMillis(1);
+               } else if (age < TimeUnit.HOURS.toMillis(21)) {
+                       text = webInterface.getL10n().getString("View.Time.XHoursAgo", "hour", String.valueOf(TimeUnit.MILLISECONDS.toHours(age + TimeUnit.MINUTES.toMillis(30))));
+                       refresh = TimeUnit.HOURS.toMillis(1);
+               } else if (age < TimeUnit.HOURS.toMillis(42)) {
                        text = webInterface.getL10n().getString("View.Time.ADayAgo");
-                       refresh = Time.DAY;
-               } else if (age < 6 * Time.DAY) {
-                       text = webInterface.getL10n().getString("View.Time.XDaysAgo", "day", String.valueOf((int) (Digits.round(age, Time.DAY) / Time.DAY)));
-                       refresh = Time.DAY;
-               } else if (age < 11 * Time.DAY) {
+                       refresh = TimeUnit.DAYS.toMillis(1);
+               } else if (age < TimeUnit.DAYS.toMillis(6)) {
+                       text = webInterface.getL10n().getString("View.Time.XDaysAgo", "day", String.valueOf(TimeUnit.MILLISECONDS.toDays(age + TimeUnit.HOURS.toMillis(12))));
+                       refresh = TimeUnit.DAYS.toMillis(1);
+               } else if (age < TimeUnit.DAYS.toMillis(11)) {
                        text = webInterface.getL10n().getString("View.Time.AWeekAgo");
-                       refresh = Time.DAY;
-               } else if (age < 4 * Time.WEEK) {
-                       text = webInterface.getL10n().getString("View.Time.XWeeksAgo", "week", String.valueOf((int) (Digits.round(age, Time.WEEK) / Time.WEEK)));
-                       refresh = Time.DAY;
-               } else if (age < 6 * Time.WEEK) {
+                       refresh = TimeUnit.DAYS.toMillis(1);
+               } else if (age < TimeUnit.DAYS.toMillis(28)) {
+                       text = webInterface.getL10n().getString("View.Time.XWeeksAgo", "week", String.valueOf((TimeUnit.MILLISECONDS.toHours(age) + 84) / (7 * 24)));
+                       refresh = TimeUnit.DAYS.toMillis(1);
+               } else if (age < TimeUnit.DAYS.toMillis(42)) {
                        text = webInterface.getL10n().getString("View.Time.AMonthAgo");
-                       refresh = Time.DAY;
-               } else if (age < 11 * Time.MONTH) {
-                       text = webInterface.getL10n().getString("View.Time.XMonthsAgo", "month", String.valueOf((int) (Digits.round(age, Time.MONTH) / Time.MONTH)));
-                       refresh = Time.DAY;
-               } else if (age < 18 * Time.MONTH) {
+                       refresh = TimeUnit.DAYS.toMillis(1);
+               } else if (age < TimeUnit.DAYS.toMillis(330)) {
+                       text = webInterface.getL10n().getString("View.Time.XMonthsAgo", "month", String.valueOf((TimeUnit.MILLISECONDS.toDays(age) + 15) / 30));
+                       refresh = TimeUnit.DAYS.toMillis(1);
+               } else if (age < TimeUnit.DAYS.toMillis(540)) {
                        text = webInterface.getL10n().getString("View.Time.AYearAgo");
-                       refresh = Time.WEEK;
+                       refresh = TimeUnit.DAYS.toMillis(7);
                } else {
-                       text = webInterface.getL10n().getString("View.Time.XYearsAgo", "year", String.valueOf((int) (Digits.round(age, Time.YEAR) / Time.YEAR)));
-                       refresh = Time.WEEK;
+                       text = webInterface.getL10n().getString("View.Time.XYearsAgo", "year", String.valueOf((long) ((TimeUnit.MILLISECONDS.toDays(age) + 182.64) / 365.28)));
+                       refresh = TimeUnit.DAYS.toMillis(7);
                }
                return new Time(text, refresh);
        }
@@ -204,27 +206,6 @@ public class GetTimesAjaxPage extends JsonPage {
         */
        public static class Time {
 
-               /** Number of milliseconds in a second. */
-               private static final long SECOND = 1000;
-
-               /** Number of milliseconds in a minute. */
-               private static final long MINUTE = 60 * SECOND;
-
-               /** Number of milliseconds in an hour. */
-               private static final long HOUR = 60 * MINUTE;
-
-               /** Number of milliseconds in a day. */
-               private static final long DAY = 24 * HOUR;
-
-               /** Number of milliseconds in a week. */
-               private static final long WEEK = 7 * DAY;
-
-               /** Number of milliseconds in a 30-day month. */
-               private static final long MONTH = 30 * DAY;
-
-               /** Number of milliseconds in a year. */
-               private static final long YEAR = 365 * DAY;
-
                /** The formatted time. */
                private final String text;
 
index 343359d..0ecfaea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - GetTranslationPage.java - Copyright © 2010–2012 David Roden
+ * Sone - GetTranslationPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 22eeec8..994f90c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - JsonPage.java - Copyright © 2010–2012 David Roden
+ * Sone - JsonPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 
 package net.pterodactylus.sone.web.ajax;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
 import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetPage;
 import net.pterodactylus.sone.web.page.FreenetRequest;
+import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.json.JsonObject;
 import net.pterodactylus.util.json.JsonUtils;
+import net.pterodactylus.util.logging.Logging;
 import net.pterodactylus.util.web.Page;
 import net.pterodactylus.util.web.Response;
 import freenet.clients.http.SessionManager.Session;
@@ -39,6 +46,9 @@ import freenet.clients.http.ToadletContext;
  */
 public abstract class JsonPage implements FreenetPage {
 
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(JsonPage.class);
+
        /** The path of the page. */
        private final String path;
 
@@ -218,8 +228,13 @@ public abstract class JsonPage implements FreenetPage {
                                return response.setStatusCode(403).setStatusText("Forbidden").setContentType("application/json").write(JsonUtils.format(new JsonObject().put("success", false).put("error", "auth-required")));
                        }
                }
-               JsonObject jsonObject = createJsonObject(request);
-               return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
+               try {
+                       JsonObject jsonObject = createJsonObject(request);
+                       return response.setStatusCode(200).setStatusText("OK").setContentType("application/json").write(JsonUtils.format(jsonObject));
+               } catch (Exception e1) {
+                       logger.log(Level.WARNING, "Error executing JSON page!", e1);
+                       return response.setStatusCode(500).setStatusText(e1.getMessage()).setContentType("text/plain").write(dumpStackTrace(e1));
+               }
        }
 
        /**
@@ -230,4 +245,36 @@ public abstract class JsonPage implements FreenetPage {
                return false;
        }
 
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Returns a byte array containing the stack trace of the given throwable.
+        *
+        * @param t
+        *            The throwable whose stack trace to dump into an array
+        * @return The array with the stack trace, or an empty array if the stack
+        *         trace could not be dumped
+        */
+       private static byte[] dumpStackTrace(Throwable t) {
+               ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+               OutputStreamWriter writer = null;
+               PrintWriter printWriter = null;
+               try {
+                       writer = new OutputStreamWriter(byteArrayOutputStream, "uTF-8");
+                       printWriter = new PrintWriter(writer);
+                       t.printStackTrace(printWriter);
+                       byteArrayOutputStream.flush();
+                       return byteArrayOutputStream.toByteArray();
+               } catch (IOException ioe1) {
+                       /* quite not possible. */
+                       return new byte[0];
+               } finally {
+                       Closer.close(printWriter);
+                       Closer.close(writer);
+                       Closer.close(byteArrayOutputStream);
+               }
+       }
+
 }
index 3f6f8db..6787711 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LikeAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - LikeAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 131d3e1..af74426 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - LockSoneAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - LockSoneAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 370bacd..6610971 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MarkAsKnownAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - MarkAsKnownAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,8 @@ import net.pterodactylus.sone.web.WebInterface;
 import net.pterodactylus.sone.web.page.FreenetRequest;
 import net.pterodactylus.util.json.JsonObject;
 
+import com.google.common.base.Optional;
+
 /**
  * AJAX page that lets the user mark a number of {@link Sone}s, {@link Post}s,
  * or {@link Reply}s as known.
@@ -57,23 +59,23 @@ public class MarkAsKnownAjaxPage extends JsonPage {
                Core core = webInterface.getCore();
                for (String id : ids) {
                        if (type.equals("post")) {
-                               Post post = core.getPost(id, false);
-                               if (post == null) {
+                               Optional<Post> post = core.getPost(id);
+                               if (!post.isPresent()) {
                                        continue;
                                }
-                               core.markPostKnown(post);
+                               core.markPostKnown(post.get());
                        } else if (type.equals("reply")) {
-                               PostReply reply = core.getReply(id, false);
-                               if (reply == null) {
+                               Optional<PostReply> reply = core.getPostReply(id);
+                               if (!reply.isPresent()) {
                                        continue;
                                }
-                               core.markReplyKnown(reply);
+                               core.markReplyKnown(reply.get());
                        } else if (type.equals("sone")) {
-                               Sone sone = core.getSone(id, false);
-                               if (sone == null) {
+                               Optional<Sone> sone = core.getSone(id);
+                               if (!sone.isPresent()) {
                                        continue;
                                }
-                               core.markSoneKnown(sone);
+                               core.markSoneKnown(sone.get());
                        }
                }
                return createSuccessJsonObject();
index 02029ef..7721aa3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - MoveProfileFieldAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - MoveProfileFieldAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 5b27d1b..d03e030 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - TrustAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - TrustAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -51,11 +53,11 @@ public class TrustAjaxPage extends JsonPage {
                        return createErrorJsonObject("auth-required");
                }
                String soneId = request.getHttpRequest().getParam("sone");
-               Sone sone = webInterface.getCore().getSone(soneId, false);
-               if (sone == null) {
+               Optional<Sone> sone = webInterface.getCore().getSone(soneId);
+               if (!sone.isPresent()) {
                        return createErrorJsonObject("invalid-sone-id");
                }
-               webInterface.getCore().trustSone(currentSone, sone);
+               webInterface.getCore().trustSone(currentSone, sone.get());
                return createSuccessJsonObject().put("trustValue", webInterface.getCore().getPreferences().getPositiveTrust());
        }
 
index 6a1154a..8d209af 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnbookmarkAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - UnbookmarkAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 6e29cbc..3d984b9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnfollowSoneAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - UnfollowSoneAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -45,7 +45,7 @@ public class UnfollowSoneAjaxPage extends JsonPage {
        @Override
        protected JsonObject createJsonObject(FreenetRequest request) {
                String soneId = request.getHttpRequest().getParam("sone");
-               if (!webInterface.getCore().hasSone(soneId)) {
+               if (!webInterface.getCore().getSone(soneId).isPresent()) {
                        return createErrorJsonObject("invalid-sone-id");
                }
                Sone currentSone = getCurrentSone(request.getToadletContext());
index f961bd5..0633fda 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlikeAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - UnlikeAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 14cb10d..32bb93c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UnlockSoneAjaxPage.java - Copyright © 2010–2012 David Roden
+ * Sone - UnlockSoneAjaxPage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 916b6e9..65d45c9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - UntrustAjaxPage.java - Copyright © 2011–2012 David Roden
+ * Sone - UntrustAjaxPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -17,6 +17,8 @@
 
 package net.pterodactylus.sone.web.ajax;
 
+import com.google.common.base.Optional;
+
 import net.pterodactylus.sone.core.Core;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.sone.web.WebInterface;
@@ -51,11 +53,11 @@ public class UntrustAjaxPage extends JsonPage {
                        return createErrorJsonObject("auth-required");
                }
                String soneId = request.getHttpRequest().getParam("sone");
-               Sone sone = webInterface.getCore().getSone(soneId, false);
-               if (sone == null) {
+               Optional<Sone> sone = webInterface.getCore().getSone(soneId);
+               if (!sone.isPresent()) {
                        return createErrorJsonObject("invalid-sone-id");
                }
-               webInterface.getCore().untrustSone(currentSone, sone);
+               webInterface.getCore().untrustSone(currentSone, sone.get());
                return createSuccessJsonObject().put("trustValue", (String) null);
        }
 
index e68444c..a67fd04 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetPage.java - Copyright © 2011–2012 David Roden
+ * Sone - FreenetPage.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 783c8eb..858a219 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetRequest.java - Copyright © 2011–2012 David Roden
+ * Sone - FreenetRequest.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 67884bd..625978b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - FreenetTemplatePage.java - Copyright © 2010–2012 David Roden
+ * Sone - FreenetTemplatePage.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index 1d2a2db..2c6706f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PageToadlet.java - Copyright © 2010–2012 David Roden
+ * Sone - PageToadlet.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
index ef5de69..a517f0a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - PageToadletFactory.java - Copyright © 2010–2012 David Roden
+ * Sone - PageToadletFactory.java - Copyright © 2010–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
diff --git a/src/main/javadoc/overview.html b/src/main/javadoc/overview.html
new file mode 100644 (file)
index 0000000..5c6fb9f
--- /dev/null
@@ -0,0 +1,8 @@
+<body>
+       <h1>Sone API Documentation</h1>
+       <p>This is the API documentation of Sone.</p>
+       <p>I recognize that this has probably little value for the average
+               user; however, should you ever plan to fiddle around with Sone, extend
+               it, change some functionality, fix bugs, or do whatever else you have
+               in mind, this documentation might come in handy.</p>
+</body>
\ No newline at end of file
index e253ad5..d0ec575 100644 (file)
@@ -117,6 +117,8 @@ Page.KnownSones.Filter.Followed=Nur gefolgte Sones anzeigen
 Page.KnownSones.Filter.NotFollowed=Gefolgte Sones nicht anzeigen
 Page.KnownSones.Filter.New=Nur neue Sones anzeigen
 Page.KnownSones.Filter.NotNew=Neue Sones nicht anzeigen
+Page.KnownSones.Filter.Own=Nur eigene Sones anzeigen
+Page.KnownSones.Filter.NotOwn=Nur fremde Sones anzeigen
 Page.KnownSones.Button.Apply=Anwenden
 Page.KnownSones.Button.FollowAllSones=Allen Sones auf dieser Seite folgen
 Page.KnownSones.Button.UnfollowAllSones=Alle Sones auf dieser Seite entfolgen
index 5c4367a..6bab23e 100644 (file)
@@ -117,6 +117,8 @@ Page.KnownSones.Filter.Followed=Show only followed Sones
 Page.KnownSones.Filter.NotFollowed=Hide followed Sones
 Page.KnownSones.Filter.New=Show only new Sones
 Page.KnownSones.Filter.NotNew=Hide new Sones
+Page.KnownSones.Filter.Own=Show only local Sones
+Page.KnownSones.Filter.NotOwn=Show only remote Sones
 Page.KnownSones.Button.Apply=Apply
 Page.KnownSones.Button.FollowAllSones=Follow all Sones on this page
 Page.KnownSones.Button.UnfollowAllSones=Unfollow all Sones on this page
index ac36e74..397032f 100644 (file)
@@ -4,9 +4,9 @@ Navigation.Menu.Sone.Item.Login.Name=Connexion
 Navigation.Menu.Sone.Item.Login.Tooltip=Se connecter à son Sone
 Navigation.Menu.Sone.Item.Index.Name=Votre Sone
 Navigation.Menu.Sone.Item.Index.Tooltip=Afficher votre Sone
-Navigation.Menu.Sone.Item.New.Name=Nouveaux messages ety réponses
+Navigation.Menu.Sone.Item.New.Name=Nouveaux messages et réponses
 Navigation.Menu.Sone.Item.New.Tooltip=Voir les nouveaux messages et réponses
-Navigation.Menu.Sone.Item.CreateSone.Name=Créer Sone
+Navigation.Menu.Sone.Item.CreateSone.Name=Créer un Sone
 Navigation.Menu.Sone.Item.CreateSone.Tooltip=Créer un nouveau Sone
 Navigation.Menu.Sone.Item.KnownSones.Name=Sones connus
 Navigation.Menu.Sone.Item.KnownSones.Tooltip=Montre tous les Sones connus
@@ -117,6 +117,8 @@ Page.KnownSones.Filter.Followed=Montrer seulement les Sones suivis
 Page.KnownSones.Filter.NotFollowed=Cacher les Sones suivis
 Page.KnownSones.Filter.New=Montrer seulement les nouveaux Sones
 Page.KnownSones.Filter.NotNew=Cacher les nouveaux Sones
+Page.KnownSones.Filter.Own=Show only local Sones
+Page.KnownSones.Filter.NotOwn=Show only remote Sones
 Page.KnownSones.Button.Apply=Appliquer
 Page.KnownSones.Button.FollowAllSones=Suivre tous les Sones de cette page
 Page.KnownSones.Button.UnfollowAllSones=Ne plus suivre tous les Sones de cette page
@@ -376,18 +378,18 @@ View.Time.InTheFuture=dans le futur
 View.Time.AFewSecondsAgo=au cours des dernières secondes passées
 View.Time.HalfAMinuteAgo=au cours des 30 dernières secondes
 View.Time.AMinuteAgo=au cours de la dernière minute
-View.Time.XMinutesAgo=${min} il y a quelques minutes
+View.Time.XMinutesAgo=il y a environs {min} minutes
 View.Time.HalfAnHourAgo=au cours de la dernière demi heure
 View.Time.AnHourAgo=il y a environ une heure
-View.Time.XHoursAgo=${heure} au cours des dernières heures
+View.Time.XHoursAgo=Il y a environ ${hour} heures
 View.Time.ADayAgo=il y a environ un jour
-View.Time.XDaysAgo=${jour} il y a quelques jours
+View.Time.XDaysAgo=il y a plus ou moins ${day} jours
 View.Time.AWeekAgo=il y a environ une semaine
-View.Time.XWeeksAgo=${semaine} au cours des dernières semaines
+View.Time.XWeeksAgo=au cours des dernières ${week}semaines
 View.Time.AMonthAgo=au cours du dernier mois
-View.Time.XMonthsAgo=${mois} au cours des derniers mois
+View.Time.XMonthsAgo=au cours des derniers ${month} mois
 View.Time.AYearAgo=au cours de la dernière année
-View.Time.XYearsAgo=${année} au cours des dernières années
+View.Time.XYearsAgo=au cours des dernières ${year} années
 
 WebInterface.DefaultText.StatusUpdate=Exprimez-vous
 WebInterface.DefaultText.Message=Écrire un message...
@@ -450,3 +452,4 @@ Notification.ImageInsertFailed.Text=Les images suivantes ne peuvent être insér
 Notification.Mention.ShortText=Vous avez été mentionné.
 Notification.Mention.Text=Vous avez été mentionné dans les messages suivants:
 Notification.SoneInsert.Duration={0,number} {0,choice,0#seconds|1#second|1<seconds}
+# 120-121
index b31ff61..cf6e026 100644 (file)
@@ -117,6 +117,8 @@ Page.KnownSones.Filter.Followed=フォローしているSoneのみ表示
 Page.KnownSones.Filter.NotFollowed=フォローしているSoneを隠す
 Page.KnownSones.Filter.New=新しいSoneのみ表示
 Page.KnownSones.Filter.NotNew=新しいSoneを隠す
+Page.KnownSones.Filter.Own=Show only local Sones
+Page.KnownSones.Filter.NotOwn=Show only remote Sones
 Page.KnownSones.Button.Apply=実行
 Page.KnownSones.Button.FollowAllSones=このページ内の全てのSoneをフォロー
 Page.KnownSones.Button.UnfollowAllSones=このページ内の全てのフォローを解除
@@ -450,3 +452,4 @@ Notification.ImageInsertFailed.Text=次の画像のインサートに失敗し
 Notification.Mention.ShortText=誰かにメンションされました。
 Notification.Mention.Text=次の投稿でメンションされています:
 Notification.SoneInsert.Duration={0,number} {0,choice,0#秒|1#秒|1<秒}
+# 120-121
index 3a7175d..c61d7a5 100644 (file)
@@ -117,6 +117,8 @@ Page.KnownSones.Filter.Followed=Vis bare soner som følges
 Page.KnownSones.Filter.NotFollowed=Gjem Soner som følges
 Page.KnownSones.Filter.New=Vis bare nye Soner
 Page.KnownSones.Filter.NotNew=Gjem nye Soner
+Page.KnownSones.Filter.Own=Show only local Sones
+Page.KnownSones.Filter.NotOwn=Show only remote Sones
 Page.KnownSones.Button.Apply=Tilføy
 Page.KnownSones.Button.FollowAllSones=Følg alle Soner på denne siden
 Page.KnownSones.Button.UnfollowAllSones=Slutt å følge alle Soner på denne siden
@@ -450,3 +452,4 @@ Notification.ImageInsertFailed.Text=De følgende bildene kunne ikke bli innsatt:
 Notification.Mention.ShortText=Du har blitt nevnt:
 Notification.Mention.Text=Du har blitt nevnt i følgende innlegg:
 Notification.SoneInsert.Duration={0,number} {0,choice,0#sekund|1#sekund|1<sekunder}
+# 120-121
index a19ea96..caac397 100644 (file)
@@ -117,6 +117,8 @@ Page.KnownSones.Filter.Followed=Pokazuj tylko śledzone Sone
 Page.KnownSones.Filter.NotFollowed=Ukryj śledzone Sone
 Page.KnownSones.Filter.New=Pokazuj tylko nowe Sone
 Page.KnownSones.Filter.NotNew=Ukryj nowe Sone
+Page.KnownSones.Filter.Own=Show only local Sones
+Page.KnownSones.Filter.NotOwn=Show only remote Sones
 Page.KnownSones.Button.Apply=Zastosuj
 Page.KnownSones.Button.FollowAllSones=Śledź wszystkie Sone na tej stronie
 Page.KnownSones.Button.UnfollowAllSones=Przestań śledzić wszystkie Sone na tej stronie
@@ -450,3 +452,4 @@ Notification.ImageInsertFailed.Text=Nie można załadowac następujących obraz
 Notification.Mention.ShortText=Zostałeś oznaczony.
 Notification.Mention.Text=Zostałeś oznaczony w następujących postach:
 Notification.SoneInsert.Duration={0,number} {0,choice,0#seconds|1#second|1<seconds}
+# 120-121
index 8263074..b34c905 100644 (file)
@@ -117,6 +117,8 @@ Page.KnownSones.Filter.Followed=Показывать только Sone, на к
 Page.KnownSones.Filter.NotFollowed=Скрыть Sone, на которые вы подписаны
 Page.KnownSones.Filter.New=Показывать только новые Sone
 Page.KnownSones.Filter.NotNew=Скрыть новые Sone
+Page.KnownSones.Filter.Own=Show only local Sones
+Page.KnownSones.Filter.NotOwn=Show only remote Sones
 Page.KnownSones.Button.Apply=Применить
 Page.KnownSones.Button.FollowAllSones=Подписаться на все Sone на этой странице
 Page.KnownSones.Button.UnfollowAllSones=Снять подписку со всех Sone на этой странице
@@ -450,3 +452,4 @@ Notification.ImageInsertFailed.Text=Следующие изображения н
 Notification.Mention.ShortText=Вас упомянули.
 Notification.Mention.Text=Вас упомянули в следующих сообщениях:
 Notification.SoneInsert.Duration={0,number} {0,choice,0#секунд|1#секунда|2#секунды|4<секунд}
+# 120-121
index 49f372f..3339d18 100644 (file)
@@ -1529,7 +1529,7 @@ function loadNewReply(replyId, soneId, postId, postSoneId) {
  *            request
  */
 function markSoneAsKnown(soneElement, skipRequest) {
-       if ($(soneElement).is(".new")) {
+       if ($(soneElement).hasClass("new")) {
                $(soneElement).removeClass("new");
                if ((typeof skipRequest == "undefined") || !skipRequest) {
                        ajaxGet("markAsKnown.ajax", {"formPassword": getFormPassword(), "type": "sone", "id": getSoneId(soneElement)});
index f0b8b56..ddc08a9 100644 (file)
 
                <ul id="avatar-selection">
                        <li id="no-avatar">
-                               <input type="radio" name="avatar-id" value="none"<%ifnull avatar-id> checked="checked"<%/if>/>
+                               <input type="radio" name="avatarId" value="none"<%ifnull avatarId> checked="checked"<%/if>/>
                                <%= Page.EditProfile.Avatar.Delete|l10n|html>
                        </li>
                        <%foreach currentSone.allImages image>
                                <li>
-                                       <input type="radio" name="avatar-id" value="<%image.id|html>"<%if avatar-id|match key=image.id> checked="checked"<%/if>/>
+                                       <input type="radio" name="avatarId" value="<%image.id|html>"<%if avatarId|match value=image.id> checked="checked"<%/if>/>
                                        <div class="post-avatar"><% image|image-link max-width==48 max-height==48 mode==enlarge title=image.title></div>
                                </li>
                        <%/foreach>
index 7120d06..efc3d53 100644 (file)
        <div class="inner-part">
                <div<%if !post.loaded> class="hidden"<%/if>>
                        <div class="author profile-link"><a href="viewSone.html?sone=<% post.sone.id|html>"><% post.sone.niceName|html></a></div>
-                       <%ifnull !post.recipient>
+                       <%if post.recipientId.present>
                                <span class="recipient-to">→</span>
-                               <%ifnull post.recipient.identity>
-                                       <div class="recipient profile-link"><a href="viewSone.html?sone=<% post.recipient.id|html>"><%= View.Post.UnknownAuthor|l10n|html></a></div>
+                               <%if !post.recipient.present>
+                                       <div class="recipient profile-link"><a href="viewSone.html?sone=<% post.recipientId.get|html>"><%= View.Post.UnknownAuthor|l10n|html></a></div>
                                <%else>
-                                       <div class="recipient profile-link"><a href="viewSone.html?sone=<% post.recipient.id|html>"><% post.recipient.niceName|html></a></div>
+                                       <div class="recipient profile-link"><a href="viewSone.html?sone=<% post.recipientId.get|html>"><% post.recipient.get.niceName|html></a></div>
                                <%/if>
                        <%/if>
                        <% post.text|html|store key==originalText text==true>
index e615725..511cbf2 100644 (file)
@@ -31,7 +31,7 @@
                <%foreach currentSone.posts post>
                <post>
                        <id><% post.id|xml></id>
-                       <recipient><%ifnull !post.recipient><% post.recipient.id|xml><%/if></recipient>
+                       <recipient><%if post.recipientId.present><% post.recipientId.get|xml><%/if></recipient>
                        <time><% post.time></time>
                        <text><% post.text|xml></text>
                </post>
@@ -42,7 +42,7 @@
                <%foreach currentSone.replies reply>
                <reply>
                        <id><% reply.id></id>
-                       <post-id><% reply.post.id|xml></post-id>
+                       <post-id><% reply.postId|xml></post-id>
                        <time><% reply.time></time>
                        <text><% reply.text|xml></text>
                </reply>
index 4ace659..c113748 100644 (file)
@@ -48,6 +48,8 @@
                                                <%/if>
                                                <option value="new"<%if filter|match value=="new"> selected="selected"<%/if>><%= Page.KnownSones.Filter.New|l10n|html></option>
                                                <option value="not-new"<%if filter|match value=="not-new"> selected="selected"<%/if>><%= Page.KnownSones.Filter.NotNew|l10n|html></option>
+                                               <option value="own"<%if filter|match value=="own"> selected="selected"<%/if>><%= Page.KnownSones.Filter.Own|l10n|html></option>
+                                               <option value="not-own"<%if filter|match value=="not-own"> selected="selected"<%/if>><%= Page.KnownSones.Filter.NotOwn|l10n|html></option>
                                        </select>
                                </div>
                        <%/if>
diff --git a/src/test/java/net/pterodactylus/sone/fcp/LockSoneCommandTest.java b/src/test/java/net/pterodactylus/sone/fcp/LockSoneCommandTest.java
new file mode 100644 (file)
index 0000000..ae1993a
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Sone - LockSoneCommandTest.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.fcp;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
+import net.pterodactylus.sone.freenet.fcp.Command.Response;
+import net.pterodactylus.sone.freenet.fcp.FcpException;
+
+import freenet.support.SimpleFieldSet;
+
+import com.google.common.base.Optional;
+import org.junit.Test;
+
+/**
+ * Tests for {@link UnlockSoneCommand}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class LockSoneCommandTest {
+
+       @Test
+       public void testLockingALocalSone() throws FcpException {
+               Sone localSone = mock(Sone.class);
+               when(localSone.getId()).thenReturn("LocalSone");
+               when(localSone.isLocal()).thenReturn(true);
+               Core core = mock(Core.class);
+               when(core.getSone(eq("LocalSone"))).thenReturn(Optional.of(localSone));
+               when(core.getLocalSone(eq("LocalSone"), anyBoolean())).thenReturn(localSone);
+               SimpleFieldSet fields = new SimpleFieldSetBuilder().put("Sone", "LocalSone").get();
+
+               LockSoneCommand lockSoneCommand = new LockSoneCommand(core);
+               Response response = lockSoneCommand.execute(fields, null, null);
+
+               verify(core).lockSone(eq(localSone));
+               assertThat(response, notNullValue());
+               assertThat(response.getReplyParameters(), notNullValue());
+               assertThat(response.getReplyParameters().get("Message"), is("SoneLocked"));
+               assertThat(response.getReplyParameters().get("Sone"), is("LocalSone"));
+       }
+
+       @Test(expected = FcpException.class)
+       public void testLockingARemoteSone() throws FcpException {
+               Sone removeSone = mock(Sone.class);
+               Core core = mock(Core.class);
+               when(core.getSone(eq("RemoteSone"))).thenReturn(Optional.of(removeSone));
+               SimpleFieldSet fields = new SimpleFieldSetBuilder().put("Sone", "RemoteSone").get();
+
+               LockSoneCommand lockSoneCommand = new LockSoneCommand(core);
+               lockSoneCommand.execute(fields, null, null);
+       }
+
+       @Test(expected = FcpException.class)
+       public void testMissingSone() throws FcpException {
+               Core core = mock(Core.class);
+               SimpleFieldSet fields = new SimpleFieldSetBuilder().get();
+
+               LockSoneCommand lockSoneCommand = new LockSoneCommand(core);
+               lockSoneCommand.execute(fields, null, null);
+       }
+
+}
diff --git a/src/test/java/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.java b/src/test/java/net/pterodactylus/sone/fcp/UnlockSoneCommandTest.java
new file mode 100644 (file)
index 0000000..b966b19
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Sone - LockSoneCommandTest.java - Copyright © 2013 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.fcp;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.freenet.SimpleFieldSetBuilder;
+import net.pterodactylus.sone.freenet.fcp.Command.Response;
+import net.pterodactylus.sone.freenet.fcp.FcpException;
+
+import freenet.support.SimpleFieldSet;
+
+import com.google.common.base.Optional;
+import org.junit.Test;
+
+/**
+ * Tests for {@link LockSoneCommand}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class UnlockSoneCommandTest {
+
+       @Test
+       public void testUnlockingALocalSone() throws FcpException {
+               Sone localSone = mock(Sone.class);
+               when(localSone.getId()).thenReturn("LocalSone");
+               when(localSone.isLocal()).thenReturn(true);
+               Core core = mock(Core.class);
+               when(core.getSone(eq("LocalSone"))).thenReturn(Optional.of(localSone));
+               when(core.getLocalSone(eq("LocalSone"), anyBoolean())).thenReturn(localSone);
+               SimpleFieldSet fields = new SimpleFieldSetBuilder().put("Sone", "LocalSone").get();
+
+               UnlockSoneCommand unlockSoneCommand = new UnlockSoneCommand(core);
+               Response response = unlockSoneCommand.execute(fields, null, null);
+
+               verify(core).unlockSone(eq(localSone));
+               assertThat(response, notNullValue());
+               assertThat(response.getReplyParameters(), notNullValue());
+               assertThat(response.getReplyParameters().get("Message"), is("SoneUnlocked"));
+               assertThat(response.getReplyParameters().get("Sone"), is("LocalSone"));
+       }
+
+       @Test(expected = FcpException.class)
+       public void testUnlockingARemoteSone() throws FcpException {
+               Sone removeSone = mock(Sone.class);
+               Core core = mock(Core.class);
+               when(core.getSone(eq("RemoteSone"))).thenReturn(Optional.of(removeSone));
+               SimpleFieldSet fields = new SimpleFieldSetBuilder().put("Sone", "RemoteSone").get();
+
+               UnlockSoneCommand unlockSoneCommand = new UnlockSoneCommand(core);
+               unlockSoneCommand.execute(fields, null, null);
+       }
+
+       @Test(expected = FcpException.class)
+       public void testMissingSone() throws FcpException {
+               Core core = mock(Core.class);
+               SimpleFieldSet fields = new SimpleFieldSetBuilder().get();
+
+               UnlockSoneCommand unlockSoneCommand = new UnlockSoneCommand(core);
+               unlockSoneCommand.execute(fields, null, null);
+       }
+
+}
index 7baceaa..7286001 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Sone - SoneTextParserTest.java - Copyright © 2011–2012 David Roden
+ * Sone - SoneTextParserTest.java - Copyright © 2011–2013 David Roden
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,10 +19,14 @@ package net.pterodactylus.sone.text;
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Collection;
+
+import com.google.common.base.Optional;
 
 import junit.framework.TestCase;
-import net.pterodactylus.sone.core.SoneProvider;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.SoneProvider;
 
 /**
  * JUnit test case for {@link SoneTextParser}.
@@ -106,6 +110,24 @@ public class SoneTextParserTest extends TestCase {
                assertEquals("Part Text", "Some text.\n\nLink to [Sone|DAxKQzS48mtaQc7sUVHIgx3fnWZPQBz0EueBreUVWrU] and stuff.", convertText(parts, PlainTextPart.class, SonePart.class));
        }
 
+       /**
+        * Test for a bug discovered in Sone 0.8.4 where a plain “http://” would be
+        * parsed into a link.
+        *
+        * @throws IOException
+        *             if an I/O error occurs
+        */
+       @SuppressWarnings({ "synthetic-access", "static-method" })
+       public void testEmpyHttpLinks() throws IOException {
+               SoneTextParser soneTextParser = new SoneTextParser(new TestSoneProvider(), null);
+               Iterable<Part> parts;
+
+               /* check empty http links. */
+               parts = soneTextParser.parse(null, new StringReader("Some text. Empty link: http:// – nice!"));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "Some text. Empty link: http:// – nice!", convertText(parts, PlainTextPart.class));
+       }
+
        //
        // PRIVATE METHODS
        //
@@ -133,7 +155,7 @@ public class SoneTextParserTest extends TestCase {
                                }
                        }
                        if (!classValid) {
-                               assertEquals("Part’s Class", null, part.getClass());
+                               fail("Part’s Class (" + part.getClass() + ") is not one of " + Arrays.toString(validClasses));
                        }
                        if (part instanceof PlainTextPart) {
                                text.append(((PlainTextPart) part).getText());
@@ -162,8 +184,8 @@ public class SoneTextParserTest extends TestCase {
                 * {@inheritDoc}
                 */
                @Override
-               public Sone getSone(final String soneId, boolean create) {
-                       return new Sone(soneId) {
+               public Optional<Sone> getSone(final String soneId) {
+                       return Optional.<Sone> fromNullable(new Sone(soneId, false) {
 
                                /**
                                 * {@inheritDoc}
@@ -172,7 +194,31 @@ public class SoneTextParserTest extends TestCase {
                                public String getName() {
                                        return soneId;
                                }
-                       };
+                       });
+               }
+
+               /**
+                * {@inheritDocs}
+                */
+               @Override
+               public Collection<Sone> getSones() {
+                       return null;
+               }
+
+               /**
+                * {@inheritDocs}
+                */
+               @Override
+               public Collection<Sone> getLocalSones() {
+                       return null;
+               }
+
+               /**
+                * {@inheritDocs}
+                */
+               @Override
+               public Collection<Sone> getRemoteSones() {
+                       return null;
                }
 
        }