Store posts in a set, not in a list!
[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.UUID;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
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         /** A GUID for this Sone. */
48         private final UUID id;
49
50         /** The name of this Sone. */
51         private String name;
52
53         /** The URI under which the Sone is stored in Freenet. */
54         private 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 FreenetURI insertUri;
59
60         /** The time of the last inserted update. */
61         private long time;
62
63         /** The profile of this Sone. */
64         private Profile profile;
65
66         /** All friend Sones. */
67         private final Set<Sone> friendSones = new HashSet<Sone>();
68
69         /** All posts. */
70         private final Set<Post> posts = new HashSet<Post>();
71
72         /** All replies. */
73         private final Set<Reply> replies = new HashSet<Reply>();
74
75         /** Modification count. */
76         private volatile long modificationCounter = 0;
77
78         /**
79          * Creates a new Sone.
80          *
81          * @param id
82          *            The ID of this Sone
83          */
84         public Sone(String id) {
85                 this.id = UUID.fromString(id);
86         }
87
88         //
89         // ACCESSORS
90         //
91
92         /**
93          * Returns the ID of this Sone.
94          *
95          * @return The ID of this Sone
96          */
97         public String getId() {
98                 return id.toString();
99         }
100
101         /**
102          * Returns the name of this Sone.
103          *
104          * @return The name of this Sone
105          */
106         public String getName() {
107                 return name;
108         }
109
110         /**
111          * Sets the name of this Sone.
112          *
113          * @param name
114          *            The name of this Sone
115          * @return This sone (for method chaining)
116          */
117         public Sone setName(String name) {
118                 this.name = name;
119                 return this;
120         }
121
122         /**
123          * Returns the request URI of this Sone.
124          *
125          * @return The request URI of this Sone
126          */
127         public FreenetURI getRequestUri() {
128                 return requestUri;
129         }
130
131         /**
132          * Sets the request URI of this Sone.
133          *
134          * @param requestUri
135          *            The request URI of this Sone
136          * @return This Sone (for method chaining)
137          */
138         public Sone setRequestUri(FreenetURI requestUri) {
139                 this.requestUri = requestUri;
140                 return this;
141         }
142
143         /**
144          * Returns the insert URI of this Sone.
145          *
146          * @return The insert URI of this Sone
147          */
148         public FreenetURI getInsertUri() {
149                 return insertUri;
150         }
151
152         /**
153          * Sets the insert URI of this Sone.
154          *
155          * @param insertUri
156          *            The insert URI of this Sone
157          * @return This Sone (for method chaining)
158          */
159         public Sone setInsertUri(FreenetURI insertUri) {
160                 this.insertUri = insertUri;
161                 return this;
162         }
163
164         /**
165          * Return the time of the last inserted update of this Sone.
166          *
167          * @return The time of the update (in milliseconds since Jan 1, 1970 UTC)
168          */
169         public long getTime() {
170                 return time;
171         }
172
173         /**
174          * Sets the time of the last inserted update of this Sone.
175          *
176          * @param time
177          *            The time of the update (in milliseconds since Jan 1, 1970 UTC)
178          * @return This Sone (for method chaining)
179          */
180         public Sone setTime(long time) {
181                 this.time = time;
182                 return this;
183         }
184
185         /**
186          * Returns a copy of the profile. If you want to update values in the
187          * profile of this Sone, update the values in the returned {@link Profile}
188          * and use {@link #setProfile(Profile)} to change the profile in this Sone.
189          *
190          * @return A copy of the profile
191          */
192         public Profile getProfile() {
193                 return new Profile(profile);
194         }
195
196         /**
197          * Sets the profile of this Sone. A copy of the given profile is stored so
198          * that subsequent modifications of the given profile are not reflected in
199          * this Sone!
200          *
201          * @param profile
202          *            The profile to set
203          */
204         public synchronized void setProfile(Profile profile) {
205                 this.profile = new Profile(profile);
206                 modificationCounter++;
207         }
208
209         /**
210          * Returns all friend Sones of this Sone.
211          *
212          * @return The friend Sones of this Sone
213          */
214         public Set<Sone> getFriends() {
215                 return Collections.unmodifiableSet(friendSones);
216         }
217
218         /**
219          * Sets all friends of this Sone at once.
220          *
221          * @param friends
222          *            The new (and only) friends of this Sone
223          * @return This Sone (for method chaining)
224          */
225         public synchronized Sone setFriends(Collection<Sone> friends) {
226                 friendSones.clear();
227                 friendSones.addAll(friends);
228                 modificationCounter++;
229                 return this;
230         }
231
232         /**
233          * Returns whether this Sone has the given Sone as a friend Sone.
234          *
235          * @param friendSone
236          *            The friend Sone to check for
237          * @return {@code true} if this Sone has the given Sone as a friend,
238          *         {@code false} otherwise
239          */
240         public boolean hasFriend(Sone friendSone) {
241                 return friendSones.contains(friendSone);
242         }
243
244         /**
245          * Adds the given Sone as a friend Sone.
246          *
247          * @param friendSone
248          *            The friend Sone to add
249          * @return This Sone (for method chaining)
250          */
251         public synchronized Sone addFriend(Sone friendSone) {
252                 if (!friendSone.equals(this) && friendSones.add(friendSone)) {
253                         modificationCounter++;
254                 }
255                 return this;
256         }
257
258         /**
259          * Removes the given Sone as a friend Sone.
260          *
261          * @param friendSone
262          *            The friend Sone to remove
263          * @return This Sone (for method chaining)
264          */
265         public synchronized Sone removeFriend(Sone friendSone) {
266                 if (friendSones.remove(friendSone)) {
267                         modificationCounter++;
268                 }
269                 return this;
270         }
271
272         /**
273          * Returns the list of posts of this Sone, sorted by time, newest first.
274          *
275          * @return All posts of this Sone
276          */
277         public List<Post> getPosts() {
278                 List<Post> sortedPosts = new ArrayList<Post>(posts);
279                 Collections.sort(sortedPosts, new Comparator<Post>() {
280
281                         @Override
282                         public int compare(Post leftPost, Post rightPost) {
283                                 return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, rightPost.getTime() - leftPost.getTime()));
284                         }
285
286                 });
287                 return sortedPosts;
288         }
289
290         /**
291          * Sets all posts of this Sone at once.
292          *
293          * @param posts
294          *            The new (and only) posts of this Sone
295          * @return This Sone (for method chaining)
296          */
297         public synchronized Sone setPosts(Collection<Post> posts) {
298                 this.posts.clear();
299                 this.posts.addAll(posts);
300                 modificationCounter++;
301                 return this;
302         }
303
304         /**
305          * Adds the given post to this Sone. The post will not be added if its
306          * {@link Post#getSone() Sone} is not this Sone.
307          *
308          * @param post
309          *            The post to add
310          */
311         public synchronized void addPost(Post post) {
312                 if (post.getSone().equals(this) && posts.add(post)) {
313                         logger.log(Level.FINEST, "Adding %s to “%s”.", new Object[] { post, getName() });
314                         modificationCounter++;
315                 }
316         }
317
318         /**
319          * Removes the given post from this Sone.
320          *
321          * @param post
322          *            The post to remove
323          */
324         public synchronized void removePost(Post post) {
325                 if (post.getSone().equals(this) && posts.remove(post)) {
326                         modificationCounter++;
327                 }
328         }
329
330         /**
331          * Returns all replies this Sone made.
332          *
333          * @return All replies this Sone made
334          */
335         public Set<Reply> getReplies() {
336                 logger.log(Level.FINEST, "Friends of %s: %s", new Object[] { this, friendSones });
337                 return Collections.unmodifiableSet(replies);
338         }
339
340         /**
341          * Sets all replies of this Sone at once.
342          *
343          * @param replies
344          *            The new (and only) replies of this Sone
345          * @return This Sone (for method chaining)
346          */
347         public synchronized Sone setReplies(Collection<Reply> replies) {
348                 this.replies.clear();
349                 this.replies.addAll(replies);
350                 modificationCounter++;
351                 return this;
352         }
353
354         /**
355          * Adds a reply to this Sone. If the given reply was not made by this Sone,
356          * nothing is added to this Sone.
357          *
358          * @param reply
359          *            The reply to add
360          */
361         public synchronized void addReply(Reply reply) {
362                 if (reply.getSone().equals(this) && replies.add(reply)) {
363                         modificationCounter++;
364                 }
365         }
366
367         /**
368          * Removes a reply from this Sone.
369          *
370          * @param reply
371          *            The reply to remove
372          */
373         public synchronized void removeReply(Reply reply) {
374                 if (reply.getSone().equals(this) && replies.remove(reply)) {
375                         modificationCounter++;
376                 }
377         }
378
379         /**
380          * Returns the modification counter.
381          *
382          * @return The modification counter
383          */
384         public synchronized long getModificationCounter() {
385                 return modificationCounter;
386         }
387
388         /**
389          * Sets the modification counter.
390          *
391          * @param modificationCounter
392          *            The new modification counter
393          */
394         public synchronized void setModificationCounter(long modificationCounter) {
395                 this.modificationCounter = modificationCounter;
396         }
397
398         /**
399          * Updates the suggested edition in both the request URI and the insert URI.
400          *
401          * @param requestUri
402          *            The request URI that resulted from an insert
403          */
404         public void updateUris(FreenetURI requestUri) {
405                 /* TODO - check for the correct URI. */
406                 long latestEdition = requestUri.getSuggestedEdition();
407                 this.requestUri = this.requestUri.setSuggestedEdition(latestEdition);
408                 if (this.insertUri != null) {
409                         this.insertUri = this.insertUri.setSuggestedEdition(latestEdition);
410                 }
411         }
412
413         //
414         // OBJECT METHODS
415         //
416
417         /**
418          * {@inheritDoc}
419          */
420         @Override
421         public int hashCode() {
422                 return id.hashCode();
423         }
424
425         /**
426          * {@inheritDoc}
427          */
428         @Override
429         public boolean equals(Object object) {
430                 if (!(object instanceof Sone)) {
431                         return false;
432                 }
433                 return ((Sone) object).id.equals(id);
434         }
435
436         /**
437          * {@inheritDoc}
438          */
439         @Override
440         public String toString() {
441                 return getClass().getName() + "[id=" + id + ",name=" + name + ",requestUri=" + requestUri + ",insertUri=" + insertUri + ",friends(" + friendSones.size() + "),posts(" + posts.size() + "),replies(" + replies.size() + ")]";
442         }
443
444 }