1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 package sk.baka.ambient.collection;
19
20 import java.io.File;
21 import java.io.Serializable;
22
23 import sk.baka.ambient.commons.Immutable;
24 import sk.baka.ambient.commons.MiscUtils;
25 import sk.baka.ambient.commons.ThreadSafe;
26 import sk.baka.ambient.commons.ThreadUnsafe;
27
28 /***
29 * Contains metadata about a track. Immutable, thread-safe.
30 *
31 * @author Martin Vysny
32 */
33 @Immutable
34 @ThreadSafe
35 public final class TrackMetadataBean implements Serializable {
36 private static final long serialVersionUID = 7772018128569847012L;
37
38 /***
39 * Database track id.
40 */
41 private transient final long trackId;
42
43 /***
44 * The year released.
45 */
46 private final String yearReleased;
47
48 /***
49 * Frequency in hz.
50 */
51 private final int frequency;
52
53 /***
54 * You can buy the album here
55 */
56 private final String buyURL;
57
58 /***
59 * The license URL
60 */
61 private final String license;
62
63 /***
64 * A link to artist's page
65 */
66 private final String artistURL;
67
68 /***
69 * The artist's description.
70 */
71 private final String artistDesc;
72
73 /***
74 * Creates new metadata bean.
75 *
76 * @param trackId
77 * the database track id
78 * @param track
79 * the track.
80 */
81 private TrackMetadataBean(final long trackId, final Builder track) {
82 super();
83 if (track.bitrate < 0)
84 throw new IllegalArgumentException("bitrate must be >=0");
85 if (track.location == null)
86 throw new IllegalArgumentException("location must not be null");
87 if (track.origin == null)
88 throw new IllegalArgumentException("origin must not be null");
89 this.trackId = trackId;
90 this.origin = track.origin;
91 this.title = track.title;
92 this.artist = track.artist;
93 this.composer = track.composer;
94 this.album = track.album;
95 this.genre = track.genre;
96 this.trackNumber = fixTrackNumber(track.trackNumber);
97 this.location = track.location;
98 this.length = track.length;
99 this.bitrate = track.bitrate;
100 this.fileSize = track.fileSize;
101 this.yearReleased = track.yearReleased;
102 this.frequency = track.frequency;
103 this.license = track.license;
104 this.artistURL = track.artistURL;
105 this.buyURL = track.buyURL;
106 this.artistDesc = track.artistDesc;
107 }
108
109 private static String fixTrackNumber(final String tn) {
110 if (tn == null) {
111 return tn;
112 }
113 if (tn.length() == 1 && Character.isDigit(tn.charAt(0))) {
114 return "0" + tn;
115 }
116 return tn;
117 }
118
119 /***
120 * Returns the name of the track. Returns {@link #getTitle()} unless it's
121 * <code>null</code> - in this case returns the file name.
122 *
123 * @return the track name, never <code>null</code>.
124 */
125 public final String getDisplayableName() {
126 if (displayableName == null) {
127 if (MiscUtils.isEmptyOrWhitespace(title)) {
128 if (origin.online) {
129 displayableName = location;
130 } else {
131 displayableName = location.substring(location
132 .lastIndexOf('/') + 1);
133 }
134 } else {
135 displayableName = title;
136 }
137 }
138 return displayableName;
139 }
140
141 private String displayableName;
142
143 /***
144 * Track title.
145 */
146 private final String title;
147
148 /***
149 * Artist.
150 */
151 private final String artist;
152
153 /***
154 * Original composer.
155 */
156 private final String composer;
157
158 /***
159 * Album name.
160 */
161 private final String album;
162
163 /***
164 * Genre.
165 */
166 private final String genre;
167
168 /***
169 * Track number.
170 */
171 private final String trackNumber;
172
173 /***
174 * Location on the filesystem. May be URL if it is an Internet stream.
175 */
176 private final String location;
177
178 /***
179 * Length in seconds. May be 0 if not known.
180 */
181 private final int length;
182
183 /***
184 * Bitrate in kbps. If zero then it is not known.
185 */
186 private final int bitrate;
187
188 /***
189 * File size in bytes. -1 if the track is an Internet stream.
190 */
191 private final long fileSize;
192
193 /***
194 * The track location.
195 */
196 private final TrackOriginEnum origin;
197
198 /***
199 * Returns length in the <code>[hh:]mm:ss</code> format.
200 *
201 * @param length
202 * the length in seconds
203 * @return displayable length. If zero then empty string is returned.
204 */
205 public static String getDisplayableLength(int length) {
206 if (length == 0)
207 return "";
208 final StringBuilder result = new StringBuilder(7);
209 appendDisplayableLength(length, result, true);
210 return result.toString();
211 }
212
213 /***
214 * Appends length in the <code>[hh:]mm:ss</code> format.
215 *
216 * @param length
217 * the length in seconds
218 * @param builder
219 * append the time here.
220 * @param emptyStringOnZero
221 * if <code>true</code> then empty string is returned on zero.
222 */
223 public static void appendDisplayableLength(final int length,
224 StringBuilder builder, boolean emptyStringOnZero) {
225 int len = length;
226 if ((len == 0) && emptyStringOnZero)
227 return;
228 boolean hours = false;
229 if (len >= 60 * 60) {
230 builder.append(len / (60 * 60));
231 builder.append(':');
232 hours = true;
233 }
234 len %= (60 * 60);
235 final int minutes = len / 60;
236 if ((hours) && (minutes < 10))
237 builder.append('0');
238 builder.append(minutes);
239 builder.append(':');
240 final int seconds = len % 60;
241 if (seconds < 10)
242 builder.append('0');
243 builder.append(seconds);
244 }
245
246 /***
247 * Returns length in the <code>[hh:]mm:ss</code> format.
248 *
249 * @return displayable length. If zero then empty string is returned.
250 */
251 public String getDisplayableLength() {
252 return getDisplayableLength(length);
253 }
254
255 /***
256 * Album name.
257 *
258 * @return the album
259 */
260 public String getAlbum() {
261 return album;
262 }
263
264 /***
265 * Artist name.
266 *
267 * @return the artist
268 */
269 public String getArtist() {
270 return artist;
271 }
272
273 /***
274 * Bitrate in kbps. If zero then it is not known.
275 *
276 * @return the bitrate
277 */
278 public int getBitrate() {
279 return bitrate;
280 }
281
282 /***
283 * Original composer.
284 *
285 * @return the composer
286 */
287 public String getComposer() {
288 return composer;
289 }
290
291 /***
292 * File size in bytes. -1 if the track is an Internet stream.
293 *
294 * @return the fileSize
295 */
296 public long getFileSize() {
297 return fileSize;
298 }
299
300 /***
301 * The year released.
302 *
303 * @return the yearReleased
304 */
305 public String getYearReleased() {
306 return yearReleased;
307 }
308
309 /***
310 * Genre.
311 *
312 * @return the genre
313 */
314 public String getGenre() {
315 return genre;
316 }
317
318 /***
319 * Length in seconds. May be 0 if not known.
320 *
321 * @return the length
322 */
323 public int getLength() {
324 return length;
325 }
326
327 /***
328 * Location on the filesystem. May be URL if it is an Internet stream.
329 *
330 * @return the location
331 */
332 public String getLocation() {
333 return location;
334 }
335
336 /***
337 * Checks if the track denoted by this object is a local one (you can pass
338 * {@link #getLocation()} to a {@link File} object) , or an Internet stream
339 * ({@link #getLocation()} is valid URL).
340 *
341 * @return <code>true</code> if the file is local.
342 */
343 public boolean isLocal() {
344 return !getOrigin().online;
345 }
346
347 /***
348 * @return the title
349 */
350 public String getTitle() {
351 return title;
352 }
353
354 /***
355 * @return the trackNumber
356 */
357 public String getTrackNumber() {
358 return trackNumber;
359 }
360
361 @Override
362 public boolean equals(Object o) {
363 if (!(o instanceof TrackMetadataBean))
364 return false;
365 return location.equals(((TrackMetadataBean) o).location);
366 }
367
368 @Override
369 public int hashCode() {
370 return location.hashCode();
371 }
372
373 @Override
374 public String toString() {
375 return "Track: " + location;
376 }
377
378 /***
379 * Frequency in hz.
380 *
381 * @return the frequency in hz, or 0 if not known.
382 */
383 public int getFrequency() {
384 return frequency;
385 }
386
387 /***
388 * Database track id.
389 *
390 * @return the trackId
391 */
392 public long getTrackId() {
393 return trackId;
394 }
395
396 /***
397 * Creates new bean having equal fields <code>null</code>ed.
398 *
399 * @param other
400 * the other bean
401 * @return bean with equal fields <code>null</code>ed.
402 */
403 public TrackMetadataBean nullsEqualFields(final TrackMetadataBean other) {
404 return nullsEqualFields(other, false);
405 }
406
407 /***
408 * Creates new bean having equal fields <code>null</code>ed.
409 *
410 * @param other
411 * the other bean
412 * @param sensibleFieldsOnly
413 * if <code>true</code> then only sensible fields are compared -
414 * i.e. the {@link #bitrate}, {@link #fileSize},
415 * {@link #frequency}, {@link #length}, {@link #title} and
416 * {@link #trackNumber} are not nulled even when equal.
417 * @return bean with equal fields <code>null</code>ed.
418 */
419 public TrackMetadataBean nullsEqualFields(final TrackMetadataBean other,
420 final boolean sensibleFieldsOnly) {
421 final Builder result = newBuilder();
422 result.album = MiscUtils.nullCompare(this.album, other.album, true) == 0 ? null
423 : this.album;
424 result.artist = MiscUtils.nullCompare(this.artist, other.artist, true) == 0 ? null
425 : this.artist;
426 result.composer = MiscUtils.nullCompare(this.composer, other.composer,
427 true) == 0 ? null : this.composer;
428 result.genre = MiscUtils.nullCompare(this.genre, other.genre, true) == 0 ? null
429 : this.genre;
430 result.yearReleased = MiscUtils.nullCompare(this.yearReleased,
431 other.yearReleased, true) == 0 ? null : this.yearReleased;
432 result.buyURL = MiscUtils.nullCompare(this.buyURL, other.buyURL, true) == 0 ? null
433 : this.buyURL;
434 result.artistURL = MiscUtils.nullCompare(this.artistURL,
435 other.artistURL, true) == 0 ? null : this.artistURL;
436 result.artistDesc = MiscUtils.nullCompare(this.artistDesc,
437 other.artistDesc, true) == 0 ? null : this.artistDesc;
438 result.license = MiscUtils.nullCompare(this.license, other.license,
439 true) == 0 ? null : this.license;
440 result.location = this.location;
441 result.origin = this.origin;
442 if (sensibleFieldsOnly) {
443 result.bitrate = this.bitrate;
444 result.fileSize = this.fileSize;
445 result.frequency = this.frequency;
446 result.length = this.length;
447 result.title = this.title;
448 result.trackNumber = this.trackNumber;
449 } else {
450 result.bitrate = this.bitrate == other.bitrate ? 0 : this.bitrate;
451 result.fileSize = this.fileSize == other.fileSize ? 0
452 : this.fileSize;
453 result.frequency = this.frequency == other.frequency ? 0
454 : this.frequency;
455 result.length = this.length == other.length ? 0 : this.length;
456 result.title = MiscUtils.nullCompare(this.title, other.title, true) == 0 ? null
457 : this.title;
458 result.trackNumber = MiscUtils.nullCompare(this.trackNumber,
459 other.trackNumber, true) == 0 ? null : this.trackNumber;
460 }
461 return result.build(trackId);
462 }
463
464 /***
465 * An empty bean.
466 */
467 public final static TrackMetadataBean EMPTY;
468 static {
469 EMPTY = newBuilder().setOrigin(TrackOriginEnum.LocalFs)
470 .setLocation("/").build(-1);
471 }
472
473 /***
474 * The track origin.
475 *
476 * @return origin, never <code>null</code>.
477 */
478 public TrackOriginEnum getOrigin() {
479
480
481 return origin == null ? TrackOriginEnum.LocalFs : origin;
482 }
483
484 /***
485 * You can buy the album here
486 *
487 * @return the buyURL
488 */
489 public String getBuyURL() {
490 return buyURL;
491 }
492
493 /***
494 * The license URL
495 *
496 * @return the license
497 */
498 public String getLicense() {
499 return license;
500 }
501
502 /***
503 * A link to artist's page
504 *
505 * @return the artistURL
506 */
507 public String getArtistURL() {
508 return artistURL;
509 }
510
511 /***
512 * The artist's description.
513 *
514 * @return the artistDesc
515 */
516 public String getArtistDesc() {
517 return artistDesc;
518 }
519
520 /***
521 * Returns new track metadata builder instance.
522 *
523 * @return the builder instance.
524 */
525 public static Builder newBuilder() {
526 return new Builder();
527 }
528
529 /***
530 * The mutable version of {@link TrackMetadataBean}. Use it to gradually
531 * build the bean. Thread-unsafe.
532 *
533 * @author Martin Vysny
534 */
535 @ThreadUnsafe
536 public final static class Builder {
537 /***
538 * The year released.
539 */
540 public String yearReleased;
541
542 /***
543 * The year released.
544 *
545 * @param yearReleased
546 * the yearReleased to set
547 * @return this
548 */
549 public Builder setYearReleased(String yearReleased) {
550 this.yearReleased = yearReleased;
551 return this;
552 }
553
554 /***
555 * Frequency in hz.
556 *
557 * @param frequency
558 * the frequency to set
559 * @return this
560 */
561 public Builder setFrequency(int frequency) {
562 this.frequency = frequency;
563 return this;
564 }
565
566 /***
567 * You can buy the album here.
568 *
569 * @param buyURL
570 * the buyURL to set
571 * @return this
572 */
573 public Builder setBuyURL(String buyURL) {
574 this.buyURL = buyURL;
575 return this;
576 }
577
578 /***
579 * The license URL
580 *
581 * @param license
582 * the license to set
583 * @return this
584 */
585 public Builder setLicense(String license) {
586 this.license = license;
587 return this;
588 }
589
590 /***
591 * A link to artist's page
592 *
593 * @param artistURL
594 * the artistURL to set
595 * @return this
596 */
597 public Builder setArtistURL(String artistURL) {
598 this.artistURL = artistURL;
599 return this;
600 }
601
602 /***
603 * The artist's description.
604 *
605 * @param artistDesc
606 * the artistDesc to set
607 * @return this
608 */
609 public Builder setArtistDesc(String artistDesc) {
610 this.artistDesc = artistDesc;
611 return this;
612 }
613
614 /***
615 * Track title.
616 *
617 * @param title
618 * the title to set
619 * @return this
620 */
621 public Builder setTitle(String title) {
622 this.title = title;
623 return this;
624 }
625
626 /***
627 * @param artist
628 * the artist to set
629 * @return this
630 */
631 public Builder setArtist(String artist) {
632 this.artist = artist;
633 return this;
634 }
635
636 /***
637 * Original composer.
638 *
639 * @param composer
640 * the composer to set
641 * @return this
642 */
643 public Builder setComposer(String composer) {
644 this.composer = composer;
645 return this;
646 }
647
648 /***
649 * Album name.
650 *
651 * @param album
652 * the album to set
653 * @return this
654 */
655 public Builder setAlbum(String album) {
656 this.album = album;
657 return this;
658 }
659
660 /***
661 * Genre.
662 *
663 * @param genre
664 * the genre to set
665 * @return this
666 */
667 public Builder setGenre(String genre) {
668 this.genre = genre;
669 return this;
670 }
671
672 /***
673 * Track number in the album ordering.
674 *
675 * @param trackNumber
676 * the trackNumber to set
677 * @return this
678 */
679 public Builder setTrackNumber(String trackNumber) {
680 this.trackNumber = trackNumber;
681 return this;
682 }
683
684 /***
685 * Location on the filesystem. May be URL if it is an Internet stream.
686 *
687 * @param location
688 * the location to set
689 * @return this
690 */
691 public Builder setLocation(String location) {
692 this.location = location;
693 return this;
694 }
695
696 /***
697 * Length in seconds. May be 0 if not known.
698 *
699 * @param length
700 * the length to set
701 * @return this
702 */
703 public Builder setLength(int length) {
704 this.length = length;
705 return this;
706 }
707
708 /***
709 * Bitrate in kbps. If zero then it is not known.
710 *
711 * @param bitrate
712 * the bitrate to set
713 * @return this
714 */
715 public Builder setBitrate(int bitrate) {
716 this.bitrate = bitrate;
717 return this;
718 }
719
720 /***
721 * File size in bytes. -1 if the track is an Internet stream.
722 *
723 * @param fileSize
724 * the fileSize to set
725 * @return this
726 */
727 public Builder setFileSize(long fileSize) {
728 this.fileSize = fileSize;
729 return this;
730 }
731
732 /***
733 * The track location.
734 *
735 * @param origin
736 * the origin to set
737 * @return this
738 */
739 public Builder setOrigin(TrackOriginEnum origin) {
740 this.origin = origin;
741 return this;
742 }
743
744 /***
745 * Frequency in hz.
746 */
747 public int frequency;
748
749 /***
750 * You can buy the album here
751 */
752 public String buyURL;
753
754 /***
755 * The license URL
756 */
757 public String license;
758
759 /***
760 * A link to artist's page
761 */
762 public String artistURL;
763
764 /***
765 * The artist's description.
766 */
767 public String artistDesc;
768
769 /***
770 * Track title.
771 */
772 public String title;
773
774 /***
775 * Artist.
776 */
777 public String artist;
778
779 /***
780 * Original composer.
781 */
782 public String composer;
783
784 /***
785 * Album name.
786 */
787 public String album;
788
789 /***
790 * Genre.
791 */
792 public String genre;
793
794 /***
795 * Track number.
796 */
797 public String trackNumber;
798
799 /***
800 * Location on the filesystem. May be URL if it is an Internet stream.
801 */
802 public String location;
803
804 /***
805 * Length in seconds. May be 0 if not known.
806 */
807 public int length;
808
809 /***
810 * Bitrate in kbps. If zero then it is not known.
811 */
812 public int bitrate;
813
814 /***
815 * File size in bytes. -1 if the track is an Internet stream.
816 */
817 public long fileSize;
818
819 /***
820 * The track location.
821 */
822 public TrackOriginEnum origin;
823
824 /***
825 * Creates new metadata bean.
826 *
827 * @param trackId
828 * the database track id
829 * @return new bean instance
830 */
831 public TrackMetadataBean build(final long trackId) {
832 return new TrackMetadataBean(trackId, this);
833 }
834
835 /***
836 * Polls data from given track.
837 *
838 * @param track
839 * copy data from given track.
840 * @return this
841 */
842 public Builder getData(final TrackMetadataBean track) {
843 album = track.album;
844 artist = track.artist;
845 artistDesc = track.artistDesc;
846 artistURL = track.artistURL;
847 bitrate = track.bitrate;
848 buyURL = track.buyURL;
849 composer = track.composer;
850 fileSize = track.fileSize;
851 frequency = track.frequency;
852 genre = track.genre;
853 length = track.length;
854 license = track.license;
855 location = track.location;
856 origin = track.origin;
857 title = track.title;
858 trackNumber = track.trackNumber;
859 yearReleased = track.yearReleased;
860 return this;
861 }
862 }
863
864 /***
865 * Appends length in the <code>[hh:]mm:ss</code> format.
866 *
867 * @param builder
868 * append the time here.
869 * @param emptyStringOnZero
870 * if <code>true</code> then empty string is returned on zero.
871 */
872 public void appendDisplayableLength(StringBuilder builder,
873 boolean emptyStringOnZero) {
874 appendDisplayableLength(getLength(), builder, emptyStringOnZero);
875 }
876 }