2 * FreenetSone - Sone.java - Copyright © 2010 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.sone.data;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.HashSet;
25 import java.util.List;
27 import java.util.UUID;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
31 import net.pterodactylus.sone.template.SoneAccessor;
32 import net.pterodactylus.util.logging.Logging;
33 import freenet.keys.FreenetURI;
36 * A Sone defines everything about a user: her profile, her status updates, her
37 * replies, her likes and dislikes, etc.
39 * Operations that modify the Sone need to synchronize on the Sone in question.
41 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
46 private static final Logger logger = Logging.getLogger(Sone.class);
48 /** A GUID for this Sone. */
49 private final UUID id;
51 /** The name of this Sone. */
52 private volatile String name;
54 /** The URI under which the Sone is stored in Freenet. */
55 private volatile FreenetURI requestUri;
57 /** The URI used to insert a new version of this Sone. */
58 /* This will be null for remote Sones! */
59 private volatile FreenetURI insertUri;
61 /** The time of the last inserted update. */
62 private volatile long time;
64 /** The profile of this Sone. */
65 private volatile Profile profile;
67 /** All friend Sones. */
68 private final Set<Sone> friendSones = Collections.synchronizedSet(new HashSet<Sone>());
71 private final Set<Post> posts = Collections.synchronizedSet(new HashSet<Post>());
74 private final Set<Reply> replies = Collections.synchronizedSet(new HashSet<Reply>());
76 /** The IDs of all blocked Sones. */
77 private final Set<String> blockedSoneIds = Collections.synchronizedSet(new HashSet<String>());
79 /** The IDs of all liked posts. */
80 private final Set<String> likedPostIds = Collections.synchronizedSet(new HashSet<String>());
82 /** The IDs of all liked replies. */
83 private final Set<String> likedReplyIds = Collections.synchronizedSet(new HashSet<String>());
85 /** Modification count. */
86 private volatile long modificationCounter = 0;
94 public Sone(String id) {
95 this.id = UUID.fromString(id);
103 * Returns the ID of this Sone.
105 * @return The ID of this Sone
107 public String getId() {
108 return id.toString();
112 * Returns the name of this Sone.
114 * @return The name of this Sone
116 public String getName() {
121 * Sets the name of this Sone.
124 * The name of this Sone
125 * @return This sone (for method chaining)
127 public Sone setName(String name) {
133 * Returns the request URI of this Sone.
135 * @return The request URI of this Sone
137 public FreenetURI getRequestUri() {
142 * Sets the request URI of this Sone.
145 * The request URI of this Sone
146 * @return This Sone (for method chaining)
148 public Sone setRequestUri(FreenetURI requestUri) {
149 if (this.requestUri == null) {
150 this.requestUri = requestUri;
154 if (!this.requestUri.equalsKeypair(requestUri)) {
155 logger.log(Level.WARNING, "Request URI %s tried to overwrite %s!", new Object[] { requestUri, this.requestUri });
158 long latestEdition = requestUri.getEdition();
159 if ((latestEdition > this.requestUri.getEdition()) || (latestEdition > this.requestUri.getSuggestedEdition())) {
160 this.requestUri.setSuggestedEdition(latestEdition);
166 * Returns the insert URI of this Sone.
168 * @return The insert URI of this Sone
170 public FreenetURI getInsertUri() {
175 * Sets the insert URI of this Sone.
178 * The insert URI of this Sone
179 * @return This Sone (for method chaining)
181 public Sone setInsertUri(FreenetURI insertUri) {
182 if (this.insertUri == null) {
183 this.insertUri = insertUri;
187 if (!this.insertUri.equalsKeypair(insertUri)) {
188 logger.log(Level.WARNING, "Request URI %s tried to overwrite %s!", new Object[] { insertUri, this.insertUri });
191 long latestEdition = insertUri.getEdition();
192 if ((latestEdition > this.insertUri.getEdition()) || (latestEdition > this.insertUri.getSuggestedEdition())) {
193 this.insertUri.setSuggestedEdition(latestEdition);
199 * Return the time of the last inserted update of this Sone.
201 * @return The time of the update (in milliseconds since Jan 1, 1970 UTC)
203 public long getTime() {
208 * Sets the time of the last inserted update of this Sone.
211 * The time of the update (in milliseconds since Jan 1, 1970 UTC)
212 * @return This Sone (for method chaining)
214 public Sone setTime(long time) {
220 * Returns a copy of the profile. If you want to update values in the
221 * profile of this Sone, update the values in the returned {@link Profile}
222 * and use {@link #setProfile(Profile)} to change the profile in this Sone.
224 * @return A copy of the profile
226 public Profile getProfile() {
227 return new Profile(profile);
231 * Sets the profile of this Sone. A copy of the given profile is stored so
232 * that subsequent modifications of the given profile are not reflected in
238 public synchronized void setProfile(Profile profile) {
239 this.profile = new Profile(profile);
240 modificationCounter++;
244 * Returns all friend Sones of this Sone.
246 * @return The friend Sones of this Sone
248 public List<Sone> getFriends() {
249 List<Sone> friends = new ArrayList<Sone>(friendSones);
250 Collections.sort(friends, new Comparator<Sone>() {
253 public int compare(Sone leftSone, Sone rightSone) {
254 int diff = SoneAccessor.getNiceName(leftSone).compareToIgnoreCase(SoneAccessor.getNiceName(rightSone));
258 return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, rightSone.getTime() - leftSone.getTime()));
265 * Sets all friends of this Sone at once.
268 * The new (and only) friends of this Sone
269 * @return This Sone (for method chaining)
271 public Sone setFriends(Collection<Sone> friends) {
273 friendSones.addAll(friends);
278 * Returns whether this Sone has the given Sone as a friend Sone.
281 * The friend Sone to check for
282 * @return {@code true} if this Sone has the given Sone as a friend,
283 * {@code false} otherwise
285 public boolean hasFriend(Sone friendSone) {
286 return friendSones.contains(friendSone);
290 * Adds the given Sone as a friend Sone.
293 * The friend Sone to add
294 * @return This Sone (for method chaining)
296 public Sone addFriend(Sone friendSone) {
297 if (!friendSone.equals(this)) {
298 friendSones.add(friendSone);
304 * Removes the given Sone as a friend Sone.
307 * The friend Sone to remove
308 * @return This Sone (for method chaining)
310 public Sone removeFriend(Sone friendSone) {
311 friendSones.remove(friendSone);
316 * Returns the list of posts of this Sone, sorted by time, newest first.
318 * @return All posts of this Sone
320 public List<Post> getPosts() {
321 List<Post> sortedPosts = new ArrayList<Post>(posts);
322 Collections.sort(sortedPosts, new Comparator<Post>() {
325 public int compare(Post leftPost, Post rightPost) {
326 return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, rightPost.getTime() - leftPost.getTime()));
334 * Sets all posts of this Sone at once.
337 * The new (and only) posts of this Sone
338 * @return This Sone (for method chaining)
340 public synchronized Sone setPosts(Collection<Post> posts) {
342 this.posts.addAll(posts);
343 modificationCounter++;
348 * Adds the given post to this Sone. The post will not be added if its
349 * {@link Post#getSone() Sone} is not this Sone.
354 public synchronized void addPost(Post post) {
355 if (post.getSone().equals(this) && posts.add(post)) {
356 logger.log(Level.FINEST, "Adding %s to “%s”.", new Object[] { post, getName() });
357 modificationCounter++;
362 * Removes the given post from this Sone.
367 public synchronized void removePost(Post post) {
368 if (post.getSone().equals(this) && posts.remove(post)) {
369 modificationCounter++;
374 * Returns all replies this Sone made.
376 * @return All replies this Sone made
378 public Set<Reply> getReplies() {
379 logger.log(Level.FINEST, "Friends of %s: %s", new Object[] { this, friendSones });
380 return Collections.unmodifiableSet(replies);
384 * Sets all replies of this Sone at once.
387 * The new (and only) replies of this Sone
388 * @return This Sone (for method chaining)
390 public synchronized Sone setReplies(Collection<Reply> replies) {
391 this.replies.clear();
392 this.replies.addAll(replies);
393 modificationCounter++;
398 * Adds a reply to this Sone. If the given reply was not made by this Sone,
399 * nothing is added to this Sone.
404 public synchronized void addReply(Reply reply) {
405 if (reply.getSone().equals(this) && replies.add(reply)) {
406 modificationCounter++;
411 * Removes a reply from this Sone.
414 * The reply to remove
416 public synchronized void removeReply(Reply reply) {
417 if (reply.getSone().equals(this) && replies.remove(reply)) {
418 modificationCounter++;
423 * Returns the IDs of all blocked Sones. These Sones will not propagated
424 * using the “known Sones” mechanism.
426 * @return The IDs of all blocked Sones
428 public Set<String> getBlockedSoneIds() {
429 return Collections.unmodifiableSet(blockedSoneIds);
433 * Returns whether the given Sone ID is blocked.
436 * The Sone ID to check
437 * @return {@code true} if the given Sone ID is blocked, {@code false}
440 public boolean isSoneBlocked(String soneId) {
441 return blockedSoneIds.contains(soneId);
445 * Adds the given ID to the list of blocked IDs.
448 * The Sone ID to block
450 public synchronized void addBlockedSoneId(String soneId) {
451 if (blockedSoneIds.add(soneId)) {
452 modificationCounter++;
457 * Removes the given ID from the list of blocked IDs.
460 * The Sone ID to unblock
462 public synchronized void removeBlockedSoneId(String soneId) {
463 if (blockedSoneIds.remove(soneId)) {
464 modificationCounter++;
469 * Returns the IDs of all liked posts.
471 * @return All liked posts’ IDs
473 public Set<String> getLikedPostIds() {
474 return Collections.unmodifiableSet(likedPostIds);
478 * Sets the IDs of all liked posts.
480 * @param likedPostIds
481 * All liked posts’ IDs
482 * @return This Sone (for method chaining)
484 public synchronized Sone setLikePostIds(Set<String> likedPostIds) {
485 this.likedPostIds.clear();
486 this.likedPostIds.addAll(likedPostIds);
487 modificationCounter++;
492 * Checks whether the given post ID is liked by this Sone.
496 * @return {@code true} if this Sone likes the given post, {@code false}
499 public boolean isLikedPostId(String postId) {
500 return likedPostIds.contains(postId);
504 * Adds the given post ID to the list of posts this Sone likes.
508 * @return This Sone (for method chaining)
510 public synchronized Sone addLikedPostId(String postId) {
511 if (likedPostIds.add(postId)) {
512 modificationCounter++;
518 * Removes the given post ID from the list of posts this Sone likes.
522 * @return This Sone (for method chaining)
524 public synchronized Sone removeLikedPostId(String postId) {
525 if (likedPostIds.remove(postId)) {
526 modificationCounter++;
532 * Returns the IDs of all liked replies.
534 * @return All liked replies’ IDs
536 public Set<String> getLikedReplyIds() {
537 return Collections.unmodifiableSet(likedReplyIds);
541 * Sets the IDs of all liked replies.
543 * @param likedReplyIds
544 * All liked replies’ IDs
545 * @return This Sone (for method chaining)
547 public synchronized Sone setLikeReplyIds(Set<String> likedReplyIds) {
548 this.likedReplyIds.clear();
549 this.likedReplyIds.addAll(likedReplyIds);
550 modificationCounter++;
555 * Checks whether the given reply ID is liked by this Sone.
558 * The ID of the reply
559 * @return {@code true} if this Sone likes the given reply, {@code false}
562 public boolean isLikedReplyId(String replyId) {
563 return likedReplyIds.contains(replyId);
567 * Adds the given reply ID to the list of replies this Sone likes.
570 * The ID of the reply
571 * @return This Sone (for method chaining)
573 public synchronized Sone addLikedReplyId(String replyId) {
574 if (likedReplyIds.add(replyId)) {
575 modificationCounter++;
581 * Removes the given post ID from the list of replies this Sone likes.
584 * The ID of the reply
585 * @return This Sone (for method chaining)
587 public synchronized Sone removeLikedReplyId(String replyId) {
588 if (likedReplyIds.remove(replyId)) {
589 modificationCounter++;
595 * Returns the modification counter.
597 * @return The modification counter
599 public synchronized long getModificationCounter() {
600 return modificationCounter;
604 * Sets the modification counter.
606 * @param modificationCounter
607 * The new modification counter
609 public synchronized void setModificationCounter(long modificationCounter) {
610 this.modificationCounter = modificationCounter;
614 * Updates the suggested edition in both the request URI and the insert URI.
616 * @param latestEdition
617 * The latest edition to update the URIs to
619 public void updateUris(long latestEdition) {
620 if ((requestUri != null) && (requestUri.getEdition() < latestEdition)) {
621 requestUri = requestUri.setSuggestedEdition(latestEdition);
623 if ((insertUri != null) && (insertUri.getEdition() < latestEdition)) {
624 insertUri = insertUri.setSuggestedEdition(latestEdition);
633 * Updates the editions of the request URI and the insert URI (if latter is
634 * not {@code null}) with the greater edition of either one.
636 private void updateEditions() {
637 long requestEdition = 0;
638 if (requestUri != null) {
639 requestEdition = requestUri.getEdition();
641 long insertEdition = 0;
642 if (insertUri != null) {
643 insertEdition = insertUri.getEdition();
645 updateUris(Math.max(requestEdition, insertEdition));
656 public int hashCode() {
657 return id.hashCode();
664 public boolean equals(Object object) {
665 if (!(object instanceof Sone)) {
668 return ((Sone) object).id.equals(id);
675 public String toString() {
676 return getClass().getName() + "[id=" + id + ",name=" + name + ",requestUri=" + requestUri + ",insertUri=" + insertUri + ",friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]";