Remove some very obvious javadoc comments.
[Sone.git] / src / main / java / net / pterodactylus / sone / data / Profile.java
1 /*
2  * Sone - Profile.java - Copyright © 2010–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;
19
20 import static com.google.common.base.Optional.absent;
21 import static com.google.common.base.Optional.fromNullable;
22 import static com.google.common.base.Optional.of;
23 import static com.google.common.base.Preconditions.checkArgument;
24 import static com.google.common.base.Preconditions.checkNotNull;
25 import static com.google.common.base.Preconditions.checkState;
26 import static java.lang.Math.max;
27 import static java.lang.Math.min;
28 import static java.util.UUID.randomUUID;
29
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33
34 import com.google.common.base.Optional;
35 import com.google.common.hash.Hasher;
36 import com.google.common.hash.Hashing;
37
38 /**
39  * A profile stores personal information about a {@link Sone}. All information
40  * is optional and can be {@code null}.
41  *
42  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
43  */
44 public class Profile implements Fingerprintable {
45
46         /** The Sone this profile belongs to. */
47         private final Sone sone;
48
49         private volatile Name name = new Name();
50         private volatile BirthDate birthDate = new BirthDate();
51
52         /** The ID of the avatar image. */
53         private volatile String avatar;
54
55         /** Additional fields in the profile. */
56         private final List<Field> fields = Collections.synchronizedList(new ArrayList<Field>());
57
58         /**
59          * Creates a new empty profile.
60          *
61          * @param sone
62          *            The Sone this profile belongs to
63          */
64         public Profile(Sone sone) {
65                 this.sone = sone;
66         }
67
68         /**
69          * Creates a copy of a profile.
70          *
71          * @param profile
72          *            The profile to copy
73          */
74         public Profile(Profile profile) {
75                 this.sone = profile.sone;
76                 this.name = profile.name;
77                 this.birthDate = profile.birthDate;
78                 this.avatar = profile.avatar;
79                 this.fields.addAll(profile.fields);
80         }
81
82         //
83         // ACCESSORS
84         //
85
86         /**
87          * Returns the Sone this profile belongs to.
88          *
89          * @return The Sone this profile belongs to
90          */
91         public Sone getSone() {
92                 return sone;
93         }
94
95         public String getFirstName() {
96                 return name.getFirst().orNull();
97         }
98
99         public String getMiddleName() {
100                 return name.getMiddle().orNull();
101         }
102
103         public String getLastName() {
104                 return name.getLast().orNull();
105         }
106
107         public Integer getBirthDay() {
108                 return birthDate.getDay().orNull();
109         }
110
111         public Integer getBirthMonth() {
112                 return birthDate.getMonth().orNull();
113         }
114
115         public Integer getBirthYear() {
116                 return birthDate.getYear().orNull();
117         }
118
119         public String getAvatar() {
120                 return avatar;
121         }
122
123         public Profile setAvatar(Optional<String> avatarId) {
124                 this.avatar = avatarId.orNull();
125                 return this;
126         }
127
128         public List<Field> getFields() {
129                 return new ArrayList<Field>(fields);
130         }
131
132         public boolean hasField(Field field) {
133                 return fields.contains(field);
134         }
135
136         public Optional<Field> getFieldById(String fieldId) {
137                 checkNotNull(fieldId, "fieldId must not be null");
138                 for (Field field : fields) {
139                         if (field.getId().equals(fieldId)) {
140                                 return of(field);
141                         }
142                 }
143                 return absent();
144         }
145
146         public Optional<Field> getFieldByName(String fieldName) {
147                 for (Field field : fields) {
148                         if (field.getName().equals(fieldName)) {
149                                 return of(field);
150                         }
151                 }
152                 return absent();
153         }
154
155         public Field addField(String fieldName) throws IllegalArgumentException {
156                 checkNotNull(fieldName, "fieldName must not be null");
157                 checkArgument(fieldName.length() > 0, "fieldName must not be empty");
158                 checkState(!getFieldByName(fieldName).isPresent(), "fieldName must be unique");
159                 @SuppressWarnings("synthetic-access")
160                 Field field = new Field(fieldName);
161                 fields.add(field);
162                 return field;
163         }
164
165         public void renameField(Field field, String newName) {
166                 int indexOfField = getFieldIndex(field);
167                 if (indexOfField == -1) {
168                         return;
169                 }
170                 fields.set(indexOfField, new Field(field.getId(), newName, field.getValue()));
171         }
172
173         public void setField(Field field, String newValue) {
174                 int indexOfField = getFieldIndex(field);
175                 if (indexOfField == -1) {
176                         return;
177                 }
178                 fields.set(indexOfField, new Field(field.getId(), field.getName(), newValue));
179         }
180
181         public void moveFieldUp(Field field) {
182                 checkNotNull(field, "field must not be null");
183                 checkArgument(hasField(field), "field must belong to this profile");
184                 int fieldIndex = getFieldIndex(field);
185                 fields.remove(field);
186                 fields.add(max(fieldIndex - 1, 0), field);
187         }
188
189         public void moveFieldDown(Field field) {
190                 checkNotNull(field, "field must not be null");
191                 checkArgument(hasField(field), "field must belong to this profile");
192                 int fieldIndex = getFieldIndex(field);
193                 fields.remove(field);
194                 fields.add(min(fieldIndex + 1, fields.size()), field);
195         }
196
197         public void removeField(Field field) {
198                 checkNotNull(field, "field must not be null");
199                 fields.remove(field);
200         }
201
202         public Modifier modify() {
203                 return new Modifier() {
204                         private Optional<String> firstName = name.getFirst();
205                         private Optional<String> middleName = name.getMiddle();
206                         private Optional<String> lastName = name.getLast();
207                         private Optional<Integer> birthYear = birthDate.getYear();
208                         private Optional<Integer> birthMonth = birthDate.getMonth();
209                         private Optional<Integer> birthDay = birthDate.getDay();
210
211                         @Override
212                         public Modifier setFirstName(String firstName) {
213                                 this.firstName = fromNullable(firstName);
214                                 return this;
215                         }
216
217                         @Override
218                         public Modifier setMiddleName(String middleName) {
219                                 this.middleName = fromNullable(middleName);
220                                 return this;
221                         }
222
223                         @Override
224                         public Modifier setLastName(String lastName) {
225                                 this.lastName = fromNullable(lastName);
226                                 return this;
227                         }
228
229                         @Override
230                         public Modifier setBirthYear(Integer birthYear) {
231                                 this.birthYear = fromNullable(birthYear);
232                                 return this;
233                         }
234
235                         @Override
236                         public Modifier setBirthMonth(Integer birthMonth) {
237                                 this.birthMonth = fromNullable(birthMonth);
238                                 return this;
239                         }
240
241                         @Override
242                         public Modifier setBirthDay(Integer birthDay) {
243                                 this.birthDay = fromNullable(birthDay);
244                                 return this;
245                         }
246
247                         @Override
248                         public Profile update() {
249                                 Profile.this.name = new Name(firstName, middleName, lastName);
250                                 Profile.this.birthDate = new BirthDate(birthYear, birthMonth, birthDay);
251                                 return Profile.this;
252                         }
253                 };
254         }
255
256         public interface Modifier {
257
258                 Modifier setFirstName(String firstName);
259                 Modifier setMiddleName(String middleName);
260                 Modifier setLastName(String lastName);
261                 Modifier setBirthYear(Integer birthYear);
262                 Modifier setBirthMonth(Integer birthMonth);
263                 Modifier setBirthDay(Integer birthDay);
264                 Profile update();
265
266         }
267
268         //
269         // PRIVATE METHODS
270         //
271
272         /**
273          * Returns the index of the field with the given name.
274          *
275          * @param field
276          *            The name of the field
277          * @return The index of the field, or {@code -1} if there is no field with
278          *         the given name
279          */
280         private int getFieldIndex(Field field) {
281                 return fields.indexOf(field);
282         }
283
284         //
285         // INTERFACE Fingerprintable
286         //
287
288         @Override
289         public String getFingerprint() {
290                 Hasher hash = Hashing.sha256().newHasher();
291                 hash.putString("Profile(");
292                 hash.putString(name.getFingerprint());
293                 hash.putString(birthDate.getFingerprint());
294                 if (avatar != null) {
295                         hash.putString("Avatar(").putString(avatar).putString(")");
296                 }
297                 hash.putString("ContactInformation(");
298                 for (Field field : fields) {
299                         if (field.getValue() != null) {
300                                 hash.putString(field.getName()).putString("(").putString(field.getValue()).putString(")");
301                         }
302                 }
303                 hash.putString(")");
304                 hash.putString(")");
305
306                 return hash.hash().toString();
307         }
308
309         /**
310          * Container for a profile field.
311          *
312          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
313          */
314         public static class Field {
315
316                 private final String id;
317                 private final String name;
318                 private final String value;
319
320                 public Field(String name) {
321                         this(name, null);
322                 }
323
324                 public Field(String name, String value) {
325                         this(randomUUID().toString(), name, value);
326                 }
327
328                 public Field(String id, String name, String value) {
329                         this.id = checkNotNull(id, "id must not be null");
330                         this.name = name;
331                         this.value = value;
332                 }
333
334                 public String getId() {
335                         return id;
336                 }
337
338                 public String getName() {
339                         return name;
340                 }
341
342                 public String getValue() {
343                         return value;
344                 }
345
346                 @Override
347                 public boolean equals(Object object) {
348                         if (!(object instanceof Field)) {
349                                 return false;
350                         }
351                         Field field = (Field) object;
352                         return id.equals(field.id);
353                 }
354
355                 @Override
356                 public int hashCode() {
357                         return id.hashCode();
358                 }
359
360         }
361
362         public static class Name implements Fingerprintable {
363
364                 private final Optional<String> first;
365                 private final Optional<String> middle;
366                 private final Optional<String> last;
367
368                 public Name() {
369                         this(Optional.<String>absent(), Optional.<String>absent(), Optional.<String>absent());
370                 }
371
372                 public Name(Optional<String> first, Optional<String> middle, Optional<String> last) {
373                         this.first = first;
374                         this.middle = middle;
375                         this.last = last;
376                 }
377
378                 public Optional<String> getFirst() {
379                         return first;
380                 }
381
382                 public Optional<String> getMiddle() {
383                         return middle;
384                 }
385
386                 public Optional<String> getLast() {
387                         return last;
388                 }
389
390                 @Override
391                 public String getFingerprint() {
392                         Hasher hash = Hashing.sha256().newHasher();
393                         hash.putString("Name(");
394                         if (first.isPresent()) {
395                                 hash.putString("First(").putString(first.get()).putString(")");
396                         }
397                         if (middle.isPresent()) {
398                                 hash.putString("Middle(").putString(middle.get()).putString(")");
399                         }
400                         if (last.isPresent()) {
401                                 hash.putString("Last(").putString(last.get()).putString(")");
402                         }
403                         hash.putString(")");
404                         return hash.hash().toString();
405                 }
406
407         }
408
409         public static class BirthDate implements Fingerprintable {
410
411                 private final Optional<Integer> year;
412                 private final Optional<Integer> month;
413                 private final Optional<Integer> day;
414
415                 public BirthDate() {
416                         this(Optional.<Integer>absent(), Optional.<Integer>absent(), Optional.<Integer>absent());
417                 }
418
419                 public BirthDate(Optional<Integer> year, Optional<Integer> month, Optional<Integer> day) {
420                         this.year = year;
421                         this.month = month;
422                         this.day = day;
423                 }
424
425                 public Optional<Integer> getYear() {
426                         return year;
427                 }
428
429                 public Optional<Integer> getMonth() {
430                         return month;
431                 }
432
433                 public Optional<Integer> getDay() {
434                         return day;
435                 }
436
437                 @Override
438                 public String getFingerprint() {
439                         Hasher hash = Hashing.sha256().newHasher();
440                         hash.putString("Birthdate(");
441                         if (year.isPresent()) {
442                                 hash.putString("Year(").putInt(year.get()).putString(")");
443                         }
444                         if (month.isPresent()) {
445                                 hash.putString("Month(").putInt(month.get()).putString(")");
446                         }
447                         if (day.isPresent()) {
448                                 hash.putString("Day(").putInt(day.get()).putString(")");
449                         }
450                         hash.putString(")");
451                         return hash.hash().toString();
452                 }
453
454         }
455
456 }