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.StringUtilities; 035import de.umass.xml.DomElement; 036 037/** 038 * Bean for Tag data and provides methods for global tags. 039 * 040 * @author Janni Kovacs 041 */ 042public class Tag implements Comparable<Tag> { 043 044 /** 045 * Implementation of {@link ItemFactory} for this class 046 */ 047 static final ItemFactory<Tag> FACTORY = new TagFactory(); 048 049 private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ", Locale.ENGLISH); 050 051 private String name; 052 private String url; 053 private int count; 054 055 private boolean streamable; 056 private int reach; 057 058 private Date wikiLastChanged; 059 private String wikiSummary; 060 private String wikiText; 061 062 private Tag(String name) { 063 this.name = name; 064 } 065 066 public int getCount() { 067 return count; 068 } 069 070 /** 071 * Returns the number of taggings of this specific tag. Alias for {@link #getCount()}. 072 * 073 * @return Number of Taggings 074 * @see Tag#getInfo(String, String) 075 */ 076 public int getTaggings() { 077 return count; 078 } 079 080 public String getName() { 081 return name; 082 } 083 084 public String getUrl() { 085 return url; 086 } 087 088 public boolean isStreamable() { 089 return streamable; 090 } 091 092 public int getReach() { 093 return reach; 094 } 095 096 public Date getWikiLastChanged() { 097 return wikiLastChanged; 098 } 099 100 public String getWikiSummary() { 101 return wikiSummary; 102 } 103 104 public String getWikiText() { 105 return wikiText; 106 } 107 108 /** 109 * Returns the sum of all <code>count</code> elements in the results. 110 * 111 * @param tags a list of tags 112 * @return the total count of all tags 113 */ 114 public static long getTagCountSum(Collection<Tag> tags) { 115 long total = 0; 116 for (Tag topTag : tags) { 117 total += topTag.count; 118 } 119 return total; 120 } 121 122 /** 123 * Filters tags from the given list; retains only those tags with a count 124 * higher than the given percentage of the total sum as from 125 * {@link #getTagCountSum(Collection)}. 126 * 127 * @param tags list of tags 128 * @param percentage cut off percentage 129 * @return the filtered list of tags 130 */ 131 public static List<Tag> filter(Collection<Tag> tags, double percentage) { 132 ArrayList<Tag> tops = new ArrayList<Tag>(); 133 long total = getTagCountSum(tags); 134 double cutOff = total / 100.0 * percentage; 135 for (Tag tag : tags) { 136 if (tag.count > cutOff) { 137 tops.add(tag); 138 } 139 } 140 return tops; 141 } 142 143 /** 144 * Search for tags similar to this one. Returns tags ranked by similarity, based on listening data. 145 * 146 * @param tag The tag name 147 * @param apiKey A Last.fm API key 148 * @return a List of <code>Tag</code>s 149 */ 150 public static Collection<Tag> getSimilar(String tag, String apiKey) { 151 Result result = Caller.getInstance().call("tag.getSimilar", apiKey, "tag", tag); 152 return ResponseBuilder.buildCollection(result, Tag.class); 153 } 154 155 public static Collection<Tag> getTopTags(String apiKey) { 156 Result result = Caller.getInstance().call("tag.getTopTags", apiKey); 157 return ResponseBuilder.buildCollection(result, Tag.class); 158 } 159 160 public static Collection<Album> getTopAlbums(String tag, String apiKey) { 161 Result result = Caller.getInstance().call("tag.getTopAlbums", apiKey, "tag", tag); 162 return ResponseBuilder.buildCollection(result, Album.class); 163 } 164 165 public static Collection<Track> getTopTracks(String tag, String apiKey) { 166 Result result = Caller.getInstance().call("tag.getTopTracks", apiKey, "tag", tag); 167 return ResponseBuilder.buildCollection(result, Track.class); 168 } 169 170 public static Collection<Artist> getTopArtists(String tag, String apiKey) { 171 Result result = Caller.getInstance().call("tag.getTopArtists", apiKey, "tag", tag); 172 return ResponseBuilder.buildCollection(result, Artist.class); 173 } 174 175 public static Collection<Tag> search(String tag, String apiKey) { 176 return search(tag, 30, apiKey); 177 } 178 179 public static Collection<Tag> search(String tag, int limit, String apiKey) { 180 Result result = Caller.getInstance().call("tag.search", apiKey, "tag", tag, "limit", String.valueOf(limit)); 181 Collection<DomElement> children = result.getContentElement().getChild("tagmatches").getChildren("tag"); 182 List<Tag> tags = new ArrayList<Tag>(children.size()); 183 for (DomElement s : children) { 184 tags.add(FACTORY.createItemFromElement(s)); 185 } 186 return tags; 187 } 188 189 public static Chart<Artist> getWeeklyArtistChart(String tag, String apiKey) { 190 return getWeeklyArtistChart(tag, null, null, -1, apiKey); 191 } 192 193 public static Chart<Artist> getWeeklyArtistChart(String tag, int limit, String apiKey) { 194 return getWeeklyArtistChart(tag, null, null, limit, apiKey); 195 } 196 197 public static Chart<Artist> getWeeklyArtistChart(String tag, String from, String to, int limit, String apiKey) { 198 return Chart.getChart("tag.getWeeklyArtistChart", "tag", tag, "artist", from, to, limit, apiKey); 199 } 200 201 public static LinkedHashMap<String, String> getWeeklyChartList(String tag, String apiKey) { 202 return Chart.getWeeklyChartList("tag.getWeeklyChartList", "tag", tag, apiKey); 203 } 204 205 public static Collection<Chart> getWeeklyChartListAsCharts(String tag, String apiKey) { 206 return Chart.getWeeklyChartListAsCharts("tag", tag, apiKey); 207 } 208 209 /** 210 * Gets the metadata for a tag. 211 * 212 * @param tag The tag name 213 * @param apiKey A Last.fm API key 214 * @return Tag metdata such as Wiki Text, reach and tag count 215 */ 216 public static Tag getInfo(String tag, String apiKey) { 217 return getInfo(tag, null, apiKey); 218 } 219 220 /** 221 * Gets the metadata for a tag. 222 * 223 * @param tag The tag name 224 * @param locale The language to fetch info in, or <code>null</code> 225 * @param apiKey A Last.fm API key 226 * @return Tag metdata such as Wiki Text, reach and tag count 227 */ 228 public static Tag getInfo(String tag, Locale locale, String apiKey) { 229 Map<String, String> params = new HashMap<String, String>(); 230 params.put("tag", tag); 231 if (locale != null && locale.getLanguage().length() != 0) { 232 params.put("lang", locale.getLanguage()); 233 } 234 Result result = Caller.getInstance().call("tag.getInfo", apiKey, params); 235 return ResponseBuilder.buildItem(result, Tag.class); 236 } 237 238 public int compareTo(Tag o) { 239 // descending order 240 return Double.compare(o.getCount(), this.getCount()); 241 } 242 243 /** 244 * This implementation of {@link ItemFactory} creates {@link Tag} objects based on the passed xml element. 245 * 246 * @see Tag 247 * @see Tag#FACTORY 248 */ 249 private static class TagFactory implements ItemFactory<Tag> { 250 public Tag createItemFromElement(DomElement element) { 251 Tag t = new Tag(element.getChildText("name")); 252 t.url = element.getChildText("url"); 253 254 if (element.hasChild("count")) 255 t.count = Integer.parseInt(element.getChildText("count")); 256 else if (element.hasChild("taggings")) 257 t.count = Integer.parseInt(element.getChildText("taggings")); 258 259 if (element.hasChild("reach")) 260 t.reach = Integer.parseInt(element.getChildText("reach")); 261 if (element.hasChild("streamable")) 262 t.streamable = StringUtilities.convertToBoolean(element.getChildText("streamable")); 263 264 // wiki 265 DomElement wiki = element.getChild("wiki"); 266 if (wiki != null) { 267 String publishedText = wiki.getChildText("published"); 268 try { 269 t.wikiLastChanged = DATE_FORMAT.parse(publishedText); 270 } catch (ParseException e) { 271 // try parsing it with current locale 272 try { 273 DateFormat clFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ", Locale.getDefault()); 274 t.wikiLastChanged = clFormat.parse(publishedText); 275 } catch (ParseException e2) { 276 // cannot parse date, wrong locale. wait for last.fm to fix. 277 } 278 } 279 t.wikiSummary = wiki.getChildText("summary"); 280 t.wikiText = wiki.getChildText("content"); 281 } 282 return t; 283 } 284 } 285}