Extract superclass out of default Sone implementation.
[Sone.git] / src / main / java / net / pterodactylus / sone / data / impl / AbstractSone.java
1 /*
2  * Sone - AbstractSone.java - Copyright © 2013 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.impl;
19
20 import static com.google.common.base.Preconditions.checkNotNull;
21
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Set;
26 import java.util.concurrent.CopyOnWriteArraySet;
27 import java.util.logging.Level;
28 import java.util.logging.Logger;
29
30 import net.pterodactylus.sone.core.Options;
31 import net.pterodactylus.sone.data.Album;
32 import net.pterodactylus.sone.data.Client;
33 import net.pterodactylus.sone.data.Post;
34 import net.pterodactylus.sone.data.PostReply;
35 import net.pterodactylus.sone.data.Profile;
36 import net.pterodactylus.sone.data.Reply;
37 import net.pterodactylus.sone.data.Sone;
38 import net.pterodactylus.sone.freenet.wot.Identity;
39 import net.pterodactylus.util.logging.Logging;
40
41 import freenet.keys.FreenetURI;
42
43 import com.google.common.hash.Hasher;
44 import com.google.common.hash.Hashing;
45
46 /**
47  * Abstract base implementation of a {@link Sone}.
48  *
49  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
50  */
51 public abstract class AbstractSone implements Sone {
52
53         /** The logger. */
54         protected static final Logger logger = Logging.getLogger(DefaultSone.class);
55         /** The ID of this Sone. */
56         protected final String id;
57         /** Whether the Sone is local. */
58         protected final boolean local;
59         /** All friend Sones. */
60         protected final Set<String> friendSones = new CopyOnWriteArraySet<String>();
61         /** The root album containing all albums. */
62         protected final DefaultAlbum rootAlbum = new DefaultAlbum(this, null);
63         /** Whether this Sone is known. */
64         protected volatile boolean known;
65         /** The identity of this Sone. */
66         private Identity identity;
67         /** The URI under which the Sone is stored in Freenet. */
68         private volatile FreenetURI requestUri;
69         /** The URI used to insert a new version of this Sone. */
70         /* This will be null for remote Sones! */
71         private volatile FreenetURI insertUri;
72         /** The latest edition of the Sone. */
73         private volatile long latestEdition;
74         /** The time of the last inserted update. */
75         private volatile long time;
76         /** The status of this Sone. */
77         private volatile SoneStatus status = SoneStatus.unknown;
78         /** The profile of this Sone. */
79         private volatile Profile profile = new Profile(this);
80         /** The client used by the Sone. */
81         private volatile Client client;
82         /** Sone-specific options. */
83         private Options options = new Options();
84
85         public AbstractSone(String id, boolean local) {
86                 this.id = id;
87                 this.local = local;
88         }
89
90         /**
91          * Returns the identity of this Sone.
92          *
93          * @return The identity of this Sone
94          */
95         public String getId() {
96                 return id;
97         }
98
99         /**
100          * Returns the identity of this Sone.
101          *
102          * @return The identity of this Sone
103          */
104         public Identity getIdentity() {
105                 return identity;
106         }
107
108         /**
109          * Sets the identity of this Sone. The {@link Identity#getId() ID} of the
110          * identity has to match this Sone’s {@link #getId()}.
111          *
112          * @param identity
113          *              The identity of this Sone
114          * @return This Sone (for method chaining)
115          * @throws IllegalArgumentException
116          *              if the ID of the identity does not match this Sone’s ID
117          */
118         public Sone setIdentity(Identity identity) throws IllegalArgumentException {
119                 if (!identity.getId().equals(id)) {
120                         throw new IllegalArgumentException("Identity’s ID does not match Sone’s ID!");
121                 }
122                 this.identity = identity;
123                 return this;
124         }
125
126         /**
127          * Returns the name of this Sone.
128          *
129          * @return The name of this Sone
130          */
131         public String getName() {
132                 return (identity != null) ? identity.getNickname() : null;
133         }
134
135         /**
136          * Returns whether this Sone is a local Sone.
137          *
138          * @return {@code true} if this Sone is a local Sone, {@code false} otherwise
139          */
140         public boolean isLocal() {
141                 return local;
142         }
143
144         /**
145          * Returns the request URI of this Sone.
146          *
147          * @return The request URI of this Sone
148          */
149         public FreenetURI getRequestUri() {
150                 return (requestUri != null) ? requestUri.setSuggestedEdition(latestEdition) : null;
151         }
152
153         /**
154          * Sets the request URI of this Sone.
155          *
156          * @param requestUri
157          *              The request URI of this Sone
158          * @return This Sone (for method chaining)
159          */
160         public Sone setRequestUri(FreenetURI requestUri) {
161                 if (this.requestUri == null) {
162                         this.requestUri = requestUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
163                         return this;
164                 }
165                 if (!this.requestUri.equalsKeypair(requestUri)) {
166                         logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", requestUri, this.requestUri));
167                         return this;
168                 }
169                 return this;
170         }
171
172         /**
173          * Returns the insert URI of this Sone.
174          *
175          * @return The insert URI of this Sone
176          */
177         public FreenetURI getInsertUri() {
178                 return (insertUri != null) ? insertUri.setSuggestedEdition(latestEdition) : null;
179         }
180
181         /**
182          * Sets the insert URI of this Sone.
183          *
184          * @param insertUri
185          *              The insert URI of this Sone
186          * @return This Sone (for method chaining)
187          */
188         public Sone setInsertUri(FreenetURI insertUri) {
189                 if (this.insertUri == null) {
190                         this.insertUri = insertUri.setKeyType("USK").setDocName("Sone").setMetaString(new String[0]);
191                         return this;
192                 }
193                 if (!this.insertUri.equalsKeypair(insertUri)) {
194                         logger.log(Level.WARNING, String.format("Request URI %s tried to overwrite %s!", insertUri, this.insertUri));
195                         return this;
196                 }
197                 return this;
198         }
199
200         /**
201          * Returns the latest edition of this Sone.
202          *
203          * @return The latest edition of this Sone
204          */
205         public long getLatestEdition() {
206                 return latestEdition;
207         }
208
209         /**
210          * Sets the latest edition of this Sone. If the given latest edition is not
211          * greater than the current latest edition, the latest edition of this Sone is
212          * not changed.
213          *
214          * @param latestEdition
215          *              The latest edition of this Sone
216          */
217         public void setLatestEdition(long latestEdition) {
218                 if (!(latestEdition > this.latestEdition)) {
219                         logger.log(Level.FINE, String.format("New latest edition %d is not greater than current latest edition %d!", latestEdition, this.latestEdition));
220                         return;
221                 }
222                 this.latestEdition = latestEdition;
223         }
224
225         /**
226          * Return the time of the last inserted update of this Sone.
227          *
228          * @return The time of the update (in milliseconds since Jan 1, 1970 UTC)
229          */
230         public long getTime() {
231                 return time;
232         }
233
234         /**
235          * Sets the time of the last inserted update of this Sone.
236          *
237          * @param time
238          *              The time of the update (in milliseconds since Jan 1, 1970 UTC)
239          * @return This Sone (for method chaining)
240          */
241         public Sone setTime(long time) {
242                 this.time = time;
243                 return this;
244         }
245
246         /**
247          * Returns the status of this Sone.
248          *
249          * @return The status of this Sone
250          */
251         public SoneStatus getStatus() {
252                 return status;
253         }
254
255         /**
256          * Sets the new status of this Sone.
257          *
258          * @param status
259          *              The new status of this Sone
260          * @return This Sone
261          * @throws IllegalArgumentException
262          *              if {@code status} is {@code null}
263          */
264         public Sone setStatus(SoneStatus status) {
265                 this.status = checkNotNull(status, "status must not be null");
266                 return this;
267         }
268
269         /**
270          * Returns a copy of the profile. If you want to update values in the profile
271          * of this Sone, update the values in the returned {@link Profile} and use
272          * {@link #setProfile(Profile)} to change the profile in this Sone.
273          *
274          * @return A copy of the profile
275          */
276         public Profile getProfile() {
277                 return new Profile(profile);
278         }
279
280         /**
281          * Sets the profile of this Sone. A copy of the given profile is stored so that
282          * subsequent modifications of the given profile are not reflected in this
283          * Sone!
284          *
285          * @param profile
286          *              The profile to set
287          */
288         public void setProfile(Profile profile) {
289                 this.profile = new Profile(profile);
290         }
291
292         /**
293          * Returns the client used by this Sone.
294          *
295          * @return The client used by this Sone, or {@code null}
296          */
297         public Client getClient() {
298                 return client;
299         }
300
301         /**
302          * Sets the client used by this Sone.
303          *
304          * @param client
305          *              The client used by this Sone, or {@code null}
306          * @return This Sone (for method chaining)
307          */
308         public Sone setClient(Client client) {
309                 this.client = client;
310                 return this;
311         }
312
313         /**
314          * Returns the root album that contains all visible albums of this Sone.
315          *
316          * @return The root album of this Sone
317          */
318         public Album getRootAlbum() {
319                 return rootAlbum;
320         }
321
322         /**
323          * Returns Sone-specific options.
324          *
325          * @return The options of this Sone
326          */
327         public Options getOptions() {
328                 return options;
329         }
330
331         /**
332          * Sets the options of this Sone.
333          *
334          * @param options
335          *              The options of this Sone
336          */
337         /* TODO - remove this method again, maybe add an option provider */
338         public void setOptions(Options options) {
339                 this.options = options;
340         }
341
342         /** {@inheritDoc} */
343         @Override
344         public synchronized String getFingerprint() {
345                 Hasher hash = Hashing.sha256().newHasher();
346                 hash.putString(profile.getFingerprint());
347
348                 hash.putString("Posts(");
349                 for (Post post : getPosts()) {
350                         hash.putString("Post(").putString(post.getId()).putString(")");
351                 }
352                 hash.putString(")");
353
354                 List<PostReply> replies = new ArrayList<PostReply>(getReplies());
355                 Collections.sort(replies, Reply.TIME_COMPARATOR);
356                 hash.putString("Replies(");
357                 for (PostReply reply : replies) {
358                         hash.putString("Reply(").putString(reply.getId()).putString(")");
359                 }
360                 hash.putString(")");
361
362                 List<String> likedPostIds = new ArrayList<String>(getLikedPostIds());
363                 Collections.sort(likedPostIds);
364                 hash.putString("LikedPosts(");
365                 for (String likedPostId : likedPostIds) {
366                         hash.putString("Post(").putString(likedPostId).putString(")");
367                 }
368                 hash.putString(")");
369
370                 List<String> likedReplyIds = new ArrayList<String>(getLikedReplyIds());
371                 Collections.sort(likedReplyIds);
372                 hash.putString("LikedReplies(");
373                 for (String likedReplyId : likedReplyIds) {
374                         hash.putString("Reply(").putString(likedReplyId).putString(")");
375                 }
376                 hash.putString(")");
377
378                 hash.putString("Albums(");
379                 for (Album album : rootAlbum.getAlbums()) {
380                         if (!Album.NOT_EMPTY.apply(album)) {
381                                 continue;
382                         }
383                         hash.putString(album.getFingerprint());
384                 }
385                 hash.putString(")");
386
387                 return hash.hash().toString();
388         }
389
390         /** {@inheritDoc} */
391         @Override
392         public int compareTo(Sone sone) {
393                 return NICE_NAME_COMPARATOR.compare(this, sone);
394         }
395
396         /** {@inheritDoc} */
397         @Override
398         public int hashCode() {
399                 return id.hashCode();
400         }
401
402         /** {@inheritDoc} */
403         @Override
404         public boolean equals(Object object) {
405                 if (!(object instanceof Sone)) {
406                         return false;
407                 }
408                 return ((Sone) object).getId().equals(id);
409         }
410
411         /** {@inheritDoc} */
412         @Override
413         public String toString() {
414                 return getClass().getName() + "[identity=" + identity + ",requestUri=" + requestUri + ",insertUri(" + String.valueOf(insertUri).length() + "),friends(" + friendSones.size() + "),posts(" + getPosts().size() + "),replies(" + getReplies().size() + "),albums(" + getRootAlbum().getAlbums().size() + ")]";
415         }
416
417 }