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