001 /*
002 * Java GPX Library (jpx-3.1.0).
003 * Copyright (c) 2016-2023 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020 package io.jenetics.jpx;
021
022 import static java.lang.String.format;
023 import static java.util.Objects.hash;
024 import static java.util.Objects.requireNonNull;
025 import static io.jenetics.jpx.Format.toIntString;
026 import static io.jenetics.jpx.Lists.copyOf;
027 import static io.jenetics.jpx.Lists.copyTo;
028
029 import java.io.DataInput;
030 import java.io.DataOutput;
031 import java.io.IOException;
032 import java.io.InvalidObjectException;
033 import java.io.ObjectInputStream;
034 import java.io.Serial;
035 import java.io.Serializable;
036 import java.net.URI;
037 import java.util.ArrayList;
038 import java.util.Iterator;
039 import java.util.List;
040 import java.util.Objects;
041 import java.util.Optional;
042 import java.util.function.Consumer;
043 import java.util.function.Function;
044 import java.util.function.Predicate;
045 import java.util.stream.Stream;
046
047 import org.w3c.dom.Document;
048
049 import io.jenetics.jpx.GPX.Version;
050
051 /**
052 * Represents a GPX track - an ordered list of points describing a path.
053 * <p>
054 * Creating a Track object with one track-segment and 3 track-points:
055 * <pre>{@code
056 * final Track track = Track.builder()
057 * .name("Track 1")
058 * .description("Mountain bike tour.")
059 * .addSegment(segment -> segment
060 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
061 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
062 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
063 * .addSegment(segment -> segment
064 * .addPoint(p -> p.lat(46.2081743).lon(16.3738189).ele(160))
065 * .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
066 * .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
067 * .build();
068 * }</pre>
069 *
070 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
071 * @version 1.5
072 * @since 1.0
073 */
074 public final class Track implements Iterable<TrackSegment>, Serializable {
075
076 @Serial
077 private static final long serialVersionUID = 2L;
078
079 private final String _name;
080 private final String _comment;
081 private final String _description;
082 private final String _source;
083 private final List<Link> _links;
084 private final UInt _number;
085 private final String _type;
086 private final Document _extensions;
087 private final List<TrackSegment> _segments;
088
089 /**
090 * Create a new {@code Track} with the given parameters.
091 *
092 * @param name the GPS name of the track
093 * @param comment the GPS comment for the track
094 * @param description user description of the track
095 * @param source the source of data. Included to give user some idea of
096 * reliability and accuracy of data.
097 * @param links the links to external information about track
098 * @param number the GPS track number
099 * @param type the type (classification) of track
100 * @param extensions the extensions document
101 * @param segments the track-segments holds a list of track-points which are
102 * logically connected in order. To represent a single GPS track
103 * where GPS reception was lost, or the GPS receiver was turned off,
104 * start a new track-segment for each continuous span of track data.
105 */
106 private Track(
107 final String name,
108 final String comment,
109 final String description,
110 final String source,
111 final List<Link> links,
112 final UInt number,
113 final String type,
114 final Document extensions,
115 final List<TrackSegment> segments
116 ) {
117 _name = name;
118 _comment = comment;
119 _description = description;
120 _source = source;
121 _links = copyOf(links);
122 _number = number;
123 _type = type;
124 _extensions = extensions;
125 _segments = copyOf(segments);
126 }
127
128 /**
129 * Return the track name.
130 *
131 * @return the track name
132 */
133 public Optional<String> getName() {
134 return Optional.ofNullable(_name);
135 }
136
137 /**
138 * Return the GPS comment of the track.
139 *
140 * @return the GPS comment of the track
141 */
142 public Optional<String> getComment() {
143 return Optional.ofNullable(_comment);
144 }
145
146 /**
147 * Return the text description of the track.
148 *
149 * @return the text description of the track
150 */
151 public Optional<String> getDescription() {
152 return Optional.ofNullable(_description);
153 }
154
155 /**
156 * Return the source of data. Included to give user some idea of reliability
157 * and accuracy of data.
158 *
159 * @return the source of data
160 */
161 public Optional<String> getSource() {
162 return Optional.ofNullable(_source);
163 }
164
165 /**
166 * Return the links to external information about the track.
167 *
168 * @return the links to external information about the track
169 */
170 public List<Link> getLinks() {
171 return _links;
172 }
173
174 /**
175 * Return the GPS track number.
176 *
177 * @return the GPS track number
178 */
179 public Optional<UInt> getNumber() {
180 return Optional.ofNullable(_number);
181 }
182
183 /**
184 * Return the type (classification) of the track.
185 *
186 * @return the type (classification) of the track
187 */
188 public Optional<String> getType() {
189 return Optional.ofNullable(_type);
190 }
191
192 /**
193 * Return the (cloned) extensions document. The root element of the returned
194 * document has the name {@code extensions}.
195 * <pre>{@code
196 * <extensions>
197 * ...
198 * </extensions>
199 * }</pre>
200 *
201 * @since 1.5
202 *
203 * @return the extensions document
204 * @throws org.w3c.dom.DOMException if the document could not be cloned,
205 * because of an erroneous XML configuration
206 */
207 public Optional<Document> getExtensions() {
208 return Optional.ofNullable(_extensions).map(XML::clone);
209 }
210
211 /**
212 * Return the sequence of route points.
213 *
214 * @return the sequence of route points
215 */
216 public List<TrackSegment> getSegments() {
217 return _segments;
218 }
219
220 /**
221 * Return a stream of {@link TrackSegment} objects this track contains.
222 *
223 * @return a stream of {@link TrackSegment} objects this track contains
224 */
225 public Stream<TrackSegment> segments() {
226 return _segments.stream();
227 }
228
229 @Override
230 public Iterator<TrackSegment> iterator() {
231 return _segments.iterator();
232 }
233
234 /**
235 * Convert the <em>immutable</em> track object into a <em>mutable</em>
236 * builder initialized with the current track values.
237 *
238 * @since 1.1
239 *
240 * @return a new track builder initialized with the values of {@code this}
241 * track
242 */
243 public Builder toBuilder() {
244 return builder()
245 .name(_name)
246 .cmt(_comment)
247 .desc(_description)
248 .src(_source)
249 .links(_links)
250 .number(_number)
251 .extensions(_extensions)
252 .segments(_segments);
253 }
254
255 /**
256 * Return {@code true} if all track properties are {@code null} or empty.
257 *
258 * @return {@code true} if all track properties are {@code null} or empty
259 */
260 public boolean isEmpty() {
261 return _name == null &&
262 _comment == null &&
263 _description == null &&
264 _source == null &&
265 _links.isEmpty() &&
266 _number == null &&
267 _extensions == null &&
268 (_segments.isEmpty() ||
269 _segments.stream().allMatch(TrackSegment::isEmpty));
270 }
271
272 /**
273 * Return {@code true} if not all track properties are {@code null} or empty.
274 *
275 * @since 1.1
276 *
277 * @return {@code true} if not all track properties are {@code null} or empty
278 */
279 public boolean nonEmpty() {
280 return !isEmpty();
281 }
282
283 @Override
284 public int hashCode() {
285 return hash(
286 _name,
287 _comment,
288 _description,
289 _source,
290 _type,
291 Lists.hashCode(_links),
292 _number,
293 _segments
294 );
295 }
296
297 @Override
298 public boolean equals(final Object obj) {
299 return obj == this ||
300 obj instanceof Track track &&
301 Objects.equals(track._name, _name) &&
302 Objects.equals(track._comment, _comment) &&
303 Objects.equals(track._description, _description) &&
304 Objects.equals(track._source, _source) &&
305 Objects.equals(track._type, _type) &&
306 Lists.equals(track._links, _links) &&
307 Objects.equals(track._number, _number) &&
308 Objects.equals(track._segments, _segments);
309 }
310
311 @Override
312 public String toString() {
313 return format("Track[name=%s, segments=%s]", _name, _segments);
314 }
315
316 /**
317 * Builder class for creating immutable {@code Track} objects.
318 * <p>
319 * Creating a Track object with one track-segment and 3 track-points:
320 * <pre>{@code
321 * final Track track = Track.builder()
322 * .name("Track 1")
323 * .description("Mountain bike tour.")
324 * .addSegment(segment -> segment
325 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
326 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
327 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
328 * .addSegment(segment -> segment
329 * .addPoint(p -> p.lat(46.2081743).lon(16.3738189).ele(160))
330 * .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
331 * .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
332 * .build();
333 * }</pre>
334 */
335 public static final class Builder implements Filter<TrackSegment, Track> {
336 private String _name;
337 private String _comment;
338 private String _description;
339 private String _source;
340 private final List<Link> _links = new ArrayList<>();
341 private UInt _number;
342 private String _type;
343 private Document _extensions;
344 private final List<TrackSegment> _segments = new ArrayList<>();
345
346 private Builder() {
347 }
348
349 /**
350 * Set the name of the track.
351 *
352 * @param name the track name
353 * @return {@code this} {@code Builder} for method chaining
354 */
355 public Builder name(final String name) {
356 _name = name;
357 return this;
358 }
359
360 /**
361 * Return the current name value.
362 *
363 * @since 1.1
364 *
365 * @return the current name value
366 */
367 public Optional<String> name() {
368 return Optional.ofNullable(_name);
369 }
370
371 /**
372 * Set the comment of the track.
373 *
374 * @param comment the track comment
375 * @return {@code this} {@code Builder} for method chaining
376 */
377 public Builder cmt(final String comment) {
378 _comment = comment;
379 return this;
380 }
381
382 public Optional<String> cmt() {
383 return Optional.ofNullable(_comment);
384 }
385
386 /**
387 * Set the description of the track.
388 *
389 * @param description the track description
390 * @return {@code this} {@code Builder} for method chaining
391 */
392 public Builder desc(final String description) {
393 _description = description;
394 return this;
395 }
396
397 /**
398 * Return the current description value.
399 *
400 * @since 1.1
401 *
402 * @return the current description value
403 */
404 public Optional<String> desc() {
405 return Optional.ofNullable(_description);
406 }
407
408 /**
409 * Set the source of the track.
410 *
411 * @param source the track source
412 * @return {@code this} {@code Builder} for method chaining
413 */
414 public Builder src(final String source) {
415 _source = source;
416 return this;
417 }
418
419 /**
420 * Return the current source value.
421 *
422 * @since 1.1
423 *
424 * @return the current source value
425 */
426 public Optional<String> src() {
427 return Optional.ofNullable(_source);
428 }
429
430 /**
431 * Set the track links. The link list may be {@code null}.
432 *
433 * @param links the track links
434 * @return {@code this} {@code Builder} for method chaining
435 * @throws NullPointerException if one of the links in the list is
436 * {@code null}
437 */
438 public Builder links(final List<Link> links) {
439 copyTo(links, _links);
440 return this;
441 }
442
443 /**
444 * Add the given {@code link} to the track
445 *
446 * @param link the link to add to the track
447 * @return {@code this} {@code Builder} for method chaining
448 */
449 public Builder addLink(final Link link) {
450 _links.add(requireNonNull(link));
451
452 return this;
453 }
454
455 /**
456 * Add the given {@code link} to the track
457 *
458 * @param href the link to add to the track
459 * @return {@code this} {@code Builder} for method chaining
460 * @throws NullPointerException if the given {@code href} is {@code null}
461 * @throws IllegalArgumentException if the given {@code href} is not a
462 * valid URL
463 */
464 public Builder addLink(final String href) {
465 return addLink(Link.of(href));
466 }
467
468 /**
469 * Return the current links. The returned link list is mutable.
470 *
471 * @since 1.1
472 *
473 * @return the current links
474 */
475 public List<Link> links() {
476 return new NonNullList<>(_links);
477 }
478
479 /**
480 * Set the track number.
481 *
482 * @param number the track number
483 * @return {@code this} {@code Builder} for method chaining
484 */
485 public Builder number(final UInt number) {
486 _number = number;
487 return this;
488 }
489
490 /**
491 * Set the track number.
492 *
493 * @param number the track number
494 * @return {@code this} {@code Builder} for method chaining
495 * @throws IllegalArgumentException if the given {@code value} is smaller
496 * than zero
497 */
498 public Builder number(final int number) {
499 _number = UInt.of(number);
500 return this;
501 }
502
503 /**
504 * Return the current number value.
505 *
506 * @since 1.1
507 *
508 * @return the current number value
509 */
510 public Optional<UInt> number() {
511 return Optional.ofNullable(_number);
512 }
513
514 /**
515 * Set the track type.
516 *
517 * @param type the track type
518 * @return {@code this} {@code Builder} for method chaining
519 */
520 public Builder type(final String type) {
521 _type = type;
522 return this;
523 }
524
525 /**
526 * Return the current type value.
527 *
528 * @since 1.1
529 *
530 * @return the current type value
531 */
532 public Optional<String> type() {
533 return Optional.ofNullable(_type);
534 }
535
536 /**
537 * Sets the extensions object, which may be {@code null}. The root
538 * element of the extensions document must be {@code extensions}.
539 * <pre>{@code
540 * <extensions>
541 * ...
542 * </extensions>
543 * }</pre>
544 *
545 * @since 1.5
546 *
547 * @param extensions the document
548 * @return {@code this} {@code Builder} for method chaining
549 * @throws IllegalArgumentException if the root element is not the
550 * an {@code extensions} node
551 */
552 public Builder extensions(final Document extensions) {
553 _extensions = XML.checkExtensions(extensions);
554 return this;
555 }
556
557 /**
558 * Return the current extensions
559 *
560 * @since 1.5
561 *
562 * @return the extensions document
563 */
564 public Optional<Document> extensions() {
565 return Optional.ofNullable(_extensions);
566 }
567
568 /**
569 * Set the track segments of the track. The list may be {@code null}.
570 *
571 * @param segments the track segments
572 * @return {@code this} {@code Builder} for method chaining
573 * @throws NullPointerException if one of the segments in the list is
574 * {@code null}
575 */
576 public Builder segments(final List<TrackSegment> segments) {
577 copyTo(segments, _segments);
578 return this;
579 }
580
581 /**
582 * Add a track segment to the track.
583 *
584 * @param segment the track segment added to the track
585 * @return {@code this} {@code Builder} for method chaining
586 * @throws NullPointerException if the given argument is {@code null}
587 */
588 public Builder addSegment(final TrackSegment segment) {
589 _segments.add(requireNonNull(segment));
590 return this;
591 }
592
593 /**
594 * Add a track segment to the track, via the given builder.
595 * <pre>{@code
596 * final Track track = Track.builder()
597 * .name("Track 1")
598 * .description("Mountain bike tour.")
599 * .addSegment(segment -> segment
600 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
601 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
602 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
603 * .addSegment(segment -> segment
604 * .addPoint(p -> p.lat(46.2081743).lon(16.3738189).ele(160))
605 * .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
606 * .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
607 * .build();
608 * }</pre>
609 *
610 * @param segment the track segment
611 * @return {@code this} {@code Builder} for method chaining
612 * @throws NullPointerException if the given argument is {@code null}
613 */
614 public Builder addSegment(final Consumer<? super TrackSegment.Builder> segment) {
615 final TrackSegment.Builder builder = TrackSegment.builder();
616 segment.accept(builder);
617 return addSegment(builder.build());
618 }
619
620 /**
621 * Return the current track segments. The returned segment list is
622 * mutable.
623 *
624 * @since 1.1
625 *
626 * @return the current track segments
627 */
628 public List<TrackSegment> segments() {
629 return new NonNullList<>(_segments);
630 }
631
632 @Override
633 public Builder filter(final Predicate<? super TrackSegment> predicate) {
634 segments(_segments.stream().filter(predicate).toList());
635 return this;
636 }
637
638 @Override
639 public Builder map(
640 final Function<? super TrackSegment, ? extends TrackSegment> mapper
641 ) {
642 segments(
643 _segments.stream()
644 .map(mapper)
645 .map(TrackSegment.class::cast)
646 .toList()
647 );
648 return this;
649 }
650
651 @Override
652 public Builder flatMap(
653 final Function<
654 ? super TrackSegment,
655 ? extends List<TrackSegment>> mapper
656 ) {
657 segments(
658 _segments.stream()
659 .flatMap(segment -> mapper.apply(segment).stream())
660 .toList()
661 );
662 return this;
663 }
664
665 @Override
666 public Builder listMap(
667 final Function<
668 ? super List<TrackSegment>,
669 ? extends List<TrackSegment>> mapper
670 ) {
671 segments(mapper.apply(_segments));
672 return this;
673 }
674
675 /**
676 * Create a new GPX track from the current builder state.
677 *
678 * @return a new GPX track from the current builder state
679 */
680 @Override
681 public Track build() {
682 return of(
683 _name,
684 _comment,
685 _description,
686 _source,
687 _links,
688 _number,
689 _type,
690 _extensions,
691 _segments
692 );
693 }
694 }
695
696 public static Builder builder() {
697 return new Builder();
698 }
699
700
701 /* *************************************************************************
702 * Static object creation methods
703 * ************************************************************************/
704
705 /**
706 * Create a new {@code Track} with the given parameters.
707 *
708 * @since 1.5
709 *
710 * @param name the GPS name of the track
711 * @param comment the GPS comment for the track
712 * @param description user description of the track
713 * @param source the source of data. Included to give user some idea of
714 * reliability and accuracy of data.
715 * @param links the links to external information about track
716 * @param number the GPS track number
717 * @param type the type (classification) of track
718 * @param extensions the extensions document
719 * @param segments the track-segments holds a list of track-points which are
720 * logically connected in order. To represent a single GPS track
721 * where GPS reception was lost, or the GPS receiver was turned off,
722 * start a new track-segment for each continuous span of track data.
723 * @return a new {@code Track} with the given parameters
724 * @throws NullPointerException if the {@code links} or the {@code segments}
725 * sequence is {@code null}
726 */
727 public static Track of(
728 final String name,
729 final String comment,
730 final String description,
731 final String source,
732 final List<Link> links,
733 final UInt number,
734 final String type,
735 final Document extensions,
736 final List<TrackSegment> segments
737 ) {
738 return new Track(
739 name,
740 comment,
741 description,
742 source,
743 links,
744 number,
745 type,
746 XML.extensions(XML.clone(extensions)),
747 segments
748 );
749 }
750
751 /**
752 * Create a new {@code Track} with the given parameters.
753 *
754 * @param name the GPS name of the track
755 * @param comment the GPS comment for the track
756 * @param description user description of the track
757 * @param source the source of data. Included to give user some idea of
758 * reliability and accuracy of data.
759 * @param links the links to external information about track
760 * @param number the GPS track number
761 * @param type the type (classification) of track
762 * @param segments the track-segments holds a list of track-points which are
763 * logically connected in order. To represent a single GPS track
764 * where GPS reception was lost, or the GPS receiver was turned off,
765 * start a new track-segment for each continuous span of track data.
766 * @return a new {@code Track} with the given parameters
767 * @throws NullPointerException if the {@code links} or the {@code segments}
768 * sequence is {@code null}
769 */
770 public static Track of(
771 final String name,
772 final String comment,
773 final String description,
774 final String source,
775 final List<Link> links,
776 final UInt number,
777 final String type,
778 final List<TrackSegment> segments
779 ) {
780 return of(
781 name,
782 comment,
783 description,
784 source,
785 links,
786 number,
787 type,
788 null,
789 segments
790 );
791 }
792
793
794 /* *************************************************************************
795 * Java object serialization
796 * ************************************************************************/
797
798 @Serial
799 private Object writeReplace() {
800 return new SerialProxy(SerialProxy.TRACK, this);
801 }
802
803 @Serial
804 private void readObject(final ObjectInputStream stream)
805 throws InvalidObjectException
806 {
807 throw new InvalidObjectException("Serialization proxy required.");
808 }
809
810 void write(final DataOutput out) throws IOException {
811 IO.writeNullableString(_name, out);
812 IO.writeNullableString(_comment, out);
813 IO.writeNullableString(_description, out);
814 IO.writeNullableString(_source, out);
815 IO.writes(_links, Link::write, out);
816 IO.writeNullable(_number, UInt::write, out);
817 IO.writeNullableString(_type, out);
818 IO.writeNullable(_extensions, IO::write, out);
819 IO.writes(_segments, TrackSegment::write, out);
820 }
821
822 static Track read(final DataInput in) throws IOException {
823 return new Track(
824 IO.readNullableString(in),
825 IO.readNullableString(in),
826 IO.readNullableString(in),
827 IO.readNullableString(in),
828 IO.reads(Link::read, in),
829 IO.readNullable(UInt::read, in),
830 IO.readNullableString(in),
831 IO.readNullable(IO::readDoc, in),
832 IO.reads(TrackSegment::read, in)
833 );
834 }
835
836 /* *************************************************************************
837 * XML stream object serialization
838 * ************************************************************************/
839
840 private static String url(final Track track) {
841 return track.getLinks().isEmpty()
842 ? null
843 : track.getLinks().get(0).getHref().toString();
844 }
845
846 private static String urlname(final Track track) {
847 return track.getLinks().isEmpty()
848 ? null
849 : track.getLinks().get(0).getText().orElse(null);
850 }
851
852 // Define the needed writers for the different versions.
853 private static XMLWriters<Track>
854 writers(final Function<? super Number, String> formatter) {
855 return new XMLWriters<Track>()
856 .v00(XMLWriter.elem("name").map(t -> t._name))
857 .v00(XMLWriter.elem("cmt").map(r -> r._comment))
858 .v00(XMLWriter.elem("desc").map(r -> r._description))
859 .v00(XMLWriter.elem("src").map(r -> r._source))
860 .v11(XMLWriter.elems(Link.WRITER).map(r -> r._links))
861 .v10(XMLWriter.elem("url").map(Track::url))
862 .v10(XMLWriter.elem("urlname").map(Track::urlname))
863 .v00(XMLWriter.elem("number").map(r -> toIntString(r._number)))
864 .v00(XMLWriter.elem("type").map(r -> r._type))
865 .v00(XMLWriter.doc("extensions").map(gpx -> gpx._extensions))
866 .v10(XMLWriter.elems(TrackSegment.xmlWriter(Version.V10, formatter)).map(r -> r._segments))
867 .v11(XMLWriter.elems(TrackSegment.xmlWriter(Version.V11, formatter)).map(r -> r._segments));
868 }
869
870 // Define the needed readers for the different versions.
871 private static XMLReaders
872 readers(final Function<? super String, Length> lengthParser) {
873 return new XMLReaders()
874 .v00(XMLReader.elem("name"))
875 .v00(XMLReader.elem("cmt"))
876 .v00(XMLReader.elem("desc"))
877 .v00(XMLReader.elem("src"))
878 .v11(XMLReader.elems(Link.READER))
879 .v10(XMLReader.elem("url").map(Format::parseURI))
880 .v10(XMLReader.elem("urlname"))
881 .v00(XMLReader.elem("number").map(UInt::parse))
882 .v00(XMLReader.elem("type"))
883 .v00(XMLReader.doc("extensions"))
884 .v10(XMLReader.elems(TrackSegment.xmlReader(Version.V10, lengthParser)))
885 .v11(XMLReader.elems(TrackSegment.xmlReader(Version.V11, lengthParser)));
886 }
887
888 static XMLWriter<Track> xmlWriter(
889 final Version version,
890 final Function<? super Number, String> formatter
891 ) {
892 return XMLWriter.elem("trk", writers(formatter).writers(version));
893 }
894
895 static XMLReader<Track> xmlReader(
896 final Version version,
897 final Function<? super String, Length> lengthParser
898 ) {
899 return XMLReader.elem(
900 version == Version.V10 ? Track::toTrackV10 : Track::toTrackV11,
901 "trk",
902 readers(lengthParser).readers(version)
903 );
904 }
905
906 @SuppressWarnings("unchecked")
907 private static Track toTrackV11(final Object[] v) {
908 return new Track(
909 (String)v[0],
910 (String)v[1],
911 (String)v[2],
912 (String)v[3],
913 (List<Link>)v[4],
914 (UInt)v[5],
915 (String)v[6],
916 XML.extensions((Document)v[7]),
917 (List<TrackSegment>)v[8]
918 );
919 }
920
921 @SuppressWarnings("unchecked")
922 private static Track toTrackV10(final Object[] v) {
923 return new Track(
924 (String)v[0],
925 (String)v[1],
926 (String)v[2],
927 (String)v[3],
928 v[4] != null
929 ? List.of(Link.of((URI)v[4], (String)v[5], null))
930 : null,
931 (UInt)v[6],
932 (String)v[7],
933 XML.extensions((Document)v[8]),
934 (List<TrackSegment>)v[9]
935 );
936 }
937
938 }
|