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