Merge branch 'next' into new-database-38 new-database-38
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Sat, 12 Jan 2013 16:41:27 +0000 (17:41 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 14 Jan 2013 05:32:50 +0000 (06:32 +0100)
Conflicts:
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/web/SearchPage.java

32 files changed:
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/SoneDownloader.java
src/main/java/net/pterodactylus/sone/data/Sone.java
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/memory/MemoryDatabase.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/fcp/CreatePostCommand.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/main/SonePlugin.java
src/main/java/net/pterodactylus/sone/template/ProfileAccessor.java
src/main/java/net/pterodactylus/sone/template/SoneAccessor.java
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/web/CreateReplyPage.java
src/main/java/net/pterodactylus/sone/web/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/DeleteReplyPage.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/SearchPage.java
src/main/java/net/pterodactylus/sone/web/ViewPostPage.java
src/main/java/net/pterodactylus/sone/web/WebInterface.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/DeleteReplyAjaxPage.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/GetLikesAjaxPage.java
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java

index 5843b30..1bb638a 100644 (file)
@@ -19,6 +19,7 @@ package net.pterodactylus.sone.core;
 
 import java.net.MalformedURLException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -47,6 +48,7 @@ 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.fcp.FcpInterface;
 import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
 import net.pterodactylus.sone.freenet.wot.Identity;
@@ -96,6 +98,9 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        /** Whether we’re currently saving the configuration. */
        private boolean storingConfiguration = false;
 
+       /** The database. */
+       private Database database;
+
        /** The identity manager. */
        private final IdentityManager identityManager;
 
@@ -135,14 +140,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        /* synchronize access on this on localSones. */
        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. */
-       /* synchronize access on this on itself. */
-       private final Map<String, Sone> remoteSones = new HashMap<String, Sone>();
-
        /** All known Sones. */
        private final Set<String> knownSones = new HashSet<String>();
 
@@ -185,6 +182,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *
         * @param configuration
         *            The configuration of the core
+        * @param database
+        *            The database to use
         * @param freenetInterface
         *            The freenet interface
         * @param identityManager
@@ -192,9 +191,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @param webOfTrustUpdater
         *            The WebOfTrust updater
         */
-       public Core(Configuration configuration, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) {
+       public Core(Configuration configuration, Database database, FreenetInterface freenetInterface, IdentityManager identityManager, WebOfTrustUpdater webOfTrustUpdater) {
                super("Sone Core");
                this.configuration = configuration;
+               this.database = database;
                this.freenetInterface = freenetInterface;
                this.identityManager = identityManager;
                this.soneDownloader = new SoneDownloader(this, freenetInterface);
@@ -297,8 +297,8 @@ 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) {
+               Validation.begin().isNotNull("Sone", sone).check().is("Local Sone", sone.isLocal()).check();
+               synchronized (soneRescuers) {
                        SoneRescuer soneRescuer = soneRescuers.get(sone);
                        if (soneRescuer == null) {
                                soneRescuer = new SoneRescuer(this, soneDownloader, sone);
@@ -327,11 +327,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *
         * @return All Sones
         */
-       public Set<Sone> getSones() {
-               Set<Sone> allSones = new HashSet<Sone>();
-               allSones.addAll(getLocalSones());
-               allSones.addAll(getRemoteSones());
-               return allSones;
+       public Collection<Sone> getSones() {
+               return database.getSones();
        }
 
        /**
@@ -362,10 +359,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        @Override
        public Sone getSone(String id, boolean create) {
-               if (isLocalSone(id)) {
-                       return getLocalSone(id);
-               }
-               return getRemoteSone(id, create);
+               return database.getSone(id, create);
        }
 
        /**
@@ -377,34 +371,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *         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());
-               }
-       }
-
-       /**
-        * 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
-        */
-       public boolean isLocalSone(String id) {
-               synchronized (localSones) {
-                       return localSones.containsKey(id);
-               }
+               return database.getSone(id, false) != null;
        }
 
        /**
@@ -412,10 +379,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *
         * @return All local Sones
         */
-       public Set<Sone> getLocalSones() {
-               synchronized (localSones) {
-                       return new HashSet<Sone>(localSones.values());
-               }
+       public Collection<Sone> getLocalSones() {
+               return database.getLocalSones();
        }
 
        /**
@@ -440,14 +405,7 @@ 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);
-                       if ((sone == null) && create) {
-                               sone = new Sone(id);
-                               localSones.put(id, sone);
-                       }
-                       return sone;
-               }
+               return database.getLocalSone(id, create);
        }
 
        /**
@@ -455,10 +413,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *
         * @return All remote Sones
         */
-       public Set<Sone> getRemoteSones() {
-               synchronized (remoteSones) {
-                       return new HashSet<Sone>(remoteSones.values());
-               }
+       public Collection<Sone> getRemoteSones() {
+               return database.getRemoteSones();
        }
 
        /**
@@ -472,42 +428,7 @@ 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);
-                       if ((sone == null) && create && (id != null) && (id.length() == 43)) {
-                               sone = new Sone(id);
-                               remoteSones.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);
-               }
+               return database.getRemoteSone(id, create);
        }
 
        /**
@@ -554,17 +475,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        }
 
        /**
-        * Returns the post with the given ID.
-        *
-        * @param postId
-        *            The ID of the post to get
-        * @return The post with the given ID, or a new post with the given ID
-        */
-       public Post getPost(String postId) {
-               return getPost(postId, true);
-       }
-
-       /**
         * Returns the post with the given ID, optionally creating a new post.
         *
         * @param postId
@@ -650,7 +560,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         * @return All replies for the given post
         */
        public List<PostReply> getReplies(Post post) {
-               Set<Sone> sones = getSones();
+               Collection<Sone> sones = getSones();
                List<PostReply> replies = new ArrayList<PostReply>();
                for (Sone sone : sones) {
                        for (PostReply reply : sone.getReplies()) {
@@ -871,27 +781,25 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        logger.log(Level.WARNING, "Given OwnIdentity is null!");
                        return null;
                }
-               synchronized (localSones) {
-                       final Sone sone;
-                       try {
-                               sone = getLocalSone(ownIdentity.getId()).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;
-                       }
-                       sone.setLatestEdition(Numbers.safeParseLong(ownIdentity.getProperty("Sone.LatestEdition"), (long) 0));
-                       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);
-                       soneInserters.put(sone, soneInserter);
-                       sone.setStatus(SoneStatus.idle);
-                       loadSone(sone);
-                       soneInserter.start();
-                       return sone;
+               Sone sone;
+               try {
+                       sone = getLocalSone(ownIdentity.getId()).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;
                }
+               sone.setLatestEdition(Numbers.safeParseLong(ownIdentity.getProperty("Sone.LatestEdition"), (long) 0));
+               sone.setClient(new Client("Sone", SonePlugin.VERSION.toString()));
+               sone.setKnown(true);
+               /* TODO - load posts ’n stuff */
+               final SoneInserter soneInserter = new SoneInserter(this, freenetInterface, sone);
+               soneInserter.addSoneInsertListener(this);
+               soneInserters.put(sone, soneInserter);
+               sone.setStatus(SoneStatus.idle);
+               loadSone(sone);
+               soneInserter.start();
+               database.saveSone(sone);
+               return sone;
        }
 
        /**
@@ -931,37 +839,36 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                        logger.log(Level.WARNING, "Given Identity is null!");
                        return null;
                }
-               synchronized (remoteSones) {
-                       final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity);
-                       boolean newSone = sone.getRequestUri() == null;
-                       sone.setRequestUri(getSoneUri(identity.getRequestUri()));
-                       sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0));
+               final Sone sone = getRemoteSone(identity.getId(), true).setIdentity(identity);
+               boolean newSone = sone.getRequestUri() == null;
+               sone.setRequestUri(getSoneUri(identity.getRequestUri()));
+               sone.setLatestEdition(Numbers.safeParseLong(identity.getProperty("Sone.LatestEdition"), (long) 0));
+               if (newSone) {
+                       synchronized (knownSones) {
+                               newSone = !knownSones.contains(sone.getId());
+                       }
+                       sone.setKnown(!newSone);
                        if (newSone) {
-                               synchronized (knownSones) {
-                                       newSone = !knownSones.contains(sone.getId());
-                               }
-                               sone.setKnown(!newSone);
-                               if (newSone) {
-                                       coreListenerManager.fireNewSoneFound(sone);
-                                       for (Sone localSone : getLocalSones()) {
-                                               if (localSone.getOptions().getBooleanOption("AutoFollow").get()) {
-                                                       followSone(localSone, sone);
-                                               }
+                               coreListenerManager.fireNewSoneFound(sone);
+                               for (Sone localSone : getLocalSones()) {
+                                       if (localSone.getOptions().getBooleanOption("AutoFollow").get()) {
+                                               followSone(localSone, sone);
                                        }
                                }
                        }
-                       soneDownloader.addSone(sone);
-                       soneDownloaders.execute(new Runnable() {
+               }
+               soneDownloader.addSone(sone);
+               soneDownloaders.execute(new Runnable() {
 
-                               @Override
-                               @SuppressWarnings("synthetic-access")
-                               public void run() {
-                                       soneDownloader.fetchSone(sone, sone.getRequestUri());
-                               }
+                       @Override
+                       @SuppressWarnings("synthetic-access")
+                       public void run() {
+                               soneDownloader.fetchSone(sone, sone.getRequestUri());
+                       }
 
-                       });
-                       return sone;
-               }
+               });
+               database.saveSone(sone);
+               return sone;
        }
 
        /**
@@ -1054,7 +961,8 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
        }
 
        /**
-        * Sets the trust value of the given origin Sone for the target Sone.
+        * Sets the trust value of the given origin Sone for
+        * the target Sone.
         *
         * @param origin
         *            The origin Sone
@@ -1258,16 +1166,14 @@ 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())) {
-                               logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
-                               return;
-                       }
-                       localSones.remove(sone.getId());
-                       SoneInserter soneInserter = soneInserters.remove(sone);
-                       soneInserter.removeSoneInsertListener(this);
-                       soneInserter.stop();
+               if (!sone.isLocal()) {
+                       logger.log(Level.WARNING, String.format("Tried to delete non-local Sone: %s", sone));
+                       return;
                }
+               database.removeSone(sone.getId());
+               SoneInserter soneInserter = soneInserters.remove(sone);
+               soneInserter.removeSoneInsertListener(this);
+               soneInserter.stop();
                webOfTrustUpdater.removeContext((OwnIdentity) sone.getIdentity(), "Sone");
                webOfTrustUpdater.removeProperty((OwnIdentity) sone.getIdentity(), "Sone.LatestEdition");
                try {
@@ -1303,7 +1209,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;
                }
@@ -1360,7 +1266,7 @@ 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);
+                       Post post = getPost(postId, true).setSone(sone).setTime(postTime).setText(postText);
                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
                                post.setRecipient(getSone(postRecipientId));
                        }
@@ -1382,7 +1288,7 @@ 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));
+                       replies.add(getReply(replyId).setSone(sone).setPost(getPost(postId, true)).setTime(replyTime).setText(replyText));
                }
 
                /* load post likes. */
@@ -1526,50 +1432,6 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         *
         * @param sone
         *            The Sone that creates the post
-        * @param text
-        *            The text of the post
-        * @return The created post
-        */
-       public Post createPost(Sone sone, String text) {
-               return createPost(sone, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param sone
-        *            The Sone that creates the post
-        * @param time
-        *            The time of the post
-        * @param text
-        *            The text of the post
-        * @return The created post
-        */
-       public Post createPost(Sone sone, long time, String text) {
-               return createPost(sone, null, time, text);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param sone
-        *            The Sone that creates the post
-        * @param recipient
-        *            The recipient Sone, or {@code null} if this post does not have
-        *            a recipient
-        * @param text
-        *            The text of the post
-        * @return The created post
-        */
-       public Post createPost(Sone sone, Sone recipient, String text) {
-               return createPost(sone, recipient, System.currentTimeMillis(), text);
-       }
-
-       /**
-        * Creates a new post.
-        *
-        * @param sone
-        *            The Sone that creates the post
         * @param recipient
         *            The recipient Sone, or {@code null} if this post does not have
         *            a recipient
@@ -1581,7 +1443,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        public Post createPost(Sone sone, Sone recipient, long time, String text) {
                Validation.begin().isNotNull("Text", text).check().isGreater("Text Length", text.length(), 0).check();
-               if (!isLocalSone(sone)) {
+               if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to create post for non-local Sone: %s", sone));
                        return null;
                }
@@ -1615,7 +1477,7 @@ 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;
                }
@@ -1722,7 +1584,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        public PostReply createReply(Sone sone, Post post, long time, String text) {
                Validation.begin().isNotNull("Text", text).check().isGreater("Text Length", text.trim().length(), 0).check();
-               if (!isLocalSone(sone)) {
+               if (!sone.isLocal()) {
                        logger.log(Level.FINE, String.format("Tried to create reply for non-local Sone: %s", sone));
                        return null;
                }
@@ -1756,7 +1618,7 @@ 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;
                }
@@ -1831,7 +1693,7 @@ 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();
+               Validation.begin().isNotNull("Album", album).check().is("Local Sone", album.getSone().isLocal()).check();
                if (!album.isEmpty()) {
                        return;
                }
@@ -1858,7 +1720,7 @@ 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();
+               Validation.begin().isNotNull("Sone", sone).isNotNull("Album", album).isNotNull("Temporary Image", temporaryImage).check().is("Local Sone", sone.isLocal()).check().isEqual("Owner and Album Owner", sone, album.getSone()).check();
                Image image = new Image(temporaryImage.getId()).setSone(sone).setCreationTime(System.currentTimeMillis());
                album.addImage(image);
                synchronized (images) {
@@ -1877,7 +1739,7 @@ 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();
+               Validation.begin().isNotNull("Image", image).check().is("Local Sone", image.getSone().isLocal()).check();
                deleteTemporaryImage(image.getId());
                image.getAlbum().removeImage(image);
                synchronized (images) {
@@ -1980,12 +1842,10 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
         */
        @Override
        public void serviceStop() {
-               synchronized (localSones) {
-                       for (Entry<Sone, SoneInserter> soneInserter : soneInserters.entrySet()) {
-                               soneInserter.getValue().removeSoneInsertListener(this);
-                               soneInserter.getValue().stop();
-                               saveSone(soneInserter.getKey());
-                       }
+               for (Entry<Sone, SoneInserter> soneInserter : soneInserters.entrySet()) {
+                       soneInserter.getValue().removeSoneInsertListener(this);
+                       soneInserter.getValue().stop();
+                       saveSone(soneInserter.getKey());
                }
                saveConfiguration();
                webOfTrustUpdater.stop();
@@ -2006,7 +1866,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;
                }
@@ -2469,9 +2329,7 @@ public class Core extends AbstractService implements IdentityListener, UpdateLis
                                }
                        }
                }
-               synchronized (remoteSones) {
-                       remoteSones.remove(identity.getId());
-               }
+               database.removeSone(identity.getId());
                coreListenerManager.fireSoneRemoved(sone);
        }
 
index 72ff008..f2d05ae 100644 (file)
@@ -34,6 +34,7 @@ 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.sone.database.memory.MemorySone;
 import net.pterodactylus.util.collection.Pair;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
@@ -236,7 +237,7 @@ public class SoneDownloader extends AbstractService {
                        return null;
                }
 
-               Sone sone = new Sone(originalSone.getId()).setIdentity(originalSone.getIdentity());
+               Sone sone = new MemorySone(originalSone.getId(), originalSone.isLocal()).setIdentity(originalSone.getIdentity());
 
                SimpleXML soneXml;
                try {
@@ -372,7 +373,7 @@ public class SoneDownloader extends AbstractService {
                                        return null;
                                }
                                try {
-                                       Post post = core.getPost(postId).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText);
+                                       Post post = core.getPost(postId, true).setSone(sone).setTime(Long.parseLong(postTime)).setText(postText);
                                        if ((postRecipientId != null) && (postRecipientId.length() == 43)) {
                                                post.setRecipient(core.getSone(postRecipientId));
                                        }
@@ -403,7 +404,7 @@ 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));
+                                       replies.add(core.getReply(replyId).setSone(sone).setPost(core.getPost(replyPostId, true)).setTime(Long.parseLong(replyTime)).setText(replyText));
                                } 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 99d65ec..28a5afa 100644 (file)
 
 package net.pterodactylus.sone.data;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-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;
@@ -34,8 +28,6 @@ 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;
 
 /**
@@ -46,7 +38,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Sone implements Fingerprintable, Comparable<Sone> {
+public interface Sone extends Fingerprintable, Comparable<Sone> {
 
        /**
         * Enumeration for the possible states of a {@link Sone}.
@@ -150,7 +142,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                }
        };
 
-       /** Filter that matches all {@link Core#isLocalSone(Sone) local Sones}. */
+       /** Filter that matches all {@link Core#getLocalSones() local Sones}. */
        public static final Filter<Sone> LOCAL_SONE_FILTER = new Filter<Sone>() {
 
                @Override
@@ -169,71 +161,6 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                }
        };
 
-       /** The logger. */
-       private static final Logger logger = Logging.getLogger(Sone.class);
-
-       /** The ID of this Sone. */
-       private final String id;
-
-       /** The identity of this Sone. */
-       private Identity identity;
-
-       /** The URI under which the Sone is stored in Freenet. */
-       private volatile FreenetURI requestUri;
-
-       /** The URI used to insert a new version of this Sone. */
-       /* This will be null for remote Sones! */
-       private volatile FreenetURI insertUri;
-
-       /** The latest edition of the Sone. */
-       private volatile long latestEdition;
-
-       /** The time of the last inserted update. */
-       private volatile long time;
-
-       /** The status of this Sone. */
-       private volatile SoneStatus status = SoneStatus.unknown;
-
-       /** The profile of this Sone. */
-       private volatile Profile profile = new Profile(this);
-
-       /** The client used by the Sone. */
-       private volatile Client client;
-
-       /** Whether this Sone is known. */
-       private volatile boolean known;
-
-       /** All friend Sones. */
-       private final Set<String> friendSones = new CopyOnWriteArraySet<String>();
-
-       /** All posts. */
-       private final Set<Post> posts = new CopyOnWriteArraySet<Post>();
-
-       /** All replies. */
-       private final Set<PostReply> replies = new CopyOnWriteArraySet<PostReply>();
-
-       /** The IDs of all liked posts. */
-       private final Set<String> likedPostIds = new CopyOnWriteArraySet<String>();
-
-       /** The IDs of all liked replies. */
-       private final Set<String> likedReplyIds = new CopyOnWriteArraySet<String>();
-
-       /** The albums of this Sone. */
-       private final List<Album> albums = new CopyOnWriteArrayList<Album>();
-
-       /** Sone-specific options. */
-       private final Options options = new Options();
-
-       /**
-        * Creates a new Sone.
-        *
-        * @param id
-        *            The ID of the Sone
-        */
-       public Sone(String id) {
-               this.id = id;
-       }
-
        //
        // ACCESSORS
        //
@@ -243,18 +170,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *
         * @return The identity of this Sone
         */
-       public String getId() {
-               return id;
-       }
+       public String getId();
 
        /**
         * Returns the identity of this Sone.
         *
         * @return The identity of this Sone
         */
-       public Identity getIdentity() {
-               return identity;
-       }
+       public Identity getIdentity();
 
        /**
         * Sets the identity of this Sone. The {@link Identity#getId() ID} of the
@@ -266,31 +189,28 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @throws IllegalArgumentException
         *             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)) {
-                       throw new IllegalArgumentException("Identity’s ID does not match Sone’s ID!");
-               }
-               this.identity = identity;
-               return this;
-       }
+       public Sone setIdentity(Identity identity) throws IllegalArgumentException;
+
+       /**
+        * Returns whether this Sone is local.
+        *
+        * @return {@code true} if this Sone is local, {@code false} otherwise
+        */
+       public boolean isLocal();
 
        /**
         * Returns the name of this Sone.
         *
         * @return The name of this Sone
         */
-       public String getName() {
-               return (identity != null) ? identity.getNickname() : null;
-       }
+       public String getName();
 
        /**
         * Returns the request URI of this Sone.
         *
         * @return The request URI of this Sone
         */
-       public FreenetURI getRequestUri() {
-               return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null;
-       }
+       public FreenetURI getRequestUri();
 
        /**
         * Sets the request URI of this Sone.
@@ -299,26 +219,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The request URI of this Sone
         * @return This Sone (for method chaining)
         */
-       public Sone setRequestUri(FreenetURI requestUri) {
-               if (this.requestUri == null) {
-                       this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
-                       return this;
-               }
-               if (!this.requestUri.equalsKeypair(requestUri)) {
-                       logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri));
-                       return this;
-               }
-               return this;
-       }
+       public Sone setRequestUri(FreenetURI requestUri);
 
        /**
         * Returns the insert URI of this Sone.
         *
         * @return The insert URI of this Sone
         */
-       public FreenetURI getInsertUri() {
-               return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null;
-       }
+       public FreenetURI getInsertUri();
 
        /**
         * Sets the insert URI of this Sone.
@@ -327,26 +235,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The insert URI of this Sone
         * @return This Sone (for method chaining)
         */
-       public Sone setInsertUri(FreenetURI insertUri) {
-               if (this.insertUri == null) {
-                       this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
-                       return this;
-               }
-               if (!this.insertUri.equalsKeypair(insertUri)) {
-                       logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri));
-                       return this;
-               }
-               return this;
-       }
+       public Sone setInsertUri(FreenetURI insertUri);
 
        /**
         * Returns the latest edition of this Sone.
         *
         * @return The latest edition of this Sone
         */
-       public long getLatestEdition() {
-               return latestEdition;
-       }
+       public long getLatestEdition();
 
        /**
         * Sets the latest edition of this Sone. If the given latest edition is not
@@ -356,22 +252,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param latestEdition
         *            The latest edition of this Sone
         */
-       public void setLatestEdition(long latestEdition) {
-               if (!(latestEdition > this.latestEdition)) {
-                       logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition));
-                       return;
-               }
-               this.latestEdition = latestEdition;
-       }
+       public void setLatestEdition(long latestEdition);
 
        /**
         * Return the time of the last inserted update of this Sone.
         *
         * @return The time of the update (in milliseconds since Jan 1, 1970 UTC)
         */
