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