2 * Sone - Profile.java - Copyright © 2010–2013 David Roden
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.
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.
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/>.
18 package net.pterodactylus.sone.data;
20 import static com.google.common.base.Optional.absent;
21 import static com.google.common.base.Optional.fromNullable;
22 import static com.google.common.base.Optional.of;
23 import static com.google.common.base.Preconditions.checkArgument;
24 import static com.google.common.base.Preconditions.checkNotNull;
25 import static com.google.common.base.Preconditions.checkState;
26 import static java.lang.Math.max;
27 import static java.lang.Math.min;
28 import static java.util.UUID.randomUUID;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
34 import com.google.common.base.Optional;
35 import com.google.common.hash.Hasher;
36 import com.google.common.hash.Hashing;
39 * A profile stores personal information about a {@link Sone}. All information
40 * is optional and can be {@code null}.
42 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
44 public class Profile implements Fingerprintable {
46 /** The Sone this profile belongs to. */
47 private final Sone sone;
49 private volatile Name name = new Name();
50 private volatile BirthDate birthDate = new BirthDate();
52 /** The ID of the avatar image. */
53 private volatile String avatar;
55 /** Additional fields in the profile. */
56 private final List<Field> fields = Collections.synchronizedList(new ArrayList<Field>());
59 * Creates a new empty profile.
62 * The Sone this profile belongs to
64 public Profile(Sone sone) {
69 * Creates a copy of a profile.
74 public Profile(Profile profile) {
75 this.sone = profile.sone;
76 this.name = profile.name;
77 this.birthDate = profile.birthDate;
78 this.avatar = profile.avatar;
79 this.fields.addAll(profile.fields);
87 * Returns the Sone this profile belongs to.
89 * @return The Sone this profile belongs to
91 public Sone getSone() {
95 public String getFirstName() {
96 return name.getFirst().orNull();
99 public String getMiddleName() {
100 return name.getMiddle().orNull();
103 public String getLastName() {
104 return name.getLast().orNull();
107 public Integer getBirthDay() {
108 return birthDate.getDay().orNull();
111 public Integer getBirthMonth() {
112 return birthDate.getMonth().orNull();
115 public Integer getBirthYear() {
116 return birthDate.getYear().orNull();
119 public String getAvatar() {
123 public Profile setAvatar(Optional<String> avatarId) {
124 this.avatar = avatarId.orNull();
128 public List<Field> getFields() {
129 return new ArrayList<Field>(fields);
132 public boolean hasField(Field field) {
133 return fields.contains(field);
136 public Optional<Field> getFieldById(String fieldId) {
137 checkNotNull(fieldId, "fieldId must not be null");
138 for (Field field : fields) {
139 if (field.getId().equals(fieldId)) {
146 public Optional<Field> getFieldByName(String fieldName) {
147 for (Field field : fields) {
148 if (field.getName().equals(fieldName)) {
155 public Field addField(String fieldName) throws IllegalArgumentException {
156 checkNotNull(fieldName, "fieldName must not be null");
157 checkArgument(fieldName.length() > 0, "fieldName must not be empty");
158 checkState(!getFieldByName(fieldName).isPresent(), "fieldName must be unique");
159 @SuppressWarnings("synthetic-access")
160 Field field = new Field(fieldName);
165 public void renameField(Field field, String newName) {
166 int indexOfField = getFieldIndex(field);
167 if (indexOfField == -1) {
170 fields.set(indexOfField, new Field(field.getId(), newName, field.getValue()));
173 public void setField(Field field, String newValue) {
174 int indexOfField = getFieldIndex(field);
175 if (indexOfField == -1) {
178 fields.set(indexOfField, new Field(field.getId(), field.getName(), newValue));
181 public void moveFieldUp(Field field) {
182 checkNotNull(field, "field must not be null");
183 checkArgument(hasField(field), "field must belong to this profile");
184 int fieldIndex = getFieldIndex(field);
185 fields.remove(field);
186 fields.add(max(fieldIndex - 1, 0), field);
189 public void moveFieldDown(Field field) {
190 checkNotNull(field, "field must not be null");
191 checkArgument(hasField(field), "field must belong to this profile");
192 int fieldIndex = getFieldIndex(field);
193 fields.remove(field);
194 fields.add(min(fieldIndex + 1, fields.size()), field);
197 public void removeField(Field field) {
198 checkNotNull(field, "field must not be null");
199 fields.remove(field);
202 public Modifier modify() {
203 return new Modifier() {
204 private Optional<String> firstName = name.getFirst();
205 private Optional<String> middleName = name.getMiddle();
206 private Optional<String> lastName = name.getLast();
207 private Optional<Integer> birthYear = birthDate.getYear();
208 private Optional<Integer> birthMonth = birthDate.getMonth();
209 private Optional<Integer> birthDay = birthDate.getDay();
212 public Modifier setFirstName(String firstName) {
213 this.firstName = fromNullable(firstName);
218 public Modifier setMiddleName(String middleName) {
219 this.middleName = fromNullable(middleName);
224 public Modifier setLastName(String lastName) {
225 this.lastName = fromNullable(lastName);
230 public Modifier setBirthYear(Integer birthYear) {
231 this.birthYear = fromNullable(birthYear);
236 public Modifier setBirthMonth(Integer birthMonth) {
237 this.birthMonth = fromNullable(birthMonth);
242 public Modifier setBirthDay(Integer birthDay) {
243 this.birthDay = fromNullable(birthDay);
248 public Profile update() {
249 Profile.this.name = new Name(firstName, middleName, lastName);
250 Profile.this.birthDate = new BirthDate(birthYear, birthMonth, birthDay);
256 public interface Modifier {
258 Modifier setFirstName(String firstName);
259 Modifier setMiddleName(String middleName);
260 Modifier setLastName(String lastName);
261 Modifier setBirthYear(Integer birthYear);
262 Modifier setBirthMonth(Integer birthMonth);
263 Modifier setBirthDay(Integer birthDay);
273 * Returns the index of the field with the given name.
276 * The name of the field
277 * @return The index of the field, or {@code -1} if there is no field with
280 private int getFieldIndex(Field field) {
281 return fields.indexOf(field);
285 // INTERFACE Fingerprintable
289 public String getFingerprint() {
290 Hasher hash = Hashing.sha256().newHasher();
291 hash.putString("Profile(");
292 hash.putString(name.getFingerprint());
293 hash.putString(birthDate.getFingerprint());
294 if (avatar != null) {
295 hash.putString("Avatar(").putString(avatar).putString(")");
297 hash.putString("ContactInformation(");
298 for (Field field : fields) {
299 if (field.getValue() != null) {
300 hash.putString(field.getName()).putString("(").putString(field.getValue()).putString(")");
306 return hash.hash().toString();
310 * Container for a profile field.
312 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
314 public static class Field {
316 private final String id;
317 private final String name;
318 private final String value;
320 public Field(String name) {
324 public Field(String name, String value) {
325 this(randomUUID().toString(), name, value);
328 public Field(String id, String name, String value) {
329 this.id = checkNotNull(id, "id must not be null");
334 public String getId() {
338 public String getName() {
342 public String getValue() {
347 public boolean equals(Object object) {
348 if (!(object instanceof Field)) {
351 Field field = (Field) object;
352 return id.equals(field.id);
356 public int hashCode() {
357 return id.hashCode();
362 public static class Name implements Fingerprintable {
364 private final Optional<String> first;
365 private final Optional<String> middle;
366 private final Optional<String> last;
369 this(Optional.<String>absent(), Optional.<String>absent(), Optional.<String>absent());
372 public Name(Optional<String> first, Optional<String> middle, Optional<String> last) {
374 this.middle = middle;
378 public Optional<String> getFirst() {
382 public Optional<String> getMiddle() {
386 public Optional<String> getLast() {
391 public String getFingerprint() {
392 Hasher hash = Hashing.sha256().newHasher();
393 hash.putString("Name(");
394 if (first.isPresent()) {
395 hash.putString("First(").putString(first.get()).putString(")");
397 if (middle.isPresent()) {
398 hash.putString("Middle(").putString(middle.get()).putString(")");
400 if (last.isPresent()) {
401 hash.putString("Last(").putString(last.get()).putString(")");
404 return hash.hash().toString();
409 public static class BirthDate implements Fingerprintable {
411 private final Optional<Integer> year;
412 private final Optional<Integer> month;
413 private final Optional<Integer> day;
416 this(Optional.<Integer>absent(), Optional.<Integer>absent(), Optional.<Integer>absent());
419 public BirthDate(Optional<Integer> year, Optional<Integer> month, Optional<Integer> day) {
425 public Optional<Integer> getYear() {
429 public Optional<Integer> getMonth() {
433 public Optional<Integer> getDay() {
438 public String getFingerprint() {
439 Hasher hash = Hashing.sha256().newHasher();
440 hash.putString("Birthdate(");
441 if (year.isPresent()) {
442 hash.putString("Year(").putInt(year.get()).putString(")");
444 if (month.isPresent()) {
445 hash.putString("Month(").putInt(month.get()).putString(")");
447 if (day.isPresent()) {
448 hash.putString("Day(").putInt(day.get()).putString(")");
451 return hash.hash().toString();