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 de.umass.xml.DomElement;
030
031import java.text.DateFormat;
032import java.text.ParseException;
033import java.text.SimpleDateFormat;
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.Date;
037import java.util.Locale;
038
039/**
040 * <code>MusicEntry</code> is the abstract superclass for {@link Track}, {@link Artist} and {@link Album}. It encapsulates data and provides
041 * methods used in all subclasses, for example: name, playcount, images and more.
042 *
043 * @author Janni Kovacs
044 */
045public abstract class MusicEntry extends ImageHolder {
046
047        private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ",
048                        Locale.ENGLISH);
049
050        protected String name;
051        protected String url;
052        protected String mbid;
053        protected int playcount;
054        protected int userPlaycount;
055        protected int listeners;
056        protected boolean streamable;
057        protected String id;
058
059        /**
060         * This property is only available on hype charts, like {@link Chart#getHypedArtists(String)} or {@link
061         * de.umass.lastfm.Group#getHype(String, String)}
062         */
063        protected int percentageChange;
064
065        protected Collection<String> tags = new ArrayList<String>();
066        private Date wikiLastChanged;
067        private String wikiSummary;
068        private String wikiText;
069
070        private float similarityMatch;
071
072        protected MusicEntry(String name, String url) {
073                this(name, url, null, -1, -1, false);
074        }
075
076        protected MusicEntry(String name, String url, String mbid, int playcount, int listeners, boolean streamable) {
077                this.name = name;
078                this.url = url;
079                this.mbid = mbid;
080                this.playcount = playcount;
081                this.listeners = listeners;
082                this.streamable = streamable;
083        }
084
085        public int getListeners() {
086                return listeners;
087        }
088
089        public String getMbid() {
090                return mbid;
091        }
092
093        public String getName() {
094                return name;
095        }
096
097        public String getId() {
098                return id;
099        }
100
101        public int getPlaycount() {
102                return playcount;
103        }
104
105        public int getUserPlaycount() {
106                return userPlaycount;
107        }
108
109        public boolean isStreamable() {
110                return streamable;
111        }
112
113        public String getUrl() {
114                return url;
115        }
116
117        public Collection<String> getTags() {
118                return tags;
119        }
120
121        /**
122         * Returns the value of the "percentage change" fields in weekly hype charts responses, such as in {@link Group#getHype(String, String)}
123         * or {@link Chart#getHypedArtists(String)}.
124         *
125         * @return Weekly percentage change
126         */
127        public int getPercentageChange() {
128                return percentageChange;
129        }
130
131        public Date getWikiLastChanged() {
132                return wikiLastChanged;
133        }
134
135        public String getWikiSummary() {
136                return wikiSummary;
137        }
138
139        public String getWikiText() {
140                return wikiText;
141        }
142
143        /**
144         * Returns the "similarity" property, which is included in Artist.getSimilar and Track.getSimilar responses
145         *
146         * @return similarity
147         */
148        public float getSimilarityMatch() {
149                return similarityMatch;
150        }
151
152        @Override
153        public String toString() {
154                return this.getClass().getSimpleName() + "[" +
155                                "name='" + name + '\'' +
156                                ", id='" + id + '\'' +
157                                ", url='" + url + '\'' +
158                                ", mbid='" + mbid + '\'' +
159                                ", playcount=" + playcount +
160                                ", listeners=" + listeners +
161                                ", streamable=" + streamable +
162                                ']';
163        }
164
165        static Integer maybeParseInt(String s) {
166                try {
167                        return Integer.parseInt(s);
168                } catch (NumberFormatException e) {
169                        return null;
170                }
171        }
172
173        /**
174         * Loads all generic information from an XML <code>DomElement</code> into the given <code>MusicEntry</code> instance, i.e. the following
175         * tags:<br/> <ul> <li>playcount/plays</li> <li>listeners</li> <li>streamable</li> <li>name</li> <li>url</li> <li>mbid</li> <li>image</li>
176         * <li>tags</li> </ul>
177         *
178         * @param entry An entry
179         * @param element XML source element
180         */
181        protected static void loadStandardInfo(MusicEntry entry, DomElement element) {
182                // playcount & listeners
183                DomElement statsChild = element.getChild("stats");
184                String playcountString;
185                String userPlaycountString;
186                String listenersString;
187                if (statsChild != null) {
188                        playcountString = statsChild.getChildText("playcount");
189                        userPlaycountString = statsChild.getChildText("userplaycount");
190                        listenersString = statsChild.getChildText("listeners");
191                } else {
192                        playcountString = element.getChildText("playcount");
193                        userPlaycountString = element.getChildText("userplaycount");
194                        listenersString = element.getChildText("listeners");
195                }
196                if (element.hasChild("id")) {
197                        entry.id = element.getChildText("id");
198                }
199                // match for similar artists/tracks response
200                if (element.hasChild("match")) {
201                        entry.similarityMatch = Float.parseFloat(element.getChildText("match"));
202                }
203                // percentagechange in getHype() responses
204                if (element.hasChild("percentagechange")) {
205                        entry.percentageChange = Integer.parseInt(element.getChildText("percentagechange"));
206                }
207                int playcount = playcountString == null || playcountString.length() == 0 ? -1 : Integer
208                                .parseInt(playcountString);
209                int userPlaycount = userPlaycountString == null || userPlaycountString.length() == 0 ? -1 : Integer
210                                .parseInt(userPlaycountString);
211                int listeners = listenersString == null || listenersString.length() == 0 ? -1 : Integer
212                                .parseInt(listenersString);
213                // streamable
214                String s = element.getChildText("streamable");
215                boolean streamable = s != null && s.length() != 0 && Integer.valueOf(1).equals(maybeParseInt(s));
216                // copy
217                entry.name = element.getChildText("name");
218                entry.url = element.getChildText("url");
219                entry.mbid = element.getChildText("mbid");
220                entry.playcount = playcount;
221                entry.userPlaycount = userPlaycount;
222                entry.listeners = listeners;
223                entry.streamable = streamable;
224                // tags
225                DomElement tags = element.getChild("tags");
226                if (tags == null)
227                        tags = element.getChild("toptags");
228                if (tags != null) {
229                        for (DomElement tage : tags.getChildren("tag")) {
230                                entry.tags.add(tage.getChildText("name"));
231                        }
232                }
233                // wiki
234                DomElement wiki = element.getChild("bio");
235                if (wiki == null)
236                        wiki = element.getChild("wiki");
237                if (wiki != null) {
238                        String publishedText = wiki.getChildText("published");
239                        try {
240                                entry.wikiLastChanged = DATE_FORMAT.parse(publishedText);
241                        } catch (ParseException e) {
242                                // try parsing it with current locale
243                                try {
244                                        DateFormat clFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZZ", Locale.getDefault());
245                                        entry.wikiLastChanged = clFormat.parse(publishedText);
246                                } catch (ParseException e2) {
247                                        // cannot parse date, wrong locale. wait for last.fm to fix.
248                                }
249                        }
250                        entry.wikiSummary = wiki.getChildText("summary");
251                        entry.wikiText = wiki.getChildText("content");
252                }
253                // images
254                ImageHolder.loadImages(entry, element);
255        }
256}