-       public long getTime() {
-               return time;
-       }
+       public long getTime();
 
        /**
         * Sets the time of the last inserted update of this Sone.
@@ -380,19 +268,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The time of the update (in milliseconds since Jan 1, 1970 UTC)
         * @return This Sone (for method chaining)
         */
-       public Sone setTime(long time) {
-               this.time = time;
-               return this;
-       }
+       public Sone setTime(long time);
 
        /**
         * Returns the status of this Sone.
         *
         * @return The status of this Sone
         */
-       public SoneStatus getStatus() {
-               return status;
-       }
+       public SoneStatus getStatus();
 
        /**
         * Sets the new status of this Sone.
@@ -403,11 +286,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @throws IllegalArgumentException
         *             if {@code status} is {@code null}
         */
-       public Sone setStatus(SoneStatus status) {
-               Validation.begin().isNotNull("Sone Status", status).check();
-               this.status = status;
-               return this;
-       }
+       public Sone setStatus(SoneStatus status);
 
        /**
         * Returns a copy of the profile. If you want to update values in the
@@ -416,9 +295,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *
         * @return A copy of the profile
         */
-       public Profile getProfile() {
-               return new Profile(profile);
-       }
+       public Profile getProfile();
 
        /**
         * Sets the profile of this Sone. A copy of the given profile is stored so
@@ -428,18 +305,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param profile
         *            The profile to set
         */
