Fix ALL the logging!
[Sone.git] / src / main / java / net / pterodactylus / sone / data / Sone.java
index e1c0391..e5ebcf1 100644 (file)
@@ -21,9 +21,10 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashSet;
 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;
 
@@ -34,6 +35,7 @@ import net.pterodactylus.sone.freenet.wot.OwnIdentity;
 import net.pterodactylus.sone.template.SoneAccessor;
 import net.pterodactylus.util.filter.Filter;
 import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.validation.Validation;
 import freenet.keys.FreenetURI;
 
 /**
@@ -46,6 +48,50 @@ import freenet.keys.FreenetURI;
  */
 public class Sone implements Fingerprintable, Comparable<Sone> {
 
+       /**
+        * Enumeration for the possible states of a {@link Sone}.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public enum SoneStatus {
+
+               /** The Sone is unknown, i.e. not yet downloaded. */
+               unknown,
+
+               /** The Sone is idle, i.e. not being downloaded or inserted. */
+               idle,
+
+               /** The Sone is currently being inserted. */
+               inserting,
+
+               /** The Sone is currently being downloaded. */
+               downloading,
+       }
+
+       /**
+        * The possible values for the “show custom avatars” option.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public static enum ShowCustomAvatars {
+
+               /** Never show custom avatars. */
+               NEVER,
+
+               /** Only show custom avatars of followed Sones. */
+               FOLLOWED,
+
+               /** Only show custom avatars of Sones you manually trust. */
+               MANUALLY_TRUSTED,
+
+               /** Only show custom avatars of automatically trusted Sones. */
+               TRUSTED,
+
+               /** Always show custom avatars. */
+               ALWAYS,
+
+       }
+
        /** comparator that sorts Sones by their nice name. */
        public static final Comparator<Sone> NICE_NAME_COMPARATOR = new Comparator<Sone>() {
 
@@ -60,6 +106,41 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
        };
 
+       /**
+        * Comparator that sorts Sones by last activity (least recent active first).
+        */
+       public static final Comparator<Sone> LAST_ACTIVITY_COMPARATOR = new Comparator<Sone>() {
+
+               @Override
+               public int compare(Sone firstSone, Sone secondSone) {
+                       return (int) Math.min(Integer.MAX_VALUE, Math.max(Integer.MIN_VALUE, secondSone.getTime() - firstSone.getTime()));
+               }
+       };
+
+       /** Comparator that sorts Sones by numbers of posts (descending). */
+       public static final Comparator<Sone> POST_COUNT_COMPARATOR = new Comparator<Sone>() {
+
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public int compare(Sone leftSone, Sone rightSone) {
+                       return (leftSone.getPosts().size() != rightSone.getPosts().size()) ? (rightSone.getPosts().size() - leftSone.getPosts().size()) : (rightSone.getReplies().size() - leftSone.getReplies().size());
+               }
+       };
+
+       /** Comparator that sorts Sones by number of images (descending). */
+       public static final Comparator<Sone> IMAGE_COUNT_COMPARATOR = new Comparator<Sone>() {
+
+               /**
+                * {@inheritDoc}
+                */
+               @Override
+               public int compare(Sone leftSone, Sone rightSone) {
+                       return rightSone.getAllImages().size() - leftSone.getAllImages().size();
+               }
+       };
+
        /** Filter to remove Sones that have not been downloaded. */
        public static final Filter<Sone> EMPTY_SONE_FILTER = new Filter<Sone>() {
 
@@ -79,6 +160,15 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
 
        };
 
+       /** Filter that matches Sones that have at least one album. */
+       public static final Filter<Sone> HAS_ALBUM_FILTER = new Filter<Sone>() {
+
+               @Override
+               public boolean filterObject(Sone sone) {
+                       return !sone.getAlbums().isEmpty();
+               }
+       };
+
        /** The logger. */
        private static final Logger logger = Logging.getLogger(Sone.class);
 
@@ -101,26 +191,35 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        /** 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();
+       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 = Collections.synchronizedSet(new HashSet<String>());
+       private final Set<String> friendSones = new CopyOnWriteArraySet<String>();
 
        /** All posts. */
-       private final Set<Post> posts = Collections.synchronizedSet(new HashSet<Post>());
+       private final Set<Post> posts = new CopyOnWriteArraySet<Post>();
 
        /** All replies. */
-       private final Set<Reply> replies = Collections.synchronizedSet(new HashSet<Reply>());
+       private final Set<PostReply> replies = new CopyOnWriteArraySet<PostReply>();
 
        /** The IDs of all liked posts. */
