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