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