2 * Sone - Album.java - Copyright © 2011–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.Preconditions.checkArgument;
21 import static com.google.common.base.Preconditions.checkNotNull;
22 import static com.google.common.base.Preconditions.checkState;
24 import java.util.ArrayList;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.UUID;
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 import com.google.common.collect.FluentIterable;
36 import com.google.common.collect.ImmutableList;
37 import com.google.common.hash.Hasher;
38 import com.google.common.hash.Hashing;
41 * Container for images that can also contain nested {@link Album}s.
43 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
45 public class Album implements Fingerprintable {
47 /** Compares two {@link Album}s by {@link #getTitle()}. */
48 public static final Comparator<Album> TITLE_COMPARATOR = new Comparator<Album>() {
51 public int compare(Album leftAlbum, Album rightAlbum) {
52 return leftAlbum.getTitle().compareToIgnoreCase(rightAlbum.getTitle());
56 /** Function that flattens the given album and all albums beneath it. */
57 public static final Function<Album, List<Album>> FLATTENER = new Function<Album, List<Album>>() {
60 public List<Album> apply(Album album) {
61 List<Album> albums = new ArrayList<Album>();
63 for (Album subAlbum : album.getAlbums()) {
64 albums.addAll(FluentIterable.from(ImmutableList.of(subAlbum)).transformAndConcat(FLATTENER).toList());
70 /** The ID of this album. */
71 private final String id;
73 /** The Sone this album belongs to. */
77 private final List<Album> albums = new ArrayList<Album>();
79 /** The image IDs in order. */
80 private final List<String> imageIds = new ArrayList<String>();
82 /** The images in this album. */
83 private final Map<String, Image> images = new HashMap<String, Image>();
85 /** The parent album. */
88 /** The title of this album. */
91 /** The description of this album. */
92 private String description;
94 /** The ID of the album picture. */
95 private String albumImage;
98 * Creates a new album with a random ID.
101 this(UUID.randomUUID().toString());
105 * Creates a new album with the given ID.
108 * The ID of the album
110 public Album(String id) {
111 this.id = checkNotNull(id, "id must not be null");
119 * Returns the ID of this album.
121 * @return The ID of this album
123 public String getId() {
128 * Returns the Sone this album belongs to.
130 * @return The Sone this album belongs to
132 public Sone getSone() {
137 * Sets the owner of the album. The owner can only be set as long as the
138 * current owner is {@code null}.
144 public Album setSone(Sone sone) {
145 checkNotNull(sone, "sone must not be null");
146 checkState((this.sone == null) || (this.sone.equals(sone)), "album owner must not already be set to some other Sone");
152 * Returns the nested albums.
154 * @return The nested albums
156 public List<Album> getAlbums() {
157 return new ArrayList<Album>(albums);
161 * Adds an album to this album.
166 public void addAlbum(Album album) {
167 checkNotNull(album, "album must not be null");
168 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
169 checkState((this.parent == null) || (this.parent.equals(album.parent)), "album must not already be set to some other Sone");
170 album.setParent(this);
171 if (!albums.contains(album)) {
177 * Removes an album from this album.
180 * The album to remove
182 public void removeAlbum(Album album) {
183 checkNotNull(album, "album must not be null");
184 checkArgument(album.sone.equals(sone), "album must belong this album’s Sone");
185 checkArgument(equals(album.parent), "album must belong to this album");
186 albums.remove(album);
187 album.removeParent();
191 * Moves the given album up in this album’s albums. If the album is already
192 * the first album, nothing happens.
195 * The album to move up
196 * @return The album that the given album swapped the place with, or
197 * <code>null</code> if the album did not change its place
199 public Album moveAlbumUp(Album album) {
200 checkNotNull(album, "album must not be null");
201 checkArgument(album.sone.equals(sone), "album must belong to the same Sone as this album");
202 checkArgument(equals(album.parent), "album must belong to this album");
203 int oldIndex = albums.indexOf(album);
207 albums.remove(oldIndex);
208 albums.add(oldIndex - 1, album);
209 return albums.get(oldIndex);
213 * Moves the given album down in this album’s albums. If the album is
214 * already the last album, nothing happens.
217 * The album to move down
218 * @return The album that the given album swapped the place with, or
219 * <code>null</code> if the album did not change its place
221 public Album moveAlbumDown(Album album) {
222 checkNotNull(album, "album must not be null");
223 checkArgument(album.sone.equals(sone), "album must belong to the same Sone as this album");
224 checkArgument(equals(album.parent), "album must belong to this album");
225 int oldIndex = albums.indexOf(album);
226 if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
229 albums.remove(oldIndex);
230 albums.add(oldIndex + 1, album);
231 return albums.get(oldIndex);
235 * Returns the images in this album.
237 * @return The images in this album
239 public List<Image> getImages() {
240 return new ArrayList<Image>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
243 @SuppressWarnings("synthetic-access")
244 public Image apply(String imageId) {
245 return images.get(imageId);
247 }), Predicates.notNull()));
251 * Adds the given image to this album.
256 public void addImage(Image image) {
257 checkNotNull(image, "image must not be null");
258 checkNotNull(image.getSone(), "image must have an owner");
259 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
260 if (image.getAlbum() != null) {
261 image.getAlbum().removeImage(image);
263 image.setAlbum(this);
264 if (imageIds.isEmpty() && (albumImage == null)) {
265 albumImage = image.getId();
267 if (!imageIds.contains(image.getId())) {
268 imageIds.add(image.getId());
269 images.put(image.getId(), image);
274 * Removes the given image from this album.
277 * The image to remove
279 public void removeImage(Image image) {
280 checkNotNull(image, "image must not be null");
281 checkNotNull(image.getSone(), "image must have an owner");
282 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
283 imageIds.remove(image.getId());
284 images.remove(image.getId());
285 if (image.getId().equals(albumImage)) {
286 if (images.isEmpty()) {
289 albumImage = images.values().iterator().next().getId();
295 * Moves the given image up in this album’s images. If the image is already
296 * the first image, nothing happens.
299 * The image to move up
300 * @return The image that the given image swapped the place with, or
301 * <code>null</code> if the image did not change its place
303 public Image moveImageUp(Image image) {
304 checkNotNull(image, "image must not be null");
305 checkNotNull(image.getSone(), "image must have an owner");
306 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
307 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
308 int oldIndex = imageIds.indexOf(image.getId());
312 imageIds.remove(image.getId());
313 imageIds.add(oldIndex - 1, image.getId());
314 return images.get(imageIds.get(oldIndex));
318 * Moves the given image down in this album’s images. If the image is
319 * already the last image, nothing happens.
322 * The image to move down
323 * @return The image that the given image swapped the place with, or
324 * <code>null</code> if the image did not change its place
326 public Image moveImageDown(Image image) {
327 checkNotNull(image, "image must not be null");
328 checkNotNull(image.getSone(), "image must have an owner");
329 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
330 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
331 int oldIndex = imageIds.indexOf(image.getId());
332 if ((oldIndex == -1) || (oldIndex >= (imageIds.size() - 1))) {
335 imageIds.remove(image.getId());
336 imageIds.add(oldIndex + 1, image.getId());
337 return images.get(imageIds.get(oldIndex));
341 * Returns the album image of this album, or {@code null} if no album image
344 * @return The image to show when this album is listed
346 public Image getAlbumImage() {
347 if (albumImage == null) {
350 return Optional.fromNullable(images.get(albumImage)).or(images.values().iterator().next());
354 * Sets the ID of the album image.
357 * The ID of the album image
360 public Album setAlbumImage(String id) {
361 this.albumImage = id;
366 * Returns whether this album contains any other albums or images.
368 * @return {@code true} if this album is empty, {@code false} otherwise
370 public boolean isEmpty() {
371 return albums.isEmpty() && images.isEmpty();
375 * Returns the parent album of this album.
377 * @return The parent album of this album, or {@code null} if this album
378 * does not have a parent
380 public Album getParent() {
385 * Sets the parent album of this album.
388 * The new parent album of this album
391 protected Album setParent(Album parent) {
392 this.parent = checkNotNull(parent, "parent must not be null");
397 * Removes the parent album of this album.
401 protected Album removeParent() {
407 * Returns the title of this album.
409 * @return The title of this album
411 public String getTitle() {
416 * Sets the title of this album.
419 * The title of this album
422 public Album setTitle(String title) {
423 this.title = checkNotNull(title, "title must not be null");
428 * Returns the description of this album.
430 * @return The description of this album
432 public String getDescription() {
437 * Sets the description of this album.
440 * The description of this album
443 public Album setDescription(String description) {
444 this.description = checkNotNull(description, "description must not be null");
449 // FINGERPRINTABLE METHODS
456 public String getFingerprint() {
457 Hasher hash = Hashing.sha256().newHasher();
458 hash.putString("Album(");
459 hash.putString("ID(").putString(id).putString(")");
460 hash.putString("Title(").putString(title).putString(")");
461 hash.putString("Description(").putString(description).putString(")");
462 if (albumImage != null) {
463 hash.putString("AlbumImage(").putString(albumImage).putString(")");
466 /* add nested albums. */
467 hash.putString("Albums(");
468 for (Album album : albums) {
469 hash.putString(album.getFingerprint());
474 hash.putString("Images(");
475 for (Image image : getImages()) {
476 if (image.isInserted()) {
477 hash.putString(image.getFingerprint());
483 return hash.hash().toString();
494 public int hashCode() {
495 return id.hashCode();
502 public boolean equals(Object object) {
503 if (!(object instanceof Album)) {
506 Album album = (Album) object;
507 return id.equals(album.id);