Don’t apply function to optionals.
[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.Preconditions.checkArgument;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.base.Preconditions.checkState;
23
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.UUID;
28
29 import com.google.common.hash.Hasher;
30 import com.google.common.hash.Hashing;
31
32 /**
33  * A profile stores personal information about a {@link Sone}. All information
34  * is optional and can be {@code null}.
35  *
36  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
37  */
38 public class Profile implements Fingerprintable {
39
40         /** The Sone this profile belongs to. */
41         private final Sone sone;
42
43         /** The first name. */
44         private volatile String firstName;
45
46         /** The middle name(s). */
47         private volatile String middleName;
48
49         /** The last name. */
50         private volatile String lastName;
51
52         /** The day of the birth date. */
53         private volatile Integer birthDay;
54
55         /** The month of the birth date. */
56         private volatile Integer birthMonth;
57
58         /** The year of the birth date. */
59         private volatile Integer birthYear;
60
61         /** The ID of the avatar image. */
62         private volatile String avatar;
63
64         /** Additional fields in the profile. */
65         private final List<Field> fields = Collections.synchronizedList(new ArrayList<Field>());
66
67         /**
68          * Creates a new empty profile.
69          *
70          * @param sone
71          *            The Sone this profile belongs to
72          */
73         public Profile(Sone sone) {
74                 this.sone = sone;
75         }
76
77         /**
78          * Creates a copy of a profile.
79          *
80          * @param profile
81          *            The profile to copy
82          */
83         public Profile(Profile profile) {
84                 this.sone = profile.sone;
85                 this.firstName = profile.firstName;
86                 this.middleName = profile.middleName;
87                 this.lastName = profile.lastName;
88                 this.birthDay = profile.birthDay;
89                 this.birthMonth = profile.birthMonth;
90                 this.birthYear = profile.birthYear;
91                 this.avatar = profile.avatar;
92                 this.fields.addAll(profile.fields);
93         }
94
95         //
96         // ACCESSORS
97         //
98
99         /**
100          * Returns the Sone this profile belongs to.
101          *
102          * @return The Sone this profile belongs to
103          */
104         public Sone getSone() {
105                 return sone;
106         }
107
108         /**
109          * Returns the first name.
110          *
111          * @return The first name
112          */
113         public String getFirstName() {
114                 return firstName;
115         }
116
117         /**
118          * Sets the first name.
119          *
120          * @param firstName
121          *            The first name to set
122          * @return This profile (for method chaining)
123          */
124         public Profile setFirstName(String firstName) {
125                 this.firstName = firstName;
126                 return this;
127         }
128
129         /**
130          * Returns the middle name(s).
131          *
132          * @return The middle name
133          */
134         public String getMiddleName() {
135                 return middleName;
136         }
137
138         /**
139          * Sets the middle name.
140          *
141          * @param middleName
142          *            The middle name to set
143          * @return This profile (for method chaining)
144          */
145         public Profile setMiddleName(String middleName) {
146                 this.middleName = middleName;
147                 return this;
148         }
149
150         /**
151          * Returns the last name.
152          *
153          * @return The last name
154          */
155         public String getLastName() {
156                 return lastName;
157         }
158
159         /**
160          * Sets the last name.
161          *
162          * @param lastName
163          *            The last name to set
164          * @return This profile (for method chaining)
165          */
166         public Profile setLastName(String lastName) {
167                 this.lastName = lastName;
168                 return this;
169         }
170
171         /**
172          * Returns the day of the birth date.
173          *
174          * @return The day of the birth date (from 1 to 31)
175          */
176         public Integer getBirthDay() {
177                 return birthDay;
178         }
179
180         /**
181          * Sets the day of the birth date.
182          *
183          * @param birthDay
184          *            The day of the birth date (from 1 to 31)
185          * @return This profile (for method chaining)
186          */
187         public Profile setBirthDay(Integer birthDay) {
188                 this.birthDay = birthDay;
189                 return this;
190         }
191
192         /**
193          * Returns the month of the birth date.
194          *
195          * @return The month of the birth date (from 1 to 12)
196          */
197         public Integer getBirthMonth() {
198                 return birthMonth;
199         }
200
201         /**
202          * Sets the month of the birth date.
203          *
204          * @param birthMonth
205          *            The month of the birth date (from 1 to 12)
206          * @return This profile (for method chaining)
207          */
208         public Profile setBirthMonth(Integer birthMonth) {
209                 this.birthMonth = birthMonth;
210                 return this;
211         }
212
213         /**
214          * Returns the year of the birth date.
215          *
216          * @return The year of the birth date
217          */
218         public Integer getBirthYear() {
219                 return birthYear;
220         }
221
222         /**
223          * Returns the ID of the currently selected avatar image.
224          *
225          * @return The ID of the currently selected avatar image, or {@code null} if
226          *         no avatar is selected.
227          */
228         public String getAvatar() {
229                 return avatar;
230         }
231
232         /**
233          * Sets the avatar image.
234          *
235          * @param avatar
236          *            The new avatar image, or {@code null} to not select an avatar
237          *            image.
238          * @return This Sone
239          */
240         public Profile setAvatar(Image avatar) {
241                 if (avatar == null) {
242                         this.avatar = null;
243                         return this;
244                 }
245                 checkArgument(avatar.getSone().equals(sone), "avatar must belong to Sone");
246                 this.avatar = avatar.getId();
247                 return this;
248         }
249
250         /**
251          * Sets the year of the birth date.
252          *
253          * @param birthYear
254          *            The year of the birth date
255          * @return This profile (for method chaining)
256          */
257         public Profile setBirthYear(Integer birthYear) {
258                 this.birthYear = birthYear;
259                 return this;
260         }
261
262         /**
263          * Returns the fields of this profile.
264          *
265          * @return The fields of this profile
266          */
267         public List<Field> getFields() {
268                 return new ArrayList<Field>(fields);
269         }
270
271         /**
272          * Returns whether this profile contains the given field.
273          *
274          * @param field
275          *            The field to check for
276          * @return {@code true} if this profile contains the field, false otherwise
277          */
278         public boolean hasField(Field field) {
279                 return fields.contains(field);
280         }
281
282         /**
283          * Returns the field with the given ID.
284          *
285          * @param fieldId
286          *            The ID of the field to get
287          * @return The field, or {@code null} if this profile does not contain a
288          *         field with the given ID
289          */
290         public Field getFieldById(String fieldId) {
291                 checkNotNull(fieldId, "fieldId must not be null");
292                 for (Field field : fields) {
293                         if (field.getId().equals(fieldId)) {
294                                 return field;
295                         }
296                 }
297                 return null;
298         }
299
300         /**
301          * Returns the field with the given name.
302          *
303          * @param fieldName
304          *            The name of the field to get
305          * @return The field, or {@code null} if this profile does not contain a
306          *         field with the given name
307          */
308         public Field getFieldByName(String fieldName) {
309                 for (Field field : fields) {
310                         if (field.getName().equals(fieldName)) {
311                                 return field;
312                         }
313                 }
314                 return null;
315         }
316
317         /**
318          * Appends a new field to the list of fields.
319          *
320          * @param fieldName
321          *            The name of the new field
322          * @return The new field
323          * @throws IllegalArgumentException
324          *             if the name is not valid
325          */
326         public Field addField(String fieldName) throws IllegalArgumentException {
327                 checkNotNull(fieldName, "fieldName must not be null");
328                 checkArgument(fieldName.length() > 0, "fieldName must not be empty");
329                 checkState(getFieldByName(fieldName) == null, "fieldName must be unique");
330                 @SuppressWarnings("synthetic-access")
331                 Field field = new Field().setName(fieldName);
332                 fields.add(field);
333                 return field;
334         }
335
336         /**
337          * Moves the given field up one position in the field list. The index of the
338          * field to move must be greater than {@code 0} (because you obviously can
339          * not move the first field further up).
340          *
341          * @param field
342          *            The field to move up
343          */
344         public void moveFieldUp(Field field) {
345                 checkNotNull(field, "field must not be null");
346                 checkArgument(hasField(field), "field must belong to this profile");
347                 checkArgument(getFieldIndex(field) > 0, "field index must be > 0");
348                 int fieldIndex = getFieldIndex(field);
349                 fields.remove(field);
350                 fields.add(fieldIndex - 1, field);
351         }
352
353         /**
354          * Moves the given field down one position in the field list. The index of
355          * the field to move must be less than the index of the last field (because
356          * you obviously can not move the last field further down).
357          *
358          * @param field
359          *            The field to move down
360          */
361         public void moveFieldDown(Field field) {
362                 checkNotNull(field, "field must not be null");
363                 checkArgument(hasField(field), "field must belong to this profile");
364                 checkArgument(getFieldIndex(field) < fields.size() - 1, "field index must be < " + (fields.size() - 1));
365                 int fieldIndex = getFieldIndex(field);
366                 fields.remove(field);
367                 fields.add(fieldIndex + 1, field);
368         }
369
370         /**
371          * Removes the given field.
372          *
373          * @param field
374          *            The field to remove
375          */
376         public void removeField(Field field) {
377                 checkNotNull(field, "field must not be null");
378                 checkArgument(hasField(field), "field must belong to this profile");
379                 fields.remove(field);
380         }
381
382         //
383         // PRIVATE METHODS
384         //
385
386         /**
387          * Returns the index of the field with the given name.
388          *
389          * @param field
390          *            The name of the field
391          * @return The index of the field, or {@code -1} if there is no field with
392          *         the given name
393          */
394         private int getFieldIndex(Field field) {
395                 return fields.indexOf(field);
396         }
397
398         //
399         // INTERFACE Fingerprintable
400         //
401
402         /**
403          * {@inheritDoc}
404          */
405         @Override
406         public String getFingerprint() {
407                 Hasher hash = Hashing.sha256().newHasher();
408                 hash.putString("Profile(");
409                 if (firstName != null) {
410                         hash.putString("FirstName(").putString(firstName).putString(")");
411                 }
412                 if (middleName != null) {
413                         hash.putString("MiddleName(").putString(middleName).putString(")");
414                 }
415                 if (lastName != null) {
416                         hash.putString("LastName(").putString(lastName).putString(")");
417                 }
418                 if (birthDay != null) {
419                         hash.putString("BirthDay(").putInt(birthDay).putString(")");
420                 }
421                 if (birthMonth != null) {
422                         hash.putString("BirthMonth(").putInt(birthMonth).putString(")");
423                 }
424                 if (birthYear != null) {
425                         hash.putString("BirthYear(").putInt(birthYear).putString(")");
426                 }
427                 if (avatar != null) {
428                         hash.putString("Avatar(").putString(avatar).putString(")");
429                 }
430                 hash.putString("ContactInformation(");
431                 for (Field field : fields) {
432                         hash.putString(field.getName()).putString("(").putString(field.getValue()).putString(")");
433                 }
434                 hash.putString(")");
435                 hash.putString(")");
436
437                 return hash.hash().toString();
438         }
439
440         /**
441          * Container for a profile field.
442          *
443          * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
444          */
445         public class Field {
446
447                 /** The ID of the field. */
448                 private final String id;
449
450                 /** The name of the field. */
451                 private String name;
452
453                 /** The value of the field. */
454                 private String value;
455
456                 /**
457                  * Creates a new field with a random ID.
458                  */
459                 private Field() {
460                         this(UUID.randomUUID().toString());
461                 }
462
463                 /**
464                  * Creates a new field with the given ID.
465                  *
466                  * @param id
467                  *            The ID of the field
468                  */
469                 private Field(String id) {
470                         this.id = checkNotNull(id, "id must not be null");
471                 }
472
473                 /**
474                  * Returns the ID of this field.
475                  *
476                  * @return The ID of this field
477                  */
478                 public String getId() {
479                         return id;
480                 }
481
482                 /**
483                  * Returns the name of this field.
484                  *
485                  * @return The name of this field
486                  */
487                 public String getName() {
488                         return name;
489                 }
490
491                 /**
492                  * Sets the name of this field. The name must not be {@code null} and
493                  * must not match any other fields in this profile but my match the name
494                  * of this field.
495                  *
496                  * @param name
497                  *            The new name of this field
498                  * @return This field
499                  */
500                 public Field setName(String name) {
501                         checkNotNull(name, "name must not be null");
502                         checkArgument(getFieldByName(name) == null, "name must be unique");
503                         this.name = name;
504                         return this;
505                 }
506
507                 /**
508                  * Returns the value of this field.
509                  *
510                  * @return The value of this field
511                  */
512                 public String getValue() {
513                         return value;
514                 }
515
516                 /**
517                  * Sets the value of this field. While {@code null} is allowed, no
518                  * guarantees are made that {@code null} values are correctly persisted
519                  * across restarts of the plugin!
520                  *
521                  * @param value
522                  *            The new value of this field
523                  * @return This field
524                  */
525                 public Field setValue(String value) {
526                         this.value = value;
527                         return this;
528                 }
529
530                 //
531                 // OBJECT METHODS
532                 //
533
534                 /**
535                  * {@inheritDoc}
536                  */
537                 @Override
538                 public boolean equals(Object object) {
539                         if (!(object instanceof Field)) {
540                                 return false;
541                         }
542                         Field field = (Field) object;
543                         return id.equals(field.id);
544                 }
545
546                 /**
547                  * {@inheritDoc}
548                  */
549                 @Override
550                 public int hashCode() {
551                         return id.hashCode();
552                 }
553
554         }
555
556 }