-       public void setProfile(Profile profile) {
-               this.profile = new Profile(profile);
-       }
+       public void setProfile(Profile profile);
 
        /**
         * Returns the client used by this Sone.
         *
         * @return The client used by this Sone, or {@code null}
         */
-       public Client getClient() {
-               return client;
-       }
+       public Client getClient();
 
        /**
         * Sets the client used by this Sone.
@@ -448,19 +321,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The client used by this Sone, or {@code null}
         * @return This Sone (for method chaining)
         */
-       public Sone setClient(Client client) {
-               this.client = client;
-               return this;
-       }
+       public Sone setClient(Client client);
 
        /**
         * Returns whether this Sone is known.
         *
         * @return {@code true} if this Sone is known, {@code false} otherwise
         */
-       public boolean isKnown() {
-               return known;
-       }
+       public boolean isKnown();
 
        /**
         * Sets whether this Sone is known.
@@ -469,20 +337,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            {@code true} if this Sone is known, {@code false} otherwise
         * @return This Sone
         */
-       public Sone setKnown(boolean known) {
-               this.known = known;
-               return this;
-       }
+       public Sone setKnown(boolean known);
 
        /**
         * Returns all friend Sones of this Sone.
         *
         * @return The friend Sones of this Sone
         */
-       public List<String> getFriends() {
-               List<String> friends = new ArrayList<String>(friendSones);
-               return friends;
-       }
+       public Collection<String> getFriends();
 
        /**
         * Returns whether this Sone has the given Sone as a friend Sone.
@@ -492,9 +354,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @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);
-       }
+       public boolean hasFriend(String friendSoneId);
 
        /**
         * Adds the given Sone as a friend Sone.
@@ -503,12 +363,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The friend Sone to add
         * @return This Sone (for method chaining)
         */
