Merge branch 'next' into wot-integration
[Sone.git] / src / main / java / net / pterodactylus / sone / data / Sone.java
1 /*
2  * FreenetSone - Sone.java - Copyright © 2010 David Roden
3  *
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.pterodactylus.sone.data;
19
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;
26 import java.util.Set;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29
30 import net.pterodactylus.sone.template.SoneAccessor;
31 import net.pterodactylus.util.logging.Logging;
32 import freenet.keys.FreenetURI;
33
34 /**
35  * A Sone defines everything about a user: her profile, her status updates, her
36  * replies, her likes and dislikes, etc.
37  * <p>
38  * Operations that modify the Sone need to synchronize on the Sone in question.
39  *
40  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
41  */
42 public class Sone {
43
44         /** The logger. */
45         private static final Logger logger = Logging.getLogger(Sone.class);
46
47         /** The ID of this Sone. */
48         private final String id;
49
50         /** The name of this Sone. */
51         private volatile String name;
52
53         /** The URI under which the Sone is stored in Freenet. */
54         private volatile FreenetURI requestUri;
55
56         /** The URI used to insert a new version of this Sone. */
57         /* This will be null for remote Sones! */
58         private volatile FreenetURI insertUri;
59
60         /** The time of the last inserted update. */
61         private volatile long time;
62
63         /** The profile of this Sone. */
64         private volatile Profile profile;
65
66         /** All friend Sones. */
67         private final Set<Sone> friendSones = Collections.synchronizedSet(new HashSet<Sone>());
68
69         /** All posts. */
70         private final Set<Post> posts = Collections.synchronizedSet(new HashSet<Post>());
71
72         /** All replies. */
73         private final Set<Reply> replies = Collections.synchronizedSet(new HashSet<Reply>());
74
75         /** The IDs of all liked posts. */
76         private final Set<String> likedPostIds = Collections.synchronizedSet(new HashSet<String>());
77
78         /** The IDs of all liked replies. */
79         private final Set<String> likedReplyIds = Collections.synchronizedSet(new HashSet<String>());
80
81         /** Modification count. */
82         private volatile long modificationCounter = 0;
83
84         /**
85          * Creates a new Sone.
86          *
87          * @param id
88          *            The ID of this Sone
89          */
90         public Sone(String id) {
91                 this.id = id;
92         }
93
94         //
95         // ACCESSORS
96         //
97
98         /**
99          * Returns the ID of this Sone.
100          *
101          * @return The ID of this Sone
102          */
103         public String getId() {
104                 return id;
105         }
106
107         /**
108          * Returns the name of this Sone.
109          *
110          * @return The name of this Sone
111          */
112         public String getName() {
113                 return name;
114         }
115
116         /**
117          * Sets the name of this Sone.
118          *
119          * @param name
120          *            The name of this Sone
121          * @return This sone (for method chaining)
122          */
123         public Sone setName(String name) {
124                 this.name = name;
125                 return this;
126         }
127
128         /**
129          * Returns the request URI of this Sone.
130          *
131          * @return The request URI of this Sone
132          */
133         public FreenetURI getRequestUri() {
134                 return requestUri;
135         }
136
137         /**
138          * Sets the request URI of this Sone.
139          *
140          * @param requestUri
141          *            The request URI of this Sone
142          * @return This Sone (for method chaining)
143          */
144         public Sone setRequestUri(FreenetURI requestUri) {
145                 if (this.requestUri == null) {
146                         this.requestUri = requestUri;
147                         updateEditions();
148                         return this;
149                 }
150                 if (!this.requestUri.equalsKeypair(requestUri)) {
151                         logger.log(Level.WARNING, "Request URI %s tried to overwrite %s!", new Object[] { requestUri, this.requestUri });
152                         return this;
153                 }
154                 long latestEdition = requestUri.getEdition();
155                 if ((latestEdition > this.requestUri.getEdition()) || (latestEdition > this.requestUri.getSuggestedEdition())) {
156                         this.requestUri.setSuggestedEdition(latestEdition);
157                 }
158                 return this;
159         }
160
161         /**
162          * Returns the insert URI of this Sone.
163          *
164          * @return The insert URI of this Sone
165          */
166         public FreenetURI getInsertUri() {
167                 return insertUri;
168         }
169
170         /**
171          * Sets the insert URI of this Sone.
172          *
173          * @param insertUri
174          *            The insert URI of this Sone
175          * @return This Sone (for method chaining)
176          */
177         public Sone setInsertUri(FreenetURI insertUri) {
178                 if (this.insertUri == null) {
179                         this.insertUri = insertUri;
180                         updateEditions();
181                         return this;
182                 }
183                 if (!this.insertUri.equalsKeypair(insertUri)) {
184                         logger.log(Level.WARNING, "Request URI %s tried to overwrite %s!", new Object[] { insertUri, this.insertUri });
185                         return this;
186                 }
187                 long latestEdition = insertUri.getEdition();
188                 if ((latestEdition > this.insertUri.getEdition()) || (latestEdition > this.insertUri.getSuggestedEdition())) {
189                         this.insertUri.setSuggestedEdition(latestEdition);
190                 }
191                 return this;
192         }
193
194         /**
195          * Return the time of the last inserted update of this Sone.
196          *
197          * @return The time of the update (in milliseconds since Jan 1, 1970 UTC)
198          */
199         public long getTime() {
200                 return time;
201         }
202
203         /**
204          * Sets the time of the last inserted update of this Sone.
205          *
206          * @param time
207          *            The time of the update (in milliseconds since Jan 1, 1970 UTC)
208          * @return This Sone (for method chaining)
209          */
210         public Sone setTime(long time) {
211                 this.time = time;
212                 return this;
213         }
214
215         /**
216          * Returns a copy of the profile. If you want to update values in the
217          * profile of this Sone, update the values in the returned {@link Profile}
218          * and use {@link #setProfile(Profile)} to change the profile in this Sone.
219          *
220          * @return A copy of the profile
221          */
222         public Profile getProfile() {
223                 return new Profile(profile);
224         }
225
226         /**
227          * Sets the profile of this Sone. A copy of the given profile is stored so
228          * that subsequent modifications of the given profile are not reflected in
229          * this Sone!
230          *
231          * @param profile
232          *            The profile to set
233          */
234         public synchronized void setProfile(Profile profile) {
235                 this.profile = new Profile(profile);
236                 modificationCounter++;
237         }
238
239         /**
240          * Returns all friend Sones of this Sone.
241          *
242          * @return The friend Sones of this Sone
243          */
244         public List<Sone> getFriends() {
245                 List<Sone> friends = new ArrayList<Sone>(friendSones);
246                 Collections.sort(friends, new Comparator<Sone>() {
247
248                         @Override
249                         public int compare(Sone leftSone, Sone rightSone) {
250                                 int diff = SoneAccessor.getNiceName(leftSone).compareToIgnoreCase(SoneAccessor.getNiceName(rightSone));
251                                 if (diff != 0) {
252                                         return diff;
253                                 }
254                                 return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, rightSone.getTime() - leftSone.getTime()));
255                         }
256                 });
257                 return friends;
258         }
259
260         /**
261          * Sets all friends of this Sone at once.
262          *
263          * @param friends
264          *            The new (and only) friends of this Sone
265          * @return This Sone (for method chaining)
266          */
267         public Sone setFriends(Collection<Sone> friends) {
268                 friendSones.clear();
269                 friendSones.addAll(friends);
270                 return this;
271         }
272
273         /**
274          * Returns whether this Sone has the given Sone as a friend Sone.
275          *
276          * @param friendSone
277          *            The friend Sone to check for
278          * @return {@code true} if this Sone has the given Sone as a friend,
279          *         {@code false} otherwise
280          */
281         public boolean hasFriend(Sone friendSone) {
282                 return friendSones.contains(friendSone);
283         }
284
285         /**
286          * Adds the given Sone as a friend Sone.
287          *
288          * @param friendSone
289          *            The friend Sone to add
290          * @return This Sone (for method chaining)
291          */
292         public Sone addFriend(Sone friendSone) {
293                 if (!friendSone.equals(this)) {
294                         friendSones.add(friendSone);
295                 }
296                 return this;
297         }
298
299         /**
300          * Removes the given Sone as a friend Sone.
301          *
302          * @param friendSone
303          *            The friend Sone to remove
304          * @return This Sone (for method chaining)
305          */
306         public Sone removeFriend(Sone friendSone) {
307                 friendSones.remove(friendSone);
308                 return this;
309         }
310
311         /**
312          * Returns the list of posts of this Sone, sorted by time, newest first.
313          *
314          * @return All posts of this Sone
315          */
316         public List<Post> getPosts() {
317                 List<Post> sortedPosts = new ArrayList<Post>(posts);
318                 Collections.sort(sortedPosts, new Comparator<Post>() {
319
320                         @Override
321                         public int compare(Post leftPost, Post rightPost) {
322                                 return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, rightPost.getTime() - leftPost.getTime()));
323                         }
324
325                 });
326                 return sortedPosts;
327         }
328
329         /**
330          * Sets all posts of this Sone at once.
331          *
332          * @param posts
333          *            The new (and only) posts of this Sone
334          * @return This Sone (for method chaining)
335          */
336         public synchronized Sone setPosts(Collection<Post> posts) {
337                 this.posts.clear();
338                 this.posts.addAll(posts);
339                 modificationCounter++;
340                 return this;
341         }
342
343         /**
344          * Adds the given post to this Sone. The post will not be added if its
345          * {@link Post#getSone() Sone} is not this Sone.
346          *
347          * @param post
348          *            The post to add
349          */
350         public synchronized void addPost(Post post) {
351                 if (post.getSone().equals(this) && posts.add(post)) {
352                         logger.log(Level.FINEST, "Adding %s to “%s”.", new Object[] { post, getName() });
353                         modificationCounter++;
354                 }
355         }
356
357         /**
358          * Removes the given post from this Sone.
359          *
360          * @param post
361          *            The post to remove
362          */
363         public synchronized void removePost(Post post) {
364                 if (post.getSone().equals(this) && posts.remove(post)) {
365                         modificationCounter++;
366                 }
367         }
368
369         /**
370          * Returns all replies this Sone made.
371          *
372          * @return All replies this Sone made
373          */
374         public Set<Reply> getReplies() {
375                 logger.log(Level.FINEST, "Friends of %s: %s", new Object[] { this, friendSones });
376                 return Collections.unmodifiableSet(replies);
377         }
378
379         /**
380          * Sets all replies of this Sone at once.
381          *
382          * @param replies
383          *            The new (and only) replies of this Sone
384          * @return This Sone (for method chaining)
385          */
386         public synchronized Sone setReplies(Collection<Reply> replies) {
387                 this.replies.clear();
388                 this.replies.addAll(replies);
389                 modificationCounter++;
390                 return this;
391         }
392
393         /**
394          * Adds a reply to this Sone. If the given reply was not made by this Sone,
395          * nothing is added to this Sone.
396          *
397          * @param reply
398          *            The reply to add
399          */
400         public synchronized void addReply(Reply reply) {
401                 if (reply.getSone().equals(this) && replies.add(reply)) {
402                         modificationCounter++;
403                 }
404         }
405
406         /**
407          * Removes a reply from this Sone.
408          *
409          * @param reply
410          *            The reply to remove
411          */
412         public synchronized void removeReply(Reply reply) {
413                 if (reply.getSone().equals(this) && replies.remove(reply)) {
414                         modificationCounter++;
415                 }
416         }
417
418         /**
419          * Returns the IDs of all liked posts.
420          *
421          * @return All liked posts’ IDs
422          */
423         public Set<String> getLikedPostIds() {
424                 return Collections.unmodifiableSet(likedPostIds);
425         }
426
427         /**
428          * Sets the IDs of all liked posts.
429          *
430          * @param likedPostIds
431          *            All liked posts’ IDs
432          * @return This Sone (for method chaining)
433          */
434         public synchronized Sone setLikePostIds(Set<String> likedPostIds) {
435                 this.likedPostIds.clear();
436                 this.likedPostIds.addAll(likedPostIds);
437                 modificationCounter++;
438                 return this;
439         }
440
441         /**
442          * Checks whether the given post ID is liked by this Sone.
443          *
444          * @param postId
445          *            The ID of the post
446          * @return {@code true} if this Sone likes the given post, {@code false}
447          *         otherwise
448          */
449         public boolean isLikedPostId(String postId) {
450                 return likedPostIds.contains(postId);
451         }
452
453         /**
454          * Adds the given post ID to the list of posts this Sone likes.
455          *
456          * @param postId
457          *            The ID of the post
458          * @return This Sone (for method chaining)
459          */
460         public synchronized Sone addLikedPostId(String postId) {
461                 if (likedPostIds.add(postId)) {
462                         modificationCounter++;
463                 }
464                 return this;
465         }
466
467         /**
468          * Removes the given post ID from the list of posts this Sone likes.
469          *
470          * @param postId
471          *            The ID of the post
472          * @return This Sone (for method chaining)
473          */
474         public synchronized Sone removeLikedPostId(String postId) {
475                 if (likedPostIds.remove(postId)) {
476                         modificationCounter++;
477                 }
478                 return this;
479         }
480
481         /**
482          * Returns the IDs of all liked replies.
483          *
484          * @return All liked replies’ IDs
485          */
486         public Set<String> getLikedReplyIds() {
487                 return Collections.unmodifiableSet(likedReplyIds);
488         }
489
490         /**
491          * Sets the IDs of all liked replies.
492          *
493          * @param likedReplyIds
494          *            All liked replies’ IDs
495          * @return This Sone (for method chaining)
496          */
497         public synchronized Sone setLikeReplyIds(Set<String> likedReplyIds) {
498                 this.likedReplyIds.clear();
499                 this.likedReplyIds.addAll(likedReplyIds);
500                 modificationCounter++;
501                 return this;
502         }
503
504         /**
505          * Checks whether the given reply ID is liked by this Sone.
506          *
507          * @param replyId
508          *            The ID of the reply
509          * @return {@code true} if this Sone likes the given reply, {@code false}
510          *         otherwise
511          */
512         public boolean isLikedReplyId(String replyId) {
513                 return likedReplyIds.contains(replyId);
514         }
515
516         /**
517          * Adds the given reply ID to the list of replies this Sone likes.
518          *
519          * @param replyId
520          *            The ID of the reply
521          * @return This Sone (for method chaining)
522          */
523         public synchronized Sone addLikedReplyId(String replyId) {
524                 if (likedReplyIds.add(replyId)) {
525                         modificationCounter++;
526                 }
527                 return this;
528         }
529
530         /**
531          * Removes the given post ID from the list of replies this Sone likes.
532          *
533          * @param replyId
534          *            The ID of the reply
535          * @return This Sone (for method chaining)
536          */
537         public synchronized Sone removeLikedReplyId(String replyId) {
538                 if (likedReplyIds.remove(replyId)) {
539                         modificationCounter++;
540                 }
541                 return this;
542         }
543
544         /**
545          * Returns the modification counter.
546          *
547          * @return The modification counter
548          */
549         public synchronized long getModificationCounter() {
550                 return modificationCounter;
551         }
552
553         /**
554          * Sets the modification counter.
555          *
556          * @param modificationCounter
557          *            The new modification counter
558          */
559         public synchronized void setModificationCounter(long modificationCounter) {
560                 this.modificationCounter = modificationCounter;
561         }
562
563         /**
564          * Updates the suggested edition in both the request URI and the insert URI.
565          *
566          * @param latestEdition
567          *            The latest edition to update the URIs to
568          */
569         public void updateUris(long latestEdition) {
570                 if ((requestUri != null) && (requestUri.getEdition() < latestEdition)) {
571                         requestUri = requestUri.setSuggestedEdition(latestEdition);
572                 }
573                 if ((insertUri != null) && (insertUri.getEdition() < latestEdition)) {
574                         insertUri = insertUri.setSuggestedEdition(latestEdition);
575                 }
576         }
577
578         //
579         // PRIVATE METHODS
580         //
581
582         /**
583          * Updates the editions of the request URI and the insert URI (if latter is
584          * not {@code null}) with the greater edition of either one.
585          */
586         private void updateEditions() {
587                 long requestEdition = 0;
588                 if (requestUri != null) {
589                         requestEdition = requestUri.getEdition();
590                 }
591                 long insertEdition = 0;
592                 if (insertUri != null) {
593                         insertEdition = insertUri.getEdition();
594                 }
595                 updateUris(Math.max(requestEdition, insertEdition));
596         }
597
598         //
599         // OBJECT METHODS
600         //
601
602         /**
603          * {@inheritDoc}
604          */
605         @Override
606         public int hashCode() {
607                 return id.hashCode();
608         }
609
610         /**
611          * {@inheritDoc}
612          */
613         @Override
614         public boolean equals(Object object) {
615                 if (!(object instanceof Sone)) {
616                         return false;
617                 }
618                 return ((Sone) object).id.equals(id);
619         }
620
621         /**
622          * {@inheritDoc}
623          */
624         @Override
625         public String toString() {
626                 return getClass().getName() + "[id=" + id + ",name=" + name + ",requestUri=" + requestUri + ",insertUri=" + insertUri + ",friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]";
627         }
628
629 }