Change album ID to be unique, add internal ID
[Sone.git] / src / main / java / net / pterodactylus / sone / data / impl / AlbumImpl.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.impl;
19
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
25 import java.util.ArrayList;
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.sone.data.Album;
32 import net.pterodactylus.sone.data.IdBuilder;
33 import net.pterodactylus.sone.data.Image;
34 import net.pterodactylus.sone.data.Sone;
35
36 import com.google.common.base.Function;
37 import com.google.common.base.Optional;
38 import com.google.common.base.Predicates;
39 import com.google.common.collect.Collections2;
40 import com.google.common.hash.Hasher;
41 import com.google.common.hash.Hashing;
42
43 /**
44  * Container for images that can also contain nested {@link AlbumImpl}s.
45  *
46  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
47  */
48 public class AlbumImpl implements Album {
49
50         private final IdBuilder idBuilder = new IdBuilder();
51
52         /** The ID of this album. */
53         private final String id;
54
55         /** The Sone this album belongs to. */
56         private final 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         /** Creates a new album with a random ID. */
80         public AlbumImpl(Sone sone) {
81                 this(sone, UUID.randomUUID().toString());
82         }
83
84         /**
85          * Creates a new album with the given ID.
86          *
87          * @param id
88          *              The ID of the album
89          */
90         public AlbumImpl(Sone sone, String id) {
91                 this.sone = checkNotNull(sone, "Sone must not be null");
92                 this.id = checkNotNull(id, "id must not be null");
93         }
94
95         //
96         // ACCESSORS
97         //
98
99         @Override
100         public String getId() {
101                 return idBuilder.buildId(sone.getId(), id);
102         }
103
104         @Override
105         public String getInternalId() {
106                 return id;
107         }
108
109         @Override
110         public Sone getSone() {
111                 return sone;
112         }
113
114         @Override
115         public List<Album> getAlbums() {
116                 return new ArrayList<Album>(albums);
117         }
118
119         @Override
120         public void addAlbum(Album album) {
121                 checkNotNull(album, "album must not be null");
122                 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
123                 album.setParent(this);
124                 if (!albums.contains(album)) {
125                         albums.add(album);
126                 }
127         }
128
129         @Override
130         public void removeAlbum(Album album) {
131                 checkNotNull(album, "album must not be null");
132                 checkArgument(album.getSone().equals(sone), "album must belong this album’s Sone");
133                 checkArgument(equals(album.getParent()), "album must belong to this album");
134                 albums.remove(album);
135                 album.removeParent();
136         }
137
138         @Override
139         public Album moveAlbumUp(Album album) {
140                 checkNotNull(album, "album must not be null");
141                 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
142                 checkArgument(equals(album.getParent()), "album must belong to this album");
143                 int oldIndex = albums.indexOf(album);
144                 if (oldIndex <= 0) {
145                         return album;
146                 }
147                 albums.remove(oldIndex);
148                 albums.add(oldIndex - 1, album);
149                 return albums.get(oldIndex);
150         }
151
152         @Override
153         public Album moveAlbumDown(Album album) {
154                 checkNotNull(album, "album must not be null");
155                 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
156                 checkArgument(equals(album.getParent()), "album must belong to this album");
157                 int oldIndex = albums.indexOf(album);
158                 if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
159                         return album;
160                 }
161                 albums.remove(oldIndex);
162                 albums.add(oldIndex + 1, album);
163                 return albums.get(oldIndex);
164         }
165
166         @Override
167         public List<Image> getImages() {
168                 return new ArrayList<Image>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
169
170                         @Override
171                         @SuppressWarnings("synthetic-access")
172                         public Image apply(String imageId) {
173                                 return images.get(imageId);
174                         }
175                 }), Predicates.notNull()));
176         }
177
178         @Override
179         public void addImage(Image image) {
180                 checkNotNull(image, "image must not be null");
181                 checkNotNull(image.getSone(), "image must have an owner");
182                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
183                 if (image.getAlbum() != null) {
184                         image.getAlbum().removeImage(image);
185                 }
186                 image.setAlbum(this);
187                 if (imageIds.isEmpty() && (albumImage == null)) {
188                         albumImage = image.getId();
189                 }
190                 if (!imageIds.contains(image.getId())) {
191                         imageIds.add(image.getId());
192                         images.put(image.getId(), image);
193                 }
194         }
195
196         @Override
197         public void removeImage(Image image) {
198                 checkNotNull(image, "image must not be null");
199                 checkNotNull(image.getSone(), "image must have an owner");
200                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
201                 imageIds.remove(image.getId());
202                 images.remove(image.getId());
203                 if (image.getId().equals(albumImage)) {
204                         if (images.isEmpty()) {
205                                 albumImage = null;
206                         } else {
207                                 albumImage = images.values().iterator().next().getId();
208                         }
209                 }
210         }
211
212         @Override
213         public Image moveImageUp(Image image) {
214                 checkNotNull(image, "image must not be null");
215                 checkNotNull(image.getSone(), "image must have an owner");
216                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
217                 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
218                 int oldIndex = imageIds.indexOf(image.getId());
219                 if (oldIndex <= 0) {
220                         return image;
221                 }
222                 imageIds.remove(image.getId());
223                 imageIds.add(oldIndex - 1, image.getId());
224                 return images.get(imageIds.get(oldIndex));
225         }
226
227         @Override
228         public Image moveImageDown(Image image) {
229                 checkNotNull(image, "image must not be null");
230                 checkNotNull(image.getSone(), "image must have an owner");
231                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
232                 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
233                 int oldIndex = imageIds.indexOf(image.getId());
234                 if ((oldIndex == -1) || (oldIndex >= (imageIds.size() - 1))) {
235                         return image;
236                 }
237                 imageIds.remove(image.getId());
238                 imageIds.add(oldIndex + 1, image.getId());
239                 return images.get(imageIds.get(oldIndex));
240         }
241
242         @Override
243         public Image getAlbumImage() {
244                 if (albumImage == null) {
245                         return null;
246                 }
247                 return Optional.fromNullable(images.get(albumImage)).or(images.values().iterator().next());
248         }
249
250         @Override
251         public boolean isEmpty() {
252                 return albums.isEmpty() && images.isEmpty();
253         }
254
255         @Override
256         public boolean isRoot() {
257                 return parent == null;
258         }
259
260         @Override
261         public Album getParent() {
262                 return parent;
263         }
264
265         @Override
266         public Album setParent(Album parent) {
267                 this.parent = checkNotNull(parent, "parent must not be null");
268                 return this;
269         }
270
271         @Override
272         public Album removeParent() {
273                 this.parent = null;
274                 return this;
275         }
276
277         @Override
278         public String getTitle() {
279                 return title;
280         }
281
282         @Override
283         public String getDescription() {
284                 return description;
285         }
286
287         @Override
288         public Modifier modify() throws IllegalStateException {
289                 // TODO: reenable check for local Sones
290                 return new Modifier() {
291                         private Optional<String> title = absent();
292
293                         private Optional<String> description = absent();
294
295                         private Optional<String> albumImage = absent();
296
297                         @Override
298                         public Modifier setTitle(String title) {
299                                 this.title = fromNullable(title);
300                                 return this;
301                         }
302
303                         @Override
304                         public Modifier setDescription(String description) {
305                                 this.description = fromNullable(description);
306                                 return this;
307                         }
308
309                         @Override
310                         public Modifier setAlbumImage(String imageId) {
311                                 this.albumImage = fromNullable(imageId);
312                                 return this;
313                         }
314
315                         @Override
316                         public Album update() throws IllegalStateException {
317                                 if (title.isPresent() && title.get().trim().isEmpty()) {
318                                         throw new AlbumTitleMustNotBeEmpty();
319                                 }
320                                 if (title.isPresent()) {
321                                         AlbumImpl.this.title = title.get();
322                                 }
323                                 if (description.isPresent()) {
324                                         AlbumImpl.this.description = description.get();
325                                 }
326                                 if (albumImage.isPresent()) {
327                                         AlbumImpl.this.albumImage = albumImage.get();
328                                 }
329                                 return AlbumImpl.this;
330                         }
331                 };
332         }
333
334         //
335         // FINGERPRINTABLE METHODS
336         //
337
338         @Override
339         public String getFingerprint() {
340                 Hasher hash = Hashing.sha256().newHasher();
341                 hash.putString("Album(");
342                 hash.putString("ID(").putString(id).putString(")");
343                 hash.putString("Title(").putString(title).putString(")");
344                 hash.putString("Description(").putString(description).putString(")");
345                 if (albumImage != null) {
346                         hash.putString("AlbumImage(").putString(albumImage).putString(")");
347                 }
348
349                 /* add nested albums. */
350                 hash.putString("Albums(");
351                 for (Album album : albums) {
352                         hash.putString(album.getFingerprint());
353                 }
354                 hash.putString(")");
355
356                 /* add images. */
357                 hash.putString("Images(");
358                 for (Image image : getImages()) {
359                         if (image.isInserted()) {
360                                 hash.putString(image.getFingerprint());
361                         }
362                 }
363                 hash.putString(")");
364
365                 hash.putString(")");
366                 return hash.hash().toString();
367         }
368
369         //
370         // OBJECT METHODS
371         //
372
373         @Override
374         public int hashCode() {
375                 return id.hashCode();
376         }
377
378         @Override
379         public boolean equals(Object object) {
380                 if (!(object instanceof AlbumImpl)) {
381                         return false;
382                 }
383                 AlbumImpl album = (AlbumImpl) object;
384                 return id.equals(album.id);
385         }
386
387 }