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