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}