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