Add custom fields to profile.
[Sone.git] / src / main / java / net / pterodactylus / sone / data / Profile.java
1 /*
2  * FreenetSone - 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.HashMap;
23 import java.util.List;
24 import java.util.Map;
25
26 import net.pterodactylus.util.validation.Validation;
27
28 /**
29  * A profile stores personal information about a {@link Sone}. All information
30  * is optional and can be {@code null}.
31  *
32  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
33  */
34 public class Profile implements Fingerprintable {
35
36         /** Whether the profile was modified. */
37         private volatile boolean modified;
38
39         /** The first name. */
40         private volatile String firstName;
41
42         /** The middle name(s). */
43         private volatile String middleName;
44
45         /** The last name. */
46         private volatile String lastName;
47
48         /** The day of the birth date. */
49         private volatile Integer birthDay;
50
51         /** The month of the birth date. */
52         private volatile Integer birthMonth;
53
54         /** The year of the birth date. */
55         private volatile Integer birthYear;
56
57         /** Additional fields in the profile. */
58         private final List<String> fields = Collections.synchronizedList(new ArrayList<String>());
59
60         /** The field values. */
61         private final Map<String, String> fieldValues = Collections.synchronizedMap(new HashMap<String, String>());
62
63         /**
64          * Creates a new empty profile.
65          */
66         public Profile() {
67                 /* do nothing. */
68         }
69
70         /**
71          * Creates a copy of a profile.
72          *
73          * @param profile
74          *            The profile to copy
75          */
76         public Profile(Profile profile) {
77                 if (profile == null) {
78                         return;
79                 }
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.fieldValues.putAll(profile.fieldValues);
87         }
88
89         //
90         // ACCESSORS
91         //
92
93         /**
94          * Returns whether this profile was modified after creation. To clear the
95          * “is modified” flag you need to create a new profile from this one using
96          * the {@link #Profile(Profile)} constructor.
97          *
98          * @return {@code true} if this profile was modified after creation,
99          *         {@code false} otherwise
100          */
101         public boolean isModified() {
102                 return modified;
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                 modified |= ((firstName != null) && (!firstName.equals(this.firstName))) || (this.firstName != null);
123                 this.firstName = firstName;
124                 return this;
125         }
126
127         /**
128          * Returns the middle name(s).
129          *
130          * @return The middle name
131          */
132         public String getMiddleName() {
133                 return middleName;
134         }
135
136         /**
137          * Sets the middle name.
138          *
139          * @param middleName
140          *            The middle name to set
141          * @return This profile (for method chaining)
142          */
143         public Profile setMiddleName(String middleName) {
144                 modified |= ((middleName != null) && (!middleName.equals(this.middleName))) || (this.middleName != null);
145                 this.middleName = middleName;
146                 return this;
147         }
148
149         /**
150          * Returns the last name.
151          *
152          * @return The last name
153          */
154         public String getLastName() {
155                 return lastName;
156         }
157
158         /**
159          * Sets the last name.
160          *
161          * @param lastName
162          *            The last name to set
163          * @return This profile (for method chaining)
164          */
165         public Profile setLastName(String lastName) {
166                 modified |= ((lastName != null) && (!lastName.equals(this.lastName))) || (this.lastName != null);
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                 modified |= ((birthDay != null) && (!birthDay.equals(this.birthDay))) || (this.birthDay != null);
189                 this.birthDay = birthDay;
190                 return this;
191         }
192
193         /**
194          * Returns the month of the birth date.
195          *
196          * @return The month of the birth date (from 1 to 12)
197          */
198         public Integer getBirthMonth() {
199                 return birthMonth;
200         }
201
202         /**
203          * Sets the month of the birth date.
204          *
205          * @param birthMonth
206          *            The month of the birth date (from 1 to 12)
207          * @return This profile (for method chaining)
208          */
209         public Profile setBirthMonth(Integer birthMonth) {
210                 modified |= ((birthMonth != null) && (!birthMonth.equals(this.birthMonth))) || (this.birthMonth != null);
211                 this.birthMonth = birthMonth;
212                 return this;
213         }
214
215         /**
216          * Returns the year of the birth date.
217          *
218          * @return The year of the birth date
219          */
220         public Integer getBirthYear() {
221                 return birthYear;
222         }
223
224         /**
225          * Sets the year of the birth date.
226          *
227          * @param birthYear
228          *            The year of the birth date
229          * @return This profile (for method chaining)
230          */
231         public Profile setBirthYear(Integer birthYear) {
232                 modified |= ((birthYear != null) && (!birthYear.equals(this.birthYear))) || (this.birthYear != null);
233                 this.birthYear = birthYear;
234                 return this;
235         }
236
237         /**
238          * Appends a new field to the list of fields.
239          *
240          * @param field
241          *            The field to add
242          * @throws IllegalArgumentException
243          *             if the name is not valid
244          */
245         public void addField(String field) throws IllegalArgumentException {
246                 Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Unique", !fields.contains(field), true).check();
247                 fields.add(field);
248         }
249
250         /**
251          * Moves the field with the given index up one position in the field list.
252          * The index of the field to move must be greater than {@code 0} (because
253          * you obviously can not move the first field further up).
254          *
255          * @param fieldIndex
256          *            The index of the field to move
257          */
258         public void moveFieldUp(int fieldIndex) {
259                 Validation.begin().isGreater("Field Index", fieldIndex, 0).isLess("Field Index", fieldIndex, fields.size()).check();
260                 String field = fields.remove(fieldIndex);
261                 fields.add(fieldIndex - 1, field);
262         }
263
264         /**
265          * Moves the field with the given name up one position in the field list.
266          * The field must not be the first field (because you obviously can not move
267          * the first field further up).
268          *
269          * @param field
270          *            The name of the field to move
271          */
272         public void moveFieldUp(String field) {
273                 Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check();
274                 moveFieldUp(getFieldIndex(field));
275         }
276
277         /**
278          * Moves the field with the given index down one position in the field list.
279          * The index of the field to move must be less than the index of the last
280          * field (because you obviously can not move the last field further down).
281          *
282          * @param fieldIndex
283          *            The index of the field to move
284          */
285         public void moveFieldDown(int fieldIndex) {
286                 Validation.begin().isGreaterOrEqual("Field Index", fieldIndex, 0).isLess("Field Index", fieldIndex, fields.size() - 1).check();
287                 String field = fields.remove(fieldIndex);
288                 fields.add(fieldIndex + 1, field);
289         }
290
291         /**
292          * Moves the field with the given name down one position in the field list.
293          * The field must not be the last field (because you obviously can not move
294          * the last field further down).
295          *
296          * @param field
297          *            The name of the field to move
298          */
299         public void moveFieldDown(String field) {
300                 Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check();
301                 moveFieldDown(getFieldIndex(field));
302         }
303
304         /**
305          * Removes the field at the given index.
306          *
307          * @param fieldIndex
308          *            The index of the field to remove
309          */
310         public void removeField(int fieldIndex) {
311                 Validation.begin().isGreaterOrEqual("Field Index", fieldIndex, 0).isLess("Field Index", fieldIndex, fields.size()).check();
312                 String field = fields.remove(fieldIndex);
313                 fieldValues.remove(field);
314         }
315
316         /**
317          * Removes the field with the given name.
318          *
319          * @param field
320          *            The name of the field
321          */
322         public void removeField(String field) {
323                 Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check();
324                 removeField(getFieldIndex(field));
325         }
326
327         /**
328          * Returns the value of the field with the given name.
329          *
330          * @param field
331          *            The name of the field
332          * @return The value of the field, or {@code null} if there is no such field
333          */
334         public String getField(String field) {
335                 return fieldValues.get(field);
336         }
337
338         /**
339          * Sets the value of the field with the given name.
340          *
341          * @param field
342          *            The name of the field
343          * @param value
344          *            The value of the field
345          */
346         public void setField(String field, String value) {
347                 Validation.begin().isNotNull("Field Name", field).check().isGreater("Field Name Length", field.length(), 0).isEqual("Field Name Existing", fields.contains(field), true).check();
348                 fieldValues.put(field, value);
349         }
350
351         /**
352          * Returns a list of all fields stored in this profile.
353          *
354          * @return The fields of this profile
355          */
356         public List<String> getFields() {
357                 return Collections.unmodifiableList(fields);
358         }
359
360         //
361         // PRIVATE METHODS
362         //
363
364         /**
365          * Returns the index of the field with the given name.
366          *
367          * @param field
368          *            The name of the field
369          * @return The index of the field, or {@code -1} if there is no field with
370          *         the given name
371          */
372         private int getFieldIndex(String field) {
373                 return fields.indexOf(field);
374         }
375
376         //
377         // INTERFACE Fingerprintable
378         //
379
380         /**
381          * {@inheritDoc}
382          */
383         @Override
384         public String getFingerprint() {
385                 StringBuilder fingerprint = new StringBuilder();
386                 fingerprint.append("Profile(");
387                 if (firstName != null) {
388                         fingerprint.append("FirstName(").append(firstName).append(')');
389                 }
390                 if (middleName != null) {
391                         fingerprint.append("MiddleName(").append(middleName).append(')');
392                 }
393                 if (lastName != null) {
394                         fingerprint.append("LastName(").append(lastName).append(')');
395                 }
396                 if (birthDay != null) {
397                         fingerprint.append("BirthDay(").append(birthDay).append(')');
398                 }
399                 if (birthMonth != null) {
400                         fingerprint.append("BirthMonth(").append(birthMonth).append(')');
401                 }
402                 if (birthYear != null) {
403                         fingerprint.append("BirthYear(").append(birthYear).append(')');
404                 }
405                 fingerprint.append("ContactInformation(");
406                 for (String field : fields) {
407                         fingerprint.append(field).append('(').append(fieldValues.get(field)).append(')');
408                 }
409                 fingerprint.append(")");
410                 fingerprint.append(")");
411
412                 return fingerprint.toString();
413         }
414
415 }