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.util.ArrayList;
030import java.util.Collection;
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Locale;
035import java.util.Map;
036
037import de.umass.util.MapUtilities;
038import de.umass.util.StringUtilities;
039import de.umass.xml.DomElement;
040
041/**
042 * Bean that contains artist information.<br/> This class contains static methods that executes API methods relating to artists.<br/> Method
043 * names are equivalent to the last.fm API method names.
044 *
045 * @author Janni Kovacs
046 */
047public class Artist extends MusicEntry {
048
049        static final ItemFactory<Artist> FACTORY = new ArtistFactory();
050
051        private Collection<Artist> similar = new ArrayList<Artist>();
052
053        protected Artist(String name, String url) {
054                super(name, url);
055        }
056
057        protected Artist(String name, String url, String mbid, int playcount, int listeners, boolean streamable) {
058                super(name, url, mbid, playcount, listeners, streamable);
059        }
060
061        /**
062         * Returns a list of similar <code>Artist</code>s. Note that this method does not retrieve this list from the server but instead returns
063         * the result of an <code>artist.getInfo</code> call.<br/> If you need to retrieve similar artists to a specified artist use the {@link
064         * #getSimilar(String, String)} method.
065         *
066         * @return list of similar artists
067         * @see #getSimilar(String, String)
068         * @see #getSimilar(String, int, String)
069         */
070        public Collection<Artist> getSimilar() {
071                return similar;
072        }
073
074        /**
075         * Retrieves detailed artist info for the given artist or mbid entry.
076         *
077         * @param artistOrMbid Name of the artist or an mbid
078         * @param apiKey The API key
079         * @return detailed artist info
080         */
081        public static Artist getInfo(String artistOrMbid, String apiKey) {
082                return getInfo(artistOrMbid, null, null, apiKey);
083        }
084
085        /**
086         * Retrieves detailed artist info for the given artist or mbid entry.
087         *
088         * @param artistOrMbid Name of the artist or an mbid
089         * @param username The username for the context of the request, or <code>null</code>. If supplied, the user's playcount for this artist is
090         * included in the response
091         * @param apiKey The API key
092         * @return detailed artist info
093         */
094        public static Artist getInfo(String artistOrMbid, String username, String apiKey) {
095                return getInfo(artistOrMbid, null, username, apiKey);
096        }
097
098        /**
099         * Retrieves detailed artist info for the given artist or mbid entry.
100         *
101         * @param artistOrMbid Name of the artist or an mbid
102         * @param locale The language to fetch info in, or <code>null</code>
103         * @param username The username for the context of the request, or <code>null</code>. If supplied, the user's playcount for this artist is
104         * included in the response
105         * @param apiKey The API key
106         * @return detailed artist info
107         */
108        public static Artist getInfo(String artistOrMbid, Locale locale, String username, String apiKey) {
109                Map<String, String> params = new HashMap<String, String>();
110                if (StringUtilities.isMbid(artistOrMbid)) {
111                        params.put("mbid", artistOrMbid);
112                } else {
113                        params.put("artist", artistOrMbid);
114                }
115                if (locale != null && locale.getLanguage().length() != 0) {
116                        params.put("lang", locale.getLanguage());
117                }
118                MapUtilities.nullSafePut(params, "username", username);
119                Result result = Caller.getInstance().call("artist.getInfo", apiKey, params);
120                return ResponseBuilder.buildItem(result, Artist.class);
121        }
122
123        /**
124         * Calls {@link #getSimilar(String, int, String)} with the default limit of 100.
125         *
126         * @param artist Artist's name
127         * @param apiKey The API key
128         * @return similar artists
129         * @see #getSimilar(String, int, String)
130         */
131        public static Collection<Artist> getSimilar(String artist, String apiKey) {
132                return getSimilar(artist, 100, apiKey);
133        }
134
135        /**
136         * Returns <code>limit</code> similar artists to the given one.
137         *
138         * @param artist Artist's name
139         * @param limit Number of maximum results
140         * @param apiKey The API key
141         * @return similar artists
142         */
143        public static Collection<Artist> getSimilar(String artist, int limit, String apiKey) {
144                Result result = Caller.getInstance().call("artist.getSimilar", apiKey, "artist", artist, "limit", String.valueOf(limit));
145                return ResponseBuilder.buildCollection(result, Artist.class);
146        }
147
148        /**
149         * Searches for an artist and returns a <code>Collection</code> of possible matches.
150         *
151         * @param name The artist name to look up
152         * @param apiKey The API key
153         * @return a list of possible matches
154         */
155        public static Collection<Artist> search(String name, String apiKey) {
156                Result result = Caller.getInstance().call("artist.search", apiKey, "artist", name);
157                Collection<DomElement> children = result.getContentElement().getChild("artistmatches").getChildren("artist");
158                List<Artist> list = new ArrayList<Artist>(children.size());
159                for (DomElement c : children) {
160                        list.add(FACTORY.createItemFromElement(c));
161                }
162                return list;
163        }
164
165        /**
166         * Returns a list of the given artist's top albums.
167         *
168         * @param artist Artist's name
169         * @param apiKey The API key
170         * @return list of top albums
171         */
172        public static Collection<Album> getTopAlbums(String artist, String apiKey) {
173                Result result = Caller.getInstance().call("artist.getTopAlbums", apiKey, "artist", artist);
174                return ResponseBuilder.buildCollection(result, Album.class);
175        }
176
177        /**
178         * Retrieves a list of the top fans of the given artist.
179         *
180         * @param artist Artist's name
181         * @param apiKey The API key
182         * @return list of top fans
183         */
184        public static Collection<User> getTopFans(String artist, String apiKey) {
185                Result result = Caller.getInstance().call("artist.getTopFans", apiKey, "artist", artist);
186                return ResponseBuilder.buildCollection(result, User.class);
187        }
188
189        /**
190         * Retrieves the top tags for the given artist.
191         *
192         * @param artist Artist's name
193         * @param apiKey The API key
194         * @return list of top tags
195         */
196        public static Collection<Tag> getTopTags(String artist, String apiKey) {
197                Result result = Caller.getInstance().call("artist.getTopTags", apiKey, "artist", artist);
198                return ResponseBuilder.buildCollection(result, Tag.class);
199        }
200
201        /**
202         * Get the top tracks by an artist on Last.fm, ordered by popularity
203         *
204         * @param artist The artist name in question
205         * @param apiKey A Last.fm API key.
206         * @return list of top tracks
207         */
208        public static Collection<Track> getTopTracks(String artist, String apiKey) {
209                Result result = Caller.getInstance().call("artist.getTopTracks", apiKey, "artist", artist);
210                return ResponseBuilder.buildCollection(result, Track.class);
211        }
212
213        /**
214         * Tag an artist with one or more user supplied tags.
215         *
216         * @param artist The artist name in question.
217         * @param tags A comma delimited list of user supplied tags to apply to this artist. Accepts a maximum of 10 tags.
218         * @param session A Session instance
219         * @return the result of the operation
220         */
221        public static Result addTags(String artist, String tags, Session session) {
222                return Caller.getInstance().call("artist.addTags", session, "artist", artist, "tags", tags);
223        }
224
225        /**
226         * Remove a user's tag from an artist.
227         *
228         * @param artist The artist name in question.
229         * @param tag A single user tag to remove from this artist.
230         * @param session A Session instance
231         * @return the result of the operation
232         */
233        public static Result removeTag(String artist, String tag, Session session) {
234                return Caller.getInstance().call("artist.removeTag", session, "artist", artist, "tag", tag);
235        }
236
237        /**
238         * Share an artist with one or more Last.fm users or other friends.
239         *
240         * @param artist The artist to share.
241         * @param recipients A comma delimited list of email addresses or Last.fm usernames. Maximum is 10.
242         * @param message An optional message to send with the recommendation.
243         * @param session A Session instance
244         * @return the Result of the operation
245         */
246        public static Result share(String artist, String recipients, String message, Session session) {
247                return Caller.getInstance().call("artist.share", session, "artist", artist, "recipient", recipients, "message", message);
248        }
249
250        /**
251         * Get the tags applied by an individual user to an artist on Last.fm.
252         *
253         * @param artist The artist name in question
254         * @param session A Session instance
255         * @return a list of tags
256         */
257        public static Collection<String> getTags(String artist, Session session) {
258                Result result = Caller.getInstance().call("artist.getTags", session, "artist", artist);
259                if (!result.isSuccessful())
260                        return Collections.emptyList();
261                DomElement element = result.getContentElement();
262                Collection<String> tags = new ArrayList<String>();
263                for (DomElement domElement : element.getChildren("tag")) {
264                        tags.add(domElement.getChildText("name"));
265                }
266                return tags;
267        }
268
269        /**
270         * Returns a list of upcoming events for an artist.
271         *
272         * @param artistOrMbid The artist name in question
273         * @param apiKey A Last.fm API key
274         * @return a list of events
275         */
276        public static PaginatedResult<Event> getEvents(String artistOrMbid, String apiKey) {
277                return getEvents(artistOrMbid, false, -1, -1, apiKey);
278        }
279        
280        /**
281         * Returns a list of upcoming events for an artist.
282         *
283         * @param artistOrMbid The artist name in question
284         * @param festivalsOnly Whether only festivals should be returned, or all events
285         * @param page The page number to fetch
286         * @param limit The number of results to fetch per page
287         * @param apiKey A Last.fm API key
288         * @return a list of events
289         */
290        public static PaginatedResult<Event> getEvents(String artistOrMbid, boolean festivalsOnly, int page, int limit, String apiKey) {
291                Map<String, String> params = new HashMap<String, String>();
292                if (StringUtilities.isMbid(artistOrMbid)) {
293                        params.put("mbid", artistOrMbid);
294                } else {
295                        params.put("artist", artistOrMbid);
296                }
297                MapUtilities.nullSafePut(params, "page", page);
298                MapUtilities.nullSafePut(params, "limit", limit);
299                if(festivalsOnly)
300                        params.put("festivalsonly", "1");
301                Result result = Caller.getInstance().call("artist.getEvents", apiKey, params);
302                return ResponseBuilder.buildPaginatedResult(result, Event.class);
303        }
304
305        /**
306         * Get a paginated list of all the events this artist has played at in the past.
307         *
308         * @param artistOrMbid The name of the artist you would like to fetch event listings for
309         * @param apiKey A Last.fm API key
310         * @return a list of past events
311         */
312        public static PaginatedResult<Event> getPastEvents(String artistOrMbid, String apiKey) {
313                return getPastEvents(artistOrMbid, false, -1, -1, apiKey);
314        }
315        
316        /**
317         * Get a paginated list of all the events this artist has played at in the past.
318         *
319         * @param artistOrMbid The name of the artist you would like to fetch event listings for
320         * @param festivalsOnly Whether only festivals should be returned, or all events
321         * @param page The page of results to return
322         * @param limit The maximum number of results to return per page
323         * @param apiKey A Last.fm API key
324         * @return a list of past events
325         */
326        public static PaginatedResult<Event> getPastEvents(String artistOrMbid, boolean festivalsOnly, int page, int limit, String apiKey) {
327                Map<String, String> params = new HashMap<String, String>();
328                if (StringUtilities.isMbid(artistOrMbid)) {
329                        params.put("mbid", artistOrMbid);
330                } else {
331                        params.put("artist", artistOrMbid);
332                }
333                MapUtilities.nullSafePut(params, "page", page);
334                MapUtilities.nullSafePut(params, "limit", limit);
335                if(festivalsOnly)
336                        params.put("festivalsonly", "1");
337                Result result = Caller.getInstance().call("artist.getPastEvents", apiKey, params);
338                return ResponseBuilder.buildPaginatedResult(result, Event.class);
339        }
340
341        /**
342         * Get {@link Image}s for this artist in a variety of sizes.
343         *
344         * @param artistOrMbid The artist name in question
345         * @param apiKey A Last.fm API key
346         * @return a list of {@link Image}s
347         */
348        public static PaginatedResult<Image> getImages(String artistOrMbid, String apiKey) {
349                return getImages(artistOrMbid, -1, -1, apiKey);
350        }
351
352        /**
353         * Get {@link Image}s for this artist in a variety of sizes.
354         *
355         * @param artistOrMbid The artist name in question
356         * @param page Which page of limit amount to display
357         * @param limit How many to return. Defaults and maxes out at 50
358         * @param apiKey A Last.fm API key
359         * @return a list of {@link Image}s
360         */
361        public static PaginatedResult<Image> getImages(String artistOrMbid, int page, int limit, String apiKey) {
362                Map<String, String> params = new HashMap<String, String>();
363                if (StringUtilities.isMbid(artistOrMbid)) {
364                        params.put("mbid", artistOrMbid);
365                } else {
366                        params.put("artist", artistOrMbid);
367                }
368                MapUtilities.nullSafePut(params, "page", page);
369                MapUtilities.nullSafePut(params, "limit", limit);
370                Result result = Caller.getInstance().call("artist.getImages", apiKey, params);
371                return ResponseBuilder.buildPaginatedResult(result, Image.class);
372        }
373
374        /**
375         * Shout on this artist's shoutbox
376         *
377         * @param artist The name of the artist to shout on
378         * @param message The message to post to the shoutbox
379         * @param session A Session instance
380         * @return the result of the operation
381         */
382        public static Result shout(String artist, String message, Session session) {
383                return Caller.getInstance().call("artist.shout", session, "artist", artist, "message", message);
384        }
385
386        /**
387         * Use the last.fm corrections data to check whether the supplied artist has a correction to a canonical artist. This method returns a new
388         * {@link Artist} object containing the corrected data, or <code>null</code> if the supplied Artist was not found.
389         *
390         * @param artist The artist name to correct
391         * @param apiKey A Last.fm API key
392         * @return a new {@link Artist}, or <code>null</code>
393         */
394        public static Artist getCorrection(String artist, String apiKey) {
395                Result result = Caller.getInstance().call("artist.getCorrection", apiKey, "artist", artist);
396                if (!result.isSuccessful())
397                        return null;
398                DomElement correctionElement = result.getContentElement().getChild("correction");
399                if (correctionElement == null)
400                        return new Artist(artist, null);
401                DomElement artistElem = correctionElement.getChild("artist");
402                return FACTORY.createItemFromElement(artistElem);
403        }
404
405        /**
406         * Get shouts for an artist.
407         *
408         * @param artistOrMbid The artist name or a musicbrainz id
409         * @param apiKey A Last.fm API key.
410         * @return a page of <code>Shout</code>s
411         */
412        public static PaginatedResult<Shout> getShouts(String artistOrMbid, String apiKey) {
413                return getShouts(artistOrMbid, -1, -1, apiKey);
414        }
415
416        /**
417         * Get shouts for an artist.
418         *
419         * @param artistOrMbid The artist name or a musicbrainz id
420         * @param page The page number to fetch
421         * @param apiKey A Last.fm API key.
422         * @return a page of <code>Shout</code>s
423         */
424        public static PaginatedResult<Shout> getShouts(String artistOrMbid, int page, String apiKey) {
425                return getShouts(artistOrMbid, page, -1, apiKey);
426        }
427
428        /**
429         * Get shouts for an artist.
430         *
431         * @param artistOrMbid The artist name or a musicbrainz id
432         * @param page The page number to fetch
433         * @param limit An integer used to limit the number of shouts returned per page or -1 for default
434         * @param apiKey A Last.fm API key.
435         * @return a page of <code>Shout</code>s
436         */
437        public static PaginatedResult<Shout> getShouts(String artistOrMbid, int page, int limit, String apiKey) {
438                Map<String, String> params = new HashMap<String, String>();
439                if (StringUtilities.isMbid(artistOrMbid)) {
440                        params.put("mbid", artistOrMbid);
441                } else {
442                        params.put("artist", artistOrMbid);
443                }
444                MapUtilities.nullSafePut(params, "limit", limit);
445                MapUtilities.nullSafePut(params, "page", page);
446                Result result = Caller.getInstance().call("artist.getShouts", apiKey, params);
447                return ResponseBuilder.buildPaginatedResult(result, Shout.class);
448        }
449
450        private static class ArtistFactory implements ItemFactory<Artist> {
451                public Artist createItemFromElement(DomElement element) {
452                        Artist artist = new Artist(null, null);
453                        MusicEntry.loadStandardInfo(artist, element);
454                        // similar artists
455                        DomElement similar = element.getChild("similar");
456                        if (similar != null) {
457                                Collection<DomElement> children = similar.getChildren("artist");
458                                for (DomElement child : children) {
459                                        artist.similar.add(createItemFromElement(child));
460                                }
461                        }
462                        return artist;
463                }
464        }
465}