X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsone%2Fdata%2FSone.java;h=e5ebcf1df439ff42ec71540233e42bd8c99e153c;hp=7d4f7ef5fd520d0189f38d90cd59b8d5fb046cfa;hb=c9e306ac8e3ada846e87a0cc256a20fc148f381c;hpb=34f87eac013c2a6c29101af7e4654bd91fcd1df8 diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 7d4f7ef..e5ebcf1 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 @@ /* - * FreenetSone - Sone.java - Copyright © 2010 David Roden + * Sone - Sone.java - Copyright © 2010 David Roden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,15 +21,21 @@ 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; +import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.core.Options; import net.pterodactylus.sone.freenet.wot.Identity; +import net.pterodactylus.sone.freenet.wot.OwnIdentity; import net.pterodactylus.sone.template.SoneAccessor; +import net.pterodactylus.util.filter.Filter; import net.pterodactylus.util.logging.Logging; +import net.pterodactylus.util.validation.Validation; import freenet.keys.FreenetURI; /** @@ -40,7 +46,51 @@ import freenet.keys.FreenetURI; * * @author David ‘Bombe’ Roden */ -public class Sone { +public class Sone implements 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, + } + + /** + * The possible values for the “show custom avatars” option. + * + * @author David ‘Bombe’ Roden + */ + 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 NICE_NAME_COMPARATOR = new Comparator() { @@ -56,6 +106,69 @@ public class Sone { }; + /** + * Comparator that sorts Sones by last activity (least recent active first). + */ + public static final Comparator LAST_ACTIVITY_COMPARATOR = new Comparator() { + + @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 POST_COUNT_COMPARATOR = new Comparator() { + + /** + * {@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 IMAGE_COUNT_COMPARATOR = new Comparator() { + + /** + * {@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 EMPTY_SONE_FILTER = new Filter() { + + @Override + public boolean filterObject(Sone sone) { + return sone.getTime() != 0; + } + }; + + /** Filter that matches all {@link Core#isLocalSone(Sone) local Sones}. */ + public static final Filter LOCAL_SONE_FILTER = new Filter() { + + @Override + public boolean filterObject(Sone sone) { + return sone.getIdentity() instanceof OwnIdentity; + } + + }; + + /** Filter that matches Sones that have at least one album. */ + public static final Filter HAS_ALBUM_FILTER = new Filter() { + + @Override + public boolean filterObject(Sone sone) { + return !sone.getAlbums().isEmpty(); + } + }; + /** The logger. */ private static final Logger logger = Logging.getLogger(Sone.class); @@ -78,26 +191,38 @@ public class 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 friendSones = Collections.synchronizedSet(new HashSet()); + private final Set friendSones = new CopyOnWriteArraySet(); /** All posts. */ - private final Set posts = Collections.synchronizedSet(new HashSet()); + private final Set posts = new CopyOnWriteArraySet(); /** All replies. */ - private final Set replies = Collections.synchronizedSet(new HashSet()); + private final Set replies = new CopyOnWriteArraySet(); /** The IDs of all liked posts. */ - private final Set likedPostIds = Collections.synchronizedSet(new HashSet()); + private final Set likedPostIds = new CopyOnWriteArraySet(); /** The IDs of all liked replies. */ - private final Set likedReplyIds = Collections.synchronizedSet(new HashSet()); + private final Set likedReplyIds = new CopyOnWriteArraySet(); + + /** The albums of this Sone. */ + private final List albums = new CopyOnWriteArrayList(); + + /** Sone-specific options. */ + private final Options options = new Options(); /** * Creates a new Sone. @@ -176,11 +301,11 @@ public class Sone { */ public Sone setRequestUri(FreenetURI requestUri) { if (this.requestUri == null) { - this.requestUri = requestUri.setDocName("Sone").setMetaString(new String[0]); + 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 }); + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri)); return this; } return this; @@ -204,11 +329,11 @@ public class Sone { */ public Sone setInsertUri(FreenetURI insertUri) { if (this.insertUri == null) { - this.insertUri = insertUri.setDocName("Sone").setMetaString(new String[0]); + 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 }); + logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri)); return this; } return this; @@ -233,7 +358,7 @@ public class 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; @@ -261,13 +386,37 @@ public class 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); } @@ -279,7 +428,7 @@ public class Sone { * @param profile * The profile to set */ - public synchronized void setProfile(Profile profile) { + public void setProfile(Profile profile) { this.profile = new Profile(profile); } @@ -305,29 +454,37 @@ public class 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 getFriends() { - List friends = new ArrayList(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 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 getFriends() { + List friends = new ArrayList(friendSones); + return friends; + } + + /** * Returns whether this Sone has the given Sone as a friend Sone. * * @param friendSoneId @@ -386,9 +543,11 @@ public class Sone { * The new (and only) posts of this Sone * @return This Sone (for method chaining) */ - public synchronized Sone setPosts(Collection posts) { - this.posts.clear(); - this.posts.addAll(posts); + public Sone setPosts(Collection posts) { + synchronized (this) { + this.posts.clear(); + this.posts.addAll(posts); + } return this; } @@ -399,9 +558,9 @@ public class 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())); } } @@ -411,7 +570,7 @@ public class 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); } @@ -422,7 +581,7 @@ public class Sone { * * @return All replies this Sone made */ - public synchronized Set getReplies() { + public Set getReplies() { return Collections.unmodifiableSet(replies); } @@ -433,7 +592,7 @@ public class Sone { * The new (and only) replies of this Sone * @return This Sone (for method chaining) */ - public synchronized Sone setReplies(Collection replies) { + public Sone setReplies(Collection replies) { this.replies.clear(); this.replies.addAll(replies); return this; @@ -446,7 +605,7 @@ public class 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); } @@ -458,7 +617,7 @@ public class 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); } @@ -480,7 +639,7 @@ public class Sone { * All liked posts’ IDs * @return This Sone (for method chaining) */ - public synchronized Sone setLikePostIds(Set likedPostIds) { + public Sone setLikePostIds(Set likedPostIds) { this.likedPostIds.clear(); this.likedPostIds.addAll(likedPostIds); return this; @@ -505,7 +664,7 @@ public class 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; } @@ -517,7 +676,7 @@ public class 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; } @@ -538,7 +697,7 @@ public class Sone { * All liked replies’ IDs * @return This Sone (for method chaining) */ - public synchronized Sone setLikeReplyIds(Set likedReplyIds) { + public Sone setLikeReplyIds(Set likedReplyIds) { this.likedReplyIds.clear(); this.likedReplyIds.addAll(likedReplyIds); return this; @@ -563,7 +722,7 @@ public class 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; } @@ -575,41 +734,151 @@ public class 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 a fingerprint of this Sone. The fingerprint only depends on data - * that is actually stored when a Sone is inserted. The fingerprint can be - * used to detect changes in Sone data and can also be used to detect if - * previous changes are reverted. + * Returns the albums of this Sone. * - * @return The fingerprint of this Sone + * @return The albums of this Sone */ - public synchronized String getFingerprint() { - StringBuilder fingerprint = new StringBuilder(); - fingerprint.append("Profile("); - if (profile.getFirstName() != null) { - fingerprint.append("FirstName(").append(profile.getFirstName()).append(')'); - } - if (profile.getMiddleName() != null) { - fingerprint.append("MiddleName(").append(profile.getMiddleName()).append(')'); + public List 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 getAllAlbums() { + List flatAlbums = new ArrayList(); + flatAlbums.addAll(albums); + int lastAlbumIndex = 0; + while (lastAlbumIndex < flatAlbums.size()) { + int previousAlbumCount = flatAlbums.size(); + for (Album album : new ArrayList(flatAlbums.subList(lastAlbumIndex, flatAlbums.size()))) { + flatAlbums.addAll(album.getAlbums()); + } + lastAlbumIndex = previousAlbumCount; } - if (profile.getLastName() != null) { - fingerprint.append("LastName(").append(profile.getLastName()).append(')'); + 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 getAllImages() { + List allImages = new ArrayList(); + for (Album album : getAllAlbums()) { + allImages.addAll(album.getImages()); } - if (profile.getBirthDay() != null) { - fingerprint.append("BirthDay(").append(profile.getBirthDay()).append(')'); + 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 albums) { + Validation.begin().isNotNull("Albums", albums).check(); + this.albums.clear(); + for (Album album : albums) { + addAlbum(album); } - if (profile.getBirthMonth() != null) { - fingerprint.append("BirthMonth(").append(profile.getBirthMonth()).append(')'); + } + + /** + * 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 + * null 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; } - if (profile.getBirthYear() != null) { - fingerprint.append("BirthYear(").append(profile.getBirthYear()).append(')'); + 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 + * null 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; } - fingerprint.append(")"); + albums.remove(oldIndex); + albums.add(oldIndex + 1, album); + return albums.get(oldIndex); + } + + /** + * 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()) { @@ -617,10 +886,10 @@ public class Sone { } fingerprint.append(")"); - List replies = new ArrayList(getReplies()); + List replies = new ArrayList(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(')'); @@ -641,10 +910,28 @@ public class Sone { } fingerprint.append(')'); + fingerprint.append("Albums("); + for (Album album : albums) { + fingerprint.append(album.getFingerprint()); + } + fingerprint.append(')'); + return fingerprint.toString(); } // + // INTERFACE Comparable + // + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Sone sone) { + return NICE_NAME_COMPARATOR.compare(this, sone); + } + + // // OBJECT METHODS //