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 */
020package io.jenetics.jpx;
021
022import static java.lang.String.format;
023import static java.util.Objects.requireNonNull;
024
025import java.io.DataInput;
026import java.io.DataOutput;
027import java.io.IOException;
028import java.io.InvalidObjectException;
029import java.io.ObjectInputStream;
030import java.io.Serial;
031import java.io.Serializable;
032import java.text.NumberFormat;
033import java.text.ParseException;
034
035/**
036 * Extent of something along its greatest dimension or the extent of space
037 * between two objects or places. The metric system unit for this quantity is
038 * "m" (metre).
039 *
040 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
041 * @version 1.2
042 * @since 1.0
043 */
044public final class Length
045        extends Number
046        implements
047                Comparable<Length>,
048                Serializable
049{
050
051        @Serial
052        private static final long serialVersionUID = 2L;
053
054        /**
055         * Represents a given length unit.
056         */
057        public enum Unit {
058
059                /**
060                 * Represents a meter.
061                 */
062                METER(1.0),
063
064                /**
065                 * Represents a kilometer: ≙ 1,000 m.
066                 */
067                KILOMETER(1_000.0),
068
069                /**
070                 * Represents an inch: ≙ 0.0254 m.
071                 */
072                INCH(127.0/5_000.0),
073
074                /**
075                 * Represents a foot: ≙ 0.3048 m.
076                 */
077                FOOT(0.3048),
078
079                /**
080                 * Represents a yard: ≙ 0.9144 m.
081                 */
082                YARD(1_143.0/1_250.0),
083
084                /**
085                 * Represents a statute mile: ≙ 1,609.344 m.
086                 */
087                MILE(201_168.0/125.0),
088
089                /**
090                 * Represents a fathom: ≙ 1.853184 m.
091                 */
092                FATHOM(1.853184),
093
094                /**
095                 * Represents a cable: ≙ 185.3184 m.
096                 */
097                CABLE(185.3184),
098
099                /**
100                 * Represents a nautical mile: ≙ 1,853.184 m.
101                 */
102                NAUTICAL_MILE(1_853.184);
103
104                private final double _factor;
105
106                Unit(final double factor) {
107                        _factor = factor;
108                }
109
110                /**
111                 * Convert the given length value of the given {@code sourceUnit} into a
112                 * length value of {@code this} length unit. The given example converts 3
113                 * inches into yards.
114                 *
115                 * <pre>{@code
116                 * final double yards = YARD.convert(3, INCH);
117                 * }</pre>
118                 *
119                 * @param length the length value
120                 * @param sourceUnit the source length unit
121                 * @return the speed value of {@code this} length unit
122                 */
123                public double convert(final double length, final Unit sourceUnit) {
124                        requireNonNull(sourceUnit);
125
126                        if (this == sourceUnit) {
127                                return length;
128                        } else {
129                                final double meters = length*sourceUnit._factor;
130                                return meters/_factor;
131                        }
132                }
133        }
134
135        private final double _value;
136
137        /**
138         * Create a new {@code Length} object with the given value in meters.
139         *
140         * @param value the value (in meters) of the new {@code Length} object
141         */
142        private Length(final double value) {
143                _value = value;
144        }
145
146        /**
147         * Return the length in meter.
148         *
149         * @return the length in meter
150         */
151        @Override
152        public double doubleValue() {
153                return _value;
154        }
155
156        /**
157         * Return the length in the desired unit.
158         *
159         * @param unit the desired length unit
160         * @return the length in the desired unit
161         * @throws NullPointerException if the given length {@code unit} is
162         *         {@code null}
163         */
164        public double to(final Unit unit) {
165                return unit.convert(_value, Unit.METER);
166        }
167
168        @Override
169        public int intValue() {
170                return (int)doubleValue();
171        }
172
173        @Override
174        public long longValue() {
175                return (long)doubleValue();
176        }
177
178        @Override
179        public float floatValue() {
180                return (float)doubleValue();
181        }
182
183        @Override
184        public int compareTo(final Length other) {
185                return Double.compare(_value, other._value);
186        }
187
188        @Override
189        public int hashCode() {
190                return Double.hashCode(_value);
191        }
192
193        @Override
194        public boolean equals(final Object obj) {
195                return obj == this ||
196                        obj instanceof Length &&
197                        Double.compare(((Length)obj)._value, _value) == 0;
198        }
199
200        @Override
201        public String toString() {
202                return format("%s m", _value);
203        }
204
205
206        /* *************************************************************************
207         *  Static object creation methods
208         * ************************************************************************/
209
210        /**
211         * Create a new {@code Length} object with the given length.
212         *
213         * @param length the length
214         * @param unit the length unit
215         * @return a new {@code Length} object with the given length.
216         * @throws NullPointerException if the given length {@code unit} is
217         *         {@code null}
218         */
219        public static Length of(final double length, final Unit unit) {
220                requireNonNull(unit);
221                return new Length(Unit.METER.convert(length, unit));
222        }
223
224        static Length parse(final String value, final NumberFormat format) {
225                final Double length = parseDouble(value, format);
226                return length !=  null ? Length.of(length, Unit.METER) : null;
227        }
228
229        private static Double parseDouble(
230                final String value,
231                final NumberFormat format
232        ) {
233                final String length = Strings.trim(value);
234
235                if (length != null) {
236                        try {
237                                return format.parse(length).doubleValue();
238                        } catch (ParseException e) {
239                                throw new NumberFormatException("Unable to parse " + value);
240                        }
241                } else {
242                        return null;
243                }
244        }
245
246        /* *************************************************************************
247         *  Java object serialization
248         * ************************************************************************/
249
250        @Serial
251        private Object writeReplace() {
252                return new SerialProxy(SerialProxy.LENGTH, this);
253        }
254
255        @Serial
256        private void readObject(final ObjectInputStream stream)
257                throws InvalidObjectException
258        {
259                throw new InvalidObjectException("Serialization proxy required.");
260        }
261
262        void write(final DataOutput out) throws IOException {
263                out.writeDouble(_value);
264        }
265
266        static Length read(final DataInput in) throws IOException {
267                return new Length(in.readDouble());
268        }
269
270}