-       public Sone addFriend(String friendSone) {
-               if (!friendSone.equals(id)) {
-                       friendSones.add(friendSone);
-               }
-               return this;
-       }
+       public Sone addFriend(String friendSone);
 
        /**
         * Removes the given Sone as a friend Sone.
@@ -517,24 +372,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the friend Sone to remove
         * @return This Sone (for method chaining)
         */
-       public Sone removeFriend(String friendSoneId) {
-               friendSones.remove(friendSoneId);
-               return this;
-       }
+       public Sone removeFriend(String friendSoneId);
 
        /**
         * Returns the list of posts of this Sone, sorted by time, newest first.
         *
         * @return All posts of this Sone
         */
-       public List<Post> getPosts() {
-               List<Post> sortedPosts;
-               synchronized (this) {
-                       sortedPosts = new ArrayList<Post>(posts);
-               }
-               Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
-               return sortedPosts;
-       }
+       public List<Post> getPosts();
 
        /**
         * Sets all posts of this Sone at once.
@@ -543,13 +388,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The new (and only) posts of this Sone
         * @return This Sone (for method chaining)
         */
-       public Sone setPosts(Collection<Post> posts) {
-               synchronized (this) {
-                       this.posts.clear();
-                       this.posts.addAll(posts);
-               }
-               return this;
-       }
+       public Sone setPosts(Collection<Post> posts);
 
        /**
         * Adds the given post to this Sone. The post will not be added if its
@@ -558,11 +397,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param post
         *            The post to add
         */
-       public void addPost(Post post) {
-               if (post.getSone().equals(this) && posts.add(post)) {
-                       logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName()));
-               }
-       }
+       public void addPost(Post post);
 
        /**
         * Removes the given post from this Sone.
@@ -570,20 +405,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param post
         *            The post to remove
         */
-       public void removePost(Post post) {
-               if (post.getSone().equals(this)) {
-                       posts.remove(post);
-               }
-       }
+       public void removePost(Post post);
 
        /**
         * Returns all replies this Sone made.
         *
         * @return All replies this Sone made
         */
-       public Set<PostReply> getReplies() {
-               return Collections.unmodifiableSet(replies);
-       }
+       public Set<PostReply> getReplies();
 
        /**
         * Sets all replies of this Sone at once.
@@ -592,11 +421,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The new (and only) replies of this Sone
         * @return This Sone (for method chaining)
         */
-       public Sone setReplies(Collection<PostReply> replies) {
-               this.replies.clear();
-               this.replies.addAll(replies);
-               return this;
-       }
+       public Sone setReplies(Collection<PostReply> replies);
 
        /**
         * Adds a reply to this Sone. If the given reply was not made by this Sone,
@@ -605,11 +430,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param reply
         *            The reply to add
         */
-       public void addReply(PostReply reply) {
-               if (reply.getSone().equals(this)) {
-                       replies.add(reply);
-               }
-       }
+       public void addReply(PostReply reply);
 
        /**
         * Removes a reply from this Sone.
@@ -617,20 +438,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param reply
         *            The reply to remove
         */
-       public void removeReply(PostReply reply) {
-               if (reply.getSone().equals(this)) {
-                       replies.remove(reply);
-               }
-       }
+       public void removeReply(PostReply reply);
 
        /**
         * Returns the IDs of all liked posts.
         *
         * @return All liked posts’ IDs
         */
-       public Set<String> getLikedPostIds() {
-               return Collections.unmodifiableSet(likedPostIds);
-       }
+       public Set<String> getLikedPostIds();
 
        /**
         * Sets the IDs of all liked posts.
@@ -639,11 +454,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            All liked posts’ IDs
         * @return This Sone (for method chaining)
         */
-       public Sone setLikePostIds(Set<String> likedPostIds) {
-               this.likedPostIds.clear();
-               this.likedPostIds.addAll(likedPostIds);
-               return this;
-       }
+       public Sone setLikePostIds(Set<String> likedPostIds);
 
        /**
         * Checks whether the given post ID is liked by this Sone.
@@ -653,9 +464,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @return {@code true} if this Sone likes the given post, {@code false}
         *         otherwise
         */
-       public boolean isLikedPostId(String postId) {
-               return likedPostIds.contains(postId);
-       }
+       public boolean isLikedPostId(String postId);
 
        /**
         * Adds the given post ID to the list of posts this Sone likes.
@@ -664,10 +473,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the post
         * @return This Sone (for method chaining)
         */
-       public Sone addLikedPostId(String postId) {
-               likedPostIds.add(postId);
-               return this;
-       }
+       public Sone addLikedPostId(String postId);
 
        /**
         * Removes the given post ID from the list of posts this Sone likes.
@@ -676,19 +482,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the post
         * @return This Sone (for method chaining)
         */
-       public Sone removeLikedPostId(String postId) {
-               likedPostIds.remove(postId);
-               return this;
-       }
+       public Sone removeLikedPostId(String postId);
 
        /**
         * Returns the IDs of all liked replies.
         *
         * @return All liked replies’ IDs
         */
-       public Set<String> getLikedReplyIds() {
-               return Collections.unmodifiableSet(likedReplyIds);
-       }
+       public Set<String> getLikedReplyIds();
 
        /**
         * Sets the IDs of all liked replies.
@@ -697,11 +498,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            All liked replies’ IDs
         * @return This Sone (for method chaining)
         */
-       public Sone setLikeReplyIds(Set<String> likedReplyIds) {
-               this.likedReplyIds.clear();
-               this.likedReplyIds.addAll(likedReplyIds);
-               return this;
-       }
+       public Sone setLikeReplyIds(Set<String> likedReplyIds);
 
        /**
         * Checks whether the given reply ID is liked by this Sone.
@@ -711,9 +508,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @return {@code true} if this Sone likes the given reply, {@code false}
         *         otherwise
         */
-       public boolean isLikedReplyId(String replyId) {
-               return likedReplyIds.contains(replyId);
-       }
+       public boolean isLikedReplyId(String replyId);
 
        /**
         * Adds the given reply ID to the list of replies this Sone likes.
@@ -722,10 +517,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the reply
         * @return This Sone (for method chaining)
         */
-       public Sone addLikedReplyId(String replyId) {
-               likedReplyIds.add(replyId);
-               return this;
-       }
+       public Sone addLikedReplyId(String replyId);
 
        /**
         * Removes the given post ID from the list of replies this Sone likes.
@@ -734,19 +526,14 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the reply
         * @return This Sone (for method chaining)
         */
-       public Sone removeLikedReplyId(String replyId) {
-               likedReplyIds.remove(replyId);
-               return this;
-       }
+       public Sone removeLikedReplyId(String replyId);
 
        /**
         * Returns the albums of this Sone.
         *
         * @return The albums of this Sone
         */
-       public List<Album> getAlbums() {
-               return Collections.unmodifiableList(albums);
-       }
+       public List<Album> getAlbums();
 
        /**
         * Returns a flattened list of all albums of this Sone. The resulting list
@@ -755,19 +542,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *
         * @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;
-       }
+       public List<Album> getAllAlbums();
 
        /**
         * Returns all images of a Sone. Images of a album are inserted into this
@@ -775,13 +550,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *
         * @return The list of all images
         */
