gt: 				ScrobbleResult scrobbleResult = new ScrobbleResult(result);
context: /*
   * Copyright (c) 2012, the Last.fm Java Project and Committers
   * All rights reserved.
   *
   * Redistribution and use of this software in source and binary forms, with or without modification, are
   * permitted provided that the following conditions are met:
   *
   * - Redistributions of source code must retain the above
   *   copyright notice, this list of conditions and the
   *   following disclaimer.
   *
   * - Redistributions in binary form must reproduce the above
   *   copyright notice, this list of conditions and the
   *   following disclaimer in the documentation and/or other
   *   materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
   * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
   * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  package net.sourceforge.subsonic.lastfm;
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  
  import net.sourceforge.subsonic.lastfm.scrobble.IgnoredMessageCode;
  import net.sourceforge.subsonic.lastfm.scrobble.ScrobbleData;
  import net.sourceforge.subsonic.lastfm.scrobble.ScrobbleResult;
  import net.sourceforge.subsonic.util.MapUtilities;
  import net.sourceforge.subsonic.util.StringUtilities;
  import net.sourceforge.subsonic.xml.DomElement;
  
  /**
   * Bean that contains information related to <code>Track</code>s and provides bindings to methods
   * in the <code>track.</code> namespace.
   *
   * @author Janni Kovacs
   */
  public class Track extends MusicEntry {
  
  	private enum ScrobbleResultType {
  		NOW_PLAYING,
  		SINGLE_SCROBBLE,
  		MULTIPLE_SCROBBLES
  	}
  
  	static final ItemFactory<Track> FACTORY = new TrackFactory();
  
  	public static final String ARTIST_PAGE = "artistpage";
  	public static final String ALBUM_PAGE = "albumpage";
  	public static final String TRACK_PAGE = "trackpage";
  
  	private String artist;
  	private String artistMbid;
  
  	protected String album;		// protected for use in Playlist.playlistFromElement
  	private String albumMbid;
  	private int position = -1;
  
  	private boolean fullTrackAvailable;
  	private boolean nowPlaying;
  
  	private Date playedWhen;
  	protected int duration;		// protected for use in Playlist.playlistFromElement
  	protected String location;		// protected for use in Playlist.playlistFromElement
  
  	protected Map<String, String> lastFmExtensionInfos = new HashMap<String, String>();		// protected for use in Playlist.playlistFromElement
  
  
  	protected Track(String name, String url, String artist) {
  		super(name, url);
  		this.artist = artist;
  	}
  
  	protected Track(String name, String url, String mbid, int playcount, int listeners, boolean streamable,
  					String artist, String artistMbid, boolean fullTrackAvailable, boolean nowPlaying) {
  		super(name, url, mbid, playcount, listeners, streamable);
  		this.artist = artist;
  		this.artistMbid = artistMbid;
  		this.fullTrackAvailable = fullTrackAvailable;
  		this.nowPlaying = nowPlaying;
  	}
  
  	/**
  	 * Returns the duration of the song, if available, in seconds. The duration attribute is only available
  	 * for tracks retrieved by {@link Playlist#fetch(String, String) Playlist.fetch} and
  	 * {@link Track#getInfo(String, String, String) Track.getInfo}.
  	 *
  	 * @return duration in seconds
  	 */
  	public int getDuration() {
  		return duration;
  	}
  
  	public String getArtist() {
  		return artist;
  	}
  
  	public String getArtistMbid() {
  		return artistMbid;
  	}
  
  	public String getAlbum() {
  		return album;
  	}
  
  	public String getAlbumMbid() {
  		return albumMbid;
  	}
  
  	public boolean isFullTrackAvailable() {
  		return fullTrackAvailable;
  	}
  
  	public boolean isNowPlaying() {
  		return nowPlaying;
  	}
  
  	/**
  	 * Returns the location (URL) of this Track. This information is only available with the {@link Radio} services.
  	 *
  	 * @return the location
  	 */
  	public String getLocation() {
  		return location;
  	}
  
  	/**
  	 * Returns last.fm specific information about this Track. Only available in Tracks fetched from
  	 * radio playlists. <tt>key</tt> can be one of the following:
  	 * <ul>
  	 * <li>artistpage</li>
  	 * <li>albumpage</li>
  	 * <li>trackpage</li>
  	 * <li>buyTrackURL</li>
  	 * <li>buyAlbumURL</li>
  	 * <li>freeTrackURL</li>
  	 * </ul>
  	 * Or use the available constants in this class.<br/>
  	 * Note that the key string is case sensitive.
  	 *
  	 * @param key A key
  	 * @return associated value
  	 * @see #ARTIST_PAGE
  	 * @see #ALBUM_PAGE
  	 * @see #TRACK_PAGE
  	 */
  	public String getLastFmInfo(String key) {
  		return lastFmExtensionInfos.get(key);
  	}
  
  	/**
  	 * Returns the time when the track was played, if this data is available (e.g. for recent tracks) or <code>null</code>,
  	 * if this data is not available.<br/>
  	 *
  	 * @return the date when the track was played or <code>null</code>
  	 */
  	public Date getPlayedWhen() {
  		return playedWhen;
  	}
  
  	/**
  	 * Returns the position of this track in its associated album, or -1 if not available.
  	 *
  	 * @return the album position
  	 */
  	public int getPosition() {
  		return position;
  	}
  
  	/**
  	 * Searches for a track with the given name and returns a list of possible matches.
  	 *
  	 * @param track Track name
  	 * @param apiKey The API key
  	 * @return a list of possible matches
  	 * @see #search(String, String, int, String)
  	 */
  	public static Collection<Track> search(String track, String apiKey) {
  		return search(null, track, 30, apiKey);
  	}
  
  	/**
  	 * Searches for a track with the given name and returns a list of possible matches.
  	 * Specify an artist name or a limit to narrow down search results.
  	 * Pass <code>null</code> for the artist parameter if you want to specify a limit but don't want
  	 * to define an artist.
  	 *
  	 * @param artist Artist's name or <code>null</code>
  	 * @param track Track name
  	 * @param limit Number of maximum results
  	 * @param apiKey The API key
  	 * @return a list of possible matches
  	 */
  	public static Collection<Track> search(String artist, String track, int limit, String apiKey) {
  		Map<String, String> params = new HashMap<String, String>();
  		params.put("track", track);
  		params.put("limit", String.valueOf(limit));
  		MapUtilities.nullSafePut(params, "artist", artist);
  		Result result = Caller.getInstance().call("track.search", apiKey, params);
  		if(!result.isSuccessful())
  			return Collections.emptyList();
  		DomElement element = result.getContentElement();
  		DomElement matches = element.getChild("trackmatches");
  		return ResponseBuilder.buildCollection(matches, Track.class);
  	}
  
  	/**
  	 * Retrieves the top tags for the given track. You either have to specify a track and artist name or
  	 * a mbid. If you specify an mbid you may pass <code>null</code> for the first parameter.
  	 *
  	 * @param artist Artist name or <code>null</code> if an MBID is specified
  	 * @param trackOrMbid Track name or MBID
  	 * @param apiKey The API key
  	 * @return list of tags
  	 */
  	public static Collection<Tag> getTopTags(String artist, String trackOrMbid, String apiKey) {
  		Map<String, String> params = new HashMap<String, String>();
  		if (StringUtilities.isMbid(trackOrMbid)) {
  			params.put("mbid", trackOrMbid);
  		} else {
  			params.put("artist", artist);
  			params.put("track", trackOrMbid);
  		}
  		Result result = Caller.getInstance().call("track.getTopTags", apiKey, params);
  		return ResponseBuilder.buildCollection(result, Tag.class);
  	}
  
  	/**
  	 * Retrieves the top fans for the given track. You either have to specify a track and artist name or
  	 * a mbid. If you specify an mbid you may pass <code>null</code> for the first parameter.
  	 *
  	 * @param artist Artist name or <code>null</code> if an MBID is specified
  	 * @param trackOrMbid Track name or MBID
  	 * @param apiKey The API key
  	 * @return list of fans
  	 */
  	public static Collection<User> getTopFans(String artist, String trackOrMbid, String apiKey) {
  		Map<String, String> params = new HashMap<String, String>();
  		if (StringUtilities.isMbid(trackOrMbid)) {
  			params.put("mbid", trackOrMbid);
  		} else {
  			params.put("artist", artist);
  			params.put("track", trackOrMbid);
  		}
  		Result result = Caller.getInstance().call("track.getTopFans", apiKey, params);
  		return ResponseBuilder.buildCollection(result, User.class);
  	}
  
  	/**
  	 * Tag an album using a list of user supplied tags.
  	 *
  	 * @param artist The artist name in question
  	 * @param track The track name in question
  	 * @param tags A comma delimited list of user supplied tags to apply to this track. Accepts a maximum of 10 tags.
  	 * @param session A Session instance.
  	 * @return the Result of the operation
  	 */
  	public static Result addTags(String artist, String track, String tags, Session session) {
  		return Caller.getInstance().call("track.addTags", session, "artist", artist, "track", track, "tags", tags);
  	}
  
  	/**
  	 * Remove a user's tag from a track.
  	 *
  	 * @param artist The artist name in question
  	 * @param track The track name in question
  	 * @param tag A single user tag to remove from this track.
  	 * @param session A Session instance.
  	 * @return the Result of the operation
  	 */
  	public static Result removeTag(String artist, String track, String tag, Session session) {
  		return Caller.getInstance().call("track.removeTag", session, "artist", artist, "track", track, "tag", tag);
  	}
  
  	/**
  	 * Share a track twith one or more Last.fm users or other friends.
  	 *
  	 * @param artist An artist name.
  	 * @param track A track name.
  	 * @param message A message to send with the recommendation or <code>null</code>. If not supplied a default message will be used.
  	 * @param recipient A comma delimited list of email addresses or Last.fm usernames. Maximum is 10.
  	 * @param session A Session instance
  	 * @return the Result of the operation
  	 */
  	public static Result share(String artist, String track, String message, String recipient, Session session) {
  		Map<String, String> params = StringUtilities.map("artist", artist, "track", track, "recipient", recipient);
  		MapUtilities.nullSafePut(params, "message", message);
  		return Caller.getInstance().call("track.share", session, params);
  	}
  
  	/**
  	 * Love a track for a user profile.
  	 *
  	 * @param artist An artist name
  	 * @param track A track name
  	 * @param session A Session instance
  	 * @return the Result of the operation
  	 */
  	public static Result love(String artist, String track, Session session) {
  		return Caller.getInstance().call("track.love", session, "artist", artist, "track", track);
  	}
  
  	/**
  	 * UnLove a track for a user profile.
  	 *
  	 * @param artist An artist name
  	 * @param track A track name
  	 * @param session A Session instance
  	 * @return the Result of the operation
  	 */
  	public static Result unlove(String artist, String track, Session session) {
  		return Caller.getInstance().call("track.unlove", session, "artist", artist, "track", track);
  	}
  
  	/**
  	 * Ban a track for a given user profile.
  	 *
  	 * @param artist An artist name
  	 * @param track A track name
  	 * @param session A Session instance
  	 * @return the Result of the operation
  	 */
  	public static Result ban(String artist, String track, Session session) {
  		return Caller.getInstance().call("track.ban", session, "artist", artist, "track", track);
  	}
  
  	/**
  	 * UnBan a track for a given user profile.
  	 *
  	 * @param artist An artist name
  	 * @param track A track name
  	 * @param session A Session instance
  	 * @return the Result of the operation
  	 */
  	public static Result unban(String artist, String track, Session session) {
  		return Caller.getInstance().call("track.unban", session, "artist", artist, "track", track);
  	}
  
  	/**
  	 * Get the similar tracks for this track on Last.fm, based on listening data.<br/>
  	 * You have to provide either an artist and a track name <i>or</i> an mbid. Pass <code>null</code>
  	 * for parameters you don't need.
  	 *
  	 * @param artist The artist name in question
  	 * @param trackOrMbid The track name in question or the track's MBID
  	 * @param apiKey A Last.fm API key.
  	 * @return a list of similar <code>Track</code>s
  	 */
  	public static Collection<Track> getSimilar(String artist, String trackOrMbid, String apiKey) {
  		Map<String, String> params = new HashMap<String, String>();
  		if (StringUtilities.isMbid(trackOrMbid)) {
  			params.put("mbid", trackOrMbid);
  		} else {
  			params.put("artist", artist);
  			params.put("track", trackOrMbid);
  		}
  		Result result = Caller.getInstance().call("track.getSimilar", apiKey, params);
  		return ResponseBuilder.buildCollection(result, Track.class);
  	}
  
  	/**
  	 * Get the tags applied by an individual user to an track on Last.fm.
  	 *
  	 * @param artist The artist name in question
  	 * @param track The track name in question
  	 * @param session A Session instance
  	 * @return a list of tags
  	 */
  	public static Collection<String> getTags(String artist, String track, Session session) {
  		Result result = Caller.getInstance().call("track.getTags", session, "artist", artist, "track", track);
  		DomElement element = result.getContentElement();
  		Collection<String> tags = new ArrayList<String>();
  		for (DomElement domElement : element.getChildren("tag")) {
  			tags.add(domElement.getChildText("name"));
  		}
  		return tags;
  	}
  
  	/**
  	 * Get the metadata for a track on Last.fm using the artist/track name or a musicbrainz id.
  	 *
  	 * @param artist The artist name in question or <code>null</code> if an mbid is specified
  	 * @param trackOrMbid The track name in question or the musicbrainz id for the track
  	 * @param apiKey A Last.fm API key.
  	 * @return Track information
  	 */
  	public static Track getInfo(String artist, String trackOrMbid, String apiKey) {
  		return getInfo(artist, trackOrMbid, null, null, apiKey);
  	}
  
  	/**
  	 * Get the metadata for a track on Last.fm using the artist/track name or a musicbrainz id.
  	 *
  	 * @param artist The artist name in question or <code>null</code> if an mbid is specified
  	 * @param trackOrMbid The track name in question or the musicbrainz id for the track
  	 * @param locale The language to fetch info in, or <code>null</code>
  	 * @param username The username for the context of the request, or <code>null</code>. If supplied, the user's playcount for this track and whether they have loved the track is included in the response
  	 * @param apiKey A Last.fm API key.
  	 * @return Track information
  	 */
  	public static Track getInfo(String artist, String trackOrMbid, Locale locale, String username, String apiKey) {
  		Map<String, String> params = new HashMap<String, String>();
  		if (StringUtilities.isMbid(trackOrMbid)) {
  			params.put("mbid", trackOrMbid);
  		} else {
  			params.put("artist", artist);
  			params.put("track", trackOrMbid);
  		}
  		if (locale != null && locale.getLanguage().length() != 0) {
  			params.put("lang", locale.getLanguage());
  		}
  		MapUtilities.nullSafePut(params, "username", username);
  		Result result = Caller.getInstance().call("track.getInfo", apiKey, params);
  		if (!result.isSuccessful())
  			return null;
  		DomElement content = result.getContentElement();
  		DomElement album = content.getChild("album");
  		Track track = FACTORY.createItemFromElement(content);
  		if (album != null) {
  			String pos = album.getAttribute("position");
  			if ((pos != null) && pos.length() != 0) {
  				track.position = Integer.parseInt(pos);
  			}
  			track.album = album.getChildText("title");
  			track.albumMbid = album.getChildText("mbid");
  			ImageHolder.loadImages(track, album);
  		}
  		return track;
  	}
  
  	/**
  	 * Get a list of Buy Links for a particular Track. It is required that you supply either the artist and track params or the mbid param.
  	 *
  	 * @param artist The artist name in question
  	 * @param albumOrMbid Track name or MBID
  	 * @param country A country name, as defined by the ISO 3166-1 country names standard
  	 * @param apiKey A Last.fm API key
  	 * @return a Collection of {@link BuyLink}s
  	 */
  	public static Collection<BuyLink> getBuylinks(String artist, String albumOrMbid, String country, String apiKey) {
  		Map<String, String> params = new HashMap<String, String>();
  		if (StringUtilities.isMbid(albumOrMbid)) {
  			params.put("mbid", albumOrMbid);
  		} else {
  			params.put("artist", artist);
  			params.put("album", albumOrMbid);
  		}
  		params.put("country", country);
  		Result result = Caller.getInstance().call("track.getBuylinks", apiKey, params);
  		if (!result.isSuccessful())
  			return Collections.emptyList();
  		DomElement element = result.getContentElement();
  		DomElement physicals = element.getChild("physicals");
  		DomElement downloads = element.getChild("downloads");
  		Collection<BuyLink> links = new ArrayList<BuyLink>();
  		for (DomElement e : physicals.getChildren("affiliation")) {
  			links.add(BuyLink.linkFromElement(BuyLink.StoreType.PHYSICAl, e));
  		}
  		for (DomElement e : downloads.getChildren("affiliation")) {
  			links.add(BuyLink.linkFromElement(BuyLink.StoreType.DIGITAL, e));
  		}
  		return links;
  	}
  
  	/**
  	 * Use the last.fm corrections data to check whether the supplied track has a correction to a canonical track. This method returns a new
  	 * {@link Track} object containing the corrected data, or <code>null</code> if the supplied Artist/Track combination was not found.
  	 *
  	 * @param artist The artist name to correct
  	 * @param track The track name to correct
  	 * @param apiKey A Last.fm API key
  	 * @return a new {@link Track}, or <code>null</code>
  	 */
  	public static Track getCorrection(String artist, String track, String apiKey) {
  		Result result = Caller.getInstance().call("track.getCorrection", apiKey, "artist", artist, "track", track);
  		if (!result.isSuccessful())
  			return null;
  		DomElement correctionElement = result.getContentElement().getChild("correction");
  		if (correctionElement == null)
  			return new Track(track, null, artist);
  		DomElement trackElem = correctionElement.getChild("track");
  		return FACTORY.createItemFromElement(trackElem);
  	}
  
  	/**
  	 * Get shouts for a track.
  	 *
  	 * @param artist The artist name
  	 * @param trackOrMbid The track name or a mausicbrainz id
  	 * @param apiKey A Last.fm API key.
  	 * @return a page of <code>Shout</code>s
  	 */
  	public static PaginatedResult<Shout> getShouts(String artist, String trackOrMbid, String apiKey) {
  		return getShouts(artist, trackOrMbid, -1, -1, apiKey);
  	}
  
  	/**
  	 * Get shouts for a track.
  	 *
  	 * @param artist The artist name
  	 * @param trackOrMbid The track name or a mausicbrainz id
  	 * @param page The page number to fetch
  	 * @param apiKey A Last.fm API key.
  	 * @return a page of <code>Shout</code>s
  	 */
  	public static PaginatedResult<Shout> getShouts(String artist, String trackOrMbid, int page, String apiKey) {
  		return getShouts(artist, trackOrMbid, page, -1, apiKey);
  	}
  
  	/**
  	 * Get shouts for a track.
  	 *
  	 * @param artist The artist name
  	 * @param trackOrMbid The track name or a mausicbrainz id
  	 * @param page The page number to fetch
  	 * @param limit An integer used to limit the number of shouts returned per page or -1 for default
  	 * @param apiKey A Last.fm API key.
  	 * @return a page of <code>Shout</code>s
  	 */
  	public static PaginatedResult<Shout> getShouts(String artist, String trackOrMbid, int page, int limit, String apiKey) {
  		Map<String, String> params = new HashMap<String, String>();
  		if (StringUtilities.isMbid(trackOrMbid)) {
  			params.put("mbid", trackOrMbid);
  		} else {
  			params.put("artist", artist);
  			params.put("track", trackOrMbid);
  		}
  		MapUtilities.nullSafePut(params, "limit", limit);
  		MapUtilities.nullSafePut(params, "page", page);
  		Result result = Caller.getInstance().call("track.getShouts", apiKey, params);
  		return ResponseBuilder.buildPaginatedResult(result, Shout.class);
  	}
  
  	/**
  	 * Converts a generic web services Result object into a more specific ScrobbleResult.
  	 *
  	 * @param result Web services Result.
  	 * @param scrobbleResultType The type of scrobble result contained within the Result.
  	 * @return A ScrobbleResult containing the original Result information plus extra fields specific to scrobble and now playing results.
  	 */
  	private static List<ScrobbleResult> convertToScrobbleResults(Result result, ScrobbleResultType scrobbleResultType) {
  		List<ScrobbleResult> scrobbleResults = new ArrayList<ScrobbleResult>();
  		if (!result.isSuccessful()) {
  			// if result failed then we have no extra information
  			ScrobbleResult scrobbleResult = new ScrobbleResult(result);
  			scrobbleResults.add(scrobbleResult);
  		} else {
  			DomElement element = result.getContentElement();
  			if (scrobbleResultType == ScrobbleResultType.NOW_PLAYING) {
  