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