X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fdata%2FSone.java;h=b761c7bca81b7b91223de629856450eafcd65804;hp=ca878564fa706665e031a0704a60cb7d768d7982;hb=ffd92ca2374c0b2218e583d02e0bdd24b8c110ae;hpb=192a3f41bcbf99fdb42213dc2680f9061ff64c40 diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index ca87856..b761c7b 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -1,5 +1,5 @@ /* - * Sone - Sone.java - Copyright © 2010 David Roden + * Sone - Sone.java - Copyright © 2010–2016 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,34 +17,57 @@ package net.pterodactylus.sone.data; -import java.util.ArrayList; +import static com.google.common.collect.FluentIterable.from; +import static java.util.Arrays.asList; +import static net.pterodactylus.sone.data.Album.FLATTENER; +import static net.pterodactylus.sone.data.Album.IMAGES; + 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.logging.Level; -import java.util.logging.Logger; -import net.pterodactylus.sone.core.Core; -import net.pterodactylus.sone.core.Options; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import net.pterodactylus.sone.freenet.wot.Identity; 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 freenet.keys.FreenetURI; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.primitives.Ints; + /** * A Sone defines everything about a user: her profile, her status updates, her * replies, her likes and dislikes, etc. - *

- * Operations that modify the Sone need to synchronize on the Sone in question. * * @author David ‘Bombe’ Roden */ -public class Sone implements Fingerprintable, Comparable { +public interface Sone extends Identified, Fingerprintable, Comparable { + + /** + * Enumeration for the possible states of a {@link Sone}. + * + * @author David ‘Bombe’ Roden + */ + 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, + } /** comparator that sorts Sones by their nice name. */ public static final Comparator NICE_NAME_COMPARATOR = new Comparator() { @@ -60,9 +83,7 @@ public class Sone implements Fingerprintable, Comparable { }; - /** - * Comparator that sorts Sones by last activity (least recent active first). - */ + /** Comparator that sorts Sones by last activity (least recent active first). */ public static final Comparator LAST_ACTIVITY_COMPARATOR = new Comparator() { @Override @@ -71,649 +92,429 @@ public class Sone implements Fingerprintable, Comparable { } }; - /** Filter to remove Sones that have not been downloaded. */ - public static final Filter EMPTY_SONE_FILTER = new Filter() { + /** Comparator that sorts Sones by numbers of posts (descending). */ + public static final Comparator POST_COUNT_COMPARATOR = new Comparator() { + /** + * {@inheritDoc} + */ @Override - public boolean filterObject(Sone sone) { - return sone.getTime() != 0; + 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()); } }; - /** Filter that matches all {@link Core#isLocalSone(Sone) local Sones}. */ - public static final Filter LOCAL_SONE_FILTER = new Filter() { + /** Comparator that sorts Sones by number of images (descending). */ + public static final Comparator IMAGE_COUNT_COMPARATOR = new Comparator() { + /** + * {@inheritDoc} + */ @Override - public boolean filterObject(Sone sone) { - return sone.getIdentity() instanceof OwnIdentity; + public int compare(Sone leftSone, Sone rightSone) { + int rightSoneImageCount = from(asList(rightSone.getRootAlbum())).transformAndConcat(FLATTENER).transformAndConcat(IMAGES).size(); + int leftSoneImageCount = from(asList(leftSone.getRootAlbum())).transformAndConcat(FLATTENER).transformAndConcat(IMAGES).size(); + /* sort descending. */ + return Ints.compare(rightSoneImageCount, leftSoneImageCount); } - }; - /** 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 profile of this Sone. */ - private volatile Profile profile = new Profile(); - - /** The client used by the Sone. */ - private volatile Client client; - - /** All friend Sones. */ - private final Set friendSones = Collections.synchronizedSet(new HashSet()); + /** Filter to remove Sones that have not been downloaded. */ + public static final Predicate EMPTY_SONE_FILTER = new Predicate() { - /** All posts. */ - private final Set posts = Collections.synchronizedSet(new HashSet()); + @Override + public boolean apply(Sone sone) { + return (sone != null) && (sone.getTime() != 0); + } + }; - /** All replies. */ - private final Set replies = Collections.synchronizedSet(new HashSet()); + /** Filter that matches all {@link Sone#isLocal() local Sones}. */ + public static final Predicate LOCAL_SONE_FILTER = new Predicate() { - /** The IDs of all liked posts. */ - private final Set likedPostIds = Collections.synchronizedSet(new HashSet()); + @Override + public boolean apply(Sone sone) { + return (sone != null) && (sone.getIdentity() instanceof OwnIdentity); + } - /** The IDs of all liked replies. */ - private final Set likedReplyIds = Collections.synchronizedSet(new HashSet()); + }; - /** Sone-specific options. */ - private final Options options = new Options(); + /** Filter that matches Sones that have at least one album. */ + public static final Predicate HAS_ALBUM_FILTER = new Predicate() { - /** - * Creates a new Sone. - * - * @param id - * The ID of the Sone - */ - public Sone(String id) { - this.id = id; - } + @Override + public boolean apply(Sone sone) { + return (sone != null) && !sone.getRootAlbum().getAlbums().isEmpty(); + } + }; - // - // ACCESSORS - // + public static final Function> toAllAlbums = new Function>() { + @Override + public List apply(@Nullable Sone sone) { + return (sone == null) ? Collections.emptyList() : FLATTENER.apply( + sone.getRootAlbum()); + } + }; - /** - * Returns the identity of this Sone. - * - * @return The identity of this Sone - */ - public String getId() { - return id; - } + public static final Function> toAllImages = new Function>() { + @Override + public List apply(@Nullable Sone sone) { + return (sone == null) ? Collections.emptyList() : + from(FLATTENER.apply(sone.getRootAlbum())) + .transformAndConcat(IMAGES).toList(); + } + }; /** * Returns the identity of this Sone. * * @return The identity of this Sone */ - public Identity getIdentity() { - return identity; - } - - /** - * Sets the identity of this Sone. The {@link Identity#getId() ID} of the - * identity has to match this Sone’s {@link #getId()}. - * - * @param identity - * The identity of this Sone - * @return This Sone (for method chaining) - * @throws IllegalArgumentException - * if the ID of the identity does not match this Sone’s ID - */ - 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; - } + @Nonnull + Identity getIdentity(); /** * Returns the name of this Sone. * * @return The name of this Sone */ - public String getName() { - return (identity != null) ? identity.getNickname() : null; - } + @Nonnull + String getName(); /** - * Returns the request URI of this Sone. + * Returns whether this Sone is a local Sone. * - * @return The request URI of this Sone + * @return {@code true} if this Sone is a local Sone, {@code false} otherwise */ - public FreenetURI getRequestUri() { - return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null; - } + boolean isLocal(); /** - * Sets the request URI of this Sone. + * Returns the request URI of this Sone. * - * @param requestUri - * The request URI of this Sone - * @return This Sone (for method chaining) + * @return The request URI of this Sone */ - 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, "Request URI %s tried to overwrite %s!", new Object[] { requestUri, this.requestUri }); - return this; - } - return this; - } + @Nonnull + FreenetURI getRequestUri(); /** * Returns the insert URI of this Sone. * * @return The insert URI of this Sone */ - public FreenetURI getInsertUri() { - return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null; - } - - /** - * Sets the insert URI of this Sone. - * - * @param insertUri - * 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, "Request URI %s tried to overwrite %s!", new Object[] { insertUri, this.insertUri }); - return this; - } - return this; - } + @Nullable + FreenetURI getInsertUri(); /** * Returns the latest edition of this Sone. * * @return The latest edition of this Sone */ - public long getLatestEdition() { - return latestEdition; - } + long getLatestEdition(); /** * Sets the latest edition of this Sone. If the given latest edition is not - * greater than the current latest edition, the latest edition of this Sone - * is not changed. + * greater than the current latest edition, the latest edition of this Sone is + * not changed. * * @param latestEdition - * The latest edition of this Sone + * The latest edition of this Sone */ - public void setLatestEdition(long latestEdition) { - if (!(latestEdition > this.latestEdition)) { - logger.log(Level.INFO, "New latest edition %d is not greater than current latest edition %d!", new Object[] { latestEdition, this.latestEdition }); - return; - } - this.latestEdition = latestEdition; - } + 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; - } + long getTime(); /** * Sets the time of the last inserted update of this Sone. * * @param time - * The time of the update (in milliseconds since Jan 1, 1970 UTC) + * The time of the update (in milliseconds since Jan 1, 1970 UTC) * @return This Sone (for method chaining) */ - public Sone setTime(long time) { - this.time = time; - return this; - } + @Nonnull + Sone setTime(long time); + + /** + * Returns the status of this Sone. + * + * @return The status of this Sone + */ + @Nonnull + SoneStatus getStatus(); + + /** + * 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} + */ + @Nonnull + Sone setStatus(@Nonnull SoneStatus status); /** - * Returns a copy of the profile. If you want to update values in the - * profile of this Sone, update the values in the returned {@link Profile} - * and use {@link #setProfile(Profile)} to change the profile in this Sone. + * Returns a copy of the profile. If you want to update values in the profile + * of this Sone, update the values in the returned {@link Profile} and use + * {@link #setProfile(Profile)} to change the profile in this Sone. * * @return A copy of the profile */ - public synchronized Profile getProfile() { - return new Profile(profile); - } + @Nonnull + Profile getProfile(); /** - * Sets the profile of this Sone. A copy of the given profile is stored so - * that subsequent modifications of the given profile are not reflected in - * this Sone! + * Sets the profile of this Sone. A copy of the given profile is stored so that + * subsequent modifications of the given profile are not reflected in this + * Sone! * * @param profile - * The profile to set + * The profile to set */ - public synchronized void setProfile(Profile profile) { - this.profile = new Profile(profile); - } + void setProfile(@Nonnull Profile profile); /** * Returns the client used by this Sone. * * @return The client used by this Sone, or {@code null} */ - public Client getClient() { - return client; - } + @Nullable + Client getClient(); /** * Sets the client used by this Sone. * * @param client - * The client used by this Sone, or {@code null} + * The client used by this Sone, or {@code null} * @return This Sone (for method chaining) */ - public Sone setClient(Client client) { - this.client = client; - return this; - } + @Nonnull + Sone setClient(@Nullable Client client); /** - * 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 getFriends() { - List friends = new ArrayList(friendSones); - return friends; - } + boolean isKnown(); /** - * 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 friends) { - friendSones.clear(); - friendSones.addAll(friends); - return this; - } + @Nonnull + Sone setKnown(boolean known); /** - * Returns whether this Sone has the given Sone as a friend Sone. - * - * @param friendSoneId - * The ID of the Sone to check for - * @return {@code true} if this Sone has the given Sone as a friend, - * {@code false} otherwise - */ - public boolean hasFriend(String friendSoneId) { - return friendSones.contains(friendSoneId); - } - - /** - * Adds the given Sone as a friend Sone. + * Returns all friend Sones of this Sone. * - * @param friendSone - * The friend Sone to add - * @return This Sone (for method chaining) + * @return The friend Sones of this Sone */ - public Sone addFriend(String friendSone) { - if (!friendSone.equals(id)) { - friendSones.add(friendSone); - } - return this; - } + @Nonnull + Collection getFriends(); /** - * Removes the given Sone as a friend Sone. + * Returns whether this Sone has the given Sone as a friend Sone. * * @param friendSoneId - * The ID of the friend Sone to remove - * @return This Sone (for method chaining) + * The ID of the Sone to check for + * @return {@code true} if this Sone has the given Sone as a friend, {@code + * false} otherwise */ - public Sone removeFriend(String friendSoneId) { - friendSones.remove(friendSoneId); - return this; - } + boolean hasFriend(@Nonnull String friendSoneId); /** * Returns the list of posts of this Sone, sorted by time, newest first. * * @return All posts of this Sone */ - public List getPosts() { - List sortedPosts; - synchronized (this) { - sortedPosts = new ArrayList(posts); - } - Collections.sort(sortedPosts, Post.TIME_COMPARATOR); - return sortedPosts; - } + @Nonnull + List getPosts(); /** * Sets all posts of this Sone at once. * * @param posts - * The new (and only) posts of this Sone + * The new (and only) posts of this Sone * @return This Sone (for method chaining) */ - public synchronized Sone setPosts(Collection posts) { - synchronized (this) { - this.posts.clear(); - this.posts.addAll(posts); - } - return this; - } + @Nonnull + Sone setPosts(@Nonnull Collection posts); /** - * Adds the given post to this Sone. The post will not be added if its - * {@link Post#getSone() Sone} is not this Sone. + * Adds the given post to this Sone. The post will not be added if its {@link + * Post#getSone() Sone} is not this Sone. * * @param post - * The post to add + * The post to add */ - public synchronized void addPost(Post post) { - if (post.getSone().equals(this) && posts.add(post)) { - logger.log(Level.FINEST, "Adding %s to “%s”.", new Object[] { post, getName() }); - } - } + void addPost(@Nonnull Post post); /** * Removes the given post from this Sone. * * @param post - * The post to remove + * The post to remove */ - public synchronized void removePost(Post post) { - if (post.getSone().equals(this)) { - posts.remove(post); - } - } + void removePost(@Nonnull Post post); /** * Returns all replies this Sone made. * * @return All replies this Sone made */ - public synchronized Set getReplies() { - return Collections.unmodifiableSet(replies); - } + @Nonnull + Set getReplies(); /** * Sets all replies of this Sone at once. * * @param replies - * The new (and only) replies of this Sone + * The new (and only) replies of this Sone * @return This Sone (for method chaining) */ - public synchronized Sone setReplies(Collection replies) { - this.replies.clear(); - this.replies.addAll(replies); - return this; - } + @Nonnull + Sone setReplies(@Nonnull Collection replies); /** * Adds a reply to this Sone. If the given reply was not made by this Sone, * nothing is added to this Sone. * * @param reply - * The reply to add + * The reply to add */ - public synchronized void addReply(Reply reply) { - if (reply.getSone().equals(this)) { - replies.add(reply); - } - } + void addReply(@Nonnull PostReply reply); /** * Removes a reply from this Sone. * * @param reply - * The reply to remove + * The reply to remove */ - public synchronized void removeReply(Reply reply) { - if (reply.getSone().equals(this)) { - replies.remove(reply); - } - } + void removeReply(@Nonnull PostReply reply); /** * Returns the IDs of all liked posts. * * @return All liked posts’ IDs */ - public Set getLikedPostIds() { - return Collections.unmodifiableSet(likedPostIds); - } + @Nonnull + Set getLikedPostIds(); /** * Sets the IDs of all liked posts. * * @param likedPostIds - * All liked posts’ IDs + * All liked posts’ IDs * @return This Sone (for method chaining) */ - public synchronized Sone setLikePostIds(Set likedPostIds) { - this.likedPostIds.clear(); - this.likedPostIds.addAll(likedPostIds); - return this; - } + @Nonnull + Sone setLikePostIds(@Nonnull Set likedPostIds); /** * Checks whether the given post ID is liked by this Sone. * * @param postId - * The ID of the post + * The ID of the post * @return {@code true} if this Sone likes the given post, {@code false} * otherwise */ - public boolean isLikedPostId(String postId) { - return likedPostIds.contains(postId); - } + boolean isLikedPostId(@Nonnull String postId); /** * Adds the given post ID to the list of posts this Sone likes. * * @param postId - * The ID of the post + * The ID of the post * @return This Sone (for method chaining) */ - public synchronized Sone addLikedPostId(String postId) { - likedPostIds.add(postId); - return this; - } + @Nonnull + Sone addLikedPostId(@Nonnull String postId); /** * Removes the given post ID from the list of posts this Sone likes. * * @param postId - * The ID of the post - * @return This Sone (for method chaining) + * The ID of the post */ - public synchronized Sone removeLikedPostId(String postId) { - likedPostIds.remove(postId); - return this; - } + void removeLikedPostId(@Nonnull String postId); /** * Returns the IDs of all liked replies. * * @return All liked replies’ IDs */ - public Set getLikedReplyIds() { - return Collections.unmodifiableSet(likedReplyIds); - } + @Nonnull + Set getLikedReplyIds(); /** * Sets the IDs of all liked replies. * * @param likedReplyIds - * All liked replies’ IDs + * All liked replies’ IDs * @return This Sone (for method chaining) */ - public synchronized Sone setLikeReplyIds(Set likedReplyIds) { - this.likedReplyIds.clear(); - this.likedReplyIds.addAll(likedReplyIds); - return this; - } + @Nonnull + Sone setLikeReplyIds(@Nonnull Set likedReplyIds); /** * Checks whether the given reply ID is liked by this Sone. * * @param replyId - * The ID of the reply + * The ID of the reply * @return {@code true} if this Sone likes the given reply, {@code false} * otherwise */ - public boolean isLikedReplyId(String replyId) { - return likedReplyIds.contains(replyId); - } + boolean isLikedReplyId(@Nonnull String replyId); /** * Adds the given reply ID to the list of replies this Sone likes. * * @param replyId - * The ID of the reply + * The ID of the reply * @return This Sone (for method chaining) */ - public synchronized Sone addLikedReplyId(String replyId) { - likedReplyIds.add(replyId); - return this; - } + @Nonnull + Sone addLikedReplyId(@Nonnull String replyId); /** * Removes the given post ID from the list of replies this Sone likes. * * @param replyId - * The ID of the reply - * @return This Sone (for method chaining) + * The ID of the reply */ - public synchronized Sone removeLikedReplyId(String replyId) { - likedReplyIds.remove(replyId); - return this; - } + void removeLikedReplyId(@Nonnull String replyId); /** - * Returns Sone-specific options. + * Returns the root album that contains all visible albums of this Sone. * - * @return The options of this Sone + * @return The root album of this Sone */ - public Options getOptions() { - return options; - } - - // - // FINGERPRINTABLE METHODS - // + @Nonnull + Album getRootAlbum(); /** - * {@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 replies = new ArrayList(getReplies()); - Collections.sort(replies, Reply.TIME_COMPARATOR); - fingerprint.append("Replies("); - for (Reply reply : replies) { - fingerprint.append("Reply(").append(reply.getId()).append(')'); - } - fingerprint.append(')'); - - List likedPostIds = new ArrayList(getLikedPostIds()); - Collections.sort(likedPostIds); - fingerprint.append("LikedPosts("); - for (String likedPostId : likedPostIds) { - fingerprint.append("Post(").append(likedPostId).append(')'); - } - fingerprint.append(')'); - - List likedReplyIds = new ArrayList(getLikedReplyIds()); - Collections.sort(likedReplyIds); - fingerprint.append("LikedReplies("); - for (String likedReplyId : likedReplyIds) { - fingerprint.append("Reply(").append(likedReplyId).append(')'); - } - fingerprint.append(')'); - - return fingerprint.toString(); - } - - // - // INTERFACE Comparable - // - - /** - * {@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} + * Returns Sone-specific options. + * + * @return The options of this Sone */ - @Override - public boolean equals(Object object) { - if (!(object instanceof Sone)) { - return false; - } - return ((Sone) object).id.equals(id); - } + @Nonnull + SoneOptions getOptions(); /** - * {@inheritDoc} + * Sets the options of this Sone. + * + * @param options + * The options of this Sone */ - @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() + ")]"; - } + /* TODO - remove this method again, maybe add an option provider */ + void setOptions(@Nonnull SoneOptions options); }