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