b910fdd7de7b3cc0becddceb87690e2575b0ed20
[Sone.git] / src / main / java / net / pterodactylus / sone / data / impl / AlbumImpl.java
1 /*
2  * Sone - AlbumImpl.java - Copyright © 2011–2019 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 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 final Sone sone;
52
53         /** Nested albums. */
54         private final List<Album> albums = new ArrayList<>();
55
56         /** The image IDs in order. */
57         private final List<String> imageIds = new ArrayList<>();
58
59         /** The images in this album. */
60         private final Map<String, Image> images = new HashMap<>();
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         /** Creates a new album with a random ID. */
72         public AlbumImpl(Sone sone) {
73                 this(sone, 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(Sone sone, String id) {
83                 this.sone = checkNotNull(sone, "Sone must not be null");
84                 this.id = checkNotNull(id, "id must not be null");
85         }
86
87         //
88         // ACCESSORS
89         //
90
91         @Override
92         public String getId() {
93                 return id;
94         }
95
96         @Override
97         public Sone getSone() {
98                 return sone;
99         }
100
101         @Override
102         public List<Album> getAlbums() {
103                 return new ArrayList<>(albums);
104         }
105
106         @Override
107         public void addAlbum(Album album) {
108                 checkNotNull(album, "album must not be null");
109                 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
110                 album.setParent(this);
111                 if (!albums.contains(album)) {
112                         albums.add(album);
113                 }
114         }
115
116         @Override
117         public void removeAlbum(Album album) {
118                 checkNotNull(album, "album must not be null");
119                 checkArgument(album.getSone().equals(sone), "album must belong this album’s Sone");
120                 checkArgument(equals(album.getParent()), "album must belong to this album");
121                 albums.remove(album);
122                 album.removeParent();
123         }
124
125         @Override
126         public Album moveAlbumUp(Album album) {
127                 checkNotNull(album, "album must not be null");
128                 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
129                 checkArgument(equals(album.getParent()), "album must belong to this album");
130                 int oldIndex = albums.indexOf(album);
131                 if (oldIndex <= 0) {
132                         return album;
133                 }
134                 albums.remove(oldIndex);
135                 albums.add(oldIndex - 1, album);
136                 return albums.get(oldIndex);
137         }
138
139         @Override
140         public Album moveAlbumDown(Album album) {
141                 checkNotNull(album, "album must not be null");
142                 checkArgument(album.getSone().equals(sone), "album must belong to the same Sone as this album");
143                 checkArgument(equals(album.getParent()), "album must belong to this album");
144                 int oldIndex = albums.indexOf(album);
145                 if ((oldIndex < 0) || (oldIndex >= (albums.size() - 1))) {
146                         return album;
147                 }
148                 albums.remove(oldIndex);
149                 albums.add(oldIndex + 1, album);
150                 return albums.get(oldIndex);
151         }
152
153         @Override
154         public List<Image> getImages() {
155                 return new ArrayList<>(Collections2.filter(Collections2.transform(imageIds, new Function<String, Image>() {
156
157                         @Override
158                         @SuppressWarnings("synthetic-access")
159                         public Image apply(String imageId) {
160                                 return images.get(imageId);
161                         }
162                 }), Predicates.notNull()));
163         }
164
165         @Override
166         public void addImage(Image image) {
167                 checkNotNull(image, "image must not be null");
168                 checkNotNull(image.getSone(), "image must have an owner");
169                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
170                 if (image.getAlbum() != null) {
171                         image.getAlbum().removeImage(image);
172                 }
173                 image.setAlbum(this);
174                 if (!imageIds.contains(image.getId())) {
175                         imageIds.add(image.getId());
176                         images.put(image.getId(), image);
177                 }
178         }
179
180         @Override
181         public void removeImage(Image image) {
182                 checkNotNull(image, "image must not be null");
183                 checkNotNull(image.getSone(), "image must have an owner");
184                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
185                 imageIds.remove(image.getId());
186                 images.remove(image.getId());
187         }
188
189         @Override
190         public Image moveImageUp(Image image) {
191                 checkNotNull(image, "image must not be null");
192                 checkNotNull(image.getSone(), "image must have an owner");
193                 checkArgument(image.getSone().equals(sone), "image must belong to the same Sone as this album");
194                 checkArgument(image.getAlbum().equals(this), "image must belong to this album");
195                 int oldIndex = imageIds.indexOf(image.getId());
196                 if (oldIndex <= 0) {
197                         return image;
198                 }
199                 imageIds.remove(image.getId());
200                 imageIds.add(oldIndex - 1, image.getId());
201                 return images.get(imageIds.get(oldIndex));
202         }
203
204         @Override
205         public Image moveImageDown(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 == -1) || (oldIndex >= (imageIds.size() - 1))) {
212                         return image;
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 boolean isEmpty() {
221                 return albums.isEmpty() && images.isEmpty();
222         }
223
224         @Override
225         public boolean isRoot() {
226                 return parent == null;
227         }
228
229         @Override
230         public Album getParent() {
231                 return parent;
232         }
233
234         @Override
235         public Album setParent(Album parent) {
236                 this.parent = checkNotNull(parent, "parent must not be null");
237                 return this;
238         }
239
240         @Override
241         public Album removeParent() {
242                 this.parent = null;
243                 return this;
244         }
245
246         @Override
247         public String getTitle() {
248                 return title;
249         }
250
251         @Override
252         public String getDescription() {
253                 return description;
254         }
255
256         @Override
257         public Modifier modify() throws IllegalStateException {
258                 // TODO: reenable check for local Sones
259                 return new Modifier() {
260                         private Optional<String> title = absent();
261
262                         private Optional<String> description = absent();
263
264                         @Override
265                         public Modifier setTitle(String title) {
266                                 this.title = fromNullable(title);
267                                 return this;
268                         }
269
270                         @Override
271                         public Modifier setDescription(String description) {
272                                 this.description = fromNullable(description);
273                                 return this;
274                         }
275
276                         @Override
277                         public Album update() throws IllegalStateException {
278                                 if (title.isPresent() && title.get().trim().isEmpty()) {
279                                         throw new AlbumTitleMustNotBeEmpty();
280                                 }
281                                 if (title.isPresent()) {
282                                         AlbumImpl.this.title = title.get();
283                                 }
284                                 if (description.isPresent()) {
285                                         AlbumImpl.this.description = description.get();
286                                 }
287                                 return AlbumImpl.this;
288                         }
289                 };
290         }
291
292         //
293         // FINGERPRINTABLE METHODS
294         //
295
296         @Override
297         public String getFingerprint() {
298                 Hasher hash = Hashing.sha256().newHasher();
299                 hash.putString("Album(");
300                 hash.putString("ID(").putString(id).putString(")");
301                 hash.putString("Title(").putString(title).putString(")");
302                 hash.putString("Description(").putString(description).putString(")");
303
304                 /* add nested albums. */
305                 hash.putString("Albums(");
306                 for (Album album : albums) {
307                         hash.putString(album.getFingerprint());
308                 }
309                 hash.putString(")");
310
311                 /* add images. */
312                 hash.putString("Images(");
313                 for (Image image : getImages()) {
314                         if (image.isInserted()) {
315                                 hash.putString(image.getFingerprint());
316                         }
317                 }
318                 hash.putString(")");
319
320                 hash.putString(")");
321                 return hash.hash().toString();
322         }
323
324         //
325         // OBJECT METHODS
326         //
327
328         @Override
329         public int hashCode() {
330                 return id.hashCode();
331         }
332
333         @Override
334         public boolean equals(Object object) {
335                 if (!(object instanceof AlbumImpl)) {
336                         return false;
337                 }
338                 AlbumImpl album = (AlbumImpl) object;
339                 return id.equals(album.id);
340         }
341
342 }