-       public List<Image> getAllImages() {
-               List<Image> allImages = new ArrayList<Image>();
-               for (Album album : getAllAlbums()) {
-                       allImages.addAll(album.getImages());
-               }
-               return allImages;
-       }
+       public List<Image> getAllImages();
 
        /**
         * Adds an album to this Sone.
@@ -789,12 +558,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param album
         *            The album to add
         */
-       public void addAlbum(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
-               if (!albums.contains(album)) {
-                       albums.add(album);
-               }
-       }
+       public void addAlbum(Album album);
 
        /**
         * Sets the albums of this Sone.
@@ -802,13 +566,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param albums
         *            The albums of this Sone
         */
-       public void setAlbums(Collection<? extends Album> albums) {
-               Validation.begin().isNotNull("Albums", albums).check();
-               this.albums.clear();
-               for (Album album : albums) {
-                       addAlbum(album);
-               }
-       }
+       public void setAlbums(Collection<? extends Album> albums);
 
        /**
         * Removes an album from this Sone.
@@ -816,10 +574,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param album
         *            The album to remove
         */
-       public void removeAlbum(Album album) {
-               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
-               albums.remove(album);
-       }
+       public void removeAlbum(Album album);
 
        /**
         * Moves the given album up in this album’s albums. If the album is already
@@ -830,16 +585,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @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();
-               int oldIndex = albums.indexOf(album);
-               if (oldIndex <= 0) {
-                       return null;
-               }
-               albums.remove(oldIndex);
-               albums.add(oldIndex - 1, album);
-               return albums.get(oldIndex);
-       }
+       public Album moveAlbumUp(Album album);
 
        /**
         * Moves the given album down in this album’s albums. If the album is
@@ -850,118 +596,13 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @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();
-               int oldIndex = albums.indexOf(album);
-               if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
-                       return null;
-               }
-               albums.remove(oldIndex);
-               albums.add(oldIndex + 1, album);
-               return albums.get(oldIndex);
-       }
+       public Album moveAlbumDown(Album album);
 
        /**
         * Returns Sone-specific options.
         *
         * @return The options of this Sone
         */
