Replace utils’ Default with Guava’s Optional.
[Sone.git] / src / main / java / net / pterodactylus / sone / data / Album.java
1 /*
2  * Sone - Album.java - Copyright © 2011–2013 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 com.google.common.base.Preconditions.checkState;
23
24 import java.util.ArrayList;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.UUID;
30
31 import com.google.common.base.Function;
32 import com.google.common.base.Optional;
33 import com.google.common.base.Predicates;
34 import com.google.common.collect.Collections2;
35
36 /**
37  * Container for images that can also contain nested {@link Album}s.
38  *
39  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
40  */
41 public class Album implements Fingerprintable {
42
43         /** Compares two {@link Album}s by {@link #getTitle()}. */
44         public static final Comparator<Album> TITLE_COMPARATOR = new Comparator<Album>() {
45
46                 @Override
47                 public int compare(Album leftAlbum, Album rightAlbum) {
48                         return leftAlbum.getTitle().compareToIgnoreCase(rightAlbum.getTitle());
49                 }
50         };
51
52         /** The ID of this album. */
53         private final String id;
54
55         /** The Sone this album belongs to. */
56         private Sone sone;
57
58         /** Nested albums. */
59         private final List<Album> albums = new ArrayList<Album>();
60
61         /** The image IDs in order. */
62         private final List<String> imageIds = new ArrayList<String>();
63
64         /** The images in this album. */
65         private final Map<String, Image> images = new HashMap<String, Image>();
66
67         /** The parent album. */
68         private Album parent;
69
70         /** The title of this album. */
71         private String title;
72
73         /** The description of this album. */
74         private String description;
75
76         /** The ID of the album picture. */
77         private String albumImage;
78
79         /**
80          * Creates a new album with a random ID.
81          */
82         public Album() {
83                 this(UUID.randomUUID().toString());
84         }
85
86         /**
87          * Creates a new album with the given ID.
88          *
89          * @param id
90          *            The ID of the album
91          */
92         public Album(String id) {
93                 this.id = checkNotNull(id, "id must not be null");
94         }
95
96         //
97         // ACCESSORS
98         //
99
100         /**
101          * Returns the ID of this album.
102          *
103          * @return The ID of this album
104          */
105         public String getId() {
106                 return id;
107         }
108
109         /**
110          * Returns the Sone this album belongs to.
111          *
112          * @return The Sone this album belongs to
113          */
114         public Sone getSone() {
115                 return sone;
116         }
117
118         /**
119          * Sets the owner of the album. The owner can only be set as long as the
120          * current owner is {@code null}.
121          *
122          * @param sone
123          *            The album owner
124          * @return This album
125          */
126         public Album setSone(Sone sone) {
127                 checkNotNull(sone, "sone must not be null");
128                 checkState((this.sone == null) || (this.sone.equals(sone)), "album owner must not already be set to some other Sone");
129                 this.sone = sone;
130                 return this;
131         }
132
133         /**
134          * Returns the nested albums.
135          *
136          * @return The nested albums
137          */
138         public List<Album> getAlbums() {
139                 return new ArrayList<Album>(albums);
140         }
141
142         /**
143          * Adds an album to this album.
144          *
145          * @param album
146          *            The album to add
147          */
148         public void addAlbum(Album album) {
149                 checkNotNull(album, "album must not be null");
150                 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
151                 checkState((this.parent == null) || (this.parent.equals(album.parent)), "album must not already be set to some other Sone");
152                 album.setParent(this);
153                 if (!albums.contains(album)) {
154                         albums.add(album);
155                 }
156         }
157
158         /**
159          * Removes an album from this album.
160          *
161          * @param album
162          *            The album to remove
163          */
164         public void removeAlbum(Album album) {
165                 checkNotNull(album, "album must not be null");
166                 checkArgument(album.sone.equals(sone), "album must belong this album’s Sone");
167                 checkArgument(equals(album.parent), "album must belong to this album");
168                 albums.remove(album);
169                 album.removeParent();
170         }
171
172         /**
173          * Moves the given album up in this album’s albums. If the album is already
174          * the first album, nothing happens.
175          *
176          * @param album
177          *            The album to move up
178          * @return The album that the given album swapped the place with, or
179          *         <code>null</code> if the album did not change its place
180          */
181         public Album moveAlbumUp(Album album) {
182                 checkNotNull(album, "album must not be null");
183                 checkArgument(album.sone.equals(sone), "album must belong to the same Sone as this album");
184                 checkArgument(equals(album.parent), "album must belong to this album");
185                 int oldIndex = albums.indexOf(album);
186                 if (oldIndex <= 0) {
187                         return null;
188                 }
189                 albums.remove(oldIndex);
190                 albums.add(oldIndex - 1, album);
191                 return albums.get(oldIndex);
192         }
193
194         /**
195          * Moves the given album down in this album’s albums. If the album is
196          * already the last album, nothing happens.
197          *
198          * @param album
199          *            The album to move down
200          * @return The album that the given album swapped the place with, or
201          *         <code>null</code> if the album did not change its place
202          */
203         public Album moveAlbumDown(Album album) {
204                 checkNotNull(album, "album must not be null");
205                 checkArgument(album.sone.equals(sone), "album must belong to the same Sone as this album");
206                 checkArgument(equals(album.parent), "album must belong to this album");
207                 int oldIndex = albums.indexOf(album);
208                 if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
209                         return null;
210                 }
211                 albums.remove(oldIndex);
212                 albums.add(oldIndex + 1, album);
213                 return albums.get(oldIndex);
214         }
215
216         /**
217          * Returns the images in this album.
218          *
219          * @return The images in this album
220          */
221         public List<Image> getImages() {
222                 return new ArrayList<Image>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
223
224                         @Override
225                         @SuppressWarnings("synthetic-access")
226                         public Image apply(String imageId) {
227                                 return images.get(imageId);
228                         }
229                 }), Predicates.notNull()));
230         }
231
232         /**
233          * Adds the given image to this album.
234          *
235          * @param image
236          *            The image to add
237          */
238         public void addImage(Image image) {
239                 checkNotNull(image, "image must not be null");
240                 checkNotNull(image.getSone(), "image must have an owner");
241                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
242                 if (image.getAlbum() != null) {
243                         image.getAlbum().removeImage(image);
244                 }
245                 image.setAlbum(this);
246                 if (imageIds.isEmpty() && (albumImage == null)) {
247                         albumImage = image.getId();
248                 }
249                 if (!imageIds.contains(image.getId())) {
250                         imageIds.add(image.getId());
251                         images.put(image.getId(), image);
252                 }
253         }
254
255         /**
256          * Removes the given image from this album.
257          *
258          * @param image
259          *            The image to remove
260          */
261         public void removeImage(Image image) {
262                 checkNotNull(image, "image must not be null");
263                 checkNotNull(image.getSone(), "image must have an owner");
264                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
265                 imageIds.remove(image.getId());
266                 images.remove(image.getId());
267                 if (image.getId().equals(albumImage)) {
268                         if (images.isEmpty()) {
269                                 albumImage = null;
270                         } else {
271                                 albumImage = images.values().iterator().next().getId();
272                         }
273                 }
274         }
275
276         /**
277          * Moves the given image up in this album’s images. If the image is already
278          * the first image, nothing happens.
279          *
280          * @param image
281          *            The image to move up
282          * @return The image that the given image swapped the place with, or
283          *         <code>null</code> if the image did not change its place
284          */
285         public Image moveImageUp(Image image) {
286                 checkNotNull(image, "image must not be null");
287                 checkNotNull(image.getSone(), "image must have an owner");
288                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
289                 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
290                 int oldIndex = imageIds.indexOf(image.getId());
291                 if (oldIndex <= 0) {
292                         return null;
293                 }
294                 imageIds.remove(image.getId());
295                 imageIds.add(oldIndex - 1, image.getId());
296                 return images.get(imageIds.get(oldIndex));
297         }
298
299         /**
300          * Moves the given image down in this album’s images. If the image is
301          * already the last image, nothing happens.
302          *
303          * @param image
304          *            The image to move down
305          * @return The image that the given image swapped the place with, or
306          *         <code>null</code> if the image did not change its place
307          */
308         public Image moveImageDown(Image image) {
309                 checkNotNull(image, "image must not be null");
310                 checkNotNull(image.getSone(), "image must have an owner");
311                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
312                 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
313                 int oldIndex = imageIds.indexOf(image.getId());
314                 if ((oldIndex == -1) || (oldIndex >= (imageIds.size() - 1))) {
315                         return null;
316                 }
317                 imageIds.remove(image.getId());
318                 imageIds.add(oldIndex + 1, image.getId());
319                 return images.get(imageIds.get(oldIndex));
320         }
321
322         /**
323          * Returns the album image of this album, or {@code null} if no album image
324          * has been set.
325          *
326          * @return The image to show when this album is listed
327          */
328         public Image getAlbumImage() {
329                 if (albumImage == null) {
330                         return null;
331                 }
332                 return Optional.fromNullable(images.get(albumImage)).or(images.values().iterator().next());
333         }
334
335         /**
336          * Sets the ID of the album image.
337          *
338          * @param id
339          *            The ID of the album image
340          * @return This album
341          */
342         public Album setAlbumImage(String id) {
343                 this.albumImage = id;
344                 return this;
345         }
346
347         /**
348          * Returns whether this album contains any other albums or images.
349          *
350          * @return {@code true} if this album is empty, {@code false} otherwise
351          */
352         public boolean isEmpty() {
353                 return albums.isEmpty() && images.isEmpty();
354         }
355
356         /**
357          * Returns the parent album of this album.
358          *
359          * @return The parent album of this album, or {@code null} if this album
360          *         does not have a parent
361          */
362         public Album getParent() {
363                 return parent;
364         }
365
366         /**
367          * Sets the parent album of this album.
368          *
369          * @param parent
370          *            The new parent album of this album
371          * @return This album
372          */
373         protected Album setParent(Album parent) {
374                 this.parent = checkNotNull(parent, "parent must not be null");
375                 return this;
376         }
377
378         /**
379          * Removes the parent album of this album.
380          *
381          * @return This album
382          */
383         protected Album removeParent() {
384                 this.parent = null;
385                 return this;
386         }
387
388         /**
389          * Returns the title of this album.
390          *
391          * @return The title of this album
392          */
393         public String getTitle() {
394                 return title;
395         }
396
397         /**
398          * Sets the title of this album.
399          *
400          * @param title
401          *            The title of this album
402          * @return This album
403          */
404         public Album setTitle(String title) {
405                 this.title = checkNotNull(title, "title must not be null");
406                 return this;
407         }
408
409         /**
410          * Returns the description of this album.
411          *
412          * @return The description of this album
413          */
414         public String getDescription() {
415                 return description;
416         }
417
418         /**
419          * Sets the description of this album.
420          *
421          * @param description
422          *            The description of this album
423          * @return This album
424          */
425         public Album setDescription(String description) {
426                 this.description = checkNotNull(description, "description must not be null");
427                 return this;
428         }
429
430         //
431         // FINGERPRINTABLE METHODS
432         //
433
434         /**
435          * {@inheritDoc}
436          */
437         @Override
438         public String getFingerprint() {
439                 StringBuilder fingerprint = new StringBuilder();
440                 fingerprint.append("Album(");
441                 fingerprint.append("ID(").append(id).append(')');
442                 fingerprint.append("Title(").append(title).append(')');
443                 fingerprint.append("Description(").append(description).append(')');
444                 if (albumImage != null) {
445                         fingerprint.append("AlbumImage(").append(albumImage).append(')');
446                 }
447
448                 /* add nested albums. */
449                 fingerprint.append("Albums(");
450                 for (Album album : albums) {
451                         fingerprint.append(album.getFingerprint());
452                 }
453                 fingerprint.append(')');
454
455                 /* add images. */
456                 fingerprint.append("Images(");
457                 for (Image image : getImages()) {
458                         if (image.isInserted()) {
459                                 fingerprint.append(image.getFingerprint());
460                         }
461                 }
462                 fingerprint.append(')');
463
464                 fingerprint.append(')');
465                 return fingerprint.toString();
466         }
467
468         //
469         // OBJECT METHODS
470         //
471
472         /**
473          * {@inheritDoc}
474          */
475         @Override
476         public int hashCode() {
477                 return id.hashCode();
478         }
479
480         /**
481          * {@inheritDoc}
482          */
483         @Override
484         public boolean equals(Object object) {
485                 if (!(object instanceof Album)) {
486                         return false;
487                 }
488                 Album album = (Album) object;
489                 return id.equals(album.id);
490         }
491
492 }