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