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.impl;
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.Preconditions.checkArgument;
23 import static com.google.common.base.Preconditions.checkNotNull;
24 import static com.google.common.base.Preconditions.checkState;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.UUID;
32 import net.pterodactylus.sone.data.Album;
33 import net.pterodactylus.sone.data.Image;
34 import net.pterodactylus.sone.data.Sone;
35 import net.pterodactylus.sone.database.ImageBuilder;
37 import com.google.common.base.Function;
38 import com.google.common.base.Optional;
39 import com.google.common.base.Predicates;
40 import com.google.common.collect.Collections2;
41 import com.google.common.hash.Hasher;
42 import com.google.common.hash.Hashing;
45 * Container for images that can also contain nested {@link Album}s.
47 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
49 public class DefaultAlbum implements Album {
51 /** The ID of this album. */
52 private final String id;
54 /** The Sone this album belongs to. */
58 private final List<Album> albums = new ArrayList<Album>();
60 /** The image IDs in order. */
61 private final List<String> imageIds = new ArrayList<String>();
63 /** The images in this album. */
64 private final Map<String, Image> images = new HashMap<String, Image>();
66 /** The parent album. */
69 /** The title of this album. */
72 /** The description of this album. */
73 private String description;
75 /** The ID of the album picture. */
76 private String albumImage;
78 /** Creates a new album with a random ID. */
79 public DefaultAlbum() {
80 this(UUID.randomUUID().toString());
84 * Creates a new album with the given ID.
89 public DefaultAlbum(String id) {
90 this.id = checkNotNull(id, "id must not be null");
98 public String getId() {
103 public Sone getSone() {
108 public Album setSone(Sone sone) {
109 checkNotNull(sone, "sone must not be null");
110 checkState((this.sone == null) || (this.sone.equals(sone)), "album owner must not already be set to some other Sone");
116 public List<Album> getAlbums() {
117 return new ArrayList<Album>(albums);
121 public void addAlbum(Album album) {
122 checkNotNull(album, "album must not be null");
123 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
124 album.setParent(this);
125 if (!albums.contains(album)) {
131 public void removeAlbum(Album album) {
132 checkNotNull(album, "album must not be null");
133 checkArgument(album.getSone().equals(sone), "album must belong this album’s Sone");
134 checkArgument(equals(album.getParent()), "album must belong to this album");
135 albums.remove(album);
136 album.removeParent();
140 public Album moveAlbumUp(Album album) {
141 checkNotNull(album, "album must not be null");
142 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
143 checkArgument(equals(album.getParent()), "album must belong to this album");
144 int oldIndex = albums.indexOf(album);
148 albums.remove(oldIndex);
149 albums.add(oldIndex - 1, album);
150 return albums.get(oldIndex);
154 public Album moveAlbumDown(Album album) {
155 checkNotNull(album, "album must not be null");
156 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
157 checkArgument(equals(album.getParent()), "album must belong to this album");
158 int oldIndex = albums.indexOf(album);
159 if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
162 albums.remove(oldIndex);
163 albums.add(oldIndex + 1, album);
164 return albums.get(oldIndex);
168 public List<Image> getImages() {
169 return new ArrayList<Image>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
172 @SuppressWarnings("synthetic-access")
173 public Image apply(String imageId) {
174 return images.get(imageId);
176 }), Predicates.notNull()));
180 public void removeImage(Image image) {
181 checkNotNull(image, "image must not be null");
182 checkNotNull(image.getSone(), "image must have an owner");
183 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
184 imageIds.remove(image.getId());
185 images.remove(image.getId());
186 if (image.getId().equals(albumImage)) {
187 if (images.isEmpty()) {
190 albumImage = images.values().iterator().next().getId();
196 public Image moveImageUp(Image image) {
197 checkNotNull(image, "image must not be null");
198 checkNotNull(image.getSone(), "image must have an owner");
199 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
200 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
201 int oldIndex = imageIds.indexOf(image.getId());
205 imageIds.remove(image.getId());
206 imageIds.add(oldIndex - 1, image.getId());
207 return images.get(imageIds.get(oldIndex));
211 public Image moveImageDown(Image image) {
212 checkNotNull(image, "image must not be null");
213 checkNotNull(image.getSone(), "image must have an owner");
214 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
215 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
216 int oldIndex = imageIds.indexOf(image.getId());
217 if ((oldIndex == -1) || (oldIndex >= (imageIds.size() - 1))) {
220 imageIds.remove(image.getId());
221 imageIds.add(oldIndex + 1, image.getId());
222 return images.get(imageIds.get(oldIndex));
226 public Image getAlbumImage() {
227 if (albumImage == null) {
230 return Optional.fromNullable(images.get(albumImage)).or(images.values().iterator().next());
234 public boolean isEmpty() {
235 return albums.isEmpty() && images.isEmpty();
239 public boolean isRoot() {
240 return parent == null;
244 public Album getParent() {
249 public Album setParent(Album parent) {
250 this.parent = checkNotNull(parent, "parent must not be null");
255 public Album removeParent() {
261 public String getTitle() {
266 public String getDescription() {
271 public ImageBuilder newImageBuilder() throws IllegalStateException {
272 return new DefaultImageBuilder(sone, this) {
274 public Image build() throws IllegalStateException {
275 Image image = super.build();
276 if (images.isEmpty() && (albumImage == null)) {
277 albumImage = image.getId();
279 images.put(image.getId(), image);
280 imageIds.add(image.getId());
287 public Modifier modify() throws IllegalStateException {
288 // TODO: reenable check for local Sones
289 return new Modifier() {
290 private Optional<String> title = absent();
292 private Optional<String> description = absent();
294 private Optional<String> albumImage = absent();
297 public Modifier setTitle(String title) {
298 this.title = fromNullable(title);
303 public Modifier setDescription(String description) {
304 this.description = fromNullable(description);
309 public Modifier setAlbumImage(String imageId) {
310 this.albumImage = fromNullable(imageId);
315 public Album update() throws IllegalStateException {
316 if (title.isPresent()) {
317 DefaultAlbum.this.title = title.get();
319 if (description.isPresent()) {
320 DefaultAlbum.this.description = description.get();
322 if (albumImage.isPresent()) {
323 DefaultAlbum.this.albumImage = albumImage.get();
325 return DefaultAlbum.this;
331 // FINGERPRINTABLE METHODS
335 public String getFingerprint() {
336 Hasher hash = Hashing.sha256().newHasher();
337 hash.putString("Album(");
338 hash.putString("ID(").putString(id).putString(")");
339 hash.putString("Title(").putString(title).putString(")");
340 hash.putString("Description(").putString(description).putString(")");
341 if (albumImage != null) {
342 hash.putString("AlbumImage(").putString(albumImage).putString(")");
345 /* add nested albums. */
346 hash.putString("Albums(");
347 for (Album album : albums) {
348 hash.putString(album.getFingerprint());
353 hash.putString("Images(");
354 for (Image image : getImages()) {
355 if (image.isInserted()) {
356 hash.putString(image.getFingerprint());
362 return hash.hash().toString();
370 public int hashCode() {
371 return id.hashCode();
375 public boolean equals(Object object) {
376 if (!(object instanceof DefaultAlbum)) {
379 DefaultAlbum album = (DefaultAlbum) object;
380 return id.equals(album.id);