View Javadoc

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 		// deserialization of older format could leave this field null. Handle
480 		// this case
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 }