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