-       public Options getOptions() {
-               return options;
-       }
-
-       //
-       // FINGERPRINTABLE METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public synchronized String getFingerprint() {
-               StringBuilder fingerprint = new StringBuilder();
-               fingerprint.append(profile.getFingerprint());
-
-               fingerprint.append("Posts(");
-               for (Post post : getPosts()) {
-                       fingerprint.append("Post(").append(post.getId()).append(')');
-               }
-               fingerprint.append(")");
-
-               List<PostReply> replies = new ArrayList<PostReply>(getReplies());
-               Collections.sort(replies, Reply.TIME_COMPARATOR);
-               fingerprint.append("Replies(");
-               for (PostReply reply : replies) {
-                       fingerprint.append("Reply(").append(reply.getId()).append(')');
-               }
-               fingerprint.append(')');
-
-               List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
-               Collections.sort(likedPostIds);
-               fingerprint.append("LikedPosts(");
-               for (String likedPostId : likedPostIds) {
-                       fingerprint.append("Post(").append(likedPostId).append(')');
-               }
-               fingerprint.append(')');
-
-               List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
-               Collections.sort(likedReplyIds);
-               fingerprint.append("LikedReplies(");
-               for (String likedReplyId : likedReplyIds) {
-                       fingerprint.append("Reply(").append(likedReplyId).append(')');
-               }
-               fingerprint.append(')');
-
-               fingerprint.append("Albums(");
-               for (Album album : albums) {
-                       fingerprint.append(album.getFingerprint());
-               }
-               fingerprint.append(')');
-
-               return fingerprint.toString();
-       }
-
-       //
-       // INTERFACE Comparable<Sone>
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public int compareTo(Sone sone) {
-               return NICE_NAME_COMPARATOR.compare(this, sone);
-       }
-
-       //
-       // OBJECT METHODS
-       //
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public int hashCode() {
-               return id.hashCode();
-       }
-
-       /**
-        * {@inheritDoc}
-        */
-       @Override
-       public boolean equals(Object object) {
-               if (!(object instanceof Sone)) {
-                       return false;
-               }
-               return ((Sone) object).id.equals(id);
-       }
-
-       /**
-        * {@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() + ")]";
-       }
+       public Options getOptions();
 
 }
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..0ff7dfb
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Sone - Database.java - Copyright © 2011 David Roden
+ *
+ * This 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;
+
+/**
+ * Interface for Sone’s database.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Database {
+
+       /**
+        * Returns the Sone with the given ID, creating a new Sone if a Sone with
+        * the given ID does not exist and {@code create} is {@code true}. When
+        * searching for a Sone with the given IDs, local Sones are preferred.
+        *
+        * @param id
+        *            The ID of the new Sone
+        * @param create
+        *            {@code true} to create a new Sone if a Sone with the given ID
+        *            does not exist, {@code false} to return {@code null} if a Sone
+        *            with the given ID does not exist
+        * @return The created Sone
+        * @throws IllegalArgumentException
+        *             if {@code id} is {@code null}
+        * @throws DatabaseException
+        *             if a database error occurs
+        */
+       public Sone getSone(String id, boolean create) throws DatabaseException;
+
+       /**
+        * Returns all known Sones.
+        *
+        * @return All known Sones
+        * @throws DatabaseException
+        *             if a database error occurs
+        */
+       public Collection<Sone> getSones() throws DatabaseException;
+
+       /**
+        * Returns all known local Sones.
+        *
+        * @return All known local Sones
+        * @throws DatabaseException
+        *             if a database error occurs
+        */
+       public Collection<Sone> getLocalSones() throws DatabaseException;
+
+       /**
+        * Returns all known remote Sones.
+        *
+        * @return All known remote Sones
+        * @throws DatabaseException
+        *             if a database error occurs
+        */
+       public Collection<Sone> getRemoteSones() throws DatabaseException;
+
+       /**
+        * Returns the local Sone with the given ID, creating it if it doesn’t exist
+        * and {@code create} is {@code true}.
+        *
+        * @param id
+        *            The ID of the Sone
+        * @param create
+        *            {@code true} to create a Sone if no Sone with the given ID
+        *            exists yet, {@code false} to return {@code null}
+        * @return The existing Sone, the created Sone, or {@code null}
+        * @throws DatabaseException
+        *             if adatabase error occurs
+        */
+       public Sone getLocalSone(String id, boolean create) throws DatabaseException;
+
+       /**
+        * Returns the remote Sone with the given ID, creating it if it doesn’t
+        * exist and {@code create} is {@code true}.
+        *
+        * @param id
+        *            The ID of the Sone
+        * @param create
+        *            {@code true} to create a Sone if no Sone with the given ID
+        *            exists yet, {@code false} to return {@code null}
+        * @return The existing Sone, the created Sone, or {@code null}
+        * @throws DatabaseException
+        *             if adatabase error occurs
+        */
+       public Sone getRemoteSone(String id, boolean create) throws DatabaseException;
+
+       /**
+        * Stores the given Sone. The given Sone has to be an object that was
+        * returned by a previous call to {@link #getSone(String, boolean)}.
+        *
+        * @param sone
+        *            The Sone to store
+        * @throws IllegalArgumentException
+        *             if {@code sone} is {@code null}, or the Sone was not
+        *             retrieved by a call to {@link #getSone(String, boolean)}
+        * @throws DatabaseException
+        *             if a database error occurs
+        */
+       public void saveSone(Sone sone) throws DatabaseException;
+
+       /**
+        * Removes the given Sone from the database.
+        *
+        * @param sone
+        *            The Sone to remove
+        * @throws DatabaseException
+        *             if a database error occurs
+        */
+       public void removeSone(Sone sone) throws DatabaseException;
+
+       /**
+        * Removes the Sone with the given ID from the database.
+        *
+        * @param id
+        *            The ID of the Sone to remove
+        * @throws DatabaseException
+        *             if a database error occurs
+        */
+       public void removeSone(String id) 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..0f4a46f
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Sone - DatabaseException.java - Copyright © 2011 David Roden
+ *
+ * This 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 an error working with the database.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DatabaseException extends RuntimeException {
+
+       /**
+        * 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/memory/MemoryDatabase.java b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.java
new file mode 100644 (file)
index 0000000..db295e8
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Sone - MemoryDatabase.java - Copyright © 2011 David Roden
+ *
+ * This 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.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.database.DatabaseException;
+import net.pterodactylus.util.collection.SetBuilder;
+import net.pterodactylus.util.validation.Validation;
+
+/**
+ * In-memory implementation of {@link Database} that stores all its data in a
+ * {@link Map}. This is roughly equivalent to what {@link Core} does today and
+ * is mainly used while Sone is transitioning to using a real database.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MemoryDatabase implements Database {
+
+       /** The local Sones. */
+       private final Map<String, Sone> localSones = new HashMap<String, Sone>();
+
+       /** The remote Sones. */
+       private final Map<String, Sone> remoteSones = new HashMap<String, Sone>();
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone getSone(String id, boolean create) throws DatabaseException {
+               if (getLocalSone(id, false) != null) {
+                       return getLocalSone(id, create);
+               }
+               return getRemoteSone(id, create);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Collection<Sone> getSones() throws DatabaseException {
+               return new SetBuilder<Sone>().addAll(getLocalSones()).addAll(getRemoteSones()).get();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Collection<Sone> getLocalSones() throws DatabaseException {
+               synchronized (localSones) {
+                       return Collections.unmodifiableCollection(localSones.values());
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Collection<Sone> getRemoteSones() throws DatabaseException {
+               synchronized (remoteSones) {
+                       return Collections.unmodifiableCollection(remoteSones.values());
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone getLocalSone(String id, boolean create) throws DatabaseException {
+               Validation.begin().isNotNull("Sone ID", id).check();
+               synchronized (localSones) {
+                       if (!localSones.containsKey(id) && create) {
+                               localSones.put(id, new MemorySone(id, true));
+                       }
+                       return localSones.get(id);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone getRemoteSone(String id, boolean create) throws DatabaseException {
+               Validation.begin().isNotNull("Sone ID", id).check();
+               synchronized (remoteSones) {
+                       if (!remoteSones.containsKey(id) && create) {
+                               remoteSones.put(id, new MemorySone(id, false));
+                       }
+                       return remoteSones.get(id);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void saveSone(Sone sone) throws DatabaseException {
+               if (sone.isLocal()) {
+                       synchronized (localSones) {
+                               localSones.put(sone.getId(), sone);
+                       }
+               } else {
+                       synchronized (remoteSones) {
+                               Validation.begin().is("Sone is from this database", remoteSones.containsKey(sone.getId())).check();
+                               remoteSones.put(sone.getId(), sone);
+                       }
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removeSone(Sone sone) throws DatabaseException {
+               Map<String, Sone> sones = sone.isLocal() ? localSones : remoteSones;
+               synchronized (sones) {
+                       sones.remove(sone.getId());
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removeSone(String id) throws DatabaseException {
+               Map<String, Sone> sones = ((getSone(id, false) != null) && getSone(id, false).isLocal()) ? localSones : remoteSones;
+               synchronized (sones) {
+                       sones.remove(id);
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java b/src/main/java/net/pterodactylus/sone/database/memory/MemorySone.java
new file mode 100644 (file)
index 0000000..da487b3
--- /dev/null
@@ -0,0 +1,737 @@
+/*
+ * Sone - MemorySone.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.database.memory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.sone.core.Options;
+import net.pterodactylus.sone.data.Album;
+import net.pterodactylus.sone.data.Client;
+import net.pterodactylus.sone.data.Image;
+import net.pterodactylus.sone.data.Post;
+import net.pterodactylus.sone.data.PostReply;
+import net.pterodactylus.sone.data.Profile;
+import net.pterodactylus.sone.data.Reply;
+import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.Database;
+import net.pterodactylus.sone.freenet.wot.Identity;
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.validation.Validation;
+import freenet.keys.FreenetURI;
+
+/**
+ * Implementation of a {@link Sone} that keeps all added data in memory. A
+ * self-created instance of this class should be converted to a {@link Database}
+ * -based instance of {@link Sone} as soon as possible (unless it was returned
+ * by a {@link MemoryDatabase}).
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class MemorySone implements Sone {
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(Sone.class);
+
+       /** The ID of this Sone. */
+       private final String id;
+
+       /** Whether this is a local Sone. */
+       private final boolean local;
+
+       /** The identity of this Sone. */
+       private Identity identity;
+
+       /** The URI under which the Sone is stored in Freenet. */
+       private volatile FreenetURI requestUri;
+
+       /** The URI used to insert a new version of this Sone. */
+       /* This will be null for remote Sones! */
+       private volatile FreenetURI insertUri;
+
+       /** The latest edition of the Sone. */
+       private volatile long latestEdition;
+
+       /** The time of the last inserted update. */
+       private volatile long time;
+
+       /** The status of this Sone. */
+       private volatile SoneStatus status = SoneStatus.unknown;
+
+       /** The profile of this Sone. */
+       private volatile Profile profile = new Profile(this);
+
+       /** The client used by the Sone. */
+       private volatile Client client;
+
+       /** Whether this Sone is known. */
+       private volatile boolean known;
+
+       /** All friend Sones. */
+       private final Set<String> friendSones = new CopyOnWriteArraySet<String>();
+
+       /** All posts. */
+       private final Set<Post> posts = new CopyOnWriteArraySet<Post>();
+
+       /** All replies. */
+       private final Set<PostReply> replies = new CopyOnWriteArraySet<PostReply>();
+
+       /** The IDs of all liked posts. */
+       private final Set<String> likedPostIds = new CopyOnWriteArraySet<String>();
+
+       /** The IDs of all liked replies. */
+       private final Set<String> likedReplyIds = new CopyOnWriteArraySet<String>();
+
+       /** The albums of this Sone. */
+       private final List<Album> albums = new CopyOnWriteArrayList<Album>();
+
+       /** Sone-specific options. */
+       private final Options options = new Options();
+
+       /**
+        * Creates a new Sone.
+        *
+        * @param id
+        *            The ID of the Sone
+        * @param local
+        *            {@code true} if this Sone is local, {@code false} otherwise
+        */
+       public MemorySone(String id, boolean local) {
+               this.id = id;
+               this.local = local;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Identity getIdentity() {
+               return identity;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setIdentity(Identity identity) throws IllegalArgumentException {
+               if (!identity.getId().equals(id)) {
+                       throw new IllegalArgumentException("Identity’s ID does not match Sone’s ID!");
+               }
+               this.identity = identity;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isLocal() {
+               return local;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getName() {
+               return (identity != null) ? identity.getNickname() : null;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public FreenetURI getRequestUri() {
+               return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setRequestUri(FreenetURI requestUri) {
+               if (this.requestUri == null) {
+                       this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
+                       return this;
+               }
+               if (!this.requestUri.equalsKeypair(requestUri)) {
+                       logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri));
+                       return this;
+               }
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public FreenetURI getInsertUri() {
+               return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setInsertUri(FreenetURI insertUri) {
+               if (this.insertUri == null) {
+                       this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
+                       return this;
+               }
+               if (!this.insertUri.equalsKeypair(insertUri)) {
+                       logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri));
+                       return this;
+               }
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public long getLatestEdition() {
+               return latestEdition;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setLatestEdition(long latestEdition) {
+               if (!(latestEdition > this.latestEdition)) {
+                       logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition));
+                       return;
+               }
+               this.latestEdition = latestEdition;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public long getTime() {
+               return time;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setTime(long time) {
+               this.time = time;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public SoneStatus getStatus() {
+               return status;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setStatus(SoneStatus status) {
+               Validation.begin().isNotNull("Sone Status", status).check();
+               this.status = status;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Profile getProfile() {
+               return new Profile(profile);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setProfile(Profile profile) {
+               this.profile = new Profile(profile);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Client getClient() {
+               return client;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setClient(Client client) {
+               this.client = client;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isKnown() {
+               return known;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setKnown(boolean known) {
+               this.known = known;
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public List<String> getFriends() {
+               List<String> friends = new ArrayList<String>(friendSones);
+               return friends;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean hasFriend(String friendSoneId) {
+               return friendSones.contains(friendSoneId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone addFriend(String friendSone) {
+               if (!friendSone.equals(id)) {
+                       friendSones.add(friendSone);
+               }
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone removeFriend(String friendSoneId) {
+               friendSones.remove(friendSoneId);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public List<Post> getPosts() {
+               List<Post> sortedPosts;
+               synchronized (this) {
+                       sortedPosts = new ArrayList<Post>(posts);
+               }
+               Collections.sort(sortedPosts, Post.TIME_COMPARATOR);
+               return sortedPosts;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setPosts(Collection<Post> posts) {
+               synchronized (this) {
+                       this.posts.clear();
+                       this.posts.addAll(posts);
+               }
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void addPost(Post post) {
+               if (post.getSone().equals(this) && posts.add(post)) {
+                       logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName()));
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removePost(Post post) {
+               if (post.getSone().equals(this)) {
+                       posts.remove(post);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Set<PostReply> getReplies() {
+               return Collections.unmodifiableSet(replies);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setReplies(Collection<PostReply> replies) {
+               this.replies.clear();
+               this.replies.addAll(replies);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void addReply(PostReply reply) {
+               if (reply.getSone().equals(this)) {
+                       replies.add(reply);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removeReply(PostReply reply) {
+               if (reply.getSone().equals(this)) {
+                       replies.remove(reply);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Set<String> getLikedPostIds() {
+               return Collections.unmodifiableSet(likedPostIds);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setLikePostIds(Set<String> likedPostIds) {
+               this.likedPostIds.clear();
+               this.likedPostIds.addAll(likedPostIds);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isLikedPostId(String postId) {
+               return likedPostIds.contains(postId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone addLikedPostId(String postId) {
+               likedPostIds.add(postId);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone removeLikedPostId(String postId) {
+               likedPostIds.remove(postId);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Set<String> getLikedReplyIds() {
+               return Collections.unmodifiableSet(likedReplyIds);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone setLikeReplyIds(Set<String> likedReplyIds) {
+               this.likedReplyIds.clear();
+               this.likedReplyIds.addAll(likedReplyIds);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isLikedReplyId(String replyId) {
+               return likedReplyIds.contains(replyId);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone addLikedReplyId(String replyId) {
+               likedReplyIds.add(replyId);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Sone removeLikedReplyId(String replyId) {
+               likedReplyIds.remove(replyId);
+               return this;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public List<Album> getAlbums() {
+               return Collections.unmodifiableList(albums);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       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;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public List<Image> getAllImages() {
+               List<Image> allImages = new ArrayList<Image>();
+               for (Album album : getAllAlbums()) {
+                       allImages.addAll(album.getImages());
+               }
+               return allImages;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void addAlbum(Album album) {
+               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
+               if (!albums.contains(album)) {
+                       albums.add(album);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setAlbums(Collection<? extends Album> albums) {
+               Validation.begin().isNotNull("Albums", albums).check();
+               this.albums.clear();
+               for (Album album : albums) {
+                       addAlbum(album);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removeAlbum(Album album) {
+               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).check();
+               albums.remove(album);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Album moveAlbumUp(Album album) {
+               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check();
+               int oldIndex = albums.indexOf(album);
+               if (oldIndex <= 0) {
+                       return null;
+               }
+               albums.remove(oldIndex);
+               albums.add(oldIndex - 1, album);
+               return albums.get(oldIndex);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Album moveAlbumDown(Album album) {
+               Validation.begin().isNotNull("Album", album).check().isEqual("Album Owner", album.getSone(), this).isNull("Album Parent", album.getParent()).check();
+               int oldIndex = albums.indexOf(album);
+               if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
+                       return null;
+               }
+               albums.remove(oldIndex);
+               albums.add(oldIndex + 1, album);
+               return albums.get(oldIndex);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Options getOptions() {
+               return options;
+       }
+
+       //
+       // FINGERPRINTABLE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public synchronized String getFingerprint() {
+               StringBuilder fingerprint = new StringBuilder();
+               fingerprint.append(profile.getFingerprint());
+
+               fingerprint.append("Posts(");
+               for (Post post : getPosts()) {
+                       fingerprint.append("Post(").append(post.getId()).append(')');
+               }
+               fingerprint.append(")");
+
+               List<PostReply> replies = new ArrayList<PostReply>(getReplies());
+               Collections.sort(replies, Reply.TIME_COMPARATOR);
+               fingerprint.append("Replies(");
+               for (PostReply reply : replies) {
+                       fingerprint.append("Reply(").append(reply.getId()).append(')');
+               }
+               fingerprint.append(')');
+
+               List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
+               Collections.sort(likedPostIds);
+               fingerprint.append("LikedPosts(");
+               for (String likedPostId : likedPostIds) {
+                       fingerprint.append("Post(").append(likedPostId).append(')');
+               }
+               fingerprint.append(')');
+
+               List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
+               Collections.sort(likedReplyIds);
+               fingerprint.append("LikedReplies(");
+               for (String likedReplyId : likedReplyIds) {
+                       fingerprint.append("Reply(").append(likedReplyId).append(')');
+               }
+               fingerprint.append(')');
+
+               fingerprint.append("Albums(");
+               for (Album album : albums) {
+                       fingerprint.append(album.getFingerprint());
+               }
+               fingerprint.append(')');
+
+               return fingerprint.toString();
+       }
+
+       //
+       // INTERFACE Comparable<Sone>
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public int compareTo(Sone sone) {
+               return NICE_NAME_COMPARATOR.compare(this, sone);
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean equals(Object object) {
+               if (!(object instanceof Sone)) {
+                       return false;
+               }
+               return ((Sone) object).getId().equals(id);
+       }
+
+       /**
+        * {@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 a37dc46..bc90594 100644 (file)
@@ -28,7 +28,7 @@ import freenet.support.api.Bucket;
 /**
  * FCP command that creates a new {@link Post}.
  *
- * @see Core#createPost(Sone, Sone, String)
+ * @see Core#createPost(Sone, Sone, long, String)
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
 public class CreatePostCommand extends AbstractSoneCommand {
@@ -57,7 +57,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, recipient, System.currentTimeMillis(), text);
                return new Response("PostCreated", new SimpleFieldSetBuilder().put("Post", post.getId()).get());
        }
 
index 54f01c8..8d929f3 100644 (file)
@@ -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..33374d9 100644 (file)
@@ -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 5b28b85..1bc8b5a 100644 (file)
@@ -25,6 +25,8 @@ 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.memory.MemoryDatabase;
 import net.pterodactylus.sone.fcp.FcpInterface;
 import net.pterodactylus.sone.freenet.PluginStoreConfigurationBackend;
 import net.pterodactylus.sone.freenet.plugin.PluginConnector;
@@ -188,12 +190,15 @@ public class SonePlugin implements FredPlugin, FredPluginFCP, FredPluginL10n, Fr
                        webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
                        identityManager = new IdentityManager(webOfTrustConnector, "Sone");
 
+                       /* create Sone database. */
+                       Database soneDatabase = new MemoryDatabase();
+
                        /* create trust updater. */
                        WebOfTrustUpdater trustUpdater = new WebOfTrustUpdater(webOfTrustConnector);
                        trustUpdater.init();
 
                        /* create core. */
-                       core = new Core(oldConfiguration, freenetInterface, identityManager, trustUpdater);
+                       core = new Core(oldConfiguration, soneDatabase, freenetInterface, identityManager, trustUpdater);
 
                        /* create the web interface. */
                        webInterface = new WebInterface(this);
index f004967..19177c3 100644 (file)
@@ -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 eca8654..9d4bdc8 100644 (file)
@@ -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 b3593ec..2c209d5 100644 (file)
@@ -30,6 +30,7 @@ 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.memory.MemorySone;
 import net.pterodactylus.util.io.Closer;
 import net.pterodactylus.util.logging.Logging;
 import freenet.keys.FreenetURI;
@@ -245,7 +246,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                                                 * don’t use create=true above, we don’t want
                                                                 * the empty shell.
                                                                 */
-                                                               sone = new Sone(soneId);
+                                                               sone = new MemorySone(soneId, false);
                                                        }
                                                        parts.add(new SonePart(sone));
                                                        line = line.substring(50);
index 82e4c51..be83549 100644 (file)
@@ -58,7 +58,7 @@ 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);
+                       Post post = webInterface.getCore().getPost(postId, false);
                        if (text.length() > 0) {
                                String senderId = request.getHttpRequest().getPartAsStringFailsafe("sender", 43);
                                Sone sender = webInterface.getCore().getLocalSone(senderId, false);
index 2f2b766..e175870 100644 (file)
@@ -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..69b1d27 100644 (file)
@@ -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..002cfbd 100644 (file)
@@ -55,15 +55,15 @@ 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);
+                       Post post = webInterface.getCore().getPost(postId, false);
                        templateContext.set("post", post);
                        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())) {
+                       Post post = webInterface.getCore().getPost(postId, false);
+                       if (!post.getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
index 900acd9..b07dead 100644 (file)
@@ -56,7 +56,7 @@ public class DeleteReplyPage extends SoneTemplatePage {
                PostReply reply = webInterface.getCore().getReply(replyId);
                String returnPage = request.getHttpRequest().getPartAsStringFailsafe("returnPage", 256);
                if (request.getMethod() == Method.POST) {
-                       if (!webInterface.getCore().isLocalSone(reply.getSone())) {
+                       if (!reply.getSone().isLocal()) {
                                throw new RedirectException("noPermission.html");
                        }
                        if (request.getHttpRequest().isPartSet("confirmDelete")) {
index d028f8d..fd28e41 100644 (file)
@@ -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..0207f8c 100644 (file)
@@ -60,7 +60,7 @@ 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))) {
index 0f12abf..524e501 100644 (file)
@@ -131,7 +131,7 @@ public class SearchPage extends SoneTemplatePage {
                        redirectIfNotNull(getImageId(phrase), "imageBrowser.html?image=");
                }
 
-               Set<Sone> sones = webInterface.getCore().getSones();
+               Collection<Sone> sones = webInterface.getCore().getSones();
                Set<Hit<Sone>> soneHits = getHits(sones, phrases, SoneStringGenerator.COMPLETE_GENERATOR);
 
                Set<Hit<Post>> postHits;
index 98b2603..336157e 100644 (file)
@@ -72,7 +72,7 @@ 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);
+               Post post = webInterface.getCore().getPost(postId, false);
                templateContext.set("post", post);
                templateContext.set("raw", raw);
        }
index 00f8cf9..75b5f50 100644 (file)
@@ -372,7 +372,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();
                }
@@ -725,8 +725,8 @@ 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 Core#getLocalSones() local Sone}s that are referenced
+        * by {@link SonePart}s in the given text (after parsing it using
         * {@link SoneTextParser}).
         *
         * @param text
@@ -789,15 +789,14 @@ public class WebInterface implements CoreListener {
         */
        @Override
        public void newPostFound(Post post) {
-               boolean isLocal = getCore().isLocalSone(post.getSone());
-               if (isLocal) {
+               if (post.getSone().isLocal()) {
                        localPostNotification.add(post);
                } else {
                        newPostNotification.add(post);
                }
                if (!hasFirstStartNotification()) {
-                       notificationManager.addNotification(isLocal ? localPostNotification : newPostNotification);
-                       if (!getMentionedSones(post.getText()).isEmpty() && !isLocal) {
+                       notificationManager.addNotification(post.getSone().isLocal() ? localPostNotification : newPostNotification);
+                       if (!getMentionedSones(post.getText()).isEmpty() && !post.getSone().isLocal()) {
                                mentionNotification.add(post);
                                notificationManager.addNotification(mentionNotification);
                        }
@@ -811,15 +810,14 @@ public class WebInterface implements CoreListener {
         */
        @Override
        public void newReplyFound(PostReply reply) {
-               boolean isLocal = getCore().isLocalSone(reply.getSone());
-               if (isLocal) {
+               if (reply.getSone().isLocal()) {
                        localReplyNotification.add(reply);
                } else {
                        newReplyNotification.add(reply);
                }
                if (!hasFirstStartNotification()) {
-                       notificationManager.addNotification(isLocal ? localReplyNotification : newReplyNotification);
-                       if (!getMentionedSones(reply.getText()).isEmpty() && !isLocal && (reply.getPost().getSone() != null) && (reply.getTime() <= System.currentTimeMillis())) {
+                       notificationManager.addNotification(reply.getSone().isLocal() ? localReplyNotification : newReplyNotification);
+                       if (!getMentionedSones(reply.getText()).isEmpty() && !reply.getSone().isLocal() && (reply.getPost().getSone() != null) && (reply.getTime() <= System.currentTimeMillis())) {
                                mentionNotification.add(reply.getPost());
                                notificationManager.addNotification(mentionNotification);
                        }
index 53a5a51..2280935 100644 (file)
@@ -62,7 +62,7 @@ public class CreatePostAjaxPage extends JsonPage {
                        return createErrorJsonObject("text-required");
                }
                text = TextFilter.filter(request.getHttpRequest().getHeader("host"), text);
-               Post newPost = webInterface.getCore().createPost(sender, recipient, text);
+               Post newPost = webInterface.getCore().createPost(sender, recipient, System.currentTimeMillis(), text);
                return createSuccessJsonObject().put("postId", newPost.getId()).put("sone", sender.getId()).put("recipient", (newPost.getRecipient() != null) ? newPost.getRecipient().getId() : null);
        }
 
index 20857f3..8626d38 100644 (file)
@@ -58,7 +58,7 @@ public class CreateReplyAjaxPage extends JsonPage {
                if (sender == null) {
                        sender = getCurrentSone(request.getToadletContext());
                }
-               Post post = webInterface.getCore().getPost(postId);
+               Post post = webInterface.getCore().getPost(postId, false);
                if ((post == null) || (post.getSone() == null)) {
                        return createErrorJsonObject("invalid-post-id");
                }
index ea2310c..b822e52 100644 (file)
@@ -53,7 +53,7 @@ public class DeletePostAjaxPage extends JsonPage {
                if ((post == null) || (post.getSone() == null)) {
                        return createErrorJsonObject("invalid-post-id");
                }
-               if (!webInterface.getCore().isLocalSone(post.getSone())) {
+               if (!post.getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
                webInterface.getCore().deletePost(post);
index 0442117..6bccf0b 100644 (file)
@@ -53,7 +53,7 @@ public class DeleteReplyAjaxPage extends JsonPage {
                if (reply == null) {
                        return createErrorJsonObject("invalid-reply-id");
                }
-               if (!webInterface.getCore().isLocalSone(reply.getSone())) {
+               if (!reply.getSone().isLocal()) {
                        return createErrorJsonObject("not-authorized");
                }
                webInterface.getCore().deleteReply(reply);
index 92e1e48..0e72ea5 100644 (file)
@@ -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..4a1689b 100644 (file)
@@ -63,7 +63,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"))) {
index 293b4b0..34018af 100644 (file)
@@ -63,7 +63,7 @@ public class GetLikesAjaxPage extends JsonPage {
                        return createErrorJsonObject("invalid-" + type + "-id");
                }
                if ("post".equals(type)) {
-                       Post post = webInterface.getCore().getPost(id);
+                       Post post = webInterface.getCore().getPost(id, false);
                        Set<Sone> sones = webInterface.getCore().getLikes(post);
                        return createSuccessJsonObject().put("likes", sones.size()).put("sones", getSones(sones));
                } else if ("reply".equals(type)) {
index e1857a0..3dc25e8 100644 (file)
@@ -24,6 +24,7 @@ import java.util.Arrays;
 import junit.framework.TestCase;
 import net.pterodactylus.sone.core.SoneProvider;
 import net.pterodactylus.sone.data.Sone;
+import net.pterodactylus.sone.database.memory.MemorySone;
 
 /**
  * JUnit test case for {@link SoneTextParser}.
@@ -182,7 +183,7 @@ public class SoneTextParserTest extends TestCase {
                 */
                @Override
                public Sone getSone(final String soneId, boolean create) {
-                       return new Sone(soneId) {
+                       return new MemorySone(soneId, false) {
 
                                /**
                                 * {@inheritDoc}