-       private final Set<String> likedPostIds = Collections.synchronizedSet(new HashSet<String>());
+       private final Set<String> likedPostIds = new CopyOnWriteArraySet<String>();
 
        /** The IDs of all liked replies. */
-       private final Set<String> likedReplyIds = Collections.synchronizedSet(new HashSet<String>());
+       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();
@@ -206,7 +305,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                        return this;
                }
                if (!this.requestUri.equalsKeypair(requestUri)) {
-                       logger.log(Level.WARNING, "Request URI %s tried to overwrite %s!", new Object[] { requestUri, this.requestUri });
+                       logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri));
                        return this;
                }
                return this;
@@ -234,7 +333,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                        return this;
                }
                if (!this.insertUri.equalsKeypair(insertUri)) {
-                       logger.log(Level.WARNING, "Request URI %s tried to overwrite %s!", new Object[] { insertUri, this.insertUri });
+                       logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri));
                        return this;
                }
                return this;
@@ -259,7 +358,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         */
        public void setLatestEdition(long latestEdition) {
                if (!(latestEdition > this.latestEdition)) {
-                       logger.log(Level.INFO, "New latest edition %d is not greater than current latest edition %d!", new Object[] { 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;
@@ -287,13 +386,37 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
+        * Returns the status of this Sone.
+        *
+        * @return The status of this Sone
+        */
+       public SoneStatus getStatus() {
+               return status;
+       }
+
+       /**
+        * Sets the new status of this Sone.
+        *
+        * @param status
+        *            The new status of this Sone
+        * @return This 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;
+       }
+
+       /**
         * Returns a copy of the profile. If you want to update values in the
         * profile of this Sone, update the values in the returned {@link Profile}
         * and use {@link #setProfile(Profile)} to change the profile in this Sone.
         *
         * @return A copy of the profile
         */
-       public synchronized Profile getProfile() {
+       public Profile getProfile() {
                return new Profile(profile);
        }
 
@@ -305,7 +428,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param profile
         *            The profile to set
         */
-       public synchronized void setProfile(Profile profile) {
+       public void setProfile(Profile profile) {
                this.profile = new Profile(profile);
        }
 
@@ -331,29 +454,37 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
        }
 
        /**
-        * Returns all friend Sones of this Sone.
+        * Returns whether this Sone is known.
         *
-        * @return The friend Sones of this Sone
+        * @return {@code true} if this Sone is known, {@code false} otherwise
         */
-       public List<String> getFriends() {
-               List<String> friends = new ArrayList<String>(friendSones);
-               return friends;
+       public boolean isKnown() {
+               return known;
        }
 
        /**
-        * Sets all friends of this Sone at once.
+        * Sets whether this Sone is known.
         *
-        * @param friends
-        *            The new (and only) friends of this Sone
-        * @return This Sone (for method chaining)
+        * @param known
+        *            {@code true} if this Sone is known, {@code false} otherwise
+        * @return This Sone
         */
-       public Sone setFriends(Collection<String> friends) {
-               friendSones.clear();
-               friendSones.addAll(friends);
+       public Sone setKnown(boolean known) {
+               this.known = known;
                return this;
        }
 
        /**
+        * 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;
+       }
+
+       /**
         * Returns whether this Sone has the given Sone as a friend Sone.
         *
         * @param friendSoneId
@@ -412,7 +543,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The new (and only) posts of this Sone
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone setPosts(Collection<Post> posts) {
+       public Sone setPosts(Collection<Post> posts) {
                synchronized (this) {
                        this.posts.clear();
                        this.posts.addAll(posts);
@@ -427,9 +558,9 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param post
         *            The post to add
         */
-       public synchronized void addPost(Post post) {
+       public void addPost(Post post) {
                if (post.getSone().equals(this) && posts.add(post)) {
-                       logger.log(Level.FINEST, "Adding %s to “%s”.", new Object[] { post, getName() });
+                       logger.log(Level.FINEST, String.format("Adding %s to “%s”.", post, getName()));
                }
        }
 
@@ -439,7 +570,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param post
         *            The post to remove
         */
-       public synchronized void removePost(Post post) {
+       public void removePost(Post post) {
                if (post.getSone().equals(this)) {
                        posts.remove(post);
                }
@@ -450,7 +581,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *
         * @return All replies this Sone made
         */
-       public synchronized Set<Reply> getReplies() {
+       public Set<PostReply> getReplies() {
                return Collections.unmodifiableSet(replies);
        }
 
@@ -461,7 +592,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The new (and only) replies of this Sone
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone setReplies(Collection<Reply> replies) {
+       public Sone setReplies(Collection<PostReply> replies) {
                this.replies.clear();
                this.replies.addAll(replies);
                return this;
@@ -474,7 +605,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param reply
         *            The reply to add
         */
-       public synchronized void addReply(Reply reply) {
+       public void addReply(PostReply reply) {
                if (reply.getSone().equals(this)) {
                        replies.add(reply);
                }
@@ -486,7 +617,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         * @param reply
         *            The reply to remove
         */
-       public synchronized void removeReply(Reply reply) {
+       public void removeReply(PostReply reply) {
                if (reply.getSone().equals(this)) {
                        replies.remove(reply);
                }
@@ -508,7 +639,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            All liked posts’ IDs
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone setLikePostIds(Set<String> likedPostIds) {
+       public Sone setLikePostIds(Set<String> likedPostIds) {
                this.likedPostIds.clear();
                this.likedPostIds.addAll(likedPostIds);
                return this;
@@ -533,7 +664,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the post
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone addLikedPostId(String postId) {
+       public Sone addLikedPostId(String postId) {
                likedPostIds.add(postId);
                return this;
        }
@@ -545,7 +676,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the post
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone removeLikedPostId(String postId) {
+       public Sone removeLikedPostId(String postId) {
                likedPostIds.remove(postId);
                return this;
        }
@@ -566,7 +697,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            All liked replies’ IDs
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone setLikeReplyIds(Set<String> likedReplyIds) {
+       public Sone setLikeReplyIds(Set<String> likedReplyIds) {
                this.likedReplyIds.clear();
                this.likedReplyIds.addAll(likedReplyIds);
                return this;
@@ -591,7 +722,7 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the reply
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone addLikedReplyId(String replyId) {
+       public Sone addLikedReplyId(String replyId) {
                likedReplyIds.add(replyId);
                return this;
        }
@@ -603,12 +734,132 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
         *            The ID of the reply
         * @return This Sone (for method chaining)
         */
-       public synchronized Sone removeLikedReplyId(String replyId) {
+       public Sone removeLikedReplyId(String replyId) {
                likedReplyIds.remove(replyId);
                return this;
        }
 
        /**
+        * Returns the albums of this Sone.
+        *
+        * @return The albums of this Sone
+        */
+       public List<Album> getAlbums() {
+               return Collections.unmodifiableList(albums);
+       }
+
+       /**
+        * Returns a flattened list of all albums of this Sone. The resulting list
+        * contains parent albums before child albums so that the resulting list can
+        * be parsed in a single pass.
+        *
+        * @return The flattened albums
+        */
+       public List<Album> getAllAlbums() {
+               List<Album> flatAlbums = new ArrayList<Album>();
+               flatAlbums.addAll(albums);
+               int lastAlbumIndex = 0;
+               while (lastAlbumIndex < flatAlbums.size()) {
+                       int previousAlbumCount = flatAlbums.size();
+                       for (Album album : new ArrayList<Album>(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) {
+                               flatAlbums.addAll(album.getAlbums());
+                       }
+                       lastAlbumIndex = previousAlbumCount;
+               }
+               return flatAlbums;
+       }
+
+       /**
+        * Returns all images of a Sone. Images of a album are inserted into this
+        * list before images of all child albums.
+        *
+        * @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;
+       }
+
+       /**
+        * Adds an album to this 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();
+               albums.add(album);
+       }
+
+       /**
+        * Sets the albums of this 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);
+               }
+       }
+
+       /**
+        * Removes an album from this 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);
+       }
+
+       /**
+        * Moves the given album up in this album’s albums. If the album is already
+        * the first album, nothing happens.
+        *
+        * @param album
+        *            The album to move up
+        * @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);
+       }
+
+       /**
+        * Moves the given album down in this album’s albums. If the album is
+        * already the last album, nothing happens.
+        *
+        * @param album
+        *            The album to move down
+        * @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);
+       }
+
+       /**
         * Returns Sone-specific options.
         *
         * @return The options of this Sone
@@ -635,10 +886,10 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                }
                fingerprint.append(")");
 
-               List<Reply> replies = new ArrayList<Reply>(getReplies());
+               List<PostReply> replies = new ArrayList<PostReply>(getReplies());
                Collections.sort(replies, Reply.TIME_COMPARATOR);
                fingerprint.append("Replies(");
-               for (Reply reply : replies) {
+               for (PostReply reply : replies) {
                        fingerprint.append("Reply(").append(reply.getId()).append(')');
                }
                fingerprint.append(')');
@@ -659,6 +910,12 @@ public class Sone implements Fingerprintable, Comparable<Sone> {
                }
                fingerprint.append(')');
 
+               fingerprint.append("Albums(");
+               for (Album album : albums) {
+                       fingerprint.append(album.getFingerprint());
+               }
+               fingerprint.append(')');
+
                return fingerprint.toString();
        }