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