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