001/*
002 * Copyright (c) 2012, the Last.fm Java Project and Committers
003 * All rights reserved.
004 *
005 * Redistribution and use of this software in source and binary forms, with or without modification, are
006 * permitted provided that the following conditions are met:
007 *
008 * - Redistributions of source code must retain the above
009 *   copyright notice, this list of conditions and the
010 *   following disclaimer.
011 *
012 * - Redistributions in binary form must reproduce the above
013 *   copyright notice, this list of conditions and the
014 *   following disclaimer in the documentation and/or other
015 *   materials provided with the distribution.
016 *
017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
018 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
019 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
020 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
021 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
022 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
023 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
024 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
025 */
026
027package de.umass.lastfm;
028
029import java.text.DateFormat;
030import java.text.ParseException;
031import java.text.SimpleDateFormat;
032import java.util.*;
033
034import de.umass.util.MapUtilities;
035import de.umass.util.StringUtilities;
036import de.umass.xml.DomElement;
037
038/**
039 * Wrapper class for Album related API calls and Album Bean.
040 *
041 * @author Janni Kovacs
042 */
043public class Album extends MusicEntry {
044
045        static final ItemFactory<Album> FACTORY = new AlbumFactory();
046
047        private static final DateFormat RELEASE_DATE_FORMAT = new SimpleDateFormat("d MMM yyyy, HH:mm", Locale.ENGLISH);
048        private static final DateFormat RELEASE_DATE_FORMAT_2 = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss Z",
049                        Locale.ENGLISH); /* only used in User.getNewReleases() */
050        
051        private String artist;
052        private Date releaseDate;
053        private Collection<Track> tracks;
054
055        private Album(String name, String url, String artist) {
056                super(name, url);
057                this.artist = artist;
058        }
059
060        private Album(String name, String url, String mbid, int playcount, int listeners, boolean streamable,
061                                        String artist) {
062                super(name, url, mbid, playcount, listeners, streamable);
063                this.artist = artist;
064        }
065
066        public String getArtist() {
067                return artist;
068        }
069
070        public Date getReleaseDate() {
071                return releaseDate;
072        }
073
074        /**
075         * Returns the list of {@link Track}s on this album. This information is only available in
076         * {@link Album#getInfo(String, String, String)} responses.
077         * 
078         * @return the list of tracks
079         * @see Album#getInfo(String, String, String) 
080         */
081        public Collection<Track> getTracks() {
082                return tracks;
083        }
084
085        /**
086         * Get the metadata for an album on Last.fm using the album name or a musicbrainz id.
087         * See playlist.fetch on how to get the album playlist.
088         *
089         * @param artist Artist's name
090         * @param albumOrMbid Album name or MBID
091         * @param apiKey The API key
092         * @return Album metadata
093         */
094        public static Album getInfo(String artist, String albumOrMbid, String apiKey) {
095                return getInfo(artist, albumOrMbid, null, apiKey);
096        }
097
098        /**
099         * Get the metadata for an album on Last.fm using the album name or a musicbrainz id.
100         * See playlist.fetch on how to get the album playlist.
101         *
102         * @param artist Artist's name
103         * @param albumOrMbid Album name or MBID
104         * @param username The username for the context of the request. If supplied, the user's playcount for this album is included in the response.
105         * @param apiKey The API key
106         * @return Album metadata
107         */
108        public static Album getInfo(String artist, String albumOrMbid, String username, String apiKey) {
109                Map<String, String> params = new HashMap<String, String>();
110                if (StringUtilities.isMbid(albumOrMbid)) {
111                        params.put("mbid", albumOrMbid);
112                } else {
113                        params.put("artist", artist);
114                        params.put("album", albumOrMbid);
115                }
116                MapUtilities.nullSafePut(params, "username", username);
117                Result result = Caller.getInstance().call("album.getInfo", apiKey, params);
118                return ResponseBuilder.buildItem(result, Album.class);
119        }
120
121        /**
122         * Tag an album using a list of user supplied tags.<br/>
123         *
124         * @param artist The artist name in question
125         * @param album The album name in question
126         * @param tags A comma delimited list of user supplied tags to apply to this album. Accepts a maximum of 10 tags.
127         * @param session The Session instance
128         * @return the Result of the operation
129         * @see Authenticator
130         */
131        public static Result addTags(String artist, String album, String tags, Session session) {
132                return Caller.getInstance().call("album.addTags", session, "artist", artist, "album", album, "tags", tags);
133        }
134
135        /**
136         * Remove a user's tag from an album.
137         *
138         * @param artist The artist name in question
139         * @param album The album name in question
140         * @param tag A single user tag to remove from this album.
141         * @param session The Session instance
142         * @return the Result of the operation
143         * @see Authenticator
144         */
145        public static Result removeTag(String artist, String album, String tag, Session session) {
146                return Caller.getInstance().call("album.removeTag", session, "artist", artist, "album", album, "tag", tag);
147        }
148
149        /**
150         * Get the tags applied by an individual user to an album on Last.fm.
151         *
152         * @param artist The artist name in question
153         * @param album The album name in question
154         * @param session A Session instance
155         * @return a list of tags
156         */
157        public static Collection<String> getTags(String artist, String album, Session session) {
158                Result result = Caller.getInstance().call("album.getTags", session, "artist", artist, "album", album);
159                if (!result.isSuccessful())
160                        return Collections.emptyList();
161                DomElement element = result.getContentElement();
162                Collection<String> tags = new ArrayList<String>();
163                for (DomElement domElement : element.getChildren("tag")) {
164                        tags.add(domElement.getChildText("name"));
165                }
166                return tags;
167        }
168
169        /**
170         * Search for an album by name. Returns album matches sorted by relevance.
171         *
172         * @param album The album name in question.
173         * @param apiKey A Last.fm API key.
174         * @return a Collection of matches
175         */
176        public static Collection<Album> search(String album, String apiKey) {
177                Result result = Caller.getInstance().call("album.search", apiKey, "album", album);
178                DomElement matches = result.getContentElement().getChild("albummatches");
179                Collection<DomElement> children = matches.getChildren("album");
180                Collection<Album> albums = new ArrayList<Album>(children.size());
181                for (DomElement element : children) {
182                        albums.add(FACTORY.createItemFromElement(element));
183                }
184                return albums;
185        }
186
187        /**
188         * Get a list of Buy Links for a particular Album. It is required that you supply either the artist and track params or the mbid param.
189         *
190         * @param artist The artist name in question
191         * @param albumOrMbid Album name or MBID
192         * @param country A country name, as defined by the ISO 3166-1 country names standard
193         * @param apiKey A Last.fm API key
194         * @return a Collection of {@link BuyLink}s
195         */
196        public static Collection<BuyLink> getBuylinks(String artist, String albumOrMbid, String country, String apiKey) {
197                Map<String, String> params = new HashMap<String, String>();
198                if (StringUtilities.isMbid(albumOrMbid)) {
199                        params.put("mbid", albumOrMbid);
200                } else {
201                        params.put("artist", artist);
202                        params.put("album", albumOrMbid);
203                }
204                params.put("country", country);
205                Result result = Caller.getInstance().call("album.getBuylinks", apiKey, params);
206                if (!result.isSuccessful())
207                        return Collections.emptyList();
208                DomElement element = result.getContentElement();
209                DomElement physicals = element.getChild("physicals");
210                DomElement downloads = element.getChild("downloads");
211                Collection<BuyLink> links = new ArrayList<BuyLink>();
212                for (DomElement e : physicals.getChildren("affiliation")) {
213                        links.add(BuyLink.linkFromElement(BuyLink.StoreType.PHYSICAl, e));
214                }
215                for (DomElement e : downloads.getChildren("affiliation")) {
216                        links.add(BuyLink.linkFromElement(BuyLink.StoreType.DIGITAL, e));
217                }
218                return links;
219        }
220
221        /**
222         * Get the top tags for an album on Last.fm, ordered by popularity. You either have to specify an album and artist name or
223         * an mbid. If you specify an mbid you may pass <code>null</code> for the first parameter.
224         *
225         * @param artist The artist name
226         * @param albumOrMbid Album name or MBID
227         * @param apiKey A Last.fm API key
228         * @return list of top tags
229         */
230        public static Collection<Tag> getTopTags(String artist, String albumOrMbid, String apiKey) {
231                Map<String, String> params = new HashMap<String, String>();
232                if (StringUtilities.isMbid(albumOrMbid)) {
233                        params.put("mbid", albumOrMbid);
234                } else {
235                        params.put("artist", artist);
236                        params.put("album", albumOrMbid);
237                }
238                Result result = Caller.getInstance().call("album.getTopTags", apiKey, params);
239                return ResponseBuilder.buildCollection(result, Tag.class);
240        }
241
242        /**
243         * Get shouts for an album.
244         *
245         * @param artist The artist name
246         * @param albumOrMbid The album name or a mausicbrainz id
247         * @param apiKey A Last.fm API key.
248         * @return a page of <code>Shout</code>s
249         */
250        public static PaginatedResult<Shout> getShouts(String artist, String albumOrMbid, String apiKey) {
251                return getShouts(artist, albumOrMbid, -1, -1, apiKey);
252        }
253
254        /**
255         * Get shouts for an album.
256         *
257         * @param artist The artist name
258         * @param albumOrMbid The album name or a mausicbrainz id
259         * @param page The page number to fetch
260         * @param apiKey A Last.fm API key.
261         * @return a page of <code>Shout</code>s
262         */
263        public static PaginatedResult<Shout> getShouts(String artist, String albumOrMbid, int page, String apiKey) {
264                return getShouts(artist, albumOrMbid, page, -1, apiKey);
265        }
266
267        /**
268         * Get shouts for an album.
269         *
270         * @param artist The artist name
271         * @param albumOrMbid The album name or a mausicbrainz id
272         * @param page The page number to fetch
273         * @param limit An integer used to limit the number of shouts returned per page or -1 for default
274         * @param apiKey A Last.fm API key.
275         * @return a page of <code>Shout</code>s
276         */
277        public static PaginatedResult<Shout> getShouts(String artist, String albumOrMbid, int page, int limit, String apiKey) {
278                Map<String, String> params = new HashMap<String, String>();
279                if (StringUtilities.isMbid(albumOrMbid)) {
280                        params.put("mbid", albumOrMbid);
281                } else {
282                        params.put("artist", artist);
283                        params.put("album", albumOrMbid);
284                }
285                MapUtilities.nullSafePut(params, "limit", limit);
286                MapUtilities.nullSafePut(params, "page", page);
287                Result result = Caller.getInstance().call("album.getShouts", apiKey, params);
288                return ResponseBuilder.buildPaginatedResult(result, Shout.class);
289        }
290
291        private static class AlbumFactory implements ItemFactory<Album> {
292                public Album createItemFromElement(DomElement element) {
293                        Album album = new Album(null, null, null);
294                        MusicEntry.loadStandardInfo(album, element);
295                        if (element.hasChild("artist")) {
296                                album.artist = element.getChild("artist").getChildText("name");
297                                if (album.artist == null)
298                                        album.artist = element.getChildText("artist");
299                        }
300                        if (element.hasChild("tracks")) {
301                                album.tracks = ResponseBuilder.buildCollection(element.getChild("tracks"), Track.class);
302                        }
303                        if (element.hasChild("releasedate")) {
304                                try {
305                                        album.releaseDate = RELEASE_DATE_FORMAT.parse(element.getChildText("releasedate"));
306                                } catch (ParseException e) {
307                                        // uh oh
308                                }
309                        }
310                        String releaseDateAttribute = element.getAttribute("releasedate");
311                        if (releaseDateAttribute != null) {
312                                try {
313                                        album.releaseDate = RELEASE_DATE_FORMAT_2.parse(releaseDateAttribute);
314                                } catch (ParseException e) {
315                                        // uh oh
316                                }
317                        }
318                        return album;
319                }
